mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 05:29:10 +02:00

As preparation for https://bugs.ruby-lang.org/issues/20205 making sure the test suite is compatible with frozen string literals is making things easier.
100 lines
2.5 KiB
Ruby
100 lines
2.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Simple DSL implementation for Ripper code generation
|
|
#
|
|
# input: /*% ripper: stmts_add!(stmts_new!, void_stmt!) %*/
|
|
# output:
|
|
# VALUE v1, v2;
|
|
# v1 = dispatch0(stmts_new);
|
|
# v2 = dispatch0(void_stmt);
|
|
# $$ = dispatch2(stmts_add, v1, v2);
|
|
#
|
|
# - The code must be a single line.
|
|
#
|
|
# - The code is basically Ruby code, even if it appears like in C and
|
|
# the result will be processed as C. e.g., comments need to be in
|
|
# Ruby style.
|
|
|
|
class DSL
|
|
TAG_PATTERN = /(?><[a-zA-Z0-9_]+>)/.source
|
|
NAME_PATTERN = /(?>\$|\d+|[a-zA-Z_][a-zA-Z0-9_]*|\[[a-zA-Z_.][-a-zA-Z0-9_.]*\])(?>(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*)*/.source
|
|
NOT_REF_PATTERN = /(?>\#.*|[^\"$@]*|"(?>\\.|[^\"])*")/.source
|
|
|
|
def initialize(code, options, lineno = nil)
|
|
@lineno = lineno
|
|
@events = {}
|
|
@error = options.include?("error")
|
|
@brace = options.include?("brace")
|
|
if options.include?("final")
|
|
@final = "p->result"
|
|
else
|
|
@final = (options.grep(/\A\$#{NAME_PATTERN}\z/o)[0] || "p->s_lvalue")
|
|
end
|
|
@vars = 0
|
|
|
|
# struct parser_params *p
|
|
p = p = "p"
|
|
|
|
@code = +""
|
|
code = code.gsub(%r[\G#{NOT_REF_PATTERN}\K(\$|\$:|@)#{TAG_PATTERN}?#{NAME_PATTERN}]o, '"\&"')
|
|
@last_value = eval(code)
|
|
rescue SyntaxError
|
|
$stderr.puts "error on line #{@lineno}" if @lineno
|
|
raise
|
|
end
|
|
|
|
attr_reader :events
|
|
|
|
undef lambda
|
|
undef hash
|
|
undef class
|
|
|
|
def generate
|
|
s = "#@code#@final=#@last_value;"
|
|
s = "{VALUE #{ (1..@vars).map {|v| "v#{ v }" }.join(",") };#{ s }}" if @vars > 0
|
|
s << "ripper_error(p);" if @error
|
|
s = "{#{ s }}" if @brace
|
|
"\t\t\t#{s}"
|
|
end
|
|
|
|
def new_var
|
|
"v#{ @vars += 1 }"
|
|
end
|
|
|
|
def opt_event(event, default, addend)
|
|
add_event(event, [default, addend], true)
|
|
end
|
|
|
|
def add_event(event, args, qundef_check = false)
|
|
event = event.to_s.sub(/!\z/, "")
|
|
@events[event] = args.size
|
|
vars = []
|
|
args.each do |arg|
|
|
vars << v = new_var
|
|
if arg =~ /\A\$:#{NAME_PATTERN}\z/
|
|
@code << "#{ v }=get_value(#{arg});"
|
|
else
|
|
@code << "#{ v }=#{ arg };"
|
|
end
|
|
end
|
|
v = new_var
|
|
d = "dispatch#{ args.size }(#{ [event, *vars].join(",") })"
|
|
d = "#{ vars.last }==rb_ripper_none ? #{ vars.first } : #{ d }" if qundef_check
|
|
@code << "#{ v }=#{ d };"
|
|
v
|
|
end
|
|
|
|
def method_missing(event, *args)
|
|
if event.to_s =~ /!\z/
|
|
add_event(event, args)
|
|
elsif args.empty? and /\Aid[A-Z_]/ =~ event.to_s
|
|
event
|
|
else
|
|
"#{ event }(#{ args.join(", ") })"
|
|
end
|
|
end
|
|
|
|
def self.const_missing(name)
|
|
name
|
|
end
|
|
end
|