105 lines
2.7 KiB
Ruby
105 lines
2.7 KiB
Ruby
|
require 'rkelly/tokenizer'
|
||
|
require 'rkelly/generated_parser'
|
||
|
|
||
|
|
||
|
module RKelly
|
||
|
class Parser < RKelly::GeneratedParser
|
||
|
TOKENIZER = Tokenizer.new
|
||
|
|
||
|
RKelly::GeneratedParser.instance_methods.each do |im|
|
||
|
next unless im.to_s =~ /^_reduce_\d+$/
|
||
|
eval(<<-eoawesomehack)
|
||
|
def #{im}(val, _values, result)
|
||
|
r = super(val.map { |v|
|
||
|
v.is_a?(Token) ? v.to_racc_token[1] : v
|
||
|
}, _values, result)
|
||
|
if token = val.find { |v| v.is_a?(Token) }
|
||
|
r.line = token.line if r.respond_to?(:line)
|
||
|
r.filename = @filename if r.respond_to?(:filename)
|
||
|
end
|
||
|
r
|
||
|
end
|
||
|
eoawesomehack
|
||
|
end
|
||
|
|
||
|
attr_accessor :logger
|
||
|
def initialize
|
||
|
@tokens = []
|
||
|
@logger = nil
|
||
|
@terminator = false
|
||
|
@prev_token = nil
|
||
|
@comments = []
|
||
|
end
|
||
|
|
||
|
# Parse +javascript+ and return an AST
|
||
|
def parse(javascript, filename = nil)
|
||
|
@tokens = TOKENIZER.raw_tokens(javascript)
|
||
|
@position = 0
|
||
|
@filename = filename
|
||
|
ast = do_parse
|
||
|
apply_comments(ast)
|
||
|
end
|
||
|
|
||
|
def yyabort
|
||
|
raise "something bad happened, please report a bug with sample JavaScript"
|
||
|
end
|
||
|
|
||
|
private
|
||
|
def apply_comments(ast)
|
||
|
ast_hash = Hash.new { |h,k| h[k] = [] }
|
||
|
(ast || []).each { |n|
|
||
|
next unless n.line
|
||
|
ast_hash[n.line] << n
|
||
|
}
|
||
|
max = ast_hash.keys.sort.last
|
||
|
@comments.each do |comment|
|
||
|
node = nil
|
||
|
comment.line.upto(max) do |line|
|
||
|
if ast_hash.key?(line)
|
||
|
node = ast_hash[line].first
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
node.comments << comment if node
|
||
|
end if max
|
||
|
ast
|
||
|
end
|
||
|
|
||
|
def on_error(error_token_id, error_value, value_stack)
|
||
|
if logger
|
||
|
logger.error(token_to_str(error_token_id))
|
||
|
logger.error("error value: #{error_value}")
|
||
|
logger.error("error stack: #{value_stack}")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def next_token
|
||
|
@terminator = false
|
||
|
begin
|
||
|
return [false, false] if @position >= @tokens.length
|
||
|
n_token = @tokens[@position]
|
||
|
@position += 1
|
||
|
case @tokens[@position - 1].name
|
||
|
when :COMMENT
|
||
|
@comments << n_token
|
||
|
@terminator = true if n_token.value =~ /^\/\//
|
||
|
when :S
|
||
|
@terminator = true if n_token.value =~ /[\r\n]/
|
||
|
end
|
||
|
end while([:COMMENT, :S].include?(n_token.name))
|
||
|
|
||
|
if @terminator &&
|
||
|
((@prev_token && %w[continue break return throw].include?(@prev_token.value)) ||
|
||
|
(n_token && %w[++ --].include?(n_token.value)))
|
||
|
@position -= 1
|
||
|
return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token
|
||
|
end
|
||
|
|
||
|
@prev_token = n_token
|
||
|
v = n_token.to_racc_token
|
||
|
v[1] = n_token
|
||
|
v
|
||
|
end
|
||
|
end
|
||
|
end
|