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,378 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peruby
|
|
4
|
+
module Op
|
|
5
|
+
# Runtime subroutine definition.
|
|
6
|
+
class SubDef < Base
|
|
7
|
+
def initialize(name, prototype, body)
|
|
8
|
+
@name = name
|
|
9
|
+
@prototype = prototype
|
|
10
|
+
@body = body
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(env, _context)
|
|
14
|
+
install(env, @body)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_callable
|
|
18
|
+
body = Compiled.new(@body.to_callable, @body.to_subroutine_callable)
|
|
19
|
+
->(env, _context) { install(env, body) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def install(env, body)
|
|
25
|
+
package = @name.include?('::') ? @name.rpartition('::').first : env.package
|
|
26
|
+
env.runtime.warning('redefine', "Subroutine #{@name} redefined") if env.runtime.stash.fetch(@name)&.code
|
|
27
|
+
code = Code.new(@name, body, env.capture, package, @prototype)
|
|
28
|
+
env.runtime.stash.glob(@name).code = code
|
|
29
|
+
env.runtime.stash.touch
|
|
30
|
+
code
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Constant subroutine installed by use constant.
|
|
35
|
+
class ConstantDef < Base
|
|
36
|
+
def initialize(name, value)
|
|
37
|
+
@name = name
|
|
38
|
+
@value = value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def run(env, _context)
|
|
42
|
+
package = @name.include?('::') ? @name.rpartition('::').first : env.package
|
|
43
|
+
env.runtime.stash.glob(@name).code = Code.new(@name, @value, env.capture, package, '')
|
|
44
|
+
env.runtime.stash.touch
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Anonymous closure creation.
|
|
49
|
+
class AnonSub < Base
|
|
50
|
+
def initialize(prototype, body)
|
|
51
|
+
@prototype = prototype
|
|
52
|
+
@body = body
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run(env, context)
|
|
56
|
+
reference(env, context, @body)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_callable
|
|
60
|
+
body = Compiled.new(@body.to_callable, @body.to_subroutine_callable)
|
|
61
|
+
->(env, context) { reference(env, context, body) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def reference(env, context, body)
|
|
67
|
+
code = Code.new(nil, body, env.capture, env.package, @prototype)
|
|
68
|
+
value = Ref.new(code)
|
|
69
|
+
context == :list ? [Scalar.new(value)] : value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# User subroutine invocation.
|
|
74
|
+
class Call < Base
|
|
75
|
+
def initialize(name, arguments)
|
|
76
|
+
@name = name
|
|
77
|
+
@arguments = arguments
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def run(env, context)
|
|
81
|
+
code = resolve(env)
|
|
82
|
+
env.runtime.call(code, @arguments.argument_cells(env, code.prototype), context)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_callable
|
|
86
|
+
arguments = @arguments.argument_callable
|
|
87
|
+
lambda do |env, context|
|
|
88
|
+
code = resolve(env)
|
|
89
|
+
env.runtime.call(code, arguments.call(env, code.prototype), context)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def scalar_callable
|
|
94
|
+
arguments = @arguments.argument_callable
|
|
95
|
+
lambda do |env|
|
|
96
|
+
code = resolve(env)
|
|
97
|
+
env.runtime.call(code, arguments.call(env, code.prototype), :scalar)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def resolve(env)
|
|
104
|
+
stash = env.runtime.stash
|
|
105
|
+
unless @cached_generation == stash.generation
|
|
106
|
+
@cached_code = stash.glob(@name).code
|
|
107
|
+
@cached_generation = stash.generation
|
|
108
|
+
end
|
|
109
|
+
raise PerlError, "Undefined subroutine &#{@name} called" unless @cached_code
|
|
110
|
+
|
|
111
|
+
@cached_code
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Invocation through a code reference.
|
|
116
|
+
class CodeCall < Base
|
|
117
|
+
def initialize(receiver, arguments, inherit: false)
|
|
118
|
+
@receiver = receiver
|
|
119
|
+
@arguments = arguments
|
|
120
|
+
@inherit = inherit
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def run(env, context)
|
|
124
|
+
code = @receiver.run(env, :scalar)
|
|
125
|
+
code = code.target if code.is_a?(Ref) && code.kind == 'CODE'
|
|
126
|
+
raise PerlError, 'Not a CODE reference' unless code.is_a?(Code)
|
|
127
|
+
|
|
128
|
+
arguments = @inherit ? env.fetch(:array, '_').cells.dup : @arguments.argument_cells(env)
|
|
129
|
+
env.runtime.call(code, arguments, context)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Non-local subroutine return.
|
|
134
|
+
class Return < Base
|
|
135
|
+
def initialize(expression)
|
|
136
|
+
@expression = expression
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def run(env, _context)
|
|
140
|
+
throw :peruby_return, @expression&.run(env, env.want)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def return_value(env, context)
|
|
144
|
+
@expression&.run(env, context)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def return_callable
|
|
148
|
+
expression = @expression&.to_callable
|
|
149
|
+
->(env, context) { expression&.call(env, context) }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Perl ref() result.
|
|
154
|
+
class RefKind < Base
|
|
155
|
+
def initialize(expression)
|
|
156
|
+
@expression = expression
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def run(env, context)
|
|
160
|
+
value = @expression.run(env, :scalar)
|
|
161
|
+
result = value.is_a?(Ref) ? value.ref_string : ''
|
|
162
|
+
context == :list ? [Scalar.new(result)] : result
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Array push with autovivifying dereference support.
|
|
167
|
+
class Push < Base
|
|
168
|
+
def initialize(array, values)
|
|
169
|
+
@array = array
|
|
170
|
+
@values = values
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def run(env, context)
|
|
174
|
+
array = @array.lvalue(env, :scalar)
|
|
175
|
+
raise PerlError, 'Type of arg 1 to push must be array' unless array.is_a?(PerlArray)
|
|
176
|
+
|
|
177
|
+
values = @values.flat_map { |value| value.argument_cells(env) }.map(&:copy)
|
|
178
|
+
array.cells.concat(values)
|
|
179
|
+
result = array.size
|
|
180
|
+
context == :list ? [Scalar.new(result)] : result
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Perl exists() over array/hash elements and slices.
|
|
185
|
+
class Exists < Base
|
|
186
|
+
def initialize(expression)
|
|
187
|
+
@expression = expression
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def run(env, context)
|
|
191
|
+
result = @expression.respond_to?(:exists?) && @expression.exists?(env) ? 1 : ''
|
|
192
|
+
context == :list ? [Scalar.new(result)] : result
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Perl delete() over array/hash elements and slices.
|
|
197
|
+
class Delete < Base
|
|
198
|
+
def initialize(expression)
|
|
199
|
+
@expression = expression
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def run(env, context)
|
|
203
|
+
values = @expression.respond_to?(:delete) ? @expression.delete(env) : []
|
|
204
|
+
return values.map { |value| Scalar.new(value) } if context == :list
|
|
205
|
+
|
|
206
|
+
values.last
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Blesses an existing reference into a package.
|
|
211
|
+
class Bless < Base
|
|
212
|
+
def initialize(arguments)
|
|
213
|
+
@arguments = arguments
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def run(env, context)
|
|
217
|
+
reference = @arguments.first&.run(env, :scalar)
|
|
218
|
+
raise PerlError, 'Blessing non-reference value' unless reference.is_a?(Ref)
|
|
219
|
+
|
|
220
|
+
package = @arguments[1] ? Conv.to_str(@arguments[1].run(env, :scalar)) : env.package
|
|
221
|
+
reference.blessed = package
|
|
222
|
+
reference.runtime = env.runtime
|
|
223
|
+
env.runtime.register_object(reference)
|
|
224
|
+
context == :list ? [Scalar.new(reference)] : reference
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Object or class method invocation through Perl's package MRO.
|
|
229
|
+
class MethodCall < Base
|
|
230
|
+
def initialize(receiver, name, arguments)
|
|
231
|
+
@receiver = receiver
|
|
232
|
+
@name = name
|
|
233
|
+
@arguments = arguments
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def run(env, context)
|
|
237
|
+
invoke(@receiver.run(env, :scalar), env, context)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def to_callable
|
|
241
|
+
receiver = @receiver.to_callable
|
|
242
|
+
scalar_receiver = @receiver.scalar_callable if @receiver.respond_to?(:scalar_callable)
|
|
243
|
+
arguments = @arguments.argument_callable
|
|
244
|
+
return static_callable(receiver, scalar_receiver, arguments, @arguments.empty?) unless @name.respond_to?(:run)
|
|
245
|
+
|
|
246
|
+
lambda do |env, context|
|
|
247
|
+
value = scalar_receiver ? scalar_receiver.call(env) : receiver.call(env, :scalar)
|
|
248
|
+
invoke(value, env, context, arguments.call(env))
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
255
|
+
def static_callable(receiver, scalar_receiver, arguments, no_arguments)
|
|
256
|
+
method = @name
|
|
257
|
+
cached_package = cached_name = cached_super = cached_generation = cached_code = nil
|
|
258
|
+
lambda do |env, context|
|
|
259
|
+
value = scalar_receiver ? scalar_receiver.call(env) : receiver.call(env, :scalar)
|
|
260
|
+
next invoke(value, env, context, arguments.call(env)) if %w[isa DOES can VERSION].include?(method)
|
|
261
|
+
|
|
262
|
+
package = value.is_a?(Ref) ? value.blessed : Conv.to_str(value)
|
|
263
|
+
raise PerlError, "Can't call method #{method} on unblessed reference" if package.nil?
|
|
264
|
+
|
|
265
|
+
super_from = method.start_with?('SUPER::') ? env.package : nil
|
|
266
|
+
resolved_name = method.delete_prefix('SUPER::')
|
|
267
|
+
generation = env.runtime.stash.generation
|
|
268
|
+
unless cached_package == package && cached_name == resolved_name && cached_super == super_from &&
|
|
269
|
+
cached_generation == generation
|
|
270
|
+
cached_package = package
|
|
271
|
+
cached_name = resolved_name
|
|
272
|
+
cached_super = super_from
|
|
273
|
+
cached_generation = generation
|
|
274
|
+
cached_code = env.runtime.mro.resolve(package, resolved_name, super_from:)&.last
|
|
275
|
+
end
|
|
276
|
+
raise PerlError, "Can't locate object method #{resolved_name} via package #{package}" unless cached_code
|
|
277
|
+
|
|
278
|
+
cells = no_arguments ? [] : arguments.call(env)
|
|
279
|
+
env.runtime.call(cached_code, [Scalar.new(value), *cells], context)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def invoke(receiver, env, context, argument_cells = @arguments.argument_cells(env))
|
|
284
|
+
method = method_name(env)
|
|
285
|
+
if method.is_a?(Code)
|
|
286
|
+
arguments = [Scalar.new(receiver), *argument_cells]
|
|
287
|
+
return env.runtime.call(method, arguments, context)
|
|
288
|
+
end
|
|
289
|
+
return universal(receiver, method, env, context) if %w[isa DOES can VERSION].include?(method)
|
|
290
|
+
|
|
291
|
+
package = receiver.is_a?(Ref) ? receiver.blessed : Conv.to_str(receiver)
|
|
292
|
+
raise PerlError, "Can't call method #{method} on unblessed reference" if package.nil?
|
|
293
|
+
|
|
294
|
+
super_from = method.start_with?('SUPER::') ? env.package : nil
|
|
295
|
+
method = method.delete_prefix('SUPER::')
|
|
296
|
+
code = resolved_method(env, package, method, super_from)
|
|
297
|
+
raise PerlError, "Can't locate object method #{method} via package #{package}" unless code
|
|
298
|
+
|
|
299
|
+
arguments = [Scalar.new(receiver), *argument_cells]
|
|
300
|
+
env.runtime.call(code, arguments, context)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def resolved_method(env, package, method, super_from)
|
|
304
|
+
stash = env.runtime.stash
|
|
305
|
+
key = [package, method, super_from]
|
|
306
|
+
unless @cached_method_key == key && @cached_method_generation == stash.generation
|
|
307
|
+
@cached_method_key = key
|
|
308
|
+
@cached_method_generation = stash.generation
|
|
309
|
+
@cached_method = env.runtime.mro.resolve(package, method, super_from:)&.last
|
|
310
|
+
end
|
|
311
|
+
@cached_method
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def method_name(env)
|
|
315
|
+
return @name unless @name.respond_to?(:run)
|
|
316
|
+
|
|
317
|
+
value = @name.run(env, :scalar)
|
|
318
|
+
return value.target if value.is_a?(Ref) && value.kind == 'CODE'
|
|
319
|
+
|
|
320
|
+
Conv.to_str(value)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def universal(receiver, method, env, context)
|
|
324
|
+
package = receiver.is_a?(Ref) ? receiver.blessed : Conv.to_str(receiver)
|
|
325
|
+
argument = @arguments.run(env, :scalar)
|
|
326
|
+
value = case method
|
|
327
|
+
when 'isa', 'DOES' then env.runtime.mro.isa?(package, Conv.to_str(argument)) ? 1 : ''
|
|
328
|
+
when 'can' then can(package, Conv.to_str(argument), env)
|
|
329
|
+
when 'VERSION' then version(package, argument, env)
|
|
330
|
+
end
|
|
331
|
+
context == :list ? [Scalar.new(value)] : value
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def can(package, method, env)
|
|
335
|
+
entry = env.runtime.mro.resolve(package, method, autoload: false)
|
|
336
|
+
entry && Ref.new(entry.last)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def version(package, required, env)
|
|
340
|
+
version = env.fetch(:scalar, "#{package}::VERSION").get
|
|
341
|
+
if !required.nil? && Conv.to_num(version) < Conv.to_num(required)
|
|
342
|
+
message = "#{package} version #{Conv.to_str(required)} required--this is only version #{Conv.to_str(version)}"
|
|
343
|
+
raise PerlError, message
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
version
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Tail-call style goto &sub preserving the current @_ cells.
|
|
351
|
+
class Goto < Base
|
|
352
|
+
def initialize(name)
|
|
353
|
+
@name = name
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def run(env, _context)
|
|
357
|
+
code = env.runtime.stash.glob(@name).code || env.runtime.stash.glob("#{env.package}::#{@name}").code
|
|
358
|
+
raise PerlError, "Undefined subroutine &#{@name} called" unless code
|
|
359
|
+
|
|
360
|
+
throw :peruby_return, GotoRequest.new(code)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# undef with optional deterministic blessed-reference release.
|
|
365
|
+
class Undef < Base
|
|
366
|
+
def initialize(expression)
|
|
367
|
+
@expression = expression
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def run(env, _context)
|
|
371
|
+
cell = @expression.lvalue(env, :scalar)
|
|
372
|
+
env.runtime.release(cell.get)
|
|
373
|
+
cell.set(nil)
|
|
374
|
+
nil
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peruby
|
|
4
|
+
module Op
|
|
5
|
+
# Shared label-aware last/next/redo handling.
|
|
6
|
+
module LoopExecution
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def run_iteration?(env, body = @body)
|
|
10
|
+
loop do
|
|
11
|
+
body.run(env, :void)
|
|
12
|
+
break
|
|
13
|
+
rescue LoopControl => e
|
|
14
|
+
raise unless e.label.nil? || e.label == @label
|
|
15
|
+
|
|
16
|
+
return false if e.kind == :last
|
|
17
|
+
break if e.kind == :next
|
|
18
|
+
end
|
|
19
|
+
@continue&.run(env, :void)
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Ordered statement execution, optionally in a lexical scope.
|
|
25
|
+
class Sequence < Base
|
|
26
|
+
def initialize(operations, scoped: false)
|
|
27
|
+
@operations = operations
|
|
28
|
+
@scoped = scoped
|
|
29
|
+
@warning_scope = operations.any?(&:warning_directive?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run(env, context)
|
|
33
|
+
execute = -> { execute_operations(env, context) }
|
|
34
|
+
scoped = -> { @scoped ? env.with_scope(&execute) : execute.call }
|
|
35
|
+
@warning_scope ? env.runtime.with_warning_scope(&scoped) : scoped.call
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def run_subroutine(env, context)
|
|
39
|
+
if !@scoped && @operations.last.respond_to?(:return_value)
|
|
40
|
+
@operations[0...-1].each { |operation| operation.run(env, :void) }
|
|
41
|
+
return @operations.last.return_value(env, context)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
catch(:peruby_return) { run(env, context) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_callable
|
|
48
|
+
operations = @operations.map(&:to_callable)
|
|
49
|
+
execute = lambda do |env, context|
|
|
50
|
+
next nil if operations.empty?
|
|
51
|
+
|
|
52
|
+
operations[0...-1].each { |operation| operation.call(env, :void) }
|
|
53
|
+
operations.last.call(env, context)
|
|
54
|
+
end
|
|
55
|
+
callable = @scoped ? ->(env, context) { env.with_scope { execute.call(env, context) } } : execute
|
|
56
|
+
warning_callable(callable)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_subroutine_callable
|
|
60
|
+
operations = @operations.map(&:to_callable)
|
|
61
|
+
if !@scoped && @operations.last.respond_to?(:return_callable)
|
|
62
|
+
returning = @operations.last.return_callable
|
|
63
|
+
callable = lambda do |env, context|
|
|
64
|
+
operations[0...-1].each { |operation| operation.call(env, :void) }
|
|
65
|
+
returning.call(env, context)
|
|
66
|
+
end
|
|
67
|
+
return warning_callable(callable)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
callable = to_callable
|
|
71
|
+
->(env, context) { catch(:peruby_return) { callable.call(env, context) } }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def to_void_callable
|
|
75
|
+
operations = @operations.map do |operation|
|
|
76
|
+
next operation.to_void_callable if operation.respond_to?(:to_void_callable)
|
|
77
|
+
|
|
78
|
+
callable = operation.to_callable
|
|
79
|
+
->(env) { callable.call(env, :void) }
|
|
80
|
+
end
|
|
81
|
+
execute = ->(env) { operations.each { |operation| operation.call(env) } }
|
|
82
|
+
callable = @scoped ? ->(env) { env.with_scope { execute.call(env) } } : execute
|
|
83
|
+
return callable unless @warning_scope
|
|
84
|
+
|
|
85
|
+
->(env) { env.runtime.with_warning_scope { callable.call(env) } }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def warning_callable(callable)
|
|
91
|
+
return callable unless @warning_scope
|
|
92
|
+
|
|
93
|
+
->(env, context) { env.runtime.with_warning_scope { callable.call(env, context) } }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def execute_operations(env, context)
|
|
97
|
+
return nil if @operations.empty?
|
|
98
|
+
|
|
99
|
+
@operations[0...-1].each { |operation| operation.run(env, :void) }
|
|
100
|
+
@operations.last.run(env, context)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Conditional branch operation.
|
|
105
|
+
class Conditional < Base
|
|
106
|
+
def initialize(condition, then_branch, else_branch)
|
|
107
|
+
@condition = condition
|
|
108
|
+
@then_branch = then_branch
|
|
109
|
+
@else_branch = else_branch
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def run(env, context)
|
|
113
|
+
branch = Conv.truthy?(@condition.run(env, :scalar)) ? @then_branch : @else_branch
|
|
114
|
+
branch&.run(env, context)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_callable
|
|
118
|
+
condition = @condition.to_callable
|
|
119
|
+
scalar_condition = @condition.scalar_callable if @condition.respond_to?(:scalar_callable)
|
|
120
|
+
then_branch = @then_branch.to_callable
|
|
121
|
+
else_branch = @else_branch&.to_callable
|
|
122
|
+
lambda do |env, context|
|
|
123
|
+
value = scalar_condition ? scalar_condition.call(env) : condition.call(env, :scalar)
|
|
124
|
+
branch = Conv.truthy?(value) ? then_branch : else_branch
|
|
125
|
+
branch&.call(env, context)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Changes the package used for unqualified global names.
|
|
131
|
+
class PackageSwitch < Base
|
|
132
|
+
def initialize(name) = @name = name
|
|
133
|
+
def run(env, _context) = env.package = @name
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Temporarily changes the current package for one lexical block.
|
|
137
|
+
class PackageBlock < Base
|
|
138
|
+
def initialize(name, body)
|
|
139
|
+
@name = name
|
|
140
|
+
@body = body
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def run(env, context) = env.with_package(@name) { @body.run(env, context) }
|
|
144
|
+
|
|
145
|
+
def to_callable
|
|
146
|
+
body = @body.to_callable
|
|
147
|
+
->(env, context) { env.with_package(@name) { body.call(env, context) } }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# A standalone Perl block behaves as a one-iteration loop.
|
|
152
|
+
class BareBlock < Base
|
|
153
|
+
def initialize(body, label)
|
|
154
|
+
@body = body
|
|
155
|
+
@label = label
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def run(env, context)
|
|
159
|
+
loop do
|
|
160
|
+
return @body.run(env, context)
|
|
161
|
+
rescue LoopControl => e
|
|
162
|
+
raise unless e.label.nil? || e.label == @label
|
|
163
|
+
|
|
164
|
+
return nil unless e.kind == :redo
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# While or until loop operation.
|
|
170
|
+
class While < Base
|
|
171
|
+
include LoopExecution
|
|
172
|
+
|
|
173
|
+
def initialize(condition, body, until_loop, continuation = nil, label = nil)
|
|
174
|
+
@condition = condition
|
|
175
|
+
@body = body
|
|
176
|
+
@until_loop = until_loop
|
|
177
|
+
@continue = continuation
|
|
178
|
+
@label = label
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def run(env, _context)
|
|
182
|
+
loop do
|
|
183
|
+
truth = Conv.truthy?(@condition.run(env, :scalar))
|
|
184
|
+
break if @until_loop ? truth : !truth
|
|
185
|
+
|
|
186
|
+
break unless run_iteration?(env)
|
|
187
|
+
end
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def to_callable
|
|
192
|
+
condition = @condition.to_callable
|
|
193
|
+
scalar_condition = @condition.scalar_callable if @condition.respond_to?(:scalar_callable)
|
|
194
|
+
body = @body.to_callable
|
|
195
|
+
void_body = @body.to_void_callable if @body.respond_to?(:to_void_callable)
|
|
196
|
+
continuation = @continue&.to_callable
|
|
197
|
+
lambda do |env, _context|
|
|
198
|
+
loop do
|
|
199
|
+
value = scalar_condition ? scalar_condition.call(env) : condition.call(env, :scalar)
|
|
200
|
+
truth = Conv.truthy?(value)
|
|
201
|
+
break if @until_loop ? truth : !truth
|
|
202
|
+
|
|
203
|
+
keep_going = compiled_iteration?(env, body, void_body, continuation)
|
|
204
|
+
break unless keep_going
|
|
205
|
+
end
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def compiled_iteration?(env, body, void_body, continuation)
|
|
213
|
+
loop do
|
|
214
|
+
void_body ? void_body.call(env) : body.call(env, :void)
|
|
215
|
+
break
|
|
216
|
+
rescue LoopControl => e
|
|
217
|
+
raise unless e.label.nil? || e.label == @label
|
|
218
|
+
|
|
219
|
+
return false if e.kind == :last
|
|
220
|
+
break if e.kind == :next
|
|
221
|
+
end
|
|
222
|
+
continuation&.call(env, :void)
|
|
223
|
+
true
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Block and string eval sharing the current lexical environment.
|
|
228
|
+
class Eval < Base
|
|
229
|
+
def initialize(body, string, file: '-e')
|
|
230
|
+
@body = body
|
|
231
|
+
@string = string
|
|
232
|
+
@file = file
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def run(env, context)
|
|
236
|
+
env.fetch(:scalar, '@').set('')
|
|
237
|
+
result = if @string
|
|
238
|
+
env.with_scope do
|
|
239
|
+
source = Conv.to_str(@body.run(env, :scalar))
|
|
240
|
+
operation = CompileUnit.new(runtime: env.runtime, file: @file).compile(source, env:)
|
|
241
|
+
operation.run_subroutine(env, context)
|
|
242
|
+
end
|
|
243
|
+
else
|
|
244
|
+
@body.run_subroutine(env, context)
|
|
245
|
+
end
|
|
246
|
+
env.fetch(:scalar, '@').set('')
|
|
247
|
+
result
|
|
248
|
+
rescue Error => e
|
|
249
|
+
value = e.is_a?(PerlError) ? e.value : e.message
|
|
250
|
+
value = env.runtime.error_message(value) unless value.is_a?(Ref)
|
|
251
|
+
env.fetch(:scalar, '@').set(value)
|
|
252
|
+
context == :list ? [] : nil
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|