kapusta 0.11.0 → 0.11.1

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: 326afa9f8872d9d92bf4c3ca32fa5c15f78559691aa447a23472a4042a4f3a4c
4
- data.tar.gz: 625c013cad69a7e2229ea99a64796c2de90f9fd9665152c74cbd6753c67319f0
3
+ metadata.gz: 85bcd0988a353fb851e5ff955c2b5196f39193e1c3c8fb6ca4a111d6260e774d
4
+ data.tar.gz: 80efbc138038947b866eaac96ef21d5cdccd04a461a25f2e17cbd7ce925ceaec
5
5
  SHA512:
6
- metadata.gz: 2768ae288a6011b651f7e084af68717a3cf6486a45483985fe8eeb7ed9fa9ea3f2ce18c6db77b94e74b130cc10c84c486970569eee760fb82ae858070486049b
7
- data.tar.gz: 04245b8c5e76773abe1f6c9efde7c64242eaec147e8f07c3d91239a893c25cf2477e4e2b7cec518aa2e82ee948238088d37f68731f41f807aebb6c911c2d03e8
6
+ metadata.gz: 16d02e652b9221990ac027423e677a8f27c285d582c6bfb368f585140305e1c48dff5add4a8491a7996b5be99e34efd761e7fb280d8932fc6548622939b166b4
7
+ data.tar.gz: 8ab10f9991b7029d9366e47d434a9e9166568b870369f1fe585007fbe2cf9b750c84e8eb3b8fb015db255977eef80fd3255750fa99006d577a9564b640d84319
@@ -148,10 +148,7 @@ module Kapusta
148
148
  return unless ruby_name
149
149
  return if captures_outer_binding?(body, env, pattern_names(pattern))
150
150
 
151
- body_env = env.child
152
- params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
153
- body_code, = emit_sequence(body, body_env, :toplevel,
154
- allow_method_definitions: false, result: false)
151
+ params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
155
152
  header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
156
153
  [
157
154
  header,
@@ -111,27 +111,30 @@ module Kapusta
111
111
  end
112
112
 
113
113
  def try_emit_native_case(value_var, clauses, env, current_scope, mode)
114
+ arms = collect_case_arms(clauses) do |pattern, body, where_guards|
115
+ try_native_arm(pattern, body, where_guards, env, current_scope, mode)
116
+ end
117
+ return unless arms
118
+
119
+ arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
120
+ ["case #{value_var}", *arms, 'end'].join("\n")
121
+ end
122
+
123
+ def collect_case_arms(clauses)
114
124
  arms = []
115
125
  i = 0
116
126
  while i < clauses.length
117
127
  pattern = clauses[i]
118
128
  body = clauses[i + 1]
119
- inner, where_guards = if where_pattern?(pattern)
120
- [pattern.items[1], pattern.items[2..]]
121
- else
122
- [pattern, []]
123
- end
129
+ inner, where_guards = extract_pattern_and_guards(pattern)
124
130
  sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
125
- sub_arms = sub_patterns.map do |sub|
126
- try_native_arm(sub, body, where_guards, env, current_scope, mode)
127
- end
131
+ sub_arms = sub_patterns.map { |sub| yield sub, body, where_guards }
128
132
  return if sub_arms.any?(&:nil?)
129
133
 
130
134
  arms.concat(sub_arms)
131
135
  i += 2
132
136
  end
133
- arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
134
- ["case #{value_var}", *arms, 'end'].join("\n")
137
+ arms
135
138
  end
136
139
 
137
140
  def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
@@ -149,25 +152,11 @@ module Kapusta
149
152
  end
150
153
 
151
154
  def try_emit_compat_case(value_var, clauses, env, current_scope, mode)
152
- arms = []
153
- i = 0
154
- while i < clauses.length
155
- pattern = clauses[i]
156
- body = clauses[i + 1]
157
- inner, where_guards = if where_pattern?(pattern)
158
- [pattern.items[1], pattern.items[2..]]
159
- else
160
- [pattern, []]
161
- end
162
- sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
163
- sub_arms = sub_patterns.map do |sub|
164
- try_compat_arm(sub, body, where_guards, value_var, env, current_scope, mode)
165
- end
166
- return if sub_arms.any?(&:nil?)
167
-
168
- arms.concat(sub_arms)
169
- i += 2
155
+ arms = collect_case_arms(clauses) do |pattern, body, where_guards|
156
+ try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
170
157
  end
158
+ return unless arms
159
+
171
160
  emit_compat_case_lines(arms)
172
161
  end
173
162
 
@@ -176,6 +165,12 @@ module Kapusta
176
165
  last_pattern.is_a?(Sym) && last_pattern.name == '_'
177
166
  end
178
167
 
168
+ def extract_pattern_and_guards(pattern)
169
+ return [pattern, []] unless where_pattern?(pattern)
170
+
171
+ [pattern.items[1], pattern.items[2..]]
172
+ end
173
+
179
174
  def try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
180
175
  allow_pins = !where_guards.empty? && mode == :case
181
176
  arm_env = env.child
@@ -185,11 +180,11 @@ module Kapusta
185
180
  where_guard_codes = where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
186
181
  if where_guard_codes.empty?
187
182
  guard_codes = plan[:conditions]
188
- prelude = plan[:prelude]
183
+ prelude = plan[:prelude]
189
184
  else
190
185
  prelude_guards = plan[:prelude].map { |line| "begin #{line}; true end" }
191
186
  guard_codes = plan[:conditions] + prelude_guards + where_guard_codes
192
- prelude = []
187
+ prelude = []
193
188
  end
194
189
  body_code = emit_expr(body, arm_env, current_scope)
195
190
  [guard_codes, prelude, body_code]
@@ -84,6 +84,7 @@ module Kapusta
84
84
  when 'module' then emit_module_expr(args, env)
85
85
  when 'class' then emit_class_expr(args, env)
86
86
  when 'end' then emit_error!(:end_outside_header)
87
+ when 'defn' then emit_error!(:defn_outside_header)
87
88
  when 'try' then emit_try(args, env, current_scope)
88
89
  when 'raise' then emit_raise(args, env, current_scope)
89
90
  when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
@@ -483,20 +483,28 @@ module Kapusta
483
483
  Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
484
484
  end
485
485
 
486
+ SIMPLE_EXPRESSION_PATTERNS = [
487
+ /\A[a-z_]\w*\z/, # local
488
+ /\A@@?[a-z_]\w*\z/, # @ivar / @@cvar
489
+ /\A\$[a-zA-Z_]\w*\z/, # $gvar
490
+ /\A[A-Z]\w*(?:::[A-Z]\w*)*\z/, # Constant / A::B::C
491
+ /\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/, # bare call foo(...)
492
+ /\A-?\d+(?:\.\d+)?\z/, # number
493
+ /\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # local + .m/[k] chain
494
+ /\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # const + chain
495
+ /\A:[a-zA-Z_]\w*[!?=]?\z/, # :symbol
496
+ /\A"(?:[^"\\]|\\.)*"\z/, # "string"
497
+ /\A'(?:[^'\\]|\\.)*'\z/, # 'string'
498
+ /\A\[[^\[\]\n]*\]\z/ # [flat, array]
499
+ ].freeze
500
+ private_constant :SIMPLE_EXPRESSION_PATTERNS
501
+
502
+ SIMPLE_EXPRESSION_KEYWORDS = %w[nil true false self].freeze
503
+ private_constant :SIMPLE_EXPRESSION_KEYWORDS
504
+
486
505
  def simple_expression?(code)
487
- code.match?(/\A[a-z_]\w*\z/) ||
488
- code.match?(/\A@@?[a-z_]\w*\z/) ||
489
- code.match?(/\A\$[a-zA-Z_]\w*\z/) ||
490
- code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
491
- code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
492
- code.match?(/\A-?\d+(?:\.\d+)?\z/) ||
493
- code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
494
- code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
495
- code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
496
- code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
497
- code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
498
- code.match?(/\A\[[^\[\]\n]*\]\z/) ||
499
- %w[nil true false self].include?(code) ||
506
+ SIMPLE_EXPRESSION_PATTERNS.any? { |re| code.match?(re) } ||
507
+ SIMPLE_EXPRESSION_KEYWORDS.include?(code) ||
500
508
  negation_simple?(code)
501
509
  end
502
510
 
@@ -56,20 +56,13 @@ module Kapusta
56
56
  parts = []
57
57
  deferred = []
58
58
  current_env = env
59
- items = pattern.items
60
- i = 0
61
- while i < items.length
62
- if items[i].is_a?(Sym) && items[i].name == '&'
63
- sub = items[i + 1]
64
- raise PatternNotTranslatable unless sub.is_a?(Sym)
65
-
59
+ each_pattern_item(pattern.items) do |kind, sub|
60
+ if kind == :rest
66
61
  parts << native_rest_target(sub, current_env)
67
- i += 2
68
62
  else
69
- code, current_env, follow_up = native_destructure_target(items[i], current_env, allow_follow_up: true)
63
+ code, current_env, follow_up = native_destructure_target(sub, current_env, allow_follow_up: true)
70
64
  parts << code
71
65
  deferred << follow_up if follow_up
72
- i += 1
73
66
  end
74
67
  end
75
68
  if deferred.empty?
@@ -122,17 +115,13 @@ module Kapusta
122
115
  inner = []
123
116
  current = env
124
117
  deferred = []
125
- items = pattern.items
126
- i = 0
127
- while i < items.length
128
- if items[i].is_a?(Sym) && items[i].name == '&'
129
- inner << native_rest_target(items[i + 1], current)
130
- i += 2
118
+ each_pattern_item(pattern.items) do |kind, sub|
119
+ if kind == :rest
120
+ inner << native_rest_target(sub, current)
131
121
  else
132
- code, current, follow_up = native_destructure_target(items[i], current)
122
+ code, current, follow_up = native_destructure_target(sub, current)
133
123
  inner << code
134
124
  deferred << follow_up if follow_up
135
- i += 1
136
125
  end
137
126
  end
138
127
  raise PatternNotTranslatable unless deferred.empty?
@@ -225,36 +214,26 @@ module Kapusta
225
214
  state[:conditions] << "#{value_code}.length >= #{min_length}"
226
215
 
227
216
  index = 0
228
- i = 0
229
- while i < items.length
230
- if rest_pattern_marker?(items, i)
231
- sub = items[i + 1]
217
+ each_pattern_item(items) do |kind, sub|
218
+ if kind == :rest
232
219
  raise PatternNotTranslatable unless sub.is_a?(Sym)
233
220
 
234
221
  unless sub.name == '_'
235
222
  ruby = define_local(arm_env, sub.name)
236
223
  state[:prelude] << "#{ruby} = #{value_code}[#{index}..]"
237
224
  end
238
- i += 2
239
225
  else
240
- compile_compat_pattern(items[i], "#{value_code}[#{index}]", env, arm_env,
226
+ compile_compat_pattern(sub, "#{value_code}[#{index}]", env, arm_env,
241
227
  mode:, allow_pins:, state:)
242
228
  index += 1
243
- i += 1
244
229
  end
245
230
  end
246
231
  end
247
232
 
248
233
  def compat_sequence_min_length(items)
249
234
  count = 0
250
- i = 0
251
- while i < items.length
252
- if rest_pattern_marker?(items, i)
253
- i += 2
254
- else
255
- count += 1
256
- i += 1
257
- end
235
+ each_pattern_item(items) do |kind, _sub|
236
+ count += 1 if kind == :item
258
237
  end
259
238
  count
260
239
  end
@@ -346,11 +325,9 @@ module Kapusta
346
325
  def compile_native_sequence(items, env, mode:, allow_pins:, state:)
347
326
  parts = []
348
327
  has_rest = false
349
- i = 0
350
- while i < items.length
351
- if rest_pattern_marker?(items, i)
328
+ each_pattern_item(items) do |kind, sub|
329
+ if kind == :rest
352
330
  has_rest = true
353
- sub = items[i + 1]
354
331
  raise PatternNotTranslatable unless sub.is_a?(Sym)
355
332
 
356
333
  if sub.name == '_'
@@ -360,10 +337,8 @@ module Kapusta
360
337
  state[:binding_names] << sub.name
361
338
  parts << "*#{sanitize_local(sub.name)}"
362
339
  end
363
- i += 2
364
340
  else
365
- parts << compile_native_pattern(items[i], env, mode:, allow_pins:, state:)
366
- i += 1
341
+ parts << compile_native_pattern(sub, env, mode:, allow_pins:, state:)
367
342
  end
368
343
  end
369
344
  parts << '*' unless has_rest
@@ -464,16 +439,8 @@ module Kapusta
464
439
  pattern.name == '_' ? [] : [pattern.name]
465
440
  when Vec
466
441
  names = []
467
- items = pattern.items
468
- i = 0
469
- while i < items.length
470
- if items[i].is_a?(Sym) && items[i].name == '&'
471
- names.concat(pattern_names(items[i + 1]))
472
- i += 2
473
- else
474
- names.concat(pattern_names(items[i]))
475
- i += 1
476
- end
442
+ each_pattern_item(pattern.items) do |_kind, sub|
443
+ names.concat(pattern_names(sub))
477
444
  end
478
445
  names
479
446
  when HashLit
@@ -504,8 +471,18 @@ module Kapusta
504
471
  name.length > 1 && (name.start_with?('?') || name.start_with?('_'))
505
472
  end
506
473
 
507
- def rest_pattern_marker?(items, index)
508
- items[index].is_a?(Sym) && items[index].name == '&'
474
+ def each_pattern_item(items)
475
+ i = 0
476
+ while i < items.length
477
+ item = items[i]
478
+ if item.is_a?(Sym) && item.name == '&'
479
+ yield :rest, items[i + 1]
480
+ i += 2
481
+ else
482
+ yield :item, item
483
+ i += 1
484
+ end
485
+ end
509
486
  end
510
487
  end
511
488
  end
@@ -123,29 +123,39 @@ module Kapusta
123
123
 
124
124
  def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
125
125
  with_current_form(form) do
126
- if allow_method_definitions &&
127
- method_definition_form?(form) &&
128
- %i[toplevel module class].include?(current_scope)
129
- code, env = emit_definition_form(form, env, current_scope)
130
- return [code, env] if code
131
- end
126
+ form = lower_defn_in_sequence(form, current_scope) if defn_form?(form)
127
+ emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
128
+ end
129
+ end
132
130
 
133
- if named_function_form?(form)
134
- emit_named_fn_assignment(form, env, current_scope)
135
- elsif local_form?(form)
136
- code, env = emit_local_form(form, env, current_scope,
137
- allow_constant: allow_method_definitions)
138
- code = code.delete_suffix("\nnil") unless result_needed
139
- [code, env]
140
- elsif do_form?(form)
141
- emit_do_form(form.rest, env, current_scope, result_needed:)
142
- elsif sequence_statement_form?(form)
143
- emit_sequence_statement_form(form, env, current_scope, result_needed:)
144
- elsif set_new_local_form?(form, env)
145
- emit_set_form(form, env, current_scope)
146
- else
147
- [emit_expr(form, env, current_scope), env]
148
- end
131
+ def lower_defn_in_sequence(form, current_scope)
132
+ emit_error!(:defn_outside_header) unless %i[module class].include?(current_scope)
133
+ lower_defn_to_fn(form)
134
+ end
135
+
136
+ def emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
137
+ if allow_method_definitions &&
138
+ method_definition_form?(form) &&
139
+ %i[toplevel module class].include?(current_scope)
140
+ code, env = emit_definition_form(form, env, current_scope)
141
+ return [code, env] if code
142
+ end
143
+
144
+ if named_function_form?(form)
145
+ emit_named_fn_assignment(form, env, current_scope)
146
+ elsif local_form?(form)
147
+ code, env = emit_local_form(form, env, current_scope,
148
+ allow_constant: allow_method_definitions)
149
+ code = code.delete_suffix("\nnil") unless result_needed
150
+ [code, env]
151
+ elsif do_form?(form)
152
+ emit_do_form(form.rest, env, current_scope, result_needed:)
153
+ elsif sequence_statement_form?(form)
154
+ emit_sequence_statement_form(form, env, current_scope, result_needed:)
155
+ elsif set_new_local_form?(form, env)
156
+ emit_set_form(form, env, current_scope)
157
+ else
158
+ [emit_expr(form, env, current_scope), env]
149
159
  end
150
160
  end
151
161
 
@@ -214,6 +224,26 @@ module Kapusta
214
224
  %w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
215
225
  end
216
226
 
227
+ def defn_form?(form)
228
+ form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'defn'
229
+ end
230
+
231
+ def lower_defn_to_fn(form)
232
+ name_sym = form.items[1]
233
+ emit_error!(:fn_no_params) unless name_sym.is_a?(Sym)
234
+
235
+ fn_sym = Sym.new('fn')
236
+ fn_sym.line = form.head.line
237
+ fn_sym.column = form.head.column
238
+ self_sym = Sym.new("self.#{name_sym.name}")
239
+ self_sym.line = name_sym.line
240
+ self_sym.column = name_sym.column
241
+ new_list = List.new([fn_sym, self_sym, *form.items[2..]])
242
+ new_list.line = form.line
243
+ new_list.column = form.column
244
+ new_list
245
+ end
246
+
217
247
  def method_definition_form?(form)
218
248
  named_function_form?(form)
219
249
  end
@@ -57,9 +57,9 @@ module Kapusta
57
57
  def expand(form)
58
58
  case form
59
59
  when List then expand_list(form)
60
- when Vec then copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
60
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
61
61
  when HashLit
62
- copy_position(
62
+ Kapusta.copy_position(
63
63
  HashLit.new(form.entries.map do |entry|
64
64
  entry.is_a?(Array) ? [expand(entry[0]), expand(entry[1])] : entry
65
65
  end),
@@ -70,14 +70,6 @@ module Kapusta
70
70
  end
71
71
  end
72
72
 
73
- def copy_position(target, source)
74
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
75
-
76
- target.line ||= source.line
77
- target.column ||= source.column
78
- target
79
- end
80
-
81
73
  def expand_list(list)
82
74
  return list if list.empty?
83
75
 
@@ -100,11 +92,11 @@ module Kapusta
100
92
  if @macros.key?(key)
101
93
  args = list.rest
102
94
  result = invoke_macro(key, args)
103
- return copy_position(expand(result), list)
95
+ return Kapusta.copy_position(expand(result), list)
104
96
  end
105
97
  end
106
98
 
107
- copy_position(List.new(list.items.map { |item| expand(item) }), list)
99
+ Kapusta.copy_position(List.new(list.items.map { |item| expand(item) }), list)
108
100
  end
109
101
 
110
102
  def lookup_key(name)
@@ -34,15 +34,15 @@ module Kapusta
34
34
 
35
35
  def lower(form)
36
36
  case form
37
- when Quasiquote then copy_position(lower_quasi(form.form), form)
37
+ when Quasiquote then Kapusta.copy_position(lower_quasi(form.form), form)
38
38
  when Unquote, UnquoteSplice
39
39
  raise @error_class, Kapusta::Errors.format(:unquote_outside_quasiquote)
40
40
  when AutoGensym
41
41
  raise @error_class, Kapusta::Errors.format(:auto_gensym_outside_quasiquote, name: form.name)
42
- when List then copy_position(List.new(form.items.map { |item| lower(item) }), form)
43
- when Vec then copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
42
+ when List then Kapusta.copy_position(List.new(form.items.map { |item| lower(item) }), form)
43
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
44
44
  when HashLit
45
- copy_position(
45
+ Kapusta.copy_position(
46
46
  HashLit.new(form.entries.map do |entry|
47
47
  entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
48
48
  end),
@@ -94,14 +94,6 @@ module Kapusta
94
94
  form.is_a?(List) && form.head.is_a?(Sym) && FN_HEADS.include?(form.head.name)
95
95
  end
96
96
 
97
- def copy_position(target, source)
98
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
99
-
100
- target.line ||= source.line
101
- target.column ||= source.column
102
- target
103
- end
104
-
105
97
  def lower_quasi(form)
106
98
  case form
107
99
  when AutoGensym then gensym_local_for(form.name)
@@ -12,9 +12,9 @@ module Kapusta
12
12
  def normalize(form)
13
13
  case form
14
14
  when List then normalize_list(form)
15
- when Vec then inherit_position(Vec.new(form.items.map { |item| normalize(item) }), form)
15
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| normalize(item) }), form)
16
16
  when HashLit
17
- inherit_position(
17
+ Kapusta.copy_position(
18
18
  HashLit.new(form.pairs.map { |key, value| [normalize_hash_key(key), normalize(value)] }),
19
19
  form
20
20
  )
@@ -37,53 +37,39 @@ module Kapusta
37
37
 
38
38
  head = list.head
39
39
  items = list.items.map { |item| normalize(item) }
40
- return inherit_position(List.new(items), list) unless head.is_a?(Sym)
40
+ return Kapusta.copy_position(List.new(items), list) unless head.is_a?(Sym)
41
41
 
42
42
  case head.name
43
- when 'defn'
44
- name_sym = items[1]
45
- raise compiler_error(:fn_no_params, list) unless name_sym.is_a?(Sym)
46
-
47
- self_name = inherit_position(Sym.new("self.#{name_sym.name}"), name_sym)
48
- inherit_position(List.new([inherit_position(Sym.new('fn'), head), self_name, *items[2..]]), list)
49
43
  when 'when'
50
44
  raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
51
45
 
52
46
  cond = items[1]
53
47
  body = wrap_do(items[2..])
54
- inherit_position(List.new([Sym.new('if'), cond, body]), list)
48
+ Kapusta.copy_position(List.new([Sym.new('if'), cond, body]), list)
55
49
  when 'unless'
56
50
  raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
57
51
 
58
52
  cond = items[1]
59
53
  body = wrap_do(items[2..])
60
- inherit_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
54
+ Kapusta.copy_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
61
55
  when 'tset'
62
56
  raise compiler_error(:tset_no_value, list) if items.length < 4
63
57
 
64
- inherit_position(
58
+ Kapusta.copy_position(
65
59
  List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
66
60
  list
67
61
  )
68
62
  when *LuaCompat::SPECIAL_FORMS
69
63
  normalize_lua_compat_form(head.name, items)
70
64
  when '->', '->>', '-?>', '-?>>'
71
- inherit_position(normalize(thread(items[1..], head.name)), list)
65
+ Kapusta.copy_position(normalize(thread(items[1..], head.name)), list)
72
66
  when 'doto'
73
- inherit_position(normalize(doto(items[1..])), list)
67
+ Kapusta.copy_position(normalize(doto(items[1..])), list)
74
68
  else
75
- inherit_position(List.new(items), list)
69
+ Kapusta.copy_position(List.new(items), list)
76
70
  end
77
71
  end
78
72
 
79
- def inherit_position(target, source)
80
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
81
-
82
- target.line ||= source.line
83
- target.column ||= source.column
84
- target
85
- end
86
-
87
73
  def compiler_error(code, form, **args)
88
74
  line = form.respond_to?(:line) ? form.line : nil
89
75
  column = form.respond_to?(:column) ? form.column : nil
@@ -25,6 +25,7 @@ module Kapusta
25
25
  dot_no_args: 'expected table argument',
26
26
  each_no_binding: 'expected binding table',
27
27
  empty_call: 'expected a function, macro, or special to call',
28
+ defn_outside_header: 'defn outside class or module',
28
29
  end_outside_header: 'end outside class or module',
29
30
  end_with_args: 'end takes no arguments',
30
31
  unclosed_header: 'class or module not closed with (end)',
@@ -17,10 +17,15 @@ module Kapusta
17
17
  end
18
18
  EndMarker = Struct.new(:line, :column, :end_column, :target, keyword_init: true)
19
19
 
20
- SKIPPED_HEADS = %w[macros quasi-sym quasi-list
21
- quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
22
-
23
20
  DISPATCHERS = {
21
+ 'macros' => :skip,
22
+ 'quasi-sym' => :skip,
23
+ 'quasi-list' => :skip,
24
+ 'quasi-list-tail' => :skip,
25
+ 'quasi-vec' => :skip,
26
+ 'quasi-vec-tail' => :skip,
27
+ 'quasi-hash' => :skip,
28
+ 'quasi-gensym' => :skip,
24
29
  'let' => :walk_let,
25
30
  'local' => :walk_local_var,
26
31
  'var' => :walk_local_var,
@@ -246,10 +251,12 @@ module Kapusta
246
251
  return
247
252
  end
248
253
 
249
- return if SKIPPED_HEADS.include?(head.name)
250
-
251
254
  dispatcher = DISPATCHERS[head.name]
252
- return send(dispatcher, list, scope) if dispatcher
255
+ if dispatcher
256
+ return if dispatcher == :skip
257
+
258
+ return send(dispatcher, list, scope)
259
+ end
253
260
 
254
261
  list.items.each { |item| walk_form(item, scope) }
255
262
  end
@@ -380,28 +387,30 @@ module Kapusta
380
387
  def walk_fn(list, scope)
381
388
  items = list.items
382
389
  if items[1].is_a?(Vec)
390
+ name_sym = nil
383
391
  params = items[1]
384
392
  body = items[2..]
385
- fn_scope = make_scope(scope, :fn)
386
- bind_param_vec(params, fn_scope)
387
- body.each { |form| walk_form(form, fn_scope) }
388
393
  elsif items[1].is_a?(Sym) && items[2].is_a?(Vec)
389
394
  name_sym = items[1]
390
395
  params = items[2]
391
396
  body = items[3..]
397
+ else
398
+ items[1..]&.each { |item| walk_form(item, scope) }
399
+ return
400
+ end
401
+
402
+ fn_scope = make_scope(scope, :fn)
403
+ if name_sym
392
404
  kind = if method_definition_context?
393
405
  :method
394
406
  else
395
407
  (scope == @root_scope ? :toplevel_fn : :fn_local)
396
408
  end
397
- fn_scope = make_scope(scope, :fn)
398
409
  binding = add_binding(name_sym, scope, kind, lexical: kind != :method)
399
410
  fn_scope.bindings[name_sym.name] = binding unless kind == :method
400
- bind_param_vec(params, fn_scope)
401
- body.each { |form| walk_form(form, fn_scope) }
402
- else
403
- items[1..]&.each { |item| walk_form(item, scope) }
404
411
  end
412
+ bind_param_vec(params, fn_scope)
413
+ body.each { |form| walk_form(form, fn_scope) }
405
414
  end
406
415
 
407
416
  def method_definition_context?
@@ -586,40 +595,35 @@ module Kapusta
586
595
  end
587
596
  end
588
597
 
589
- def bind_param_vec(vec, scope)
590
- items = vec.items
598
+ def each_pattern_item(items)
591
599
  i = 0
592
600
  while i < items.length
593
- item = items[i]
594
- if item.is_a?(Sym) && item.name == '&'
595
- rest = items[i + 1]
596
- bind_pattern(rest, scope, :fn_param) if rest.is_a?(Sym) && rest.name != '_'
601
+ if items[i].is_a?(Sym) && items[i].name == '&'
602
+ yield :rest, items[i + 1]
597
603
  i += 2
598
- elsif item.is_a?(Sym) && ['...', '_'].include?(item.name)
599
- i += 1
600
604
  else
601
- bind_pattern(item, scope, :fn_param)
605
+ yield :item, items[i]
602
606
  i += 1
603
607
  end
604
608
  end
605
609
  end
606
610
 
607
- def bind_vec_pattern(vec, scope, kind)
608
- items = vec.items
609
- i = 0
610
- while i < items.length
611
- item = items[i]
612
- if item.is_a?(Sym) && item.name == '&'
613
- rest = items[i + 1]
614
- bind_pattern(rest, scope, kind) if rest
615
- i += 2
616
- else
617
- bind_pattern(item, scope, kind)
618
- i += 1
611
+ def bind_param_vec(vec, scope)
612
+ each_pattern_item(vec.items) do |kind, item|
613
+ if kind == :rest
614
+ bind_pattern(item, scope, :fn_param) if item.is_a?(Sym) && item.name != '_'
615
+ elsif !(item.is_a?(Sym) && ['...', '_'].include?(item.name))
616
+ bind_pattern(item, scope, :fn_param)
619
617
  end
620
618
  end
621
619
  end
622
620
 
621
+ def bind_vec_pattern(vec, scope, kind)
622
+ each_pattern_item(vec.items) do |item_kind, item|
623
+ bind_pattern(item, scope, kind) if item_kind == :item || item
624
+ end
625
+ end
626
+
623
627
  def bind_hash_pattern(hash, scope, kind)
624
628
  hash.pairs.each do |pair|
625
629
  bind_pattern(pair[1], scope, kind)
@@ -35,7 +35,7 @@ module Kapusta
35
35
  end
36
36
 
37
37
  def remove(uri)
38
- path = uri_to_path(uri)
38
+ path = LSP.uri_to_path(uri)
39
39
  if path && File.file?(path)
40
40
  store(uri, File.read(path))
41
41
  else
@@ -178,7 +178,7 @@ module Kapusta
178
178
  end
179
179
 
180
180
  def resolve_module_uris(importing_uri, module_label)
181
- importing_path = uri_to_path(importing_uri)
181
+ importing_path = LSP.uri_to_path(importing_uri)
182
182
  return [] unless importing_path
183
183
 
184
184
  base_dir = File.dirname(importing_path)
@@ -209,17 +209,6 @@ module Kapusta
209
209
  def path_to_uri(path)
210
210
  "file://#{URI::DEFAULT_PARSER.escape(File.expand_path(path))}"
211
211
  end
212
-
213
- def uri_to_path(uri)
214
- return unless uri.is_a?(String)
215
-
216
- parsed = URI.parse(uri)
217
- return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
218
-
219
- uri
220
- rescue URI::InvalidURIError
221
- nil
222
- end
223
212
  end
224
213
  end
225
214
  end
data/lib/kapusta/lsp.rb CHANGED
@@ -19,6 +19,17 @@ module Kapusta
19
19
  new(input:, output:, log:).run
20
20
  end
21
21
 
22
+ def self.uri_to_path(uri)
23
+ return unless uri.is_a?(String)
24
+
25
+ parsed = URI.parse(uri)
26
+ return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
27
+
28
+ uri
29
+ rescue URI::InvalidURIError
30
+ nil
31
+ end
32
+
22
33
  def initialize(input:, output:, log:)
23
34
  @input = input.binmode
24
35
  @output = output.binmode
@@ -150,8 +161,8 @@ module Kapusta
150
161
 
151
162
  def on_initialize(params)
152
163
  folders = params['workspaceFolders'] || []
153
- roots = folders.filter_map { |f| uri_to_path(f['uri']) }
154
- roots << uri_to_path(params['rootUri']) if params['rootUri']
164
+ roots = folders.filter_map { |f| LSP.uri_to_path(f['uri']) }
165
+ roots << LSP.uri_to_path(params['rootUri']) if params['rootUri']
155
166
  roots.compact!
156
167
  roots.uniq!
157
168
  debug("initialize: roots=#{roots.inspect}")
@@ -213,7 +224,7 @@ module Kapusta
213
224
  entry = @sources[uri]
214
225
  return [] unless entry
215
226
 
216
- Formatting.text_edits(entry[:text], uri_to_path(uri))
227
+ Formatting.text_edits(entry[:text], LSP.uri_to_path(uri))
217
228
  end
218
229
 
219
230
  def definition(params)
@@ -283,23 +294,12 @@ module Kapusta
283
294
  end
284
295
 
285
296
  def publish_diagnostics(uri, text, version)
286
- diagnostics = Diagnostics.collect(text, uri_to_path(uri))
297
+ diagnostics = Diagnostics.collect(text, LSP.uri_to_path(uri))
287
298
  params = { uri:, diagnostics: }
288
299
  params[:version] = version unless version.nil?
289
300
  notify('textDocument/publishDiagnostics', params)
290
301
  end
291
302
 
292
- def uri_to_path(uri)
293
- return unless uri
294
-
295
- parsed = URI.parse(uri)
296
- return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
297
-
298
- uri
299
- rescue URI::InvalidURIError
300
- uri
301
- end
302
-
303
303
  def log(message)
304
304
  @log.puts "kapusta-ls: #{message}"
305
305
  end
@@ -4,4 +4,12 @@ module Kapusta
4
4
  def self.kebab_to_snake(name)
5
5
  name.tr('-', '_')
6
6
  end
7
+
8
+ def self.copy_position(target, source)
9
+ return target unless target.respond_to?(:line=) && source.respond_to?(:line)
10
+
11
+ target.line ||= source.line
12
+ target.column ||= source.column
13
+ target
14
+ end
7
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.11.0'
4
+ VERSION = '0.11.1'
5
5
  end
@@ -92,6 +92,11 @@ RSpec.describe 'examples-errors' do
92
92
  .to eq("destructure-literal-table.kap:4:1: could not destructure literal\n")
93
93
  end
94
94
 
95
+ it 'defn-outside-header.kap' do
96
+ expect(run_error_example('defn-outside-header.kap'))
97
+ .to eq("defn-outside-header.kap:1:1: defn outside class or module\n")
98
+ end
99
+
95
100
  it 'end-outside-header.kap' do
96
101
  expect(run_error_example('end-outside-header.kap'))
97
102
  .to eq("end-outside-header.kap:1:1: end outside class or module\n")
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.11.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov