peruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +35 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +92 -0
- data/bin/peruby +9 -0
- data/doc/COMPAT.md +54 -0
- data/doc/CONTRIBUTING.md +21 -0
- data/doc/DESIGN.md +25 -0
- data/doc/INCOMPATIBILITIES.md +30 -0
- data/doc/PERF.md +51 -0
- data/doc/ROADMAP.md +18 -0
- data/examples/hello.pl +1 -0
- data/examples/json.pl +2 -0
- data/examples/object.pl +5 -0
- data/examples/word_count.pl +6 -0
- data/lib/peruby/cli.rb +224 -0
- data/lib/peruby/compile_unit.rb +103 -0
- data/lib/peruby/compiler.rb +224 -0
- data/lib/peruby/errors.rb +40 -0
- data/lib/peruby/lexer/heredoc.rb +8 -0
- data/lib/peruby/lexer/keywords.rb +63 -0
- data/lib/peruby/lexer/number.rb +32 -0
- data/lib/peruby/lexer/quote_like.rb +72 -0
- data/lib/peruby/lexer/source_scanner.rb +104 -0
- data/lib/peruby/lexer/state.rb +46 -0
- data/lib/peruby/lexer/structure_scanner.rb +149 -0
- data/lib/peruby/lexer/term_scanner.rb +301 -0
- data/lib/peruby/lexer/token.rb +16 -0
- data/lib/peruby/lexer.rb +123 -0
- data/lib/peruby/node.rb +88 -0
- data/lib/peruby/op/assign.rb +128 -0
- data/lib/peruby/op/builtin.rb +903 -0
- data/lib/peruby/op/call.rb +378 -0
- data/lib/peruby/op/control.rb +256 -0
- data/lib/peruby/op/element.rb +136 -0
- data/lib/peruby/op/expression.rb +342 -0
- data/lib/peruby/op/io.rb +113 -0
- data/lib/peruby/op/list.rb +102 -0
- data/lib/peruby/op/literal.rb +84 -0
- data/lib/peruby/op/loop.rb +158 -0
- data/lib/peruby/op/regexp.rb +288 -0
- data/lib/peruby/op/variable.rb +534 -0
- data/lib/peruby/op.rb +47 -0
- data/lib/peruby/parser/grammar.rb +5797 -0
- data/lib/peruby/parser/grammar.y +576 -0
- data/lib/peruby/parser.rb +14 -0
- data/lib/peruby/runtime/code.rb +21 -0
- data/lib/peruby/runtime/conv.rb +140 -0
- data/lib/peruby/runtime/directory_handle.rb +18 -0
- data/lib/peruby/runtime/env.rb +129 -0
- data/lib/peruby/runtime/glob.rb +24 -0
- data/lib/peruby/runtime/interpolation.rb +223 -0
- data/lib/peruby/runtime/io_handle.rb +37 -0
- data/lib/peruby/runtime/local_stack.rb +90 -0
- data/lib/peruby/runtime/match_state.rb +62 -0
- data/lib/peruby/runtime/module_loader.rb +133 -0
- data/lib/peruby/runtime/mro.rb +94 -0
- data/lib/peruby/runtime/perl_array.rb +81 -0
- data/lib/peruby/runtime/perl_hash.rb +57 -0
- data/lib/peruby/runtime/ref.rb +43 -0
- data/lib/peruby/runtime/regexp_compiler.rb +75 -0
- data/lib/peruby/runtime/scalar.rb +27 -0
- data/lib/peruby/runtime/sprintf.rb +54 -0
- data/lib/peruby/runtime/stash.rb +50 -0
- data/lib/peruby/runtime/test_builder.rb +47 -0
- data/lib/peruby/runtime.rb +325 -0
- data/lib/peruby/validator.rb +236 -0
- data/lib/peruby/version.rb +5 -0
- data/lib/peruby.rb +31 -0
- data/t/00-basic.t +5 -0
- data/t/lib/MiniTest.pm +22 -0
- metadata +130 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peruby
|
|
4
|
+
module Op
|
|
5
|
+
# Variable read and lvalue lookup.
|
|
6
|
+
class Variable < Base
|
|
7
|
+
def initialize(sigil, name)
|
|
8
|
+
@sigil = sigil
|
|
9
|
+
@name = name
|
|
10
|
+
@key = [sigil, name].freeze
|
|
11
|
+
@dynamic_match = (sigil == :scalar && name.match?(/\A(?:\d+|[&`'+])\z/)) ||
|
|
12
|
+
(%i[array hash].include?(sigil) && %w[- +].include?(name))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(env, context)
|
|
16
|
+
value = lookup(env)
|
|
17
|
+
result(value, context)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_callable
|
|
21
|
+
lookup = lookup_callable
|
|
22
|
+
lambda do |env, context|
|
|
23
|
+
value = lookup.call(env)
|
|
24
|
+
return value.list if context == :list && value.is_a?(PerlHash)
|
|
25
|
+
return value.list if context == :list && value.is_a?(PerlArray)
|
|
26
|
+
return value.cells.dup if context == :list && value.respond_to?(:cells)
|
|
27
|
+
return [value] if context == :list
|
|
28
|
+
return value.size if @sigil == :array
|
|
29
|
+
return value.cells.size if @sigil == :hash
|
|
30
|
+
|
|
31
|
+
value.value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def scalar_callable
|
|
36
|
+
lookup = lookup_callable
|
|
37
|
+
sigil = @sigil
|
|
38
|
+
lambda do |env|
|
|
39
|
+
value = lookup.call(env)
|
|
40
|
+
next value.size if sigil == :array
|
|
41
|
+
next value.cells.size if sigil == :hash
|
|
42
|
+
|
|
43
|
+
value.value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def lvalue_callable = lookup_callable
|
|
48
|
+
|
|
49
|
+
def first_cell_callable
|
|
50
|
+
lookup = lookup_callable
|
|
51
|
+
->(env) { (value = lookup.call(env)).respond_to?(:cells) ? value.cells.first : value }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def result(value, context)
|
|
55
|
+
return value.list if context == :list && value.is_a?(PerlHash)
|
|
56
|
+
return value.list if context == :list && value.is_a?(PerlArray)
|
|
57
|
+
return value.cells.dup if context == :list && value.respond_to?(:cells)
|
|
58
|
+
return [value] if context == :list
|
|
59
|
+
return value.size if @sigil == :array
|
|
60
|
+
return value.cells.size if @sigil == :hash
|
|
61
|
+
|
|
62
|
+
value.get
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def lvalue(env, context)
|
|
66
|
+
value = lookup(env)
|
|
67
|
+
context == :list && value.respond_to?(:cells) ? value.cells : value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def argument_cells(env)
|
|
71
|
+
value = lookup(env)
|
|
72
|
+
return value.list if value.is_a?(PerlHash)
|
|
73
|
+
|
|
74
|
+
return value.list if value.is_a?(PerlArray)
|
|
75
|
+
|
|
76
|
+
value.respond_to?(:cells) ? value.cells.dup : [value]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def first_cell(env)
|
|
80
|
+
value = lookup(env)
|
|
81
|
+
value.respond_to?(:cells) ? value.cells.first : value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def aggregate_assignment? = %i[array hash].include?(@sigil)
|
|
85
|
+
|
|
86
|
+
def localize(env, stack)
|
|
87
|
+
value = lookup(env)
|
|
88
|
+
return stack.localize_scalar(value) if @sigil == :scalar
|
|
89
|
+
return stack.localize_array_value(value) if @sigil == :array
|
|
90
|
+
return stack.localize_hash_value(value) if @sigil == :hash
|
|
91
|
+
return stack.localize_entire_glob(value) if @sigil == :glob
|
|
92
|
+
|
|
93
|
+
raise PerlError, "Can't localize #{@sigil} variable"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def assign_values(env, values)
|
|
97
|
+
target = lookup(env)
|
|
98
|
+
if @sigil == :array
|
|
99
|
+
target.cells.replace(values.map { |value| Scalar.new(value) })
|
|
100
|
+
else
|
|
101
|
+
target.cells.clear
|
|
102
|
+
values.each_slice(2) { |key, value| target.slot(Conv.to_str(key)).set(value) }
|
|
103
|
+
end
|
|
104
|
+
target
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def aggregate(env, kind, dereference: false)
|
|
108
|
+
return dereference_aggregate(env, kind) if dereference
|
|
109
|
+
|
|
110
|
+
kind == @sigil ? lookup(env) : env.fetch(kind, @name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def aggregate_callable(kind, dereference: false)
|
|
114
|
+
return ->(env) { aggregate(env, kind, dereference:) } if dereference || kind != @sigil
|
|
115
|
+
|
|
116
|
+
lookup_callable
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def lookup_callable
|
|
122
|
+
key = @key
|
|
123
|
+
return ->(env) { env.fetch_key(key) } if @dynamic_match
|
|
124
|
+
|
|
125
|
+
cached_env = cached_generation = cached_value = nil
|
|
126
|
+
lambda do |env|
|
|
127
|
+
generation = env.generation
|
|
128
|
+
unless cached_env.equal?(env) && cached_generation == generation
|
|
129
|
+
cached_env = env
|
|
130
|
+
cached_generation = generation
|
|
131
|
+
cached_value = env.fetch_key(key)
|
|
132
|
+
end
|
|
133
|
+
cached_value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def lookup(env)
|
|
138
|
+
return env.fetch_key(@key) if @dynamic_match
|
|
139
|
+
return @cached_value if @cached_env.equal?(env) && @cached_generation == env.generation
|
|
140
|
+
|
|
141
|
+
@cached_env = env
|
|
142
|
+
@cached_generation = env.generation
|
|
143
|
+
@cached_value = env.fetch_key(@key)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def dereference_aggregate(env, kind)
|
|
147
|
+
expected = kind == :array ? PerlArray : PerlHash
|
|
148
|
+
cell = lookup(env)
|
|
149
|
+
cell.set(Ref.new(expected.new)) if cell.is_a?(Scalar) && cell.get.nil?
|
|
150
|
+
value = cell.is_a?(Scalar) ? cell.get : cell
|
|
151
|
+
raise PerlError, "Not a #{kind.to_s.upcase} reference" unless value.is_a?(Ref) && value.target.is_a?(expected)
|
|
152
|
+
|
|
153
|
+
value.target
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Explicit scalar/array/hash/code dereference.
|
|
158
|
+
class Dereference < Base
|
|
159
|
+
def initialize(kind, name)
|
|
160
|
+
@kind = kind
|
|
161
|
+
@name = name
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def run(env, context)
|
|
165
|
+
value = target(env)
|
|
166
|
+
return value.get if @kind == :scalar && context != :list
|
|
167
|
+
return [value] if @kind == :scalar
|
|
168
|
+
return value.list if context == :list && value.is_a?(PerlHash)
|
|
169
|
+
return value.list if context == :list && value.is_a?(PerlArray)
|
|
170
|
+
return value.cells.dup if context == :list && value.respond_to?(:cells)
|
|
171
|
+
return value.size if value.respond_to?(:size)
|
|
172
|
+
|
|
173
|
+
value
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def lvalue(env, _context)
|
|
177
|
+
target(env)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def aggregate(env, kind, dereference: false)
|
|
181
|
+
raise PerlError, 'Invalid nested dereference' if dereference
|
|
182
|
+
|
|
183
|
+
value = @kind == :scalar ? referenced_target(env) : target(env)
|
|
184
|
+
expected = kind == :array ? PerlArray : PerlHash
|
|
185
|
+
raise PerlError, "Not a #{kind.to_s.upcase} reference" unless value.is_a?(expected)
|
|
186
|
+
|
|
187
|
+
value
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def target(env)
|
|
193
|
+
target = referenced_target(env)
|
|
194
|
+
expected = { scalar: Scalar, array: PerlArray, hash: PerlHash, code: Code }.fetch(@kind)
|
|
195
|
+
raise PerlError, "Not a #{@kind.to_s.upcase} reference" unless target.is_a?(expected)
|
|
196
|
+
|
|
197
|
+
target
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def referenced_target(env)
|
|
201
|
+
value = env.fetch(:scalar, @name).get
|
|
202
|
+
return value.target if value.is_a?(Ref)
|
|
203
|
+
return symbolic_target(env, value) if value.is_a?(String)
|
|
204
|
+
|
|
205
|
+
raise PerlError, 'Not a reference'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def symbolic_target(env, name)
|
|
209
|
+
raise PerlError, "Can't use string (#{name}) as a reference while strict refs in use" if env.strict_refs?
|
|
210
|
+
|
|
211
|
+
env.fetch(@kind, name)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Dereference whose operand is an arbitrary expression, such as @{$h{list}}.
|
|
216
|
+
class DereferenceExpression < Base
|
|
217
|
+
def initialize(kind, expression)
|
|
218
|
+
@kind = kind
|
|
219
|
+
@expression = expression
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def run(env, context)
|
|
223
|
+
value = target(env)
|
|
224
|
+
return value.get if @kind == :scalar && context != :list
|
|
225
|
+
return [value] if @kind == :scalar
|
|
226
|
+
return value.list if context == :list && value.is_a?(PerlHash)
|
|
227
|
+
return value.list if context == :list && value.is_a?(PerlArray)
|
|
228
|
+
return value.cells.dup if context == :list && value.respond_to?(:cells)
|
|
229
|
+
return value.size if value.respond_to?(:size)
|
|
230
|
+
|
|
231
|
+
value
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def lvalue(env, _context)
|
|
235
|
+
target(env)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def aggregate(env, kind, dereference: false)
|
|
239
|
+
raise PerlError, 'Invalid nested dereference' if dereference
|
|
240
|
+
|
|
241
|
+
value = target(env)
|
|
242
|
+
expected = kind == :array ? PerlArray : PerlHash
|
|
243
|
+
raise PerlError, "Not a #{kind.to_s.upcase} reference" unless value.is_a?(expected)
|
|
244
|
+
|
|
245
|
+
value
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
def target(env)
|
|
251
|
+
expected = { scalar: Scalar, array: PerlArray, hash: PerlHash, code: Code }.fetch(@kind)
|
|
252
|
+
cell = begin
|
|
253
|
+
@expression.lvalue(env, :scalar)
|
|
254
|
+
rescue PerlError
|
|
255
|
+
nil
|
|
256
|
+
end
|
|
257
|
+
cell.set(Ref.new(expected.new)) if cell && cell.get.nil? && %i[array hash].include?(@kind)
|
|
258
|
+
value = cell ? cell.get : @expression.run(env, :scalar)
|
|
259
|
+
|
|
260
|
+
resolve_target(env, value, expected)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def resolve_target(env, value, expected)
|
|
264
|
+
target = value.target if value.is_a?(Ref)
|
|
265
|
+
if value.is_a?(String)
|
|
266
|
+
raise PerlError, "Can't use string (#{value}) as a reference while strict refs in use" if env.strict_refs?
|
|
267
|
+
|
|
268
|
+
target = env.fetch(@kind, value)
|
|
269
|
+
end
|
|
270
|
+
raise PerlError, 'Not a reference' unless target.is_a?(expected)
|
|
271
|
+
|
|
272
|
+
target
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Lexical variable declaration.
|
|
277
|
+
class Declare < Base
|
|
278
|
+
def initialize(sigil, name, initializer, predeclared = nil)
|
|
279
|
+
@sigil = sigil
|
|
280
|
+
@name = name
|
|
281
|
+
@initializer = initializer
|
|
282
|
+
@predeclared = predeclared
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def run(env, context)
|
|
286
|
+
values = @initializer&.run(env, @sigil == :scalar ? :scalar : :list)
|
|
287
|
+
target = declaration_target(env)
|
|
288
|
+
assign(target, values) if @initializer
|
|
289
|
+
return Array(target.respond_to?(:cells) ? target.cells : target) if context == :list
|
|
290
|
+
|
|
291
|
+
declaration_value(target)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def to_callable
|
|
295
|
+
initializer = @initializer&.to_callable
|
|
296
|
+
scalar_initializer = @initializer.scalar_callable if @initializer.respond_to?(:scalar_callable)
|
|
297
|
+
lambda do |env, context|
|
|
298
|
+
values = if @sigil == :scalar && scalar_initializer
|
|
299
|
+
scalar_initializer.call(env)
|
|
300
|
+
else
|
|
301
|
+
initializer&.call(env, @sigil == :scalar ? :scalar : :list)
|
|
302
|
+
end
|
|
303
|
+
target = declaration_target(env)
|
|
304
|
+
assign(target, values) if initializer
|
|
305
|
+
next Array(target.respond_to?(:cells) ? target.cells : target) if context == :list
|
|
306
|
+
|
|
307
|
+
declaration_value(target)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def lvalue(env, _context)
|
|
312
|
+
env.fetch(@sigil, @name)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
def declaration_target(env)
|
|
318
|
+
return env.declare(@sigil, @name) unless @predeclared
|
|
319
|
+
|
|
320
|
+
env.bind(@sigil, @name, @predeclared).tap { @predeclared = nil }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def assign(target, values)
|
|
324
|
+
return target.set(values) if target.is_a?(Scalar)
|
|
325
|
+
return target.cells.replace(values.map(&:copy)) if target.is_a?(PerlArray)
|
|
326
|
+
|
|
327
|
+
target.cells.clear
|
|
328
|
+
values.each_slice(2) { |key, value| target.slot(Conv.to_str(key&.get)).set(value&.get) }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def declaration_value(target)
|
|
332
|
+
target.is_a?(Scalar) ? target.get : target.cells.size
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Persistent lexical declaration, scoped independently for each closure.
|
|
337
|
+
class State < Base
|
|
338
|
+
def initialize(sigil, name, initializer, predeclared = nil)
|
|
339
|
+
@sigil = sigil
|
|
340
|
+
@name = name
|
|
341
|
+
@initializer = initializer
|
|
342
|
+
@predeclared = predeclared
|
|
343
|
+
@values = ObjectSpace::WeakMap.new
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def run(env, context)
|
|
347
|
+
owner = env.state_owner || env.runtime
|
|
348
|
+
value = @values[owner]
|
|
349
|
+
unless value
|
|
350
|
+
value = initial_value(owner)
|
|
351
|
+
initialize_value(value, env)
|
|
352
|
+
@values[owner] = value
|
|
353
|
+
end
|
|
354
|
+
env.bind(@sigil, @name, value)
|
|
355
|
+
return Array(value.respond_to?(:cells) ? value.cells : value) if context == :list
|
|
356
|
+
|
|
357
|
+
scalar_value(value)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
private
|
|
361
|
+
|
|
362
|
+
def initial_value(owner)
|
|
363
|
+
value = @predeclared
|
|
364
|
+
@predeclared = nil
|
|
365
|
+
# perlsub: cloned anonymous closures do not retain the compile-time state pad.
|
|
366
|
+
return value if value && (!owner.is_a?(Code) || owner.name)
|
|
367
|
+
|
|
368
|
+
empty_value
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def empty_value
|
|
372
|
+
{ scalar: -> { Scalar.new }, array: -> { PerlArray.new }, hash: -> { PerlHash.new } }.fetch(@sigil).call
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def initialize_value(target, env)
|
|
376
|
+
return unless @initializer
|
|
377
|
+
|
|
378
|
+
values = @initializer.run(env, @sigil == :scalar ? :scalar : :list)
|
|
379
|
+
return target.set(values) if target.is_a?(Scalar)
|
|
380
|
+
return target.cells.replace(values.map(&:copy)) if target.is_a?(PerlArray)
|
|
381
|
+
|
|
382
|
+
target.cells.clear
|
|
383
|
+
values.each_slice(2) { |key, value| target.slot(Conv.to_str(key&.get)).set(value&.get) }
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def scalar_value(value)
|
|
387
|
+
value.is_a?(Scalar) ? value.get : value.cells.size
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Destructuring lexical declaration.
|
|
392
|
+
class DeclareList < Base
|
|
393
|
+
def initialize(variables, initializer, predeclared = nil)
|
|
394
|
+
@variables = variables
|
|
395
|
+
@initializer = initializer
|
|
396
|
+
@predeclared = predeclared
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
400
|
+
def run(env, context)
|
|
401
|
+
return declare_scalar(env, context) if @variables.length == 1 && @variables.first.first == :scalar &&
|
|
402
|
+
@initializer.respond_to?(:first_cell)
|
|
403
|
+
|
|
404
|
+
values = @initializer&.run(env, :list) || []
|
|
405
|
+
targets = declaration_targets(env)
|
|
406
|
+
return context == :list ? targets : 0 unless @initializer
|
|
407
|
+
|
|
408
|
+
index = 0
|
|
409
|
+
targets.each do |target|
|
|
410
|
+
if target.is_a?(Scalar)
|
|
411
|
+
target.set(values[index]&.get)
|
|
412
|
+
index += 1
|
|
413
|
+
elsif target.is_a?(PerlArray)
|
|
414
|
+
target.cells.replace(values.drop(index).map(&:copy))
|
|
415
|
+
index = values.length
|
|
416
|
+
else
|
|
417
|
+
target.cells.clear
|
|
418
|
+
values.drop(index).each_slice(2) do |key, value|
|
|
419
|
+
target.slot(Conv.to_str(key&.get)).set(value&.get)
|
|
420
|
+
end
|
|
421
|
+
index = values.length
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
context == :list ? targets : values.size
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def to_callable
|
|
428
|
+
return scalar_declaration_callable if @variables.length == 1 && @variables.first.first == :scalar &&
|
|
429
|
+
@initializer.respond_to?(:first_cell_callable)
|
|
430
|
+
|
|
431
|
+
initializer = @initializer&.to_callable
|
|
432
|
+
lambda do |env, context|
|
|
433
|
+
values = initializer ? initializer.call(env, :list) : []
|
|
434
|
+
declare_values(env, context, values)
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
private
|
|
439
|
+
|
|
440
|
+
def scalar_declaration_callable
|
|
441
|
+
source = @initializer.first_cell_callable
|
|
442
|
+
lambda do |env, context|
|
|
443
|
+
target = declaration_targets(env).first
|
|
444
|
+
target.set(source.call(env)&.value)
|
|
445
|
+
context == :list ? [target] : 1
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def declare_values(env, context, values)
|
|
450
|
+
targets = declaration_targets(env)
|
|
451
|
+
return context == :list ? targets : 0 unless @initializer
|
|
452
|
+
|
|
453
|
+
index = 0
|
|
454
|
+
targets.each do |target|
|
|
455
|
+
if target.is_a?(Scalar)
|
|
456
|
+
target.set(values[index]&.value)
|
|
457
|
+
index += 1
|
|
458
|
+
elsif target.is_a?(PerlArray)
|
|
459
|
+
target.cells.replace(values.drop(index).map(&:copy))
|
|
460
|
+
index = values.length
|
|
461
|
+
else
|
|
462
|
+
target.cells.clear
|
|
463
|
+
values.drop(index).each_slice(2) { |key, value| target.slot(Conv.to_str(key&.value)).set(value&.value) }
|
|
464
|
+
index = values.length
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
context == :list ? targets : values.size
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def declare_scalar(env, context)
|
|
471
|
+
source = @initializer.first_cell(env)
|
|
472
|
+
target = declaration_targets(env).first
|
|
473
|
+
target.set(source&.get)
|
|
474
|
+
context == :list ? [target] : 1
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def declaration_targets(env)
|
|
478
|
+
return @variables.map { |sigil, name| env.declare(sigil, name) } unless @predeclared
|
|
479
|
+
|
|
480
|
+
targets = @variables.zip(@predeclared).map { |(sigil, name), value| env.bind(sigil, name, value) }
|
|
481
|
+
@predeclared = nil
|
|
482
|
+
targets
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Package variable declaration through our.
|
|
487
|
+
class GlobalDeclare < Base
|
|
488
|
+
def initialize(sigil, name, initializer)
|
|
489
|
+
@sigil = sigil
|
|
490
|
+
@name = name
|
|
491
|
+
@initializer = initializer
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def run(env, context)
|
|
495
|
+
target = env.fetch(@sigil, @name)
|
|
496
|
+
bind_alias(env, target)
|
|
497
|
+
return target.get unless @initializer
|
|
498
|
+
|
|
499
|
+
values = @initializer.run(env, @sigil == :scalar ? :scalar : :list)
|
|
500
|
+
if target.is_a?(Scalar)
|
|
501
|
+
target.set(values)
|
|
502
|
+
elsif target.is_a?(PerlArray)
|
|
503
|
+
target.cells.replace(values.map(&:copy))
|
|
504
|
+
else
|
|
505
|
+
target.cells.clear
|
|
506
|
+
values.each_slice(2) { |key, value| target.slot(Conv.to_str(key&.get)).set(value&.get) }
|
|
507
|
+
end
|
|
508
|
+
context == :list && target.respond_to?(:list) ? target.list : values
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
private
|
|
512
|
+
|
|
513
|
+
def bind_alias(env, target)
|
|
514
|
+
name = @name.rpartition('::').last
|
|
515
|
+
env.bind(@sigil, name, target) unless name == @name
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Dynamically localizes a package variable or aggregate element.
|
|
520
|
+
class Local < Base
|
|
521
|
+
def initialize(target, assignment)
|
|
522
|
+
@target = target
|
|
523
|
+
@assignment = assignment
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def run(env, context)
|
|
527
|
+
@target.localize(env, env.runtime.local_stack)
|
|
528
|
+
return @assignment.run(env, context) if @assignment
|
|
529
|
+
|
|
530
|
+
nil
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
data/lib/peruby/op.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peruby
|
|
4
|
+
# Executable operation tree.
|
|
5
|
+
module Op
|
|
6
|
+
# Base evaluation protocol. Context is one of :scalar, :list, or :void.
|
|
7
|
+
class Base
|
|
8
|
+
def run(_env, _context)
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def lvalue(_env, _context)
|
|
13
|
+
raise PerlError, "Can't modify #{self.class} in assignment"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def local_slot(_env)
|
|
17
|
+
raise PerlError, "Can't localize #{self.class}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def argument_cells(env)
|
|
21
|
+
run(env, :list)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_callable
|
|
25
|
+
->(env, context) { run(env, context) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run_subroutine(env, context)
|
|
29
|
+
catch(:peruby_return) { run(env, context) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def warning_directive? = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Lambda-composed executable with the original tree retained for fallback.
|
|
36
|
+
class Compiled < Base
|
|
37
|
+
def initialize(callable, subroutine = nil)
|
|
38
|
+
super()
|
|
39
|
+
@callable = callable
|
|
40
|
+
@subroutine = subroutine || ->(env, context) { catch(:peruby_return) { @callable.call(env, context) } }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def run(env, context) = @callable.call(env, context)
|
|
44
|
+
def run_subroutine(env, context) = @subroutine.call(env, context)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|