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
|