lib/hotcell/parser.y in hotcell-0.0.1 vs lib/hotcell/parser.y in hotcell-0.1.0

- old
+ new

@@ -50,182 +50,240 @@ nonassoc COMMA COLON left SEMICOLON NEWLINE preclow start document rule - document: document document_unit { val[0].children.push(val[1]) } - | document_unit { result = Joiner.build :JOINER, val[0] } - document_unit: template | command_tag | block_tag | tag + document: document document_unit { pospoppush(2); val[0].children.push(val[1]) } + | document_unit { result = build Joiner, :JOINER, val[0], position: pospoppush(1) } + document_unit: template | tag | block_tag | command_tag template: TEMPLATE { result = val[0] } - tag: TOPEN TCLOSE { result = Tag.build :TAG, mode: TAG_MODES[val[0]] } - | TOPEN sequence TCLOSE { result = Tag.build :TAG, *val[1].flatten, mode: TAG_MODES[val[0]] } + tag: TOPEN TCLOSE { result = build Tag, :TAG, mode: TAG_MODES[val[0]], position: pospoppush(2) } + | TOPEN sequence TCLOSE { + result = build Tag, :TAG, *Array.wrap(val[1]).flatten, mode: TAG_MODES[val[0]], position: pospoppush(3) + } - command_tag: TOPEN assigned_command TCLOSE { - command = val[1][:command] - assign = val[1][:assign] - command.options[:mode] = TAG_MODES[val[0]] - command.options[:assign] = assign if assign - command.validate! - result = command - } - block_open: TOPEN assigned_block TCLOSE { - block = val[1][:block] - assign = val[1][:assign] - block.options[:mode] = TAG_MODES[val[0]] - block.options[:assign] = assign if assign - result = block - } - block_close: TOPEN endblock TCLOSE - block_body: block_body document_unit { - val[0][-1].is_a?(Joiner) ? - val[0][-1].children.push(val[1]) : - val[0].push(Joiner.build(:JOINER, val[1])) + command_body: COMMAND { result = build @commands[val[0]] || Command, val[0], position: pospoppush(1) } + | COMMAND arguments { + result = build @commands[val[0]] || Command, val[0], *val[1], position: pospoppush(2) + } + command: command_body + | IDENTIFER ASSIGN command_body { + result = build Assigner, val[0], val[2], position: pospoppush(3) + } + command_tag: TOPEN command TCLOSE { + command = val[1].is_a?(Command) ? val[1] : val[1].children[0] + command.validate! + result = build Tag, :TAG, val[1], mode: TAG_MODES[val[0]], position: pospoppush(3) + } + + subcommand: SUBCOMMAND { result = build @substack.last[val[0]], val[0], position: pospoppush(1) } + | SUBCOMMAND arguments { + result = build @substack.last[val[0]], val[0], *val[1], position: pospoppush(2) } - | block_body subcommand_tag { val[0].push(val[1]) } - | document_unit { result = [Joiner.build(:JOINER, val[0])] } - | subcommand_tag { result = [val[0]] } - block_tag: block_open block_close - | block_open block_body block_close { val[0].options[:subnodes] = val[1]; val[0].validate! } - subcommand_tag: TOPEN subcommand TCLOSE { result = val[1] } + subcommand_tag: TOPEN subcommand TCLOSE { pospoppush(3); result = val[1] } - command: COMMAND { result = Command.build val[0] } - | COMMAND arguments { result = Command.build val[0], *val[1] } - assigned_command: command { result = { command: val[0] } } - | IDENTIFER ASSIGN command { result = { command: val[2], assign: val[0] } } - block: BLOCK { result = Block.build val[0] } - | BLOCK arguments { result = Block.build val[0], *val[1] } - assigned_block: block { result = { block: val[0] } } - | IDENTIFER ASSIGN block { result = { block: val[2], assign: val[0] } } - endblock: ENDBLOCK - | END BLOCK - subcommand: SUBCOMMAND { result = { name: val[0] } } - | SUBCOMMAND arguments { result = { name: val[0], args: Arrayer.build(:ARRAY, *val[1]) } } + block_body: BLOCK { result = build @blocks[val[0]] || Block, val[0], position: pospoppush(1) } + | BLOCK arguments { + result = build @blocks[val[0]] || Block, val[0], *val[1], position: pospoppush(2) + } + block_open: block_body + | IDENTIFER ASSIGN block_body { + result = build Assigner, val[0], val[2], position: pospoppush(3) + } + block_close: ENDBLOCK + | END BLOCK { pospoppush(2) } + | END + block_open_tag: TOPEN block_open TCLOSE { + result = build Tag, :TAG, val[1], mode: TAG_MODES[val[0]], position: pospoppush(3) + } + block_close_tag: TOPEN block_close TCLOSE { pospoppush(3) } + block_subnodes: block_subnodes document_unit { + pospoppush(2) + val[0][-1].is_a?(Joiner) ? + val[0][-1].children.push(val[1]) : + val[0].push(build(Joiner, :JOINER, val[1])) + } + | block_subnodes subcommand_tag { pospoppush(2); val[0].push(val[1]) } + | document_unit { result = [build(Joiner, :JOINER, val[0], position: pospoppush(1))] } + | subcommand_tag { result = [val[0]] } + block_tag: block_open_tag block_close_tag { + pospoppush(2) + block = val[0].children[0].is_a?(Block) ? + val[0].children[0] : val[0].children[0].children[0] + block.validate! + } + | block_open_tag block_subnodes block_close_tag { + pospoppush(3) + block = val[0].children[0].is_a?(Block) ? + val[0].children[0] : val[0].children[0].children[0] + block.options[:subnodes] = val[1] + block.validate! + } - sequence: sequence SEMICOLON sequence { result = val[0].push(val[2]) } - | sequence SEMICOLON - | SEMICOLON sequence { result = val[1] } + sequence: sequence SEMICOLON sequence { pospoppush(2); result = val[0].push(val[2]) } + | sequence SEMICOLON { pospoppush(2) } + | SEMICOLON sequence { pospoppush(2, 1); result = val[1] } | SEMICOLON { result = [] } - | sequence NEWLINE sequence { result = val[0].push(val[2]) } - | sequence NEWLINE - | NEWLINE sequence { result = val[1] } + | sequence NEWLINE sequence { pospoppush(2); result = val[0].push(val[2]) } + | sequence NEWLINE { pospoppush(2) } + | NEWLINE sequence { pospoppush(2, 1); result = val[1] } | NEWLINE { result = [] } | expr { result = [val[0]] } - expr: expr MULTIPLY expr { result = Calculator.build :MULTIPLY, val[0], val[2] } - | expr POWER expr { result = Calculator.build :POWER, val[0], val[2] } - | expr DIVIDE expr { result = Calculator.build :DIVIDE, val[0], val[2] } - | expr PLUS expr { result = Calculator.build :PLUS, val[0], val[2] } - | expr MINUS expr { result = Calculator.build :MINUS, val[0], val[2] } - | expr MODULO expr { result = Calculator.build :MODULO, val[0], val[2] } - | MINUS expr =UMINUS { result = Calculator.build :UMINUS, val[1] } - | PLUS expr =UPLUS { result = Calculator.build :UPLUS, val[1] } - | expr AND expr { result = Calculator.build :AND, val[0], val[2] } - | expr OR expr { result = Calculator.build :OR, val[0], val[2] } - | expr GT expr { result = Calculator.build :GT, val[0], val[2] } - | expr GTE expr { result = Calculator.build :GTE, val[0], val[2] } - | expr LT expr { result = Calculator.build :LT, val[0], val[2] } - | expr LTE expr { result = Calculator.build :LTE, val[0], val[2] } - | expr EQUAL expr { result = Calculator.build :EQUAL, val[0], val[2] } - | expr INEQUAL expr { result = Calculator.build :INEQUAL, val[0], val[2] } - | NOT expr { result = Calculator.build :NOT, val[1] } - | IDENTIFER ASSIGN expr { result = Assigner.build val[0], val[2] } - | expr PERIOD method { val[2].children[0] = val[0]; result = val[2] } - | expr AOPEN arguments ACLOSE { result = Summoner.build val[0], '[]', *val[2] } - | POPEN PCLOSE { result = nil } + expr: expr MULTIPLY expr { result = build Calculator, :MULTIPLY, val[0], val[2], position: pospoppush(3) } + | expr POWER expr { result = build Calculator, :POWER, val[0], val[2], position: pospoppush(3) } + | expr DIVIDE expr { result = build Calculator, :DIVIDE, val[0], val[2], position: pospoppush(3) } + | expr PLUS expr { result = build Calculator, :PLUS, val[0], val[2], position: pospoppush(3) } + | expr MINUS expr { result = build Calculator, :MINUS, val[0], val[2], position: pospoppush(3) } + | expr MODULO expr { result = build Calculator, :MODULO, val[0], val[2], position: pospoppush(3) } + | MINUS expr =UMINUS { result = build Calculator, :UMINUS, val[1], position: pospoppush(2) } + | PLUS expr =UPLUS { result = build Calculator, :UPLUS, val[1], position: pospoppush(2) } + | expr AND expr { result = build Calculator, :AND, val[0], val[2], position: pospoppush(3) } + | expr OR expr { result = build Calculator, :OR, val[0], val[2], position: pospoppush(3) } + | expr GT expr { result = build Calculator, :GT, val[0], val[2], position: pospoppush(3) } + | expr GTE expr { result = build Calculator, :GTE, val[0], val[2], position: pospoppush(3) } + | expr LT expr { result = build Calculator, :LT, val[0], val[2], position: pospoppush(3) } + | expr LTE expr { result = build Calculator, :LTE, val[0], val[2], position: pospoppush(3) } + | expr EQUAL expr { result = build Calculator, :EQUAL, val[0], val[2], position: pospoppush(3) } + | expr INEQUAL expr { result = build Calculator, :INEQUAL, val[0], val[2], position: pospoppush(3) } + | NOT expr { result = build Calculator, :NOT, val[1], position: pospoppush(2) } + | IDENTIFER ASSIGN expr { result = build Assigner, val[0], val[2], position: pospoppush(3) } + | expr PERIOD method { pospoppush(3); val[2].children[0] = val[0]; result = val[2] } + | expr AOPEN arguments ACLOSE { + result = build Summoner, 'manipulator_brackets', val[0], *val[2], position: pospoppush(4) + } + | POPEN PCLOSE { pospoppush(2); result = nil } | POPEN sequence PCLOSE { - result = case val[1].size - when 0 - nil - when 1 - val[1][0] - else - Sequencer.build :SEQUENCE, *val[1].flatten - end - } + position = pospoppush(3) + result = case val[1].size + when 1 + val[1][0] + else + build Sequencer, :SEQUENCE, *val[1].flatten, position: position + end + } | value - value: const | number | string | array | hash | method# | ternary + value: const | number | string | array | hash | method const: NIL | TRUE | FALSE number: INTEGER | FLOAT string: STRING | REGEXP - # ternary: expr QUESTION expr COLON expr =TERNARY { result = Node.build :TERNARY, val[0], val[2], val[4] } - - array: AOPEN ACLOSE { result = Arrayer.build :ARRAY } - | AOPEN params ACLOSE { result = Arrayer.build :ARRAY, *val[1] } - params: params COMMA expr { val[0].push(val[2]) } + array: AOPEN ACLOSE { result = build Arrayer, :ARRAY, position: pospoppush(2) } + | AOPEN params ACLOSE { result = build Arrayer, :ARRAY, *val[1], position: pospoppush(3) } + params: params COMMA expr { pospoppush(3); val[0].push(val[2]) } | expr { result = [val[0]] } - hash: HOPEN HCLOSE { result = Hasher.build :HASH } - | HOPEN pairs HCLOSE { result = Hasher.build :HASH, *val[1] } - pairs: pairs COMMA pair { val[0].push(val[2]) } + hash: HOPEN HCLOSE { result = build Hasher, :HASH, position: pospoppush(2) } + | HOPEN pairs HCLOSE { result = build Hasher, :HASH, *val[1], position: pospoppush(3) } + pairs: pairs COMMA pair { pospoppush(3); val[0].push(val[2]) } | pair { result = [val[0]] } - pair: IDENTIFER COLON expr { result = Arrayer.build :PAIR, val[0], val[2] } + pair: IDENTIFER COLON expr { result = build Arrayer, :PAIR, val[0], val[2], position: pospoppush(3) } - arguments: params COMMA pairs { result = [*val[0], Hasher.build(:HASH, *val[2])] } + arguments: params COMMA pairs { result = [*val[0], build(Hasher, :HASH, *val[2], position: pospoppush(3))] } | params - | pairs { result = Hasher.build(:HASH, *val[0]) } - method: IDENTIFER { result = Summoner.build nil, val[0] } - | IDENTIFER POPEN PCLOSE { result = Summoner.build nil, val[0] } - | IDENTIFER POPEN arguments PCLOSE { result = Summoner.build nil, val[0], *val[2] } + | pairs { result = build Hasher, :HASH, *val[0], position: pospoppush(1) } + method: IDENTIFER { result = build Summoner, val[0], position: pospoppush(1) } + | IDENTIFER POPEN PCLOSE { result = build Summoner, val[0], position: pospoppush(3) } + | IDENTIFER POPEN arguments PCLOSE { + result = build Summoner, val[0], nil, *val[2], position: pospoppush(4) + } ----- header - require 'hotcell/lexer' ---- inner - NEWLINE_PRED = Set.new(Lexer::BOPEN.values + Lexer::OPERATIONS.values) - NEWLINE_NEXT = Set.new(Lexer::BCLOSE.values + [:NEWLINE]) + OPERATIONS = { + '+' => :PLUS, '-' => :MINUS, '*' => :MULTIPLY, '**' => :POWER, '/' => :DIVIDE, '%' => :MODULO, + '&&' => :AND, '||' => :OR, '!' => :NOT, '==' => :EQUAL, '!=' => :INEQUAL, + '>' => :GT, '>=' => :GTE, '<' => :LT, '<=' => :LTE, + + '=' => :ASSIGN, ',' => :COMMA, '.' => :PERIOD, ':' => :COLON, '?' => :QUESTION, + ';' => :SEMICOLON + } + + BOPEN = { '[' => :AOPEN, '{' => :HOPEN, '(' => :POPEN } + BCLOSE = { ']' => :ACLOSE, '}' => :HCLOSE, ')' => :PCLOSE } + + NEWLINE_PRED = Set.new(BOPEN.values + OPERATIONS.values) + NEWLINE_NEXT = Set.new(BCLOSE.values + [:NEWLINE]) + TAG_MODES = { '{{' => :normal, '{{!' => :silence } - def initialize string, options = {} - @lexer = Lexer.new(string) + def initialize source, options = {} + @source = Source.wrap(source) + @lexer = Lexer.new(source) @tokens = @lexer.tokens @position = -1 - @commands = Set.new(Array.wrap(options[:commands]).map(&:to_s)) - @blocks = Set.new(Array.wrap(options[:blocks]).map(&:to_s)) - @endblocks = Set.new(Array.wrap(options[:blocks]).map { |identifer| "end#{identifer}" }) - @subcommands = Set.new(Array.wrap(options[:subcommands]).map(&:to_s)) + @commands = options[:commands] || {} + @blocks = options[:blocks] || {} + @endblocks = Set.new(@blocks.keys.map { |identifer| "end#{identifer}" }) + + @substack = [] + @posstack = [] end + def build klass, *args + options = args.extract_options! + options[:source] = @source + klass.build *args.push(options) + end + + def pospoppush pop, push = 0 + # because fuck the brains, that's why! + last = @posstack.pop + reduced = @posstack.push(@posstack.pop(pop)[push])[-1] + @posstack.push last + reduced + end + def parse if @tokens.size == 0 - Joiner.build :JOINER + build Joiner, :JOINER, position: 0 else do_parse end end def next_token @position = @position + 1 - tcurr = @tokens[@position] - tnext = @tokens[@position.next] - tpred = @tokens[@position.pred] if tcurr && (tcurr[0] == :COMMENT || tcurr[0] == :NEWLINE && ( - (tpred && NEWLINE_PRED.include?(tpred[0])) || - (tnext && NEWLINE_NEXT.include?(tnext[0])) + ((tpred = @tokens[@position.pred]) && NEWLINE_PRED.include?(tpred[0])) || + ((tnext = @tokens[@position.next]) && NEWLINE_NEXT.include?(tnext[0])) )) next_token else + if tcurr + @posstack << tcurr[1][1] + tcurr = [tcurr[0], tcurr[1][0]] + end + if tcurr && tcurr[0] == :IDENTIFER - if @commands.include?(tcurr[1]) + if @commands.key?(tcurr[1]) [:COMMAND, tcurr[1]] - elsif @blocks.include?(tcurr[1]) + elsif @blocks.key?(tcurr[1]) + @substack.push(@blocks[tcurr[1]].subcommands) [:BLOCK, tcurr[1]] + elsif @substack.last && @substack.last.key?(tcurr[1]) + [:SUBCOMMAND, tcurr[1]] elsif @endblocks.include?(tcurr[1]) + @substack.pop [:ENDBLOCK, tcurr[1]] - elsif @subcommands.include?(tcurr[1]) - [:SUBCOMMAND, tcurr[1]] elsif tcurr[1] == 'end' + @substack.pop [:END, tcurr[1]] else tcurr end else tcurr || [false, false] end end + end + + def on_error(token, value, vstack) + raise Hotcell::UnexpectedLexem.new("#{token_to_str(token) || '?'} `#{value}`", + *@source.info(@posstack.last).values_at(:line, :column)) end