# Copyright (C) 2017 The Meme Factory, Inc.  http://www.meme.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Karl O. Pinc <kop@meme.com>

'''
Grammer for file name convention rules, the conventions which
the file name conventions must follow.

The point of having a grammer is two-fold.  We make sure that
we are seeing (minimally) what we expect to see.  And because
we know the expectations we know where the user is allowed
to insert optional version numbers and/or parentheticals.

Parsing a name returns a list containing the following nodes:
string
user_str
hash
entity
date
year
last_4_digits
version
paren
repeated_parens
optional

An optional node contains a list holding the above nodes.

The 'repeated_parens' node represents the zero or more repeated
parenthesis that the user may include at their option in any file name.

(deal with unspecified optional parenthesis and version numbers in
user file names)

Here is the informal BNF (the formal BNF is this file):
Note that in this informal BNF, the capitalized elements
are those that are implicit in the specification -- they
don't show up in the input rules that are parsed but they
are _added_ to the output.  The capitalized elements are
what can show up in the file system names even though
the elements do not appear in the naming convention rules.

The capitalized elements do not appear in the formal BNF;
the code implementing the respective grammer production
inserts them into the output.

name : bare_name IMPLICIT_SUFFIXES
     | suffixed_name
     | numbered_bullet " " bare_name IMPLICIT_SUFFIXES
     | numbered_bullet " " suffixed_name

IMPLICIT_SUFFIXES : IMPLICIT_VERSION IMPLICIT_PARENS

IMPLICIT_VERSION : "[ v<#>]"

IMPLICIT_PARENS : IMPLICIT_PAREN
                | IMPLICIT_PAREN IMPLICIT_PARENS

IMPLICIT_PAREN : "[" paren_str "]"

bare_name : string
          | user_value
          | bare_name " - " bare_name
          | bare_name " " user_value
          | bare_name " " string

suffixed_name : bare_name opt_user_value_suffix IMPLICIT_PARENS

opt_user_value_suffix : opt_user_values IMPLICIT_VERSION
                      | opt_user_values version_suffix
                      | version_suffix

opt_user_values : "[ - " user_value "]"
                | "[ " string " " user_value "]"
                | "[ - " user_value "]" opt_user_values

version_suffix : " v<#>"
               | " v<#>" paren_str_suffix
               | IMPLICIT_VERSION paren_str_suffix

paren_str_suffix : paren_strs
                 | paren_strs opt_paren_strs
                 | opt_paren_strs

paren_strs : paren_str
           | paren_str paren_strs

opt_paren_strs : "[ "paren_str "]"
               | "[ "paren_str "]" opt_paren_strs

paren_str : " (" paren_str_chars ")"

paren_str_chars : paren_str_char
                | paren_str_char paren_str_chars

paren_str_char : word_char
               | " "

numbered_bullet : "<#>."

user_value : "<#>"
           | "<entity>"
           | "<date>"
           | "<year>"
           | "<last 4 digits>"
           | "<" user_string ">"
           | user_value " " prefaced_user_value

prefaced_user_value : WORD " " user_value

user_string : lower_letter
            | number
            | "("
            | ")"
            | " "

string : word
       | word " " string

word : word_char
     | word_char word_chars

word_char : letter_or_number
          | ","
          | " "
          | "#"
          | "."

letter_or_number : upper_letter
                 | lower_letter
                 | number

upper_letter : A-Z

lower_letter : a-z

number : 0-9
'''


import ply.yacc as yacc
from .rules_lex import tokens
from . import exceptions as ex


# Constants
IMPLICIT_PARENS = [('optional', [('repeated_parens',)])]
IMPLICIT_VERSION = [('optional', [('version', ' v<#>')])]
IMPLICIT_SUFFIXES = IMPLICIT_VERSION + IMPLICIT_PARENS

# Here we avoid shift-reduce conflicts.
# These resolve conflicts related to "strings are made of words".
# (PLY does the right thing anyway, it shifts, but this gets rid of the
# warnings.)
precedence = (
    ('left', 'WORD'),
    ('left', 'DASH'),
    ('left', 'SPACE'),
)


# Helper functions
def pval(parsenode):
    return parsenode[0][1]


#
# Grammer
#
def p_name_simple(syms):
    '''name : bare_name'''
    syms[0] = syms[1] + IMPLICIT_SUFFIXES


def p_name_suffixed(syms):
    '''name : suffixed_name'''
    syms[0] = syms[1]


def p_name_complex_bare(syms):
    '''name : numbered_bullet SPACE bare_name'''
    syms[0] = ([syms[1][0], ('string', syms[1][1][1] + syms[2])] +
               syms[3] + IMPLICIT_SUFFIXES)


def p_name_complex_suffixed(syms):
    '''name : numbered_bullet SPACE suffixed_name'''
    syms[0] = ([syms[1][0], ('string', syms[1][1][1] + syms[2])] +
               syms[3])


def p_bare_name_simple_string(syms):
    '''bare_name : string'''
    syms[0] = syms[1]


def p_bare_name_simple_user_value(syms):
    '''bare_name : user_value'''
    syms[0] = syms[1]


def p_bare_name_complex_string(syms):
    '''bare_name : bare_name DASH bare_name'''
    syms[0] = syms[1] + [('string', syms[2])] + syms[3]


def p_bare_name_string_user_value(syms):
    '''bare_name : string SPACE user_value'''
    syms[0] = [('string', pval(syms[1]) + syms[2])] + syms[3]


def p_bare_name_space_user_value(syms):
    '''bare_name : bare_name SPACE user_value'''
    syms[0] = syms[1] + [('string', syms[2])] + syms[3]


def p_bare_name_space_string(syms):
    '''bare_name : bare_name SPACE string'''
    syms[0] = syms[1] + [('string', syms[2])] + syms[3]


def p_suffixed_name(syms):
    '''suffixed_name : bare_name opt_user_value_suffix'''
    syms[0] = syms[1] + syms[2] + IMPLICIT_PARENS


def p_opt_user_value_suffix_simple(syms):
    '''opt_user_value_suffix : opt_user_values'''
    syms[0] = syms[1] + IMPLICIT_VERSION


def p_opt_user_value_suffix_version(syms):
    '''opt_user_value_suffix : version_suffix'''
    syms[0] = syms[1]


def p_opt_user_value_suffix_complex(syms):
    '''opt_user_value_suffix : opt_user_values version_suffix'''
    syms[0] = syms[1] + syms[2]


def p_opt_user_value(syms):
    '''opt_user_value : LBRACE DASH user_value RBRACE'''
    syms[0] = [('optional', [('string', syms[2])] + syms[3])]


def p_opt_user_value_complex(syms):
    '''opt_user_value : LBRACE SPACE string SPACE user_value RBRACE'''
    syms[0] = [('optional',
                [('string', syms[2] + pval(syms[3]) + syms[4])] + syms[5])]


def p_opt_user_values_term(syms):
    '''opt_user_values : opt_user_value'''
    syms[0] = syms[1]


def p_opt_user_values_complex(syms):
    '''opt_user_values : opt_user_values opt_user_value'''
    syms[0] = syms[1] + syms[2]


def p_version_suffix_simple(syms):
    '''version_suffix : version_hash'''
    syms[0] = syms[1]


def p_version_suffix_complex(syms):
    '''version_suffix : version_hash paren_str_suffix'''
    syms[0] = syms[1] + syms[2]


def p_version_suffix_paren(syms):
    '''version_suffix : paren_str_suffix'''
    syms[0] = IMPLICIT_VERSION + syms[1]


def p_version_hash(syms):
    '''version_hash : VERSION'''
    syms[0] = [('version', syms[1])]


def p_paren_str_suffix_simple(syms):
    '''paren_str_suffix : paren_strs'''
    syms[0] = syms[1]


def p_paren_str_suffix_complex(syms):
    '''paren_str_suffix : paren_strs opt_paren_strs'''
    syms[0] = syms[1] + syms[2]


def p_paren_str_suffix_opt(syms):
    '''paren_str_suffix : opt_paren_strs'''
    syms[0] = syms[1]


def p_paren_strs_term(syms):
    '''paren_strs : paren_str'''
    syms[0] = syms[1]


def p_paren_strs_terms(syms):
    '''paren_strs : paren_str paren_strs'''
    syms[0] = syms[1] + syms[2]


def p_opt_paren_strs_term(syms):
    '''opt_paren_strs : opt_paren_str'''
    syms[0] = syms[1]


def p_opt_paren_strs_terms(syms):
    '''opt_paren_strs : opt_paren_str opt_paren_strs'''
    syms[0] = syms[1] + syms[2]


def p_opt_paren_str(syms):
    '''opt_paren_str : LBRACE paren_str RBRACE'''
    syms[0] = [('optional', syms[2])]


def p_paren_str(syms):
    '''paren_str : PAREN_STR'''
    syms[0] = [('paren', syms[1])]


def p_user_value(syms):
    '''user_value : hash
                  | entity
                  | date
                  | year
                  | last_4_digits
                  | user_str'''
    syms[0] = syms[1]


def p_user_value_complex(syms):
    '''user_value : user_value SPACE prefaced_user_value'''
    syms[0] = syms[1] + [('string', syms[2])] + syms[3]


def p_prefaced_user_value(syms):
    '''prefaced_user_value : WORD SPACE user_value'''
    syms[0] = [('string', syms[1] + syms[2])] + syms[3]


def p_numbered_bullet(syms):
    '''numbered_bullet : HASH PERIOD'''
    syms[0] = [('hash', syms[1]), ('string', syms[2])]


def p_hash(syms):
    '''hash : HASH'''
    syms[0] = [('hash', syms[1])]


def p_entity(syms):
    '''entity : ENTITY'''
    syms[0] = [('entity', syms[1])]


def p_date(syms):
    '''date : DATE'''
    syms[0] = [('date', syms[1])]


def p_year(syms):
    '''year : YEAR'''
    syms[0] = [('year', syms[1])]


def p_last_4_digits(syms):
    '''last_4_digits : LAST_4_DIGITS'''
    syms[0] = [('last_4_digits', syms[1])]


def p_user_str(syms):
    '''user_str : USER_STR'''
    syms[0] = [('user_str', syms[1])]


def p_string_word(syms):
    '''string : WORD'''
    syms[0] = [('string', syms[1])]


def p_string_words(syms):
    '''string : string SPACE WORD'''
    syms[0] = [('string', pval(syms[1]) + syms[2] + syms[3])]


def p_error(tok):
    if tok:
        lexer = tok.lexer
        raise ex.YaccError(lexer.lexpos, lexer.lexdata, tok.value)
    raise ex.YaccEOFError()


# Make flake8 happy that variables are used.
tokens = tokens

parser = yacc.yacc(write_tables=False, errorlog=yacc.NullLogger())
