1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
# 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 self.line?(line, lineno = nil, indent: nil)
if %r<(?<space>\s*)/\*% *ripper(?:\[(?<option>.*?)\])?: *(?<code>.*?) *%\*/> =~ line
new(code, comma_split(option), lineno, indent: indent || space)
end
end
def self.comma_split(str)
str or return []
str.scan(/(([^(,)]+|\((?:,|\g<0>)*\))+)/).map(&:first)
end
using Module.new {
refine Array do
def to_s
if empty?
"rb_ary_new()"
else
"rb_ary_new_from_args(#{size}, #{map(&:to_s).join(', ')})"
end
end
end
}
class Var
class Table < Hash
def initialize(&block)
super() {|tbl, arg|
tbl.fetch(arg, &block)
}
end
def fetch(arg, &block)
super {
self[arg] = Var.new(self, arg, &block)
}
end
def add(&block)
v = new_var
self[v] = Var.new(self, v, &block)
end
def defined?(name)
name = name.to_s
any? {|_, v| v.var == name}
end
def new_var
"v#{size+1}"
end
end
attr_reader :var, :value
PRETTY_PRINT_INSTANCE_VARIABLES = instance_methods(false).freeze
def pretty_print_instance_variables
PRETTY_PRINT_INSTANCE_VARIABLES
end
alias to_s var
def initialize(table, arg, &block)
@var = table.new_var
@value = yield arg
@table = table
end
# Indexing.
#
# $:1 -> v1=get_value($:1)
# $:1[0] -> rb_ary_entry(v1, 0)
# $:1[0..1] -> [rb_ary_entry(v1, 0), rb_ary_entry(v1, 1)]
# *$:1[0..1] -> rb_ary_entry(v1, 0), rb_ary_entry(v1, 1)
#
# Splat needs `[range]` because `Var` does not have the length info.
def [](idx)
if ::Range === idx
idx.map {|i| self[i]}
else
@table.fetch("#@var[#{idx}]") {"rb_ary_entry(#{@var}, #{idx})"}
end
end
end
def initialize(code, options, lineno = nil, indent: "\t\t\t")
@lineno = lineno
@indent = indent
@events = {}
@error = options.include?("error")
if options.include?("final")
@final = "p->result"
else
@final = (options.grep(/\A\$#{NAME_PATTERN}\z/o)[0] || "p->s_lvalue")
end
bind = dsl_binding
@var_table = Var::Table.new {|arg| "get_value(#{arg})"}
code = code.gsub(%r[\G#{NOT_REF_PATTERN}\K(\$|\$:|@)#{TAG_PATTERN}?#{NAME_PATTERN}]o) {
if (arg = $&) == "$:$"
'"p->s_lvalue"'
elsif arg.start_with?("$:")
"(#{@var_table[arg]}=@var_table[#{arg.dump}])"
else
arg.dump
end
}
@last_value = bind.eval(code)
rescue SyntaxError
$stderr.puts "error on line #{@lineno}" if @lineno
raise
end
def dsl_binding(p = "p")
# struct parser_params *p
binding
end
attr_reader :events
undef lambda
undef hash
undef :class
def generate
s = "#@final=#@last_value;"
s << "ripper_error(p);" if @error
unless @var_table.empty?
vars = @var_table.map {|_, v| "#{v.var}=#{v.value}"}.join(", ")
s = "VALUE #{ vars }; #{ s }"
end
"#{@indent}{#{s}}"
end
def add_event(event, args)
event = event.to_s.sub(/!\z/, "")
@events[event] = args.size
vars = []
args.each do |arg|
arg = @var_table.add {arg} unless Var === arg
vars << arg
end
@var_table.add {"dispatch#{ args.size }(#{ [event, *vars].join(",") })"}
end
def method_missing(event, *args)
if event.to_s =~ /!\z/
add_event(event, args)
elsif args.empty? and (/\Aid[A-Z_]/ =~ event or @var_table.defined?(event))
event
else
"#{ event }(#{ args.map(&:to_s).join(", ") })"
end
end
def self.const_missing(name)
name
end
end
|