from nml import generic, expression, tokens, nmlop
from nml.ast import assignment, basecost, cargotable, conditional, deactivate, disable_item, error, font, general, grf, item, loop, produce, railtypetable, replace, spriteblock, switch, switch_range, townnames, snowline, skipall, tilelayout, alt_sprites, base_sprites, override
from nml.actions import action2, action2random, actionD, real_sprite
import ply.yacc as yacc

class NMLParser(object):
    def __init__(self):
        self.lexer = tokens.NMLLexer()
        self.lexer.build()
        self.tokens = self.lexer.tokens
        self.parser = yacc.yacc(debug = False, module = self)

    def parse(self, text):
        return self.parser.parse(text, lexer = self.lexer.lexer)


    #operator precedence (lower in the list = higher priority)
    precedence = (
        ('left','COMMA'),
        ('right','TERNARY_OPEN','COLON'),
        ('left','LOGICAL_OR'),
        ('left','LOGICAL_AND'),
        ('left','OR'),
        ('left','XOR'),
        ('left','AND'),
        ('left','COMP_EQ','COMP_NEQ','COMP_LE','COMP_GE','COMP_LT','COMP_GT'),
        ('left','SHIFT_LEFT','SHIFT_RIGHT','SHIFTU_RIGHT'),
        ('left','PLUS','MINUS'),
        ('left','TIMES','DIVIDE','MODULO'),
        ('left','LOGICAL_NOT','BINARY_NOT'),
    )

    def p_error(self, t):
        if t is None:
            raise generic.ScriptError('Syntax error, unexpected end-of-file')
        else:
            raise generic.ScriptError('Syntax error, unexpected token "%s"' % t.value, t.lineno)


    #
    # Main script blocks
    #
    def p_main_script(self, t):
        'main_script : script'
        t[0] = general.MainScript(t[1])

    def p_script(self, t):
        '''script :
                  | script main_block'''
        if len(t) == 1: t[0] = []
        else: t[0] = t[1] + [t[2]]

    def p_main_block(self, t):
        '''main_block : switch
                      | random_switch
                      | produce
                      | spriteset
                      | spritegroup
                      | spritelayout
                      | template_declaration
                      | tilelayout
                      | town_names
                      | cargotable
                      | railtype
                      | grf_block
                      | param_assignment
                      | skip_all
                      | conditional
                      | loop
                      | item
                      | property_block
                      | graphics_block
                      | liveryoverride_block
                      | error_block
                      | disable_item
                      | deactivate
                      | replace
                      | replace_new
                      | base_sprites
                      | font_glyph
                      | alt_sprites
                      | snowline
                      | engine_override
                      | basecost'''
        t[0] = t[1]

    #
    # Expressions
    #
    def p_expression(self, t):
        '''expression : NUMBER
                      | FLOAT
                      | param
                      | variable
                      | ID
                      | STRING_LITERAL
                      | string'''
        t[0] = t[1]

    def p_parenthesed_expression(self, t):
        'expression : LPAREN expression RPAREN'
        t[0] = t[2]

    def p_parameter(self, t):
        'param : PARAMETER LBRACKET expression RBRACKET'
        t[0] = expression.Parameter(t[3], t.lineno(1), True)

    def p_parameter_other_grf(self, t):
        'param : PARAMETER LBRACKET expression COMMA expression RBRACKET'
        t[0] = expression.OtherGRFParameter(t[3], t[5], t.lineno(1))

    code_to_op = {
        '+'  : nmlop.ADD,
        '-'  : nmlop.SUB,
        '*'  : nmlop.MUL,
        '/'  : nmlop.DIV,
        '%'  : nmlop.MOD,
        '&'  : nmlop.AND,
        '|'  : nmlop.OR,
        '^'  : nmlop.XOR,
        '&&' : nmlop.AND,
        '||' : nmlop.OR,
        '==' : nmlop.CMP_EQ,
        '!=' : nmlop.CMP_NEQ,
        '<=' : nmlop.CMP_LE,
        '>=' : nmlop.CMP_GE,
        '<'  : nmlop.CMP_LT,
        '>'  : nmlop.CMP_GT,
        '<<' : nmlop.SHIFT_LEFT,
        '>>' : nmlop.SHIFT_RIGHT,
        '>>>': nmlop.SHIFTU_RIGHT,
    }

    def p_binop(self, t):
        '''expression : expression PLUS expression
                      | expression MINUS expression
                      | expression TIMES expression
                      | expression DIVIDE expression
                      | expression MODULO expression
                      | expression AND expression
                      | expression OR expression
                      | expression XOR expression
                      | expression SHIFT_LEFT expression
                      | expression SHIFT_RIGHT expression
                      | expression SHIFTU_RIGHT expression
                      | expression COMP_EQ expression
                      | expression COMP_NEQ expression
                      | expression COMP_LE expression
                      | expression COMP_GE expression
                      | expression COMP_LT expression
                      | expression COMP_GT expression'''
        t[0] = expression.BinOp(self.code_to_op[t[2]], t[1], t[3], t[1].pos)

    def p_binop_logical(self, t):
        '''expression : expression LOGICAL_AND expression
                      | expression LOGICAL_OR expression'''
        t[0] = expression.BinOp(self.code_to_op[t[2]], expression.Boolean(t[1]), expression.Boolean(t[3]), t[1].pos)

    def p_logical_not(self, t):
        'expression : LOGICAL_NOT expression'
        t[0] = expression.Not(expression.Boolean(t[2]), t.lineno(1))

    def p_binary_not(self, t):
        'expression : BINARY_NOT expression'
        t[0] = expression.BinNot(t[2], t.lineno(1))

    def p_ternary_op(self, t):
        'expression : expression TERNARY_OPEN expression COLON expression'
        t[0] = expression.TernaryOp(t[1], t[3], t[5], t[1].pos)

    def p_unary_minus(self, t):
        'expression : MINUS expression'
        t[0] = expression.BinOp(self.code_to_op[t[1]], expression.ConstantNumeric(0), t[2], t.lineno(1))

    def p_variable(self, t):
        'variable : VARIABLE LBRACKET expression_list RBRACKET'
        t[0] = expression.Variable(*t[3])
        t[0].pos = t.lineno(1)

    def p_function(self, t):
        'expression : ID LPAREN expression_list RPAREN'
        t[0] = expression.FunctionCall(t[1], t[3], t[1].pos)

    def p_array(self, t):
        'expression : LBRACKET expression_list RBRACKET'
        t[0] = expression.Array(t[2], t.lineno(1))


    #
    # Commonly used non-terminals that are not expressions
    #
    def p_assignment_list(self, t):
        '''assignment_list : assignment
                           | param_desc
                           | assignment_list assignment
                           | assignment_list param_desc'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_assignment(self, t):
        'assignment : ID COLON expression SEMICOLON'
        t[0] = assignment.Assignment(t[1], t[3], t[1].pos)

    def p_param_desc(self, t):
        '''param_desc : PARAMETER expression LBRACE setting_list RBRACE
                      | PARAMETER LBRACE setting_list RBRACE'''
        if len(t) == 5: t[0] = grf.ParameterDescription(t[3])
        else: t[0] = grf.ParameterDescription(t[4], t[2])

    def p_setting_list(self, t):
        '''setting_list : setting
                        | setting_list setting'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_setting(self, t):
        'setting : ID LBRACE setting_value_list RBRACE'
        t[0] = grf.ParameterSetting(t[1], t[3])

    def p_setting_value_list(self, t):
        '''setting_value_list : setting_value
                              | setting_value_list setting_value'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_setting_value(self, t):
        'setting_value : assignment'
        t[0] =  t[1]

    def p_names_setting_value(self, t):
        'setting_value : ID COLON LBRACE name_string_list RBRACE SEMICOLON'
        t[0] =  assignment.Assignment(t[1], t[4], t[1].pos)

    def p_name_string_list(self, t):
        '''name_string_list : name_string_item
                            | name_string_list name_string_item'''
        if len(t) == 2: t[0] = expression.Array([t[1]], t[1].pos)
        else: t[0] = expression.Array(t[1].values + [t[2]], t[1].pos)

    def p_name_string_item(self, t):
        'name_string_item : expression COLON string SEMICOLON'
        t[0] = assignment.Assignment(t[1], t[3], t[1].pos)

    def p_string(self, t):
        'string : STRING LPAREN expression_list RPAREN'
        t[0] = expression.String(t[3][0], t[3][1:], t.lineno(1))

    def p_non_empty_expression_list(self, t):
        '''non_empty_expression_list : expression
                                     | non_empty_expression_list COMMA expression'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[3]]

    def p_expression_list(self, t):
        '''expression_list :
                           | non_empty_expression_list
                           | non_empty_expression_list COMMA'''
        t[0] = [] if len(t) == 1 else t[1]

    def p_non_empty_id_list(self, t):
        '''non_empty_id_list : ID
                             | non_empty_id_list COMMA ID'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[3]]

    def p_id_list(self, t):
        '''id_list :
                   | non_empty_id_list
                   | non_empty_id_list COMMA'''
        t[0] = [] if len(t) == 1 else t[1]

    def p_generic_assignment(self, t):
        'generic_assignment : expression COLON expression SEMICOLON'
        t[0] = assignment.Assignment(t[1], t[3], t.lineno(1))

    def p_generic_assignment_list(self, t):
        '''generic_assignment_list : 
                                   | generic_assignment_list generic_assignment'''
        t[0] = [] if len(t) == 1 else t[1] + [t[2]]

    #
    # Item blocks
    #
    def p_item(self, t):
        'item : ITEM LPAREN expression_list RPAREN LBRACE script RBRACE'
        t[0] = item.Item(t[3], t[6], t.lineno(1))

    def p_property_block(self, t):
        'property_block : PROPERTY LBRACE property_list RBRACE'
        t[0] = item.PropertyBlock(t[3], t.lineno(1))

    def p_property_list(self, t):
        '''property_list : property_assignment
                         | property_list property_assignment'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_property_assignment(self, t):
        '''property_assignment : ID COLON expression SEMICOLON
                               | ID COLON expression UNIT SEMICOLON
                               | NUMBER COLON expression SEMICOLON
                               | NUMBER COLON expression UNIT SEMICOLON'''
        name = t[1]
        unit = None if len(t) == 5 else item.Unit(t[4])
        t[0] = item.Property(name, t[3], unit, t.lineno(1))

    def p_graphics_block(self, t):
        'graphics_block : GRAPHICS LBRACE graphics_list RBRACE'
        t[0] = item.GraphicsBlock(t[3][0], t[3][1], t.lineno(1))

    def p_liveryoverride_block(self, t):
        'liveryoverride_block : LIVERYOVERRIDE LPAREN expression RPAREN LBRACE graphics_list RBRACE'
        t[0] = item.LiveryOverride(t[3], item.GraphicsBlock(t[6][0], t[6][1], t.lineno(1)), t.lineno(1))

    def p_graphics_list(self, t):
        '''graphics_list : graphics_assignment_list
                         | graphics_assignment_list expression SEMICOLON
                         | expression SEMICOLON'''
        # Save graphics block as a tuple, we need to add position info later
        if len(t) == 2:
            t[0] = (t[1], None)
        elif len(t) == 4:
            t[0] = (t[1], t[2])
        else:
            t[0] = ([], t[1])

    def p_graphics_assignment(self, t):
        'graphics_assignment : expression COLON expression SEMICOLON'
        t[0] = item.GraphicsDefinition(t[1], t[3])

    def p_graphics_assignment_list(self, t):
        '''graphics_assignment_list : graphics_assignment
                                    | graphics_assignment_list graphics_assignment'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    #
    # Program flow control (if/else/while)
    #
    def p_conditional(self, t):
        '''conditional : if_else_parts
                       | if_else_parts else_block'''
        parts = t[1]
        if len(t) > 2:
            parts.append(t[2])
        t[0] = conditional.ConditionalList(parts)

    def p_else_block(self, t):
        'else_block : ELSE LBRACE script RBRACE'
        t[0] = conditional.Conditional(None, t[3], t.lineno(1))

    def p_if_else_parts(self, t):
        '''if_else_parts : IF LPAREN expression RPAREN LBRACE script RBRACE
                         | if_else_parts ELSE IF LPAREN expression RPAREN LBRACE script RBRACE'''
        if len(t) == 8: t[0] = [conditional.Conditional(t[3], t[6], t.lineno(1))]
        else: t[0] = t[1] + [conditional.Conditional(t[5], t[8], t.lineno(2))]

    def p_loop(self, t):
        'loop : WHILE LPAREN expression RPAREN LBRACE script RBRACE'
        t[0] = loop.Loop(t[3], t[6], t.lineno(1))

    #
    # (Random) Switch block
    #
    def p_switch(self, t):
        'switch : SWITCH LPAREN expression COMMA ID COMMA ID COMMA expression RPAREN LBRACE switch_body RBRACE'
        t[0] = switch.Switch(t[3], t[5], t[7], t[9], t[12], t.lineno(1))

    def p_switch_body(self, t):
        'switch_body : switch_ranges switch_value'
        t[0] = switch.SwitchBody(t[1], t[2])

    def p_switch_ranges(self, t):
        '''switch_ranges :
                         | switch_ranges expression COLON switch_value
                         | switch_ranges expression UNIT COLON switch_value
                         | switch_ranges expression RANGE expression COLON switch_value
                         | switch_ranges expression RANGE expression UNIT COLON switch_value'''
        if len(t) == 1: t[0] = []
        elif len(t) == 5: t[0] = t[1] + [switch_range.SwitchRange(t[2], t[2], t[4])]
        elif len(t) == 6: t[0] = t[1] + [switch_range.SwitchRange(t[2], t[2], t[5], t[3])]
        elif len(t) == 7: t[0] = t[1] + [switch_range.SwitchRange(t[2], t[4], t[6])]
        else: t[0] = t[1] + [switch_range.SwitchRange(t[2], t[4], t[7], t[5])]

    def p_switch_value(self, t):
        '''switch_value : RETURN expression SEMICOLON
                        | RETURN SEMICOLON
                        | expression SEMICOLON'''
        if len(t) == 4: t[0] = t[2]
        elif t[1] == 'return': t[0] = None
        else: t[0] = t[1]

    def p_random_switch(self, t):
        'random_switch : RANDOMSWITCH LPAREN expression_list RPAREN LBRACE random_body RBRACE'
        t[0] = switch.RandomSwitch(t[3], t[6], t.lineno(1))

    def p_random_body(self, t):
        '''random_body :
                       | random_body expression COLON switch_value'''
        if len(t) == 1: t[0] = []
        else: t[0] = t[1] + [action2random.RandomChoice(t[2], t[4])]

    def p_produce(self, t):
        'produce : PRODUCE LPAREN expression_list RPAREN SEMICOLON'
        t[0] = produce.Produce(t[3], t.lineno(1))

    #
    # Real sprites and related stuff
    #
    def p_real_sprite(self, t):
        '''real_sprite : LBRACKET expression_list RBRACKET
                       | ID COLON LBRACKET expression_list RBRACKET'''
        if len(t) == 4:
            t[0] = real_sprite.RealSprite(t[2])
        else:
            t[0] = real_sprite.RealSprite(t[4], t[1])

    def p_recolour_assignment_list(self, t):
        '''recolour_assignment_list :
                                    | recolour_assignment_list recolour_assignment'''
        t[0] = [] if len(t) == 1 else t[1] + [t[2]]

    def p_recolour_assignment_1(self, t):
        'recolour_assignment : expression COLON expression SEMICOLON'
        t[0] = assignment.Assignment(assignment.Range(t[1], None), assignment.Range(t[3], None), t[1].pos)

    def p_recolour_assignment_2(self, t):
        'recolour_assignment : expression RANGE expression COLON expression RANGE expression SEMICOLON'
        t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], t[7]), t[1].pos)

    def p_recolour_assignment_3(self, t):
        'recolour_assignment : expression RANGE expression COLON expression SEMICOLON'
        t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], None), t[1].pos)

    def p_recolour_sprite(self, t):
        'real_sprite : RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE'
        t[0] = real_sprite.RecolourSprite(t[3])

    def p_template_declaration(self, t):
        'template_declaration : TEMPLATE ID LPAREN id_list RPAREN LBRACE spriteset_contents RBRACE'
        t[0] = spriteblock.TemplateDeclaration(t[2], t[4], t[7], t.lineno(1))

    def p_template_usage(self, t):
        '''template_usage : ID LPAREN expression_list RPAREN
                          | ID COLON ID LPAREN expression_list RPAREN'''
        if len(t) == 5:
            t[0] = real_sprite.TemplateUsage(t[1], t[3], None, t.lineno(1))
        else:
            t[0] = real_sprite.TemplateUsage(t[3], t[5], t[1], t.lineno(1))

    def p_spriteset_contents(self, t):
        '''spriteset_contents : real_sprite
                              | template_usage
                              | spriteset_contents real_sprite
                              | spriteset_contents template_usage'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_replace(self, t):
        '''replace : REPLACESPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE
                   | REPLACESPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'''
        if len(t) == 9: t[0] = replace.ReplaceSprite(t[4], t[7], t[2], t.lineno(1))
        else: t[0] = replace.ReplaceSprite(t[3], t[6], None, t.lineno(1))

    def p_replace_new(self, t):
        '''replace_new : REPLACENEWSPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE
                       | REPLACENEWSPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'''
        if len(t) == 9: t[0] = replace.ReplaceNewSprite(t[4], t[7], t[2], t.lineno(1))
        else: t[0] = replace.ReplaceNewSprite(t[3], t[6], None, t.lineno(1))

    def p_base_sprites(self, t):
        '''base_sprites : BASE_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE
                        | BASE_SPRITES ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'''
        if len(t) == 9: t[0] = base_sprites.BaseSprite(t[4], t[7], t[2], t.lineno(1))
        else: t[0] = base_sprites.BaseSprite(t[3], t[6], None, t.lineno(1))

    def p_font_glyph(self, t):
        '''font_glyph : FONTGLYPH LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE
                      | FONTGLYPH ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'''
        if len(t) == 9: t[0] = font.FontGlyphBlock(t[4], t[7], t[2], t.lineno(1))
        else: t[0] = font.FontGlyphBlock(t[3], t[6], None, t.lineno(1))

    def p_alt_sprites(self, t):
        'alt_sprites : ALT_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'
        t[0] = alt_sprites.AltSpritesBlock(t[3], t[6], t.lineno(1))

    #
    # Sprite sets/groups and such
    #

    def p_spriteset(self, t):
        'spriteset : SPRITESET LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE'
        t[0] = spriteblock.SpriteSet(t[3], t[6], t.lineno(1))

    def p_spritegroup_normal(self, t):
        'spritegroup : SPRITEGROUP ID LBRACE spriteview_list RBRACE'
        t[0] = spriteblock.SpriteGroup(t[2], t[4], t.lineno(1))

    def p_spritelayout(self, t):
        '''spritelayout : SPRITELAYOUT ID LBRACE layout_sprite_list RBRACE
                        | SPRITELAYOUT ID LPAREN id_list RPAREN LBRACE layout_sprite_list RBRACE'''
        if len(t) == 6:
            t[0] = spriteblock.SpriteLayout(t[2], [], t[4], t.lineno(1))
        else:
            t[0] = spriteblock.SpriteLayout(t[2], t[4], t[7], t.lineno(1))

    def p_spriteview_list(self, t):
        '''spriteview_list :
                           | spriteview_list spriteview'''
        if len(t) == 1: t[0] = []
        else: t[0] = t[1] + [t[2]]

    def p_spriteview(self, t):
        '''spriteview : ID COLON LBRACKET expression_list RBRACKET SEMICOLON
                      | ID COLON expression SEMICOLON'''
        if len(t) == 7: t[0] = spriteblock.SpriteView(t[1], t[4], t.lineno(1))
        else: t[0] = spriteblock.SpriteView(t[1], [t[3]], t.lineno(1))

    def p_layout_sprite_list(self, t):
        '''layout_sprite_list :
                              | layout_sprite_list layout_sprite'''
        if len(t) == 1: t[0] = []
        else: t[0] = t[1] + [t[2]]

    def p_layout_sprite(self, t):
        'layout_sprite : ID LBRACE layout_param_list RBRACE'
        t[0] = spriteblock.LayoutSprite(t[1], t[3], t.lineno(1))

    def p_layout_param_list(self, t):
        '''layout_param_list : assignment
                             | layout_param_list assignment'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    #
    # Town names
    #
    def p_town_names(self, t):
        '''town_names : TOWN_NAMES LPAREN expression RPAREN LBRACE town_names_param_list RBRACE
                      | TOWN_NAMES LBRACE town_names_param_list RBRACE'''
        if len(t) == 8: t[0] = townnames.TownNames(t[3], t[6], t.lineno(1))
        else: t[0] = townnames.TownNames(None, t[3], t.lineno(1))

    def p_town_names_param_list(self, t):
        '''town_names_param_list : town_names_param
                                 | town_names_param_list town_names_param'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_town_names_param(self, t):
        '''town_names_param : ID COLON string SEMICOLON
                            | LBRACE town_names_part_list RBRACE
                            | LBRACE town_names_part_list COMMA RBRACE'''
        if t[1] != '{': t[0] = townnames.TownNamesParam(t[1], t[3], t.lineno(1))
        else: t[0] = townnames.TownNamesPart(t[2], t.lineno(1))

    def p_town_names_part_list(self, t):
        '''town_names_part_list : town_names_part
                                | town_names_part_list COMMA town_names_part'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[3]]

    def p_town_names_part(self, t):
        '''town_names_part : TOWN_NAMES LPAREN expression COMMA expression RPAREN
                           | ID LPAREN STRING_LITERAL COMMA expression RPAREN'''
        if t[1] == 'town_names': t[0] = townnames.TownNamesEntryDefinition(t[3], t[5], t.lineno(1))
        else: t[0] = townnames.TownNamesEntryText(t[1], t[3], t[5], t.lineno(1))

    #
    # Snow line
    #
    def p_snowline(self, t):
        'snowline : SNOWLINE LPAREN ID RPAREN LBRACE generic_assignment_list RBRACE'
        t[0] = snowline.Snowline(t[3], t[6], t.lineno(1))

    #
    # Various misc. main script blocks that don't belong anywhere else
    #
    def p_param_assignment(self, t):
        'param_assignment : expression EQ expression SEMICOLON'
        t[0] = actionD.ParameterAssignment(t[1], t[3])

    def p_error_block(self, t):
        'error_block : ERROR LPAREN expression_list RPAREN SEMICOLON'
        t[0] = error.Error(t[3], t.lineno(1))

    def p_disable_item(self, t):
        'disable_item : DISABLE_ITEM LPAREN expression_list RPAREN SEMICOLON'
        t[0] = disable_item.DisableItem(t[3], t.lineno(1))

    def p_cargotable(self, t):
        '''cargotable : CARGOTABLE LBRACE cargotable_list RBRACE
                      | CARGOTABLE LBRACE cargotable_list COMMA RBRACE'''
        t[0] = cargotable.CargoTable(t[3], t.lineno(1))

    def p_cargotable_list(self, t):
        '''cargotable_list : ID
                           | STRING_LITERAL
                           | cargotable_list COMMA ID
                           | cargotable_list COMMA STRING_LITERAL'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[3]]

    def p_railtypetable(self, t):
        '''railtype : RAILTYPETABLE LBRACE railtypetable_list RBRACE
                    | RAILTYPETABLE LBRACE railtypetable_list COMMA RBRACE'''
        t[0] = railtypetable.RailtypeTable(t[3], t.lineno(1))

    def p_railtypetable_list(self, t):
        '''railtypetable_list : railtypetable_item
                              | railtypetable_list COMMA railtypetable_item'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[3]]

    def p_railtypetable_item(self, t):
        '''railtypetable_item : ID
                              | STRING_LITERAL
                              | ID COLON LBRACKET expression_list RBRACKET'''
        if len(t) == 2: t[0] = t[1]
        else: t[0] = assignment.Assignment(t[1], t[4], t[1].pos)

    def p_basecost(self, t):
        'basecost : BASECOST LBRACE generic_assignment_list RBRACE'
        t[0] = basecost.BaseCost(t[3], t.lineno(1))

    def p_deactivate(self, t):
        'deactivate : DEACTIVATE LPAREN expression_list RPAREN SEMICOLON'
        t[0] = deactivate.DeactivateBlock(t[3], t.lineno(1))

    def p_grf_block(self, t):
        'grf_block : GRF LBRACE assignment_list RBRACE'
        t[0] = grf.GRF(t[3], t.lineno(1))

    def p_skip_all(self, t):
        'skip_all : SKIP_ALL SEMICOLON'
        t[0] = skipall.SkipAll(t.lineno(1))

    def p_engine_override(self, t):
        'engine_override : ENGINE_OVERRIDE LPAREN expression_list RPAREN SEMICOLON'
        t[0] = override.EngineOverride(t[3], t.lineno(1))

    def p_tilelayout(self, t):
        'tilelayout : TILELAYOUT ID LBRACE tilelayout_list RBRACE'
        t[0] = tilelayout.TileLayout(t[2], t[4], t.lineno(1))

    def p_tilelayout_list(self, t):
        '''tilelayout_list : tilelayout_item
                           | tilelayout_list tilelayout_item'''
        if len(t) == 2: t[0] = [t[1]]
        else: t[0] = t[1] + [t[2]]

    def p_tilelayout_item_tile(self, t):
        'tilelayout_item : expression COMMA expression COLON expression SEMICOLON'
        t[0] = tilelayout.LayoutTile(t[1], t[3], t[5])

    def p_tilelayout_item_prop(self, t):
        'tilelayout_item : assignment'
        t[0] = t[1]
