mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
313 lines
8.8 KiB
Text
313 lines
8.8 KiB
Text
# Copyright (c) 2012-2013 Peter Zotov <whitequark@whitequark.org>
|
|
# 2012 Yaroslav Markin <yaroslav@markin.net>
|
|
# 2012 Nate Gadgibalaev <nat@xnsv.ru>
|
|
#
|
|
# MIT License
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
class Liquor::Parser
|
|
token comma dot endtag ident integer keyword lblock lblock2 lbracket
|
|
linterp lparen op_div op_eq op_gt op_geq op_lt op_leq op_minus
|
|
op_mod op_mul op_neq op_not op_plus pipe plaintext rblock
|
|
rbracket rinterp rparen string tag_ident
|
|
|
|
prechigh
|
|
left dot
|
|
nonassoc op_uminus op_not
|
|
left op_mul op_div op_mod
|
|
left op_plus op_minus
|
|
left op_eq op_neq op_lt op_leq op_gt op_geq
|
|
left op_and
|
|
left op_or
|
|
preclow
|
|
|
|
expect 15
|
|
|
|
start block
|
|
|
|
rule
|
|
block: /* empty */
|
|
{ result = [] }
|
|
| plaintext block
|
|
{ result = [ val[0], *val[1] ] }
|
|
| interp block
|
|
{ result = [ val[0], *val[1] ] }
|
|
| tag block
|
|
{ result = [ val[0], *val[1] ] }
|
|
|
|
interp:
|
|
linterp expr rinterp
|
|
{ result = [ :interp, retag(val), val[1] ] }
|
|
| linterp filter_chain rinterp
|
|
{ result = [ :interp, retag(val), val[1] ] }
|
|
|
|
primary_expr:
|
|
ident
|
|
| lparen expr rparen
|
|
{ result = [ val[1][0], retag(val), *val[1][2..-1] ] }
|
|
|
|
expr:
|
|
integer
|
|
| string
|
|
| tuple
|
|
| ident function_args
|
|
{ result = [ :call, retag(val), val[0], val[1] ] }
|
|
| expr lbracket expr rbracket
|
|
{ result = [ :index, retag(val), val[0], val[2] ] }
|
|
| expr dot ident function_args
|
|
{ result = [ :external, retag(val), val[0], val[2], val[3] ] }
|
|
| expr dot ident
|
|
{ result = [ :external, retag(val), val[0], val[2], nil ] }
|
|
| op_minus expr =op_uminus
|
|
{ result = [ :uminus, retag(val), val[1] ] }
|
|
| op_not expr
|
|
{ result = [ :not, retag(val), val[1] ] }
|
|
| expr op_mul expr
|
|
{ result = [ :mul, retag(val), val[0], val[2] ] }
|
|
| expr op_div expr
|
|
{ result = [ :div, retag(val), val[0], val[2] ] }
|
|
| expr op_mod expr
|
|
{ result = [ :mod, retag(val), val[0], val[2] ] }
|
|
| expr op_plus expr
|
|
{ result = [ :plus, retag(val), val[0], val[2] ] }
|
|
| expr op_minus expr
|
|
{ result = [ :minus, retag(val), val[0], val[2] ] }
|
|
| expr op_eq expr
|
|
{ result = [ :eq, retag(val), val[0], val[2] ] }
|
|
| expr op_neq expr
|
|
{ result = [ :neq, retag(val), val[0], val[2] ] }
|
|
| expr op_lt expr
|
|
{ result = [ :lt, retag(val), val[0], val[2] ] }
|
|
| expr op_leq expr
|
|
{ result = [ :leq, retag(val), val[0], val[2] ] }
|
|
| expr op_gt expr
|
|
{ result = [ :gt, retag(val), val[0], val[2] ] }
|
|
| expr op_geq expr
|
|
{ result = [ :geq, retag(val), val[0], val[2] ] }
|
|
| expr op_and expr
|
|
{ result = [ :and, retag(val), val[0], val[2] ] }
|
|
| expr op_or expr
|
|
{ result = [ :or, retag(val), val[0], val[2] ] }
|
|
| primary_expr
|
|
|
|
tuple:
|
|
lbracket tuple_content rbracket
|
|
{ result = [ :tuple, retag(val), val[1].compact ] }
|
|
|
|
tuple_content:
|
|
expr comma tuple_content
|
|
{ result = [ val[0], *val[2] ] }
|
|
| expr
|
|
{ result = [ val[0] ] }
|
|
| /* empty */
|
|
{ result = [ ] }
|
|
|
|
function_args:
|
|
lparen function_args_inside rparen
|
|
{ result = [ :args, retag(val), *val[1] ] }
|
|
|
|
function_args_inside:
|
|
expr function_keywords
|
|
{ result = [ val[0], val[1][2] ] }
|
|
| function_keywords
|
|
{ result = [ nil, val[0][2] ] }
|
|
|
|
function_keywords:
|
|
keyword expr function_keywords
|
|
{ name = val[0][2].to_sym
|
|
tail = val[2][2]
|
|
loc = retag([ val[0], val[1] ])
|
|
|
|
if tail.include? name
|
|
@errors << SyntaxError.new("duplicate keyword argument `#{val[0][2]}'",
|
|
tail[name][1])
|
|
end
|
|
|
|
hash = {
|
|
name => [ val[1][0], loc, *val[1][2..-1] ]
|
|
}.merge(tail)
|
|
|
|
result = [ :keywords, retag([ loc, val[2] ]), hash ]
|
|
}
|
|
| /* empty */
|
|
{ result = [ :keywords, nil, {} ] }
|
|
|
|
filter_chain:
|
|
expr pipe filter_chain_cont
|
|
{ result = [ val[0], *val[2] ].
|
|
reduce { |tree, node| node[3][2] = tree; node }
|
|
}
|
|
|
|
filter_chain_cont:
|
|
filter_call pipe filter_chain_cont
|
|
{ result = [ val[0], *val[2] ] }
|
|
| filter_call
|
|
{ result = [ val[0] ] }
|
|
|
|
filter_call:
|
|
ident function_keywords
|
|
{ ident_loc = val[0][1]
|
|
empty_args_loc = { line: ident_loc[:line],
|
|
start: ident_loc[:end] + 1,
|
|
end: ident_loc[:end] + 1, }
|
|
result = [ :call, val[0][1], val[0],
|
|
[ :args, val[1][1] || empty_args_loc, nil, val[1][2] ] ]
|
|
}
|
|
|
|
tag:
|
|
lblock ident expr tag_first_cont
|
|
{ result = [ :tag, retag(val), val[1], val[2], *reduce_tag_args(val[3][2]) ] }
|
|
| lblock ident tag_first_cont
|
|
{ result = [ :tag, retag(val), val[1], nil, *reduce_tag_args(val[2][2]) ] }
|
|
|
|
# Racc cannot do lookahead across rules. I had to add states
|
|
# explicitly to avoid S/R conflicts. You are not expected to
|
|
# understand this.
|
|
|
|
tag_first_cont:
|
|
rblock
|
|
{ result = [ :cont, retag(val), [] ] }
|
|
| keyword tag_first_cont2
|
|
{ result = [ :cont, retag(val), [ val[0], *val[1][2] ] ] }
|
|
|
|
tag_first_cont2:
|
|
rblock block lblock2 tag_next_cont
|
|
{ result = [ :cont2, val[0][1], [ [:block, val[0][1], val[1] ], *val[3] ] ] }
|
|
| expr tag_first_cont
|
|
{ result = [ :cont2, retag(val), [ val[0], *val[1][2] ] ] }
|
|
|
|
tag_next_cont:
|
|
endtag rblock
|
|
{ result = [] }
|
|
| keyword tag_next_cont2
|
|
{ result = [ val[0], *val[1] ] }
|
|
|
|
tag_next_cont2:
|
|
rblock block lblock2 tag_next_cont
|
|
{ result = [ [:block, val[0][1], val[1] ], *val[3] ] }
|
|
| expr keyword tag_next_cont3
|
|
{ result = [ val[0], val[1], *val[2] ] }
|
|
|
|
tag_next_cont3:
|
|
rblock block lblock2 tag_next_cont
|
|
{ result = [ [:block, val[0][1], val[1] ], *val[3] ] }
|
|
| expr tag_next_cont
|
|
{ result = [ val[0], *val[1] ] }
|
|
|
|
---- inner
|
|
attr_reader :errors, :ast
|
|
|
|
def initialize(tags={})
|
|
super()
|
|
|
|
@errors = []
|
|
@ast = nil
|
|
@tags = tags
|
|
end
|
|
|
|
def success?
|
|
@errors.empty?
|
|
end
|
|
|
|
def parse(string, name='(code)')
|
|
@errors.clear
|
|
@name = name
|
|
@ast = nil
|
|
|
|
begin
|
|
@stream = Lexer.lex(string, @name, @tags)
|
|
@ast = do_parse
|
|
rescue Liquor::SyntaxError => e
|
|
@errors << e
|
|
end
|
|
|
|
success?
|
|
end
|
|
|
|
def next_token
|
|
tok = @stream.shift
|
|
[ tok[0], tok ] if tok
|
|
end
|
|
|
|
TOKEN_NAME_MAP = {
|
|
:comma => ',',
|
|
:dot => '.',
|
|
:lblock => '{%',
|
|
:rblock => '%}',
|
|
:linterp => '{{',
|
|
:rinterp => '}}',
|
|
:lbracket => '[',
|
|
:rbracket => ']',
|
|
:lparen => '(',
|
|
:rparen => ')',
|
|
:pipe => '|',
|
|
:op_not => '!',
|
|
:op_mul => '*',
|
|
:op_div => '/',
|
|
:op_mod => '%',
|
|
:op_plus => '+',
|
|
:op_minus => '-',
|
|
:op_eq => '==',
|
|
:op_neq => '!=',
|
|
:op_lt => '<',
|
|
:op_leq => '<=',
|
|
:op_gt => '>',
|
|
:op_geq => '>=',
|
|
:keyword => 'keyword argument name',
|
|
:kwarg => 'keyword argument',
|
|
:ident => 'identifier',
|
|
}
|
|
|
|
def on_error(error_token_id, error_token, value_stack)
|
|
if token_to_str(error_token_id) == "$end"
|
|
raise Liquor::SyntaxError.new("unexpected end of program", {
|
|
file: @name
|
|
})
|
|
else
|
|
type, (loc, value) = error_token
|
|
type = TOKEN_NAME_MAP[type] || type
|
|
|
|
raise Liquor::SyntaxError.new("unexpected token `#{type}'", loc)
|
|
end
|
|
end
|
|
|
|
def retag(nodes)
|
|
loc = nodes.map { |node| node[1] }.compact
|
|
first, *, last = loc
|
|
return first if last.nil?
|
|
|
|
{
|
|
file: first[:file],
|
|
line: first[:line],
|
|
start: first[:start],
|
|
end: last[:end],
|
|
}
|
|
end
|
|
|
|
def reduce_tag_args(list)
|
|
list.each_slice(2).reduce([]) { |args, (k, v)|
|
|
if v[0] == :block
|
|
args << [ :blockarg, retag([ k, v ]), k, v[2] || [] ]
|
|
else
|
|
args << [ :kwarg, retag([ k, v ]), k, v ]
|
|
end
|
|
}
|
|
end
|