kapusta 0.3.0 → 0.4.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 +4 -4
- data/README.md +1 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +157 -0
- data/examples/macros-dbg.kap +9 -0
- data/examples/macros-multi.kap +12 -0
- data/examples/macros-swap.kap +9 -0
- data/examples/macros-thrice-if.kap +18 -0
- data/examples/macros-unless.kap +7 -0
- data/examples/macros-when-let.kap +7 -0
- data/examples/packet-router.kap +2 -5
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +42 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +34 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +7 -11
- data/lib/kapusta/compiler/emitter/support.rb +2 -1
- data/lib/kapusta/compiler/macro_expander.rb +256 -0
- data/lib/kapusta/compiler.rb +8 -0
- data/lib/kapusta/formatter.rb +216 -87
- data/lib/kapusta/reader.rb +46 -10
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +5 -5
- data/spec/examples_spec.rb +51 -0
- data/spec/formatter_spec.rb +8 -10
- metadata +11 -1
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -114,36 +114,33 @@ module Kapusta
|
|
|
114
114
|
raise Error, e.message
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def separator_for(
|
|
118
|
-
|
|
119
|
-
(groupable_top_level_form?(previous) && groupable_top_level_form?(current))
|
|
120
|
-
"\n"
|
|
121
|
-
else
|
|
122
|
-
"\n\n"
|
|
123
|
-
end
|
|
117
|
+
def separator_for(_previous, _current)
|
|
118
|
+
"\n"
|
|
124
119
|
end
|
|
125
120
|
|
|
126
121
|
def top_level_entries(forms)
|
|
127
122
|
entries = []
|
|
128
123
|
leading_comments = []
|
|
124
|
+
pending_blank = false
|
|
129
125
|
|
|
130
126
|
forms.each do |form|
|
|
131
|
-
if
|
|
127
|
+
if form.is_a?(BlankLine)
|
|
128
|
+
pending_blank = true
|
|
129
|
+
elsif comment?(form)
|
|
132
130
|
leading_comments << form
|
|
133
131
|
else
|
|
134
|
-
entries << { comments: leading_comments, form: }
|
|
132
|
+
entries << { comments: leading_comments, form:, blank_before: pending_blank }
|
|
135
133
|
leading_comments = []
|
|
134
|
+
pending_blank = false
|
|
136
135
|
end
|
|
137
136
|
end
|
|
138
137
|
|
|
139
|
-
entries << { comments: leading_comments, form: nil } unless leading_comments.empty?
|
|
138
|
+
entries << { comments: leading_comments, form: nil, blank_before: pending_blank } unless leading_comments.empty?
|
|
140
139
|
entries
|
|
141
140
|
end
|
|
142
141
|
|
|
143
|
-
def separator_for_entries(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
separator_for(previous[:form], current[:form])
|
|
142
|
+
def separator_for_entries(_previous, current)
|
|
143
|
+
current[:blank_before] ? "\n\n" : "\n"
|
|
147
144
|
end
|
|
148
145
|
|
|
149
146
|
def render_top_level_entry(entry)
|
|
@@ -156,6 +153,14 @@ module Kapusta
|
|
|
156
153
|
form.is_a?(Comment)
|
|
157
154
|
end
|
|
158
155
|
|
|
156
|
+
def blank_line?(form)
|
|
157
|
+
form.is_a?(BlankLine)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def non_semantic?(form)
|
|
161
|
+
comment?(form) || blank_line?(form)
|
|
162
|
+
end
|
|
163
|
+
|
|
159
164
|
def render(form, indent, layout: nil, top_level: false, force_expand: false)
|
|
160
165
|
flat = flat_render(form)
|
|
161
166
|
return flat if !force_expand && flat && fits?(flat, indent) && allow_flat?(form, top_level:, layout:)
|
|
@@ -165,30 +170,65 @@ module Kapusta
|
|
|
165
170
|
when List then render_list(form, indent, top_level:)
|
|
166
171
|
when Vec then render_vec(form, indent, layout:, top_level:, force_expand:)
|
|
167
172
|
when HashLit then render_hash(form, indent)
|
|
173
|
+
when Quasiquote then render_prefix('`', form.form, indent, force_expand:)
|
|
174
|
+
when Unquote then render_prefix(',', form.form, indent, force_expand:)
|
|
175
|
+
when UnquoteSplice then render_prefix(',@', form.form, indent, force_expand:)
|
|
168
176
|
else
|
|
169
177
|
flat || raise(Error, "cannot format form: #{form.inspect}")
|
|
170
178
|
end
|
|
171
179
|
end
|
|
172
180
|
|
|
181
|
+
def render_prefix(prefix, inner, indent, force_expand: false)
|
|
182
|
+
rendered = render(inner, indent + prefix.length, force_expand:)
|
|
183
|
+
lines = rendered.lines(chomp: true)
|
|
184
|
+
pad = ' ' * prefix.length
|
|
185
|
+
lines.each_with_index.map { |line, i| i.zero? ? "#{prefix}#{line}" : "#{pad}#{line}" }.join("\n")
|
|
186
|
+
end
|
|
187
|
+
|
|
173
188
|
def flat_render(form)
|
|
174
189
|
case form
|
|
175
190
|
when Comment
|
|
176
191
|
nil
|
|
192
|
+
when AutoGensym
|
|
193
|
+
"#{form.name}#"
|
|
177
194
|
when Sym
|
|
178
195
|
form.name
|
|
179
196
|
when Vec
|
|
180
197
|
return if contains_comments?(form.items)
|
|
198
|
+
return if multiline_in_source?(form)
|
|
199
|
+
|
|
200
|
+
rendered = form.items.map { |item| flat_render(item) }
|
|
201
|
+
return if rendered.any?(&:nil?)
|
|
181
202
|
|
|
182
|
-
"[#{
|
|
203
|
+
"[#{rendered.join(' ')}]"
|
|
183
204
|
when HashLit
|
|
184
205
|
return if contains_comments?(form.entries)
|
|
206
|
+
return if multiline_in_source?(form)
|
|
185
207
|
|
|
186
|
-
|
|
208
|
+
rendered = form.pairs.map { |key, value| flat_hash_pair(key, value) }
|
|
209
|
+
return if rendered.any?(&:nil?)
|
|
210
|
+
|
|
211
|
+
"{#{rendered.join(' ')}}"
|
|
187
212
|
when List
|
|
188
213
|
return if contains_comments?(form.items)
|
|
189
214
|
return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
|
|
190
|
-
|
|
191
|
-
|
|
215
|
+
return if multiline_in_source?(form)
|
|
216
|
+
return if let_with_multiple_bindings?(form)
|
|
217
|
+
return if let_with_nested_binding_value?(form)
|
|
218
|
+
|
|
219
|
+
rendered = form.items.map { |item| flat_render(item) }
|
|
220
|
+
return if rendered.any?(&:nil?)
|
|
221
|
+
|
|
222
|
+
"(#{rendered.join(' ')})"
|
|
223
|
+
when Quasiquote
|
|
224
|
+
inner = flat_render(form.form)
|
|
225
|
+
inner ? "`#{inner}" : nil
|
|
226
|
+
when Unquote
|
|
227
|
+
inner = flat_render(form.form)
|
|
228
|
+
inner ? ",#{inner}" : nil
|
|
229
|
+
when UnquoteSplice
|
|
230
|
+
inner = flat_render(form.form)
|
|
231
|
+
inner ? ",@#{inner}" : nil
|
|
192
232
|
when String, Numeric, true, false, nil
|
|
193
233
|
form.inspect
|
|
194
234
|
when Symbol
|
|
@@ -207,7 +247,7 @@ module Kapusta
|
|
|
207
247
|
raw_args = list_raw_rest(list)
|
|
208
248
|
|
|
209
249
|
case head_name
|
|
210
|
-
when 'fn', 'lambda', 'λ' then render_fn(head_name, list, indent, top_level:)
|
|
250
|
+
when 'fn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
|
|
211
251
|
when 'let' then render_let(list, indent)
|
|
212
252
|
when 'do', 'finally' then render_prefix_body_form(head_name, [], raw_args, indent)
|
|
213
253
|
when 'try' then render_try(list, indent)
|
|
@@ -237,7 +277,12 @@ module Kapusta
|
|
|
237
277
|
raw_args = list_raw_rest(list)
|
|
238
278
|
prefix_length = args[0].is_a?(Sym) && args[1].is_a?(Vec) ? 2 : 1
|
|
239
279
|
raw_prefix, raw_body = split_raw_items(raw_args, prefix_length)
|
|
240
|
-
|
|
280
|
+
force = top_level || fn_body_has_quasi_list?(raw_body)
|
|
281
|
+
render_prefix_body_form(head, raw_prefix, raw_body, indent, force_body_multiline: force)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def fn_body_has_quasi_list?(body_forms)
|
|
285
|
+
body_forms.any? { |form| form.is_a?(Quasiquote) && form.form.is_a?(List) }
|
|
241
286
|
end
|
|
242
287
|
|
|
243
288
|
def render_catch(list, indent)
|
|
@@ -300,6 +345,35 @@ module Kapusta
|
|
|
300
345
|
append_suffix(lines, ')')
|
|
301
346
|
end
|
|
302
347
|
|
|
348
|
+
def append_prefix_form(lines, form, indent, current_first_line, inline_prefix, layouts, layout_index)
|
|
349
|
+
if blank_line?(form)
|
|
350
|
+
lines << ''
|
|
351
|
+
return [current_first_line, false, layout_index]
|
|
352
|
+
end
|
|
353
|
+
if comment?(form)
|
|
354
|
+
lines << indent_block(render(form, indent + INDENT), INDENT)
|
|
355
|
+
return [current_first_line, false, layout_index]
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
rendered = render(form, indent + current_first_line.length + 1, layout: layouts[layout_index])
|
|
359
|
+
rendered_lines = rendered.lines.map(&:chomp)
|
|
360
|
+
candidate_first = "#{current_first_line} #{rendered_lines.first}"
|
|
361
|
+
|
|
362
|
+
if inline_prefix && fits?(candidate_first, indent)
|
|
363
|
+
lines[-1] = candidate_first
|
|
364
|
+
if rendered_lines.length == 1
|
|
365
|
+
[candidate_first, true, layout_index + 1]
|
|
366
|
+
else
|
|
367
|
+
hanging = ' ' * (current_first_line.length + 1)
|
|
368
|
+
rendered_lines.drop(1).each { |line| lines << "#{hanging}#{line}" }
|
|
369
|
+
[current_first_line, false, layout_index + 1]
|
|
370
|
+
end
|
|
371
|
+
else
|
|
372
|
+
lines << indent_block(rendered, INDENT)
|
|
373
|
+
[current_first_line, false, layout_index + 1]
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
303
377
|
def render_prefix_body_form(head, prefix_forms, body_forms, indent, layouts: [], force_body_multiline: false)
|
|
304
378
|
line = "(#{head}"
|
|
305
379
|
lines = [line]
|
|
@@ -308,26 +382,15 @@ module Kapusta
|
|
|
308
382
|
inline_prefix = true
|
|
309
383
|
|
|
310
384
|
prefix_forms.each do |form|
|
|
311
|
-
|
|
312
|
-
lines
|
|
313
|
-
inline_prefix = false
|
|
314
|
-
next
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
rendered = render(form, indent + INDENT, layout: layouts[layout_index])
|
|
318
|
-
layout_index += 1
|
|
319
|
-
candidate = "#{current_first_line} #{rendered}"
|
|
320
|
-
|
|
321
|
-
if inline_prefix && single_line?(rendered) && fits?(candidate, indent)
|
|
322
|
-
current_first_line = candidate
|
|
323
|
-
lines[0] = current_first_line
|
|
324
|
-
else
|
|
325
|
-
lines << indent_block(rendered, INDENT)
|
|
326
|
-
inline_prefix = false
|
|
327
|
-
end
|
|
385
|
+
current_first_line, inline_prefix, layout_index =
|
|
386
|
+
append_prefix_form(lines, form, indent, current_first_line, inline_prefix, layouts, layout_index)
|
|
328
387
|
end
|
|
329
388
|
|
|
330
389
|
body_forms.each do |form|
|
|
390
|
+
if blank_line?(form)
|
|
391
|
+
lines << ''
|
|
392
|
+
next
|
|
393
|
+
end
|
|
331
394
|
if comment?(form)
|
|
332
395
|
lines << indent_block(render(form, indent + INDENT), INDENT)
|
|
333
396
|
next
|
|
@@ -356,8 +419,8 @@ module Kapusta
|
|
|
356
419
|
return flat if inline_three_arg_if?(args) && flat && fits?(flat, indent)
|
|
357
420
|
|
|
358
421
|
lines << "(if #{render(args[0], indent + '(if '.length)}"
|
|
359
|
-
lines <<
|
|
360
|
-
lines <<
|
|
422
|
+
lines << prefix_continuation(hanging, render(args[1], indent + '(if '.length))
|
|
423
|
+
lines << prefix_continuation(hanging, render(args[2], indent + '(if '.length))
|
|
361
424
|
return append_suffix(lines, ')')
|
|
362
425
|
end
|
|
363
426
|
|
|
@@ -368,7 +431,7 @@ module Kapusta
|
|
|
368
431
|
lines << "(if #{first_pair}"
|
|
369
432
|
else
|
|
370
433
|
lines << "(if #{render(args[0], indent + '(if '.length)}"
|
|
371
|
-
lines <<
|
|
434
|
+
lines << prefix_continuation(hanging, render(args[1], indent + '(if '.length))
|
|
372
435
|
end
|
|
373
436
|
index = 2
|
|
374
437
|
else
|
|
@@ -382,12 +445,12 @@ module Kapusta
|
|
|
382
445
|
if pair
|
|
383
446
|
lines << "#{hanging}#{pair}"
|
|
384
447
|
else
|
|
385
|
-
lines <<
|
|
386
|
-
lines <<
|
|
448
|
+
lines << prefix_continuation(hanging, render(args[index], indent + '(if '.length))
|
|
449
|
+
lines << prefix_continuation(hanging, render(args[index + 1], indent + '(if '.length))
|
|
387
450
|
end
|
|
388
451
|
index += 2
|
|
389
452
|
else
|
|
390
|
-
lines <<
|
|
453
|
+
lines << prefix_continuation(hanging, render(args[index], indent + '(if '.length))
|
|
391
454
|
index += 1
|
|
392
455
|
end
|
|
393
456
|
end
|
|
@@ -395,6 +458,12 @@ module Kapusta
|
|
|
395
458
|
append_suffix(lines, ')')
|
|
396
459
|
end
|
|
397
460
|
|
|
461
|
+
def prefix_continuation(prefix, rendered)
|
|
462
|
+
first_line, *rest = rendered.lines(chomp: true)
|
|
463
|
+
pad = ' ' * prefix.length
|
|
464
|
+
["#{prefix}#{first_line}", *rest.map { |line| "#{pad}#{line}" }].join("\n")
|
|
465
|
+
end
|
|
466
|
+
|
|
398
467
|
def render_case(head, args, indent)
|
|
399
468
|
subject = args.first
|
|
400
469
|
clauses = args.drop(1)
|
|
@@ -505,15 +574,44 @@ module Kapusta
|
|
|
505
574
|
flat = flat_render(vec)
|
|
506
575
|
return flat if !force_expand && flat && fits?(flat, indent) && allow_flat?(vec, top_level:, layout:)
|
|
507
576
|
|
|
508
|
-
if layout == :pairwise && !contains_comments?(vec.items)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
577
|
+
return render_pairwise_vec(vec, indent) if layout == :pairwise && !contains_comments?(vec.items)
|
|
578
|
+
return render_filled_vec(vec, indent) if !contains_comments?(vec.items) && !vec.items.empty?
|
|
579
|
+
|
|
580
|
+
lines = ['[']
|
|
581
|
+
vec.items.each do |item|
|
|
582
|
+
lines << indent_block(render(item, indent + INDENT), INDENT)
|
|
583
|
+
end
|
|
584
|
+
append_suffix(lines, ']')
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def render_filled_vec(vec, indent)
|
|
588
|
+
output_lines = ['[']
|
|
589
|
+
|
|
590
|
+
vec.items.each_with_index do |item, idx|
|
|
591
|
+
if idx.zero?
|
|
592
|
+
item_col = output_lines.last.length
|
|
593
|
+
rendered_lines = render(item, indent + item_col).lines.map(&:chomp)
|
|
594
|
+
output_lines[-1] += rendered_lines.first
|
|
595
|
+
rendered_lines.drop(1).each { |line| output_lines << ((' ' * item_col) + line) }
|
|
596
|
+
next
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
inline_col = output_lines.last.length + 1
|
|
600
|
+
flat = flat_render(item)
|
|
601
|
+
|
|
602
|
+
if flat && indent + inline_col + flat.length <= MAX_WIDTH
|
|
603
|
+
output_lines[-1] += " #{flat}"
|
|
604
|
+
elsif flat && indent + 1 + flat.length <= MAX_WIDTH
|
|
605
|
+
output_lines << " #{flat}"
|
|
606
|
+
else
|
|
607
|
+
rendered_lines = render(item, indent + inline_col).lines.map(&:chomp)
|
|
608
|
+
output_lines[-1] += " #{rendered_lines.first}"
|
|
609
|
+
rendered_lines.drop(1).each { |line| output_lines << ((' ' * inline_col) + line) }
|
|
514
610
|
end
|
|
515
|
-
append_suffix(lines, ']')
|
|
516
611
|
end
|
|
612
|
+
|
|
613
|
+
output_lines[-1] += ']'
|
|
614
|
+
output_lines.join("\n")
|
|
517
615
|
end
|
|
518
616
|
|
|
519
617
|
def render_pairwise_vec(vec, indent)
|
|
@@ -567,33 +665,45 @@ module Kapusta
|
|
|
567
665
|
def render_hash(hash, indent)
|
|
568
666
|
flat = flat_render(hash)
|
|
569
667
|
return flat if flat && fits?(flat, indent)
|
|
668
|
+
return '{}' if hash.entries.empty?
|
|
570
669
|
|
|
571
|
-
|
|
670
|
+
output_lines = []
|
|
572
671
|
|
|
573
|
-
hash.entries.
|
|
672
|
+
hash.entries.each_with_index do |entry, idx|
|
|
574
673
|
if comment?(entry)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
end
|
|
674
|
+
output_lines << "#{idx.zero? ? '{' : ' '}#{render(entry, indent + 1)}"
|
|
675
|
+
next
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
key, value = entry
|
|
679
|
+
first_pair = output_lines.empty?
|
|
680
|
+
if hash_shorthand?(key, value)
|
|
681
|
+
output_lines << "#{first_pair ? '{' : ' '}: #{value.name}"
|
|
682
|
+
next
|
|
585
683
|
end
|
|
684
|
+
|
|
685
|
+
key_str = render_hash_key(key)
|
|
686
|
+
value_col = indent + 1 + key_str.length + 1
|
|
687
|
+
rendered_value = render(value, value_col)
|
|
688
|
+
value_lines = rendered_value.lines(chomp: true)
|
|
689
|
+
|
|
690
|
+
prefix = "#{first_pair ? '{' : ' '}#{key_str} "
|
|
691
|
+
output_lines << "#{prefix}#{value_lines.first}"
|
|
692
|
+
pad = ' ' * prefix.length
|
|
693
|
+
value_lines.drop(1).each { |line| output_lines << "#{pad}#{line}" }
|
|
586
694
|
end
|
|
587
695
|
|
|
588
|
-
|
|
696
|
+
output_lines[-1] = "#{output_lines[-1]}}"
|
|
697
|
+
output_lines.join("\n")
|
|
589
698
|
end
|
|
590
699
|
|
|
591
700
|
def flat_hash_pair(key, value)
|
|
592
|
-
if hash_shorthand?(key, value)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
701
|
+
return ": #{value.name}" if hash_shorthand?(key, value)
|
|
702
|
+
|
|
703
|
+
rendered_value = flat_render(value)
|
|
704
|
+
return unless rendered_value
|
|
705
|
+
|
|
706
|
+
"#{render_hash_key(key)} #{rendered_value}"
|
|
597
707
|
end
|
|
598
708
|
|
|
599
709
|
def render_hash_key(key)
|
|
@@ -642,16 +752,17 @@ module Kapusta
|
|
|
642
752
|
items[0].name == 'hashfn'
|
|
643
753
|
end
|
|
644
754
|
|
|
645
|
-
def allow_flat?(form, top_level
|
|
755
|
+
def allow_flat?(form, top_level: false, layout: nil)
|
|
646
756
|
return false if layout == :pairwise && form.is_a?(Vec) && semantic_items(form.items).length > 2
|
|
647
757
|
return true unless form.is_a?(List)
|
|
758
|
+
return true if !multiline_in_source?(form) && form.respond_to?(:multiline_source)
|
|
648
759
|
|
|
649
760
|
head = list_head(form)
|
|
650
761
|
return true unless head.is_a?(Sym)
|
|
651
762
|
|
|
652
763
|
case head.name
|
|
653
|
-
when 'fn', 'lambda', 'λ', 'when', 'unless', 'for', 'each', 'icollect', 'collect', 'fcollect',
|
|
654
|
-
'faccumulate'
|
|
764
|
+
when 'fn', 'lambda', 'λ', 'macro', 'when', 'unless', 'for', 'each', 'icollect', 'collect', 'fcollect',
|
|
765
|
+
'accumulate', 'faccumulate'
|
|
655
766
|
!top_level
|
|
656
767
|
else
|
|
657
768
|
!%w[let case match try catch finally do -> ->> -?> -?>> doto].include?(head.name)
|
|
@@ -659,14 +770,16 @@ module Kapusta
|
|
|
659
770
|
end
|
|
660
771
|
|
|
661
772
|
def force_multiline_body?(form)
|
|
773
|
+
return force_multiline_body?(form.form) if form.is_a?(Quasiquote)
|
|
662
774
|
return false unless form.is_a?(List)
|
|
775
|
+
return true if multiline_in_source?(form)
|
|
663
776
|
|
|
664
777
|
head = list_head(form)
|
|
665
778
|
return false unless head.is_a?(Sym)
|
|
666
779
|
|
|
667
780
|
case head.name
|
|
668
781
|
when 'if', 'case', 'match', 'let', 'try', 'catch', 'finally', 'do', 'for', '->', '->>', '-?>', '-?>>', 'doto',
|
|
669
|
-
'fn', 'lambda', 'λ'
|
|
782
|
+
'fn', 'lambda', 'λ', 'macro'
|
|
670
783
|
true
|
|
671
784
|
else
|
|
672
785
|
flat = flat_render(form)
|
|
@@ -674,27 +787,43 @@ module Kapusta
|
|
|
674
787
|
end
|
|
675
788
|
end
|
|
676
789
|
|
|
677
|
-
def
|
|
678
|
-
|
|
790
|
+
def multiline_in_source?(form)
|
|
791
|
+
form.respond_to?(:multiline_source) && form.multiline_source
|
|
679
792
|
end
|
|
680
793
|
|
|
681
|
-
def
|
|
682
|
-
|
|
683
|
-
return false unless
|
|
794
|
+
def let_with_multiple_bindings?(form)
|
|
795
|
+
head = list_head(form)
|
|
796
|
+
return false unless head.is_a?(Sym) && head.name == 'let'
|
|
797
|
+
|
|
798
|
+
bindings = semantic_items(form.items)[1]
|
|
799
|
+
return false unless bindings.is_a?(Vec)
|
|
800
|
+
|
|
801
|
+
semantic_items(bindings.items).length > 2
|
|
802
|
+
end
|
|
684
803
|
|
|
804
|
+
def let_with_nested_binding_value?(form)
|
|
685
805
|
head = list_head(form)
|
|
686
|
-
return false unless head.is_a?(Sym)
|
|
806
|
+
return false unless head.is_a?(Sym) && head.name == 'let'
|
|
807
|
+
|
|
808
|
+
bindings = semantic_items(form.items)[1]
|
|
809
|
+
return false unless bindings.is_a?(Vec)
|
|
687
810
|
|
|
688
|
-
|
|
811
|
+
semantic_items(bindings.items).each_slice(2).any? do |_pattern, value|
|
|
812
|
+
value && contains_collection?(value)
|
|
813
|
+
end
|
|
689
814
|
end
|
|
690
815
|
|
|
691
|
-
def
|
|
692
|
-
|
|
816
|
+
def contains_collection?(form)
|
|
817
|
+
case form
|
|
818
|
+
when List then semantic_items(form.items).any? { |item| collection?(item) }
|
|
819
|
+
when Vec then form.items.any? { |item| collection?(item) }
|
|
820
|
+
when HashLit then form.pairs.any? { |k, v| collection?(k) || collection?(v) }
|
|
821
|
+
else false
|
|
822
|
+
end
|
|
823
|
+
end
|
|
693
824
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
items[0].is_a?(Sym) &&
|
|
697
|
-
items[0].name == 'require'
|
|
825
|
+
def collection?(form)
|
|
826
|
+
form.is_a?(List) || form.is_a?(Vec) || form.is_a?(HashLit)
|
|
698
827
|
end
|
|
699
828
|
|
|
700
829
|
def fn_form?(form)
|
|
@@ -784,11 +913,11 @@ module Kapusta
|
|
|
784
913
|
end
|
|
785
914
|
|
|
786
915
|
def contains_comments?(items)
|
|
787
|
-
items.any? { |item|
|
|
916
|
+
items.any? { |item| non_semantic?(item) }
|
|
788
917
|
end
|
|
789
918
|
|
|
790
919
|
def semantic_items(items)
|
|
791
|
-
items.reject { |item|
|
|
920
|
+
items.reject { |item| non_semantic?(item) }
|
|
792
921
|
end
|
|
793
922
|
|
|
794
923
|
def list_head(list)
|
data/lib/kapusta/reader.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Kapusta
|
|
|
6
6
|
class Reader
|
|
7
7
|
class Error < Kapusta::Error; end
|
|
8
8
|
|
|
9
|
-
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v"
|
|
10
|
-
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
9
|
+
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v"].freeze
|
|
10
|
+
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';', '`', ','].freeze
|
|
11
11
|
CLOSING_DELIMS = [')', ']', '}'].freeze
|
|
12
12
|
|
|
13
13
|
def self.read_all(source, preserve_comments: false)
|
|
@@ -23,9 +23,10 @@ module Kapusta
|
|
|
23
23
|
def read_all
|
|
24
24
|
forms = []
|
|
25
25
|
loop do
|
|
26
|
-
skip_ws
|
|
26
|
+
had_blank = skip_ws
|
|
27
27
|
break if eof?
|
|
28
28
|
|
|
29
|
+
forms << BlankLine.new if @preserve_comments && had_blank && !forms.empty?
|
|
29
30
|
forms << read_next_item
|
|
30
31
|
end
|
|
31
32
|
forms
|
|
@@ -47,10 +48,14 @@ module Kapusta
|
|
|
47
48
|
char
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
def skip_ws
|
|
51
|
+
def skip_ws # rubocop:disable Naming/PredicateMethod
|
|
52
|
+
newlines = 0
|
|
51
53
|
until eof?
|
|
52
54
|
char = peek
|
|
53
|
-
if
|
|
55
|
+
if char == "\n"
|
|
56
|
+
newlines += 1
|
|
57
|
+
advance
|
|
58
|
+
elsif WHITESPACE.include?(char)
|
|
54
59
|
advance
|
|
55
60
|
elsif !@preserve_comments && char == ';'
|
|
56
61
|
advance until eof? || peek == "\n"
|
|
@@ -58,6 +63,7 @@ module Kapusta
|
|
|
58
63
|
break
|
|
59
64
|
end
|
|
60
65
|
end
|
|
66
|
+
newlines >= 2
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def delim?(char)
|
|
@@ -86,6 +92,8 @@ module Kapusta
|
|
|
86
92
|
when '{' then read_hash
|
|
87
93
|
when '"' then read_string
|
|
88
94
|
when '#' then read_hashfn
|
|
95
|
+
when '`' then read_quasiquote
|
|
96
|
+
when ',' then read_unquote
|
|
89
97
|
when *CLOSING_DELIMS then raise unexpected_closing_delim(peek)
|
|
90
98
|
else
|
|
91
99
|
read_atom
|
|
@@ -94,19 +102,38 @@ module Kapusta
|
|
|
94
102
|
read_postfix(form)
|
|
95
103
|
end
|
|
96
104
|
|
|
105
|
+
def read_quasiquote
|
|
106
|
+
advance
|
|
107
|
+
Quasiquote.new(read_form)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def read_unquote
|
|
111
|
+
advance
|
|
112
|
+
if peek == '@'
|
|
113
|
+
advance
|
|
114
|
+
UnquoteSplice.new(read_form)
|
|
115
|
+
else
|
|
116
|
+
Unquote.new(read_form)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
97
120
|
def read_list
|
|
98
121
|
opening_position = source_position
|
|
99
122
|
advance
|
|
100
123
|
items = []
|
|
101
124
|
loop do
|
|
102
|
-
skip_ws
|
|
125
|
+
had_blank = skip_ws
|
|
103
126
|
raise unclosed_opening_delim('(', opening_position) if eof?
|
|
104
127
|
break if peek == ')'
|
|
105
128
|
|
|
129
|
+
items << BlankLine.new if @preserve_comments && had_blank && !items.empty?
|
|
106
130
|
items << read_next_item
|
|
107
131
|
end
|
|
132
|
+
closing_position = source_position
|
|
108
133
|
advance
|
|
109
|
-
List.new(items)
|
|
134
|
+
list = List.new(items)
|
|
135
|
+
list.multiline_source = closing_position[0] != opening_position[0]
|
|
136
|
+
list
|
|
110
137
|
end
|
|
111
138
|
|
|
112
139
|
def read_vec
|
|
@@ -114,14 +141,18 @@ module Kapusta
|
|
|
114
141
|
advance
|
|
115
142
|
items = []
|
|
116
143
|
loop do
|
|
117
|
-
skip_ws
|
|
144
|
+
had_blank = skip_ws
|
|
118
145
|
raise unclosed_opening_delim('[', opening_position) if eof?
|
|
119
146
|
break if peek == ']'
|
|
120
147
|
|
|
148
|
+
items << BlankLine.new if @preserve_comments && had_blank && !items.empty?
|
|
121
149
|
items << read_next_item
|
|
122
150
|
end
|
|
151
|
+
closing_position = source_position
|
|
123
152
|
advance
|
|
124
|
-
Vec.new(items)
|
|
153
|
+
vec = Vec.new(items)
|
|
154
|
+
vec.multiline_source = closing_position[0] != opening_position[0]
|
|
155
|
+
vec
|
|
125
156
|
end
|
|
126
157
|
|
|
127
158
|
def read_hash
|
|
@@ -146,11 +177,14 @@ module Kapusta
|
|
|
146
177
|
entries << normalize_hash_pair(pending[0], pending[1])
|
|
147
178
|
pending.clear
|
|
148
179
|
end
|
|
180
|
+
closing_position = source_position
|
|
149
181
|
advance
|
|
150
182
|
|
|
151
183
|
raise Error, 'odd number of forms in hash' unless pending.empty?
|
|
152
184
|
|
|
153
|
-
HashLit.new(entries)
|
|
185
|
+
hash = HashLit.new(entries)
|
|
186
|
+
hash.multiline_source = closing_position[0] != opening_position[0]
|
|
187
|
+
hash
|
|
154
188
|
end
|
|
155
189
|
|
|
156
190
|
def read_string
|
|
@@ -251,6 +285,8 @@ module Kapusta
|
|
|
251
285
|
|
|
252
286
|
if token.start_with?(':') && token.length > 1
|
|
253
287
|
Kapusta.kebab_to_snake(token[1..]).to_sym
|
|
288
|
+
elsif token.length > 1 && token.end_with?('#') && !token[0..-2].include?('#')
|
|
289
|
+
AutoGensym.new(token.chomp('#'))
|
|
254
290
|
else
|
|
255
291
|
Sym.new(token)
|
|
256
292
|
end
|
data/lib/kapusta/version.rb
CHANGED
data/lib/kapusta.rb
CHANGED
|
@@ -42,14 +42,14 @@ module Kapusta
|
|
|
42
42
|
|
|
43
43
|
@installed = true
|
|
44
44
|
Kernel.module_eval do
|
|
45
|
-
alias_method :__kapusta_original_require_relative, :require_relative
|
|
46
|
-
private :__kapusta_original_require_relative
|
|
47
|
-
|
|
48
45
|
def require_relative(path)
|
|
49
|
-
|
|
46
|
+
location = caller_locations(1, 1).first
|
|
47
|
+
kap_path = Kapusta.send(:resolve_kap_relative, path, location)
|
|
50
48
|
return Kapusta.send(:require_kapusta_file, kap_path) if kap_path
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
base_file = location&.absolute_path || location&.path
|
|
51
|
+
target = base_file ? File.expand_path(path, File.dirname(base_file)) : path
|
|
52
|
+
Kernel.require(target)
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|