kapusta 0.13.0 → 0.13.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b547075d7dcb73121e61dc17577cb8262265eaffb6ee0d74b88a74ce83c8e09a
4
- data.tar.gz: 877f490eca3f0d4419e38b4619f77470aff18ddd8fede555f2963beb8db5994f
3
+ metadata.gz: fa363e8e708ddf94ca3406bda52d7603663e9de82a79eec94328082f5310b638
4
+ data.tar.gz: cf1a0911c16fa481bc1680380eb2677329276289c513e8d66d9f4450cad9d762
5
5
  SHA512:
6
- metadata.gz: cddc220382f4c7f59788f4624f9e86f55bc58fe2b3fa6978f4e6d8bebaa870ff73be57786ecf2db94f14d6db460b3f485b6f82195e7fd21e255c7a7612ab50c2
7
- data.tar.gz: 6c653e095b1c3d406f95e7f1659b615a2a59eaa21ef77e709cfc2dfc398d3526fb493f5054cb541acd36b203e690dbc28626e951e3e8f9d5e27366c0d5885834
6
+ metadata.gz: d2e4405abbab87cc70e8b00ab05840f50b6050c965d63351e6db3a2e5ab2f97219bb517d837ef8f8166df27ae4d53d0bd9a595de6bbed3d5e0319de0c13b2a88
7
+ data.tar.gz: 8e88867b98adba5e17d0eaf6a506518bdb0a7f7b2628d5ce3937fad5f29e73766dba4767340716f06ec7ff3e9b68df6924b7cadf936a71cb18bf47e88a26ca4b
@@ -18,11 +18,11 @@ module Kapusta
18
18
  fn_env = env.child
19
19
  ruby_name = define_local(fn_env, name_sym.name)
20
20
  <<~RUBY.chomp
21
- (-> do
21
+ lambda do
22
22
  #{ruby_name} = nil
23
23
  #{ruby_name} = #{emit_lambda(pattern, body, fn_env, current_scope)}
24
24
  #{ruby_name}
25
- end).call
25
+ end.call
26
26
  RUBY
27
27
  end
28
28
  end
@@ -259,9 +259,9 @@ module Kapusta
259
259
  def emit_let(args, env, current_scope)
260
260
  binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
261
261
  [
262
- '(-> do',
262
+ 'lambda do',
263
263
  indent(join_code(binding_code, body_code)),
264
- 'end).call'
264
+ 'end.call'
265
265
  ].join("\n")
266
266
  end
267
267
 
@@ -293,7 +293,23 @@ module Kapusta
293
293
  body_code, = emit_sequence(body, child_env, current_scope,
294
294
  allow_method_definitions: false,
295
295
  result:)
296
- [binding_codes.reject(&:empty?).join("\n"), body_code]
296
+ [join_binding_codes(binding_codes), body_code]
297
+ end
298
+
299
+ def join_binding_codes(binding_codes)
300
+ codes = binding_codes.reject(&:empty?)
301
+ return '' if codes.empty?
302
+
303
+ result = codes.first.dup
304
+ codes.each_cons(2) do |prev, curr|
305
+ separator = block_binding?(prev) || block_binding?(curr) ? "\n\n" : "\n"
306
+ result << separator << curr
307
+ end
308
+ result
309
+ end
310
+
311
+ def block_binding?(code)
312
+ code.match?(/\bdo\b/) && code.match?(/\bend\b/)
297
313
  end
298
314
 
299
315
  def join_code(*chunks)
@@ -376,7 +392,7 @@ module Kapusta
376
392
 
377
393
  def emit_local_expr(args, env, current_scope)
378
394
  code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
379
- "(-> do\n#{indent(code)}\nend).call"
395
+ "lambda do\n#{indent(code)}\nend.call"
380
396
  end
381
397
 
382
398
  def emit_global_expr(args, _env, _current_scope)
@@ -89,11 +89,11 @@ module Kapusta
89
89
  emit_sequence_value_assignment(acc_var, body_code)
90
90
  end
91
91
  [
92
- '(-> do',
92
+ 'lambda do',
93
93
  indent("#{acc_var} = #{init_code}"),
94
94
  indent(iter_code),
95
95
  indent(acc_var),
96
- 'end).call'
96
+ 'end.call'
97
97
  ].join("\n")
98
98
  end
99
99
 
@@ -221,11 +221,11 @@ module Kapusta
221
221
 
222
222
  def emit_collection_result(result_var, initial_code, iter_code)
223
223
  [
224
- '(-> do',
224
+ 'lambda do',
225
225
  indent("#{result_var} = #{initial_code}"),
226
226
  indent(iter_code),
227
227
  indent(result_var),
228
- 'end).call'
228
+ 'end.call'
229
229
  ].join("\n")
230
230
  end
231
231
 
@@ -61,10 +61,10 @@ module Kapusta
61
61
  return body unless value_var
62
62
 
63
63
  [
64
- '(-> do',
64
+ 'lambda do',
65
65
  indent("#{value_var} = #{value_code}"),
66
66
  indent(body),
67
- 'end).call'
67
+ 'end.call'
68
68
  ].join("\n")
69
69
  end
70
70
 
@@ -212,30 +212,30 @@ module Kapusta
212
212
 
213
213
  def emit_while(args, env, current_scope)
214
214
  <<~RUBY.chomp
215
- (-> do
215
+ lambda do
216
216
  #{indent(emit_while_statement(args, env, current_scope))}
217
217
  nil
218
- end).call
218
+ end.call
219
219
  RUBY
220
220
  end
221
221
 
222
222
  def emit_for(args, env, current_scope)
223
223
  loop_code = emit_for_statement(args, env, current_scope)
224
224
  <<~RUBY.chomp
225
- (-> do
225
+ lambda do
226
226
  #{indent(loop_code)}
227
227
  nil
228
- end).call
228
+ end.call
229
229
  RUBY
230
230
  end
231
231
 
232
232
  def emit_each(args, env, current_scope)
233
233
  iter_code = emit_each_statement(args, env, current_scope)
234
234
  <<~RUBY.chomp
235
- (-> do
235
+ lambda do
236
236
  #{iter_code}
237
237
  nil
238
- end).call
238
+ end.call
239
239
  RUBY
240
240
  end
241
241
 
@@ -35,7 +35,7 @@ module Kapusta
35
35
  if literal_name && binary_operator_call?(literal_name.to_s, positional, kwargs, block_form)
36
36
  return emit_binary_operator_call(receiver, literal_name.to_s, positional[0])
37
37
  end
38
- if literal_name && direct_method_name?(literal_name.to_s)
38
+ if literal_name && colon_call_direct_method_name?(literal_name.to_s)
39
39
  return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s),
40
40
  positional, kwargs, block_form, env, current_scope)
41
41
  end
@@ -99,7 +99,7 @@ module Kapusta
99
99
  segments = constant_segments(name_sym)
100
100
  emit_error!(:invalid_module_name, name: name_sym.name) unless segments
101
101
  inner = build_nested_module(segments, body)
102
- ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
102
+ ['lambda do', indent(inner), indent(segments.join('::')), 'end.call'].join("\n")
103
103
  end
104
104
 
105
105
  def emit_direct_module_header(name_sym, body)
@@ -114,7 +114,7 @@ module Kapusta
114
114
  emit_error!(:invalid_class_name, name: name_sym.name) unless segments
115
115
  super_code = class_super_code(supers, env)
116
116
  inner = build_nested_class(segments, super_code, body)
117
- ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
117
+ ['lambda do', indent(inner), indent(segments.join('::')), 'end.call'].join("\n")
118
118
  end
119
119
 
120
120
  def emit_direct_class_header(name_sym, supers, body, env)
@@ -488,40 +488,16 @@ module Kapusta
488
488
  Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
489
489
  end
490
490
 
491
+ def colon_call_direct_method_name?(name)
492
+ Kapusta.kebab_to_snake(name).match?(/\A[a-zA-Z_]\w*[!?=]?\z/)
493
+ end
494
+
491
495
  def global_name(name)
492
496
  Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
493
497
  end
494
498
 
495
- SIMPLE_EXPRESSION_PATTERNS = [
496
- /\A[a-z_]\w*\z/, # local
497
- /\A@@?[a-z_]\w*\z/, # @ivar / @@cvar
498
- /\A\$[a-zA-Z_]\w*\z/, # $gvar
499
- /\A[A-Z]\w*(?:::[A-Z]\w*)*\z/, # Constant / A::B::C
500
- /\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/, # bare call foo(...)
501
- /\A-?\d+(?:\.\d+)?\z/, # number
502
- /\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # local + .m/[k] chain
503
- /\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # const + chain
504
- /\A:[a-zA-Z_]\w*[!?=]?\z/, # :symbol
505
- /\A"(?:[^"\\]|\\.)*"\z/, # "string"
506
- /\A'(?:[^'\\]|\\.)*'\z/, # 'string'
507
- /\A\[[^\[\]\n]*\]\z/ # [flat, array]
508
- ].freeze
509
- private_constant :SIMPLE_EXPRESSION_PATTERNS
510
-
511
- SIMPLE_EXPRESSION_KEYWORDS = %w[nil true false self].freeze
512
- private_constant :SIMPLE_EXPRESSION_KEYWORDS
513
-
514
499
  def simple_expression?(code)
515
- SIMPLE_EXPRESSION_PATTERNS.any? { |re| code.match?(re) } ||
516
- SIMPLE_EXPRESSION_KEYWORDS.include?(code) ||
517
- negation_simple?(code)
518
- end
519
-
520
- def negation_simple?(code)
521
- return false unless code.start_with?('!') && code.length > 1
522
-
523
- rest = code[1..]
524
- simple_expression?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
500
+ SimpleExpression.match?(code)
525
501
  end
526
502
  end
527
503
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module SimpleExpression
7
+ module_function
8
+
9
+ KEYWORDS = %w[nil true false self].freeze
10
+
11
+ OPENERS = { '(' => ')', '[' => ']', '{' => '}' }.freeze
12
+ CLOSERS = OPENERS.values.freeze
13
+
14
+ # `{...}` is intentionally excluded — a top-level hash or block literal
15
+ # shouldn't be treated as a bare primary even though balanced.
16
+ PRIMARY_OPENERS = { '(' => ')', '[' => ']' }.freeze
17
+
18
+ PRIMARY_PATTERNS = [
19
+ /\A-?\d+(?:\.\d+)?/, # number (incl. negative literal)
20
+ /\A:[a-zA-Z_]\w*[!?=]?/, # :symbol
21
+ /\A"(?:[^"\\]|\\.)*"/, # "string"
22
+ /\A'(?:[^'\\]|\\.)*'/, # 'string'
23
+ /\A@@?[a-z_]\w*/, # @ivar / @@cvar
24
+ /\A\$[a-zA-Z_]\w*/, # $gvar
25
+ /\A[A-Z]\w*(?:::[A-Z]\w*)*/, # Constant / A::B::C
26
+ /\A[a-z_]\w*[!?=]?/ # local or bare call head
27
+ ].freeze
28
+
29
+ CHAIN_METHOD = /\A\.[a-zA-Z_]\w*[!?=]?/
30
+
31
+ def match?(code)
32
+ return false if code.empty? || code.include?("\n")
33
+ return true if KEYWORDS.include?(code)
34
+ return negation?(code) if code.start_with?('!')
35
+
36
+ consume(code, 0) == code.length
37
+ end
38
+
39
+ def negation?(code)
40
+ return false if code.length < 2
41
+
42
+ rest = code[1..]
43
+ match?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
44
+ end
45
+
46
+ def consume(code, pos)
47
+ pos = consume_primary(code, pos)
48
+ while pos && pos < code.length
49
+ advanced = consume_segment(code, pos)
50
+ break unless advanced
51
+
52
+ pos = advanced
53
+ end
54
+ pos
55
+ end
56
+
57
+ def consume_primary(code, pos)
58
+ return consume_group(code, pos) if PRIMARY_OPENERS.key?(code[pos])
59
+
60
+ regex = PRIMARY_PATTERNS.find { |re| code[pos..].match?(re) }
61
+ return unless regex
62
+
63
+ after = pos + regex.match(code[pos..]).end(0)
64
+ code[after] == '(' ? consume_group(code, after) : after
65
+ end
66
+
67
+ def consume_segment(code, pos)
68
+ return consume_group(code, pos) if code[pos] == '['
69
+ return unless code[pos] == '.'
70
+
71
+ match = CHAIN_METHOD.match(code[pos..])
72
+ return unless match
73
+
74
+ after = pos + match.end(0)
75
+ code[after] == '(' ? consume_group(code, after) : after
76
+ end
77
+
78
+ def consume_group(code, pos)
79
+ return unless OPENERS.key?(code[pos])
80
+
81
+ stack = [OPENERS[code[pos]]]
82
+ pos += 1
83
+ quote = nil
84
+ while pos < code.length
85
+ ch = code[pos]
86
+ if quote
87
+ if ch == '\\' && pos + 1 < code.length
88
+ pos += 2
89
+ else
90
+ quote = nil if ch == quote
91
+ pos += 1
92
+ end
93
+ elsif ['"', "'"].include?(ch)
94
+ quote = ch
95
+ pos += 1
96
+ elsif OPENERS.key?(ch)
97
+ stack.push(OPENERS[ch])
98
+ pos += 1
99
+ elsif CLOSERS.include?(ch)
100
+ return unless stack.last == ch
101
+
102
+ stack.pop
103
+ pos += 1
104
+ return pos if stack.empty?
105
+ else
106
+ pos += 1
107
+ end
108
+ end
109
+ nil
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -158,11 +158,38 @@ module Kapusta
158
158
  emit_sequence_statement_form(form, env, current_scope, result_needed:)
159
159
  elsif set_new_local_form?(form, env)
160
160
  emit_set_form(form, env, current_scope)
161
+ elsif !result_needed && class_or_module_form?(form)
162
+ [emit_class_or_module_statement(form, env), env]
161
163
  else
162
164
  [emit_expr(form, env, current_scope), env]
163
165
  end
164
166
  end
165
167
 
168
+ def class_or_module_form?(form)
169
+ form.is_a?(List) && form.head.is_a?(Sym) &&
170
+ %w[class module].include?(form.head.name)
171
+ end
172
+
173
+ def emit_class_or_module_statement(form, env)
174
+ args = form.rest
175
+ if form.head.name == 'module'
176
+ name_sym = args[0]
177
+ body = with_class_body do
178
+ emit_sequence(args[1..], env.child, :module, allow_method_definitions: true,
179
+ result: false).first
180
+ end
181
+ emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
182
+ else
183
+ name_sym, supers, body_forms = split_class_args(args)
184
+ body = with_class_body do
185
+ emit_sequence(body_forms, env.child, :class, allow_method_definitions: true,
186
+ result: false).first
187
+ end
188
+ emit_direct_class_header(name_sym, supers, body, env) ||
189
+ emit_class_wrapper(name_sym, supers, env, body)
190
+ end
191
+ end
192
+
166
193
  def emit_do_form(forms, env, current_scope, result_needed: true)
167
194
  body, new_env = emit_sequence(forms, env, current_scope,
168
195
  allow_method_definitions: false,
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'emitter/simple_expression'
3
4
  require_relative 'emitter/support'
4
5
  require_relative 'emitter/expressions'
5
6
  require_relative 'emitter/bindings'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.13.0'
4
+ VERSION = '0.13.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kapusta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
@@ -153,6 +153,7 @@ files:
153
153
  - lib/kapusta/compiler/emitter/expressions.rb
154
154
  - lib/kapusta/compiler/emitter/interop.rb
155
155
  - lib/kapusta/compiler/emitter/patterns.rb
156
+ - lib/kapusta/compiler/emitter/simple_expression.rb
156
157
  - lib/kapusta/compiler/emitter/support.rb
157
158
  - lib/kapusta/compiler/lua_compat.rb
158
159
  - lib/kapusta/compiler/macro_expander.rb