# Released under an MIT License (http://www.opensource.org/licenses/MIT) # By Jay Strybis (https://github.com/unreal) class TPPlus::Parser token ASSIGN AT_SYM COMMENT JUMP IO_METHOD INPUT OUTPUT token NUMREG POSREG VREG SREG TIME_SEGMENT ARG UALM token MOVE DOT TO AT TERM OFFSET SKIP GROUP token SEMICOLON NEWLINE STRING token REAL DIGIT WORD EQUAL token EEQUAL NOTEQUAL GTE LTE LT GT BANG token PLUS MINUS STAR SLASH DIV AND OR MOD token IF ELSE END UNLESS FOR IN WHILE token WAIT_FOR WAIT_UNTIL TIMEOUT AFTER token FANUC_USE SET_SKIP_CONDITION NAMESPACE token CASE WHEN INDIRECT POSITION token EVAL TIMER TIMER_METHOD RAISE ABORT token POSITION_DATA TRUE_FALSE RUN TP_HEADER PAUSE token LPAREN RPAREN COLON COMMA LBRACK RBRACK LBRACE RBRACE token LABEL ADDRESS token false prechigh right BANG left STAR SLASH DIV MOD left PLUS MINUS left GT GTE LT LTE left EEQUAL NOTEQUAL left AND left OR right EQUAL preclow rule program #: statements { @interpreter.nodes = val[0].flatten } : statements { @interpreter.nodes = val[0] } | ; statements : statement terminator { result = [val[0]] result << val[1] unless val[1].nil? } | statements statement terminator { result = val[0] << val[1] result << val[2] unless val[2].nil? } ; block : NEWLINE statements { result = val[1] } ; optional_newline : NEWLINE | ; statement : comment | definition | namespace #| assignment | motion_statement #| jump #| io_method | label_definition | address | conditional | inline_conditional | forloop | while_loop #| program_call | use_statement | set_skip_statement | wait_statement | case_statement | fanuc_eval | timer_method | position_data | raise | tp_header_definition | empty_stmt | PAUSE { result = PauseNode.new } | ABORT { result = AbortNode.new } ; empty_stmt : NEWLINE { result = EmptyStmtNode.new() } ; tp_header_definition : TP_HEADER EQUAL tp_header_value { result = HeaderNode.new(val[0],val[2]) } ; tp_header_value : STRING | TRUE_FALSE ; raise : RAISE var_or_indirect { result = RaiseNode.new(val[1]) } ; timer_method : TIMER_METHOD var_or_indirect { result = TimerMethodNode.new(val[0],val[1]) } ; fanuc_eval : EVAL STRING { result = EvalNode.new(val[1]) } ; wait_statement : WAIT_FOR LPAREN indirectable COMMA STRING RPAREN { result = WaitForNode.new(val[2], val[4]) } | WAIT_UNTIL LPAREN expression RPAREN { result = WaitUntilNode.new(val[2], nil) } | WAIT_UNTIL LPAREN expression RPAREN DOT wait_modifier { result = WaitUntilNode.new(val[2],val[5]) } | WAIT_UNTIL LPAREN expression RPAREN DOT wait_modifier DOT wait_modifier { result = WaitUntilNode.new(val[2],val[5].merge(val[7])) } ; wait_modifier : timeout_modifier | after_modifier ; timeout_modifier : swallow_newlines TIMEOUT LPAREN label RPAREN { result = { label: val[3] } } ; after_modifier : swallow_newlines AFTER LPAREN indirectable COMMA STRING RPAREN { result = { timeout: [val[3],val[5]] } } ; label : LABEL { result = val[0] } ; use_statement : FANUC_USE indirectable { result = UseNode.new(val[0],val[1]) } ; # set_skip_condition x set_skip_statement : SET_SKIP_CONDITION expression { result = SetSkipNode.new(val[1]) } ; program_call : WORD LPAREN args RPAREN { result = CallNode.new(val[0],val[2]) } | RUN WORD LPAREN args RPAREN { result = CallNode.new(val[1],val[3],async: true) } ; args : arg { result = [val[0]] } | args COMMA arg { result = val[0] << val[2] } | { result = [] } ; arg : number | var | string | address ; string : STRING { result = StringNode.new(val[0]) } ; io_method : IO_METHOD var_or_indirect { result = IOMethodNode.new(val[0],val[1]) } | IO_METHOD LPAREN var_or_indirect RPAREN { result = IOMethodNode.new(val[0],val[2]) } | IO_METHOD LPAREN var_or_indirect COMMA number COMMA STRING RPAREN { result = IOMethodNode.new(val[0],val[2],{ pulse_time: val[4], pulse_units: val[6] }) } ; var_or_indirect : var | indirect_thing ; jump : JUMP label { result = JumpNode.new(val[1]) } ; conditional : IF expression block else_block END { result = ConditionalNode.new("if",val[1],val[2],val[3]) } | UNLESS expression block else_block END { result = ConditionalNode.new("unless",val[1],val[2],val[3]) } ; forloop : FOR var IN LPAREN minmax_val TO minmax_val RPAREN block END { result = ForNode.new(val[1],val[4],val[6],val[8]) } ; while_loop : WHILE expression block END { result = WhileNode.new(val[1],val[2]) } ; minmax_val : integer | var ; namespace : NAMESPACE WORD block END { result = NamespaceNode.new(val[1],val[2]) } ; case_statement : CASE var swallow_newlines case_conditions case_else END { result = CaseNode.new(val[1],val[3],val[4]) } ; case_conditions : case_condition { result = val } | case_conditions case_condition { result = val[0] << val[1] << val[2] } ; case_condition : WHEN case_allowed_condition swallow_newlines case_allowed_statement terminator { result = CaseConditionNode.new(val[1],val[3]) } ; case_allowed_condition : number | var ; case_else : ELSE swallow_newlines case_allowed_statement terminator { result = CaseConditionNode.new(nil,val[2]) } | ; case_allowed_statement : program_call | jump ; inline_conditional : inlineable | inlineable IF expression { result = InlineConditionalNode.new(val[1], val[2], val[0]) } | inlineable UNLESS expression { result = InlineConditionalNode.new(val[1], val[2], val[0]) } ; inlineable : jump | assignment | io_method | program_call ; else_block : ELSE block { result = val[1] } | { result = [] } ; motion_statement : MOVE DOT swallow_newlines TO LPAREN var RPAREN motion_modifiers { result = MotionNode.new(val[0],val[5],val[7]) } ; motion_modifiers : motion_modifier { result = val } | motion_modifiers motion_modifier { result = val[0] << val[1] } ; motion_modifier : DOT swallow_newlines AT LPAREN speed RPAREN { result = SpeedNode.new(val[4]) } | DOT swallow_newlines TERM LPAREN valid_terminations RPAREN { result = TerminationNode.new(val[4]) } | DOT swallow_newlines OFFSET LPAREN var RPAREN { result = OffsetNode.new(val[2],val[4]) } | DOT swallow_newlines TIME_SEGMENT LPAREN time COMMA time_seg_actions RPAREN { result = TimeNode.new(val[2],val[4],val[6]) } | DOT swallow_newlines SKIP LPAREN label optional_lpos_arg RPAREN { result = SkipNode.new(val[4],val[5]) } ; valid_terminations : integer | var | MINUS DIGIT { raise Racc::ParseError, sprintf("\ninvalid termination type: (%s)", val[1]) if val[1] != 1 result = DigitNode.new(val[1].to_i * -1) } ; optional_lpos_arg : COMMA var { result = val[1] } | ; indirectable : number | var ; time_seg_actions : program_call | io_method ; time : var | number ; speed : indirectable COMMA STRING { result = { speed: val[0], units: val[2] } } | STRING { result = { speed: val[0], units: nil } } ; label_definition : label { result = LabelDefinitionNode.new(val[0]) }#@interpreter.add_label(val[1]) } ; definition : WORD ASSIGN definable { result = DefinitionNode.new(val[0],val[2]) } ; assignment : var_or_indirect EQUAL expression { result = AssignmentNode.new(val[0],val[2]) } | var_or_indirect PLUS EQUAL expression { result = AssignmentNode.new( val[0], ExpressionNode.new(val[0],"+",val[3]) ) } | var_or_indirect MINUS EQUAL expression { result = AssignmentNode.new( val[0], ExpressionNode.new(val[0],"-",val[3]) ) } ; var : var_without_namespaces | var_with_namespaces ; var_without_namespaces : WORD { result = VarNode.new(val[0]) } | WORD var_method_modifiers { result = VarMethodNode.new(val[0],val[1]) } ; var_with_namespaces : namespaces var_without_namespaces { result = NamespacedVarNode.new(val[0],val[1]) } ; var_method_modifiers : var_method_modifier { result = val[0] } | var_method_modifiers var_method_modifier { result = val[0].merge(val[1]) } ; var_method_modifier : DOT swallow_newlines WORD { result = { method: val[2] } } | DOT swallow_newlines GROUP LPAREN integer RPAREN { result = { group: val[4] } } ; namespaces : ns { result = [val[0]] } | namespaces ns { result = val[0] << val[1] } ; ns : WORD COLON COLON { result = val[0] } ; expression : unary_expression | binary_expression ; unary_expression : factor { result = val[0] } | address | BANG factor { result = ExpressionNode.new(val[1], "!", nil) } ; binary_expression : expression operator expression { result = ExpressionNode.new(val[0], val[1], val[2]) } ; operator : EEQUAL { result = "==" } | NOTEQUAL { result = "<>" } | LT { result = "<" } | GT { result = ">" } | GTE { result = ">=" } | LTE { result = "<=" } | PLUS { result = "+" } | MINUS { result = "-" } | OR { result = "||" } | STAR { result = "*" } | SLASH { result = "/" } | DIV { result = "DIV" } | MOD { result = "%" } | AND { result = "&&" } ; factor : number | signed_number | var | indirect_thing | paren_expr ; paren_expr : LPAREN expression RPAREN { result = ParenExpressionNode.new(val[1]) } ; indirect_thing : INDIRECT LPAREN STRING COMMA indirectable RPAREN { result = IndirectNode.new(val[2].to_sym, val[4]) } ; signed_number : sign DIGIT { val[1] = val[1].to_i * -1 if val[0] == "-" result = DigitNode.new(val[1]) } | sign REAL { val[1] = val[1].to_f * -1 if val[0] == "-"; result = RealNode.new(val[1]) } ; sign : MINUS { result = "-" } ; number : integer | REAL { result = RealNode.new(val[0]) } ; integer : DIGIT { result = DigitNode.new(val[0]) } ; definable : numreg | output | input | posreg | position | vreg | number | signed_number | argument | timer | ualm | sreg ; sreg : SREG LBRACK DIGIT RBRACK { result = StringRegisterNode.new(val[2].to_i) } ; ualm : UALM LBRACK DIGIT RBRACK { result = UserAlarmNode.new(val[2].to_i) } ; timer : TIMER LBRACK DIGIT RBRACK { result = TimerNode.new(val[2].to_i) } ; argument : ARG LBRACK DIGIT RBRACK { result = ArgumentNode.new(val[2].to_i) } ; vreg : VREG LBRACK DIGIT RBRACK { result = VisionRegisterNode.new(val[2].to_i) } ; position : POSITION LBRACK DIGIT RBRACK { result = PositionNode.new(val[2].to_i) } ; numreg : NUMREG LBRACK DIGIT RBRACK { result = NumregNode.new(val[2].to_i) } ; posreg : POSREG LBRACK DIGIT RBRACK { result = PosregNode.new(val[2].to_i) } ; output : OUTPUT LBRACK DIGIT RBRACK { result = IONode.new(val[0], val[2].to_i) } ; input : INPUT LBRACK DIGIT RBRACK { result = IONode.new(val[0], val[2].to_i) } ; address : ADDRESS { result = AddressNode.new(val[0]) } ; comment : COMMENT { result = CommentNode.new(val[0]) } ; terminator : NEWLINE { result = TerminatorNode.new } | comment optional_newline { result = val[0] } # ^-- consume newlines or else we will get an extra space from EmptyStmt in the output | false | ; swallow_newlines : NEWLINE { result = TerminatorNode.new } | ; position_data : POSITION_DATA sn hash sn END { result = PositionDataNode.new(val[2]) } ; sn : swallow_newlines ; hash : LBRACE sn hash_attributes sn RBRACE { result = val[2] } | LBRACE sn RBRACE { result = {} } ; hash_attributes : hash_attribute { result = val[0] } | hash_attributes COMMA sn hash_attribute { result = val[0].merge(val[3]) } ; hash_attribute : STRING COLON hash_value { result = { val[0].to_sym => val[2] } } ; hash_value : STRING | hash | array | optional_sign DIGIT { val[1] = val[1].to_i * -1 if val[0] == "-"; result = val[1] } | optional_sign REAL { val[1] = val[1].to_f * -1 if val[0] == "-"; result = val[1] } | TRUE_FALSE { result = val[0] == "true" } ; optional_sign : sign | ; array : LBRACK sn array_values sn RBRACK { result = val[2] } ; array_values : array_value { result = val } | array_values COMMA sn array_value { result = val[0] << val[3] } ; array_value : hash_value ; end ---- inner include TPPlus::Nodes attr_reader :interpreter def initialize(scanner, interpreter = TPPlus::Interpreter.new) @scanner = scanner @interpreter = interpreter super() end def next_token t = @scanner.next_token @interpreter.line_count += 1 if t && t[0] == :NEWLINE #puts t.inspect t end def parse #@yydebug =true do_parse @interpreter end def on_error(t, val, vstack) raise ParseError, sprintf("Parse error on line #{@scanner.tok_line} column #{@scanner.tok_col}: %s (%s)", val.inspect, token_to_str(t) || '?') end class ParseError < StandardError ; end