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