kapusta 0.3.0 → 0.5.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 +4 -4
- data/README.md +1 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +187 -0
- data/examples/even-squares.kap +22 -7
- 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/roman-to-integer.kap +3 -3
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +77 -1
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +52 -6
- data/lib/kapusta/compiler/emitter/collections.rb +64 -20
- data/lib/kapusta/compiler/emitter/control_flow.rb +7 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +58 -13
- data/lib/kapusta/compiler/emitter/interop.rb +6 -4
- data/lib/kapusta/compiler/emitter/patterns.rb +29 -12
- data/lib/kapusta/compiler/emitter/support.rb +46 -23
- data/lib/kapusta/compiler/macro_expander.rb +286 -0
- data/lib/kapusta/compiler/normalizer.rb +35 -9
- data/lib/kapusta/compiler.rb +13 -1
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +69 -0
- data/lib/kapusta/formatter.rb +228 -92
- data/lib/kapusta/reader.rb +80 -22
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +6 -5
- data/spec/examples_errors_spec.rb +229 -0
- data/spec/examples_spec.rb +51 -0
- data/spec/formatter_spec.rb +15 -16
- metadata +13 -2
- data/spec/reader_spec.rb +0 -26
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -27,7 +27,8 @@ module Kapusta
|
|
|
27
27
|
|
|
28
28
|
formatted = @files.map do |path|
|
|
29
29
|
original = read_source(path)
|
|
30
|
-
|
|
30
|
+
validate_kapusta_source(original, path)
|
|
31
|
+
[path, original, format_source(original, path)]
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
case @mode
|
|
@@ -48,13 +49,17 @@ module Kapusta
|
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
0
|
|
51
|
-
rescue Error => e
|
|
52
|
-
warn e.
|
|
52
|
+
rescue Kapusta::Error => e
|
|
53
|
+
warn e.formatted
|
|
53
54
|
1
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
private
|
|
57
58
|
|
|
59
|
+
def validate_kapusta_source(source, path)
|
|
60
|
+
Kapusta::Compiler.compile(source, path:)
|
|
61
|
+
end
|
|
62
|
+
|
|
58
63
|
def parse_args(argv)
|
|
59
64
|
argv.each do |arg|
|
|
60
65
|
case arg
|
|
@@ -99,7 +104,7 @@ module Kapusta
|
|
|
99
104
|
$stdin.read
|
|
100
105
|
end
|
|
101
106
|
|
|
102
|
-
def format_source(source)
|
|
107
|
+
def format_source(source, path = nil)
|
|
103
108
|
forms = Reader.read_all(source, preserve_comments: true)
|
|
104
109
|
entries = top_level_entries(forms)
|
|
105
110
|
return '' if entries.empty?
|
|
@@ -110,40 +115,39 @@ module Kapusta
|
|
|
110
115
|
output << render_top_level_entry(entry)
|
|
111
116
|
end
|
|
112
117
|
output << "\n"
|
|
118
|
+
rescue Kapusta::Error => e
|
|
119
|
+
raise e.with_defaults(path:)
|
|
113
120
|
rescue StandardError => e
|
|
114
|
-
raise Error
|
|
121
|
+
raise Error.new(e.message, path:)
|
|
115
122
|
end
|
|
116
123
|
|
|
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
|
|
124
|
+
def separator_for(_previous, _current)
|
|
125
|
+
"\n"
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def top_level_entries(forms)
|
|
127
129
|
entries = []
|
|
128
130
|
leading_comments = []
|
|
131
|
+
pending_blank = false
|
|
129
132
|
|
|
130
133
|
forms.each do |form|
|
|
131
|
-
if
|
|
134
|
+
if form.is_a?(BlankLine)
|
|
135
|
+
pending_blank = true
|
|
136
|
+
elsif comment?(form)
|
|
132
137
|
leading_comments << form
|
|
133
138
|
else
|
|
134
|
-
entries << { comments: leading_comments, form: }
|
|
139
|
+
entries << { comments: leading_comments, form:, blank_before: pending_blank }
|
|
135
140
|
leading_comments = []
|
|
141
|
+
pending_blank = false
|
|
136
142
|
end
|
|
137
143
|
end
|
|
138
144
|
|
|
139
|
-
entries << { comments: leading_comments, form: nil } unless leading_comments.empty?
|
|
145
|
+
entries << { comments: leading_comments, form: nil, blank_before: pending_blank } unless leading_comments.empty?
|
|
140
146
|
entries
|
|
141
147
|
end
|
|
142
148
|
|
|
143
|
-
def separator_for_entries(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
separator_for(previous[:form], current[:form])
|
|
149
|
+
def separator_for_entries(_previous, current)
|
|
150
|
+
current[:blank_before] ? "\n\n" : "\n"
|
|
147
151
|
end
|
|
148
152
|
|
|
149
153
|
def render_top_level_entry(entry)
|
|
@@ -156,6 +160,14 @@ module Kapusta
|
|
|
156
160
|
form.is_a?(Comment)
|
|
157
161
|
end
|
|
158
162
|
|
|
163
|
+
def blank_line?(form)
|
|
164
|
+
form.is_a?(BlankLine)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def non_semantic?(form)
|
|
168
|
+
comment?(form) || blank_line?(form)
|
|
169
|
+
end
|
|
170
|
+
|
|
159
171
|
def render(form, indent, layout: nil, top_level: false, force_expand: false)
|
|
160
172
|
flat = flat_render(form)
|
|
161
173
|
return flat if !force_expand && flat && fits?(flat, indent) && allow_flat?(form, top_level:, layout:)
|
|
@@ -165,30 +177,65 @@ module Kapusta
|
|
|
165
177
|
when List then render_list(form, indent, top_level:)
|
|
166
178
|
when Vec then render_vec(form, indent, layout:, top_level:, force_expand:)
|
|
167
179
|
when HashLit then render_hash(form, indent)
|
|
180
|
+
when Quasiquote then render_prefix('`', form.form, indent, force_expand:)
|
|
181
|
+
when Unquote then render_prefix(',', form.form, indent, force_expand:)
|
|
182
|
+
when UnquoteSplice then render_prefix(',@', form.form, indent, force_expand:)
|
|
168
183
|
else
|
|
169
184
|
flat || raise(Error, "cannot format form: #{form.inspect}")
|
|
170
185
|
end
|
|
171
186
|
end
|
|
172
187
|
|
|
188
|
+
def render_prefix(prefix, inner, indent, force_expand: false)
|
|
189
|
+
rendered = render(inner, indent + prefix.length, force_expand:)
|
|
190
|
+
lines = rendered.lines(chomp: true)
|
|
191
|
+
pad = ' ' * prefix.length
|
|
192
|
+
lines.each_with_index.map { |line, i| i.zero? ? "#{prefix}#{line}" : "#{pad}#{line}" }.join("\n")
|
|
193
|
+
end
|
|
194
|
+
|
|
173
195
|
def flat_render(form)
|
|
174
196
|
case form
|
|
175
197
|
when Comment
|
|
176
198
|
nil
|
|
199
|
+
when AutoGensym
|
|
200
|
+
"#{form.name}#"
|
|
177
201
|
when Sym
|
|
178
202
|
form.name
|
|
179
203
|
when Vec
|
|
180
204
|
return if contains_comments?(form.items)
|
|
205
|
+
return if multiline_in_source?(form)
|
|
206
|
+
|
|
207
|
+
rendered = form.items.map { |item| flat_render(item) }
|
|
208
|
+
return if rendered.any?(&:nil?)
|
|
181
209
|
|
|
182
|
-
"[#{
|
|
210
|
+
"[#{rendered.join(' ')}]"
|
|
183
211
|
when HashLit
|
|
184
212
|
return if contains_comments?(form.entries)
|
|
213
|
+
return if multiline_in_source?(form)
|
|
214
|
+
|
|
215
|
+
rendered = form.pairs.map { |key, value| flat_hash_pair(key, value) }
|
|
216
|
+
return if rendered.any?(&:nil?)
|
|
185
217
|
|
|
186
|
-
"{#{
|
|
218
|
+
"{#{rendered.join(' ')}}"
|
|
187
219
|
when List
|
|
188
220
|
return if contains_comments?(form.items)
|
|
189
221
|
return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
|
|
190
|
-
|
|
191
|
-
|
|
222
|
+
return if multiline_in_source?(form)
|
|
223
|
+
return if let_with_multiple_bindings?(form)
|
|
224
|
+
return if let_with_nested_binding_value?(form)
|
|
225
|
+
|
|
226
|
+
rendered = form.items.map { |item| flat_render(item) }
|
|
227
|
+
return if rendered.any?(&:nil?)
|
|
228
|
+
|
|
229
|
+
"(#{rendered.join(' ')})"
|
|
230
|
+
when Quasiquote
|
|
231
|
+
inner = flat_render(form.form)
|
|
232
|
+
inner ? "`#{inner}" : nil
|
|
233
|
+
when Unquote
|
|
234
|
+
inner = flat_render(form.form)
|
|
235
|
+
inner ? ",#{inner}" : nil
|
|
236
|
+
when UnquoteSplice
|
|
237
|
+
inner = flat_render(form.form)
|
|
238
|
+
inner ? ",@#{inner}" : nil
|
|
192
239
|
when String, Numeric, true, false, nil
|
|
193
240
|
form.inspect
|
|
194
241
|
when Symbol
|
|
@@ -207,7 +254,7 @@ module Kapusta
|
|
|
207
254
|
raw_args = list_raw_rest(list)
|
|
208
255
|
|
|
209
256
|
case head_name
|
|
210
|
-
when 'fn', 'lambda', 'λ' then render_fn(head_name, list, indent, top_level:)
|
|
257
|
+
when 'fn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
|
|
211
258
|
when 'let' then render_let(list, indent)
|
|
212
259
|
when 'do', 'finally' then render_prefix_body_form(head_name, [], raw_args, indent)
|
|
213
260
|
when 'try' then render_try(list, indent)
|
|
@@ -237,7 +284,12 @@ module Kapusta
|
|
|
237
284
|
raw_args = list_raw_rest(list)
|
|
238
285
|
prefix_length = args[0].is_a?(Sym) && args[1].is_a?(Vec) ? 2 : 1
|
|
239
286
|
raw_prefix, raw_body = split_raw_items(raw_args, prefix_length)
|
|
240
|
-
|
|
287
|
+
force = top_level || fn_body_has_quasi_list?(raw_body)
|
|
288
|
+
render_prefix_body_form(head, raw_prefix, raw_body, indent, force_body_multiline: force)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def fn_body_has_quasi_list?(body_forms)
|
|
292
|
+
body_forms.any? { |form| form.is_a?(Quasiquote) && form.form.is_a?(List) }
|
|
241
293
|
end
|
|
242
294
|
|
|
243
295
|
def render_catch(list, indent)
|
|
@@ -300,6 +352,35 @@ module Kapusta
|
|
|
300
352
|
append_suffix(lines, ')')
|
|
301
353
|
end
|
|
302
354
|
|
|
355
|
+
def append_prefix_form(lines, form, indent, current_first_line, inline_prefix, layouts, layout_index)
|
|
356
|
+
if blank_line?(form)
|
|
357
|
+
lines << ''
|
|
358
|
+
return [current_first_line, false, layout_index]
|
|
359
|
+
end
|
|
360
|
+
if comment?(form)
|
|
361
|
+
lines << indent_block(render(form, indent + INDENT), INDENT)
|
|
362
|
+
return [current_first_line, false, layout_index]
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
rendered = render(form, indent + current_first_line.length + 1, layout: layouts[layout_index])
|
|
366
|
+
rendered_lines = rendered.lines.map(&:chomp)
|
|
367
|
+
candidate_first = "#{current_first_line} #{rendered_lines.first}"
|
|
368
|
+
|
|
369
|
+
if inline_prefix && fits?(candidate_first, indent)
|
|
370
|
+
lines[-1] = candidate_first
|
|
371
|
+
if rendered_lines.length == 1
|
|
372
|
+
[candidate_first, true, layout_index + 1]
|
|
373
|
+
else
|
|
374
|
+
hanging = ' ' * (current_first_line.length + 1)
|
|
375
|
+
rendered_lines.drop(1).each { |line| lines << "#{hanging}#{line}" }
|
|
376
|
+
[current_first_line, false, layout_index + 1]
|
|
377
|
+
end
|
|
378
|
+
else
|
|
379
|
+
lines << indent_block(rendered, INDENT)
|
|
380
|
+
[current_first_line, false, layout_index + 1]
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
303
384
|
def render_prefix_body_form(head, prefix_forms, body_forms, indent, layouts: [], force_body_multiline: false)
|
|
304
385
|
line = "(#{head}"
|
|
305
386
|
lines = [line]
|
|
@@ -308,26 +389,15 @@ module Kapusta
|
|
|
308
389
|
inline_prefix = true
|
|
309
390
|
|
|
310
391
|
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
|
|
392
|
+
current_first_line, inline_prefix, layout_index =
|
|
393
|
+
append_prefix_form(lines, form, indent, current_first_line, inline_prefix, layouts, layout_index)
|
|
328
394
|
end
|
|
329
395
|
|
|
330
396
|
body_forms.each do |form|
|
|
397
|
+
if blank_line?(form)
|
|
398
|
+
lines << ''
|
|
399
|
+
next
|
|
400
|
+
end
|
|
331
401
|
if comment?(form)
|
|
332
402
|
lines << indent_block(render(form, indent + INDENT), INDENT)
|
|
333
403
|
next
|
|
@@ -356,8 +426,8 @@ module Kapusta
|
|
|
356
426
|
return flat if inline_three_arg_if?(args) && flat && fits?(flat, indent)
|
|
357
427
|
|
|
358
428
|
lines << "(if #{render(args[0], indent + '(if '.length)}"
|
|
359
|
-
lines <<
|
|
360
|
-
lines <<
|
|
429
|
+
lines << prefix_continuation(hanging, render(args[1], indent + '(if '.length))
|
|
430
|
+
lines << prefix_continuation(hanging, render(args[2], indent + '(if '.length))
|
|
361
431
|
return append_suffix(lines, ')')
|
|
362
432
|
end
|
|
363
433
|
|
|
@@ -368,7 +438,7 @@ module Kapusta
|
|
|
368
438
|
lines << "(if #{first_pair}"
|
|
369
439
|
else
|
|
370
440
|
lines << "(if #{render(args[0], indent + '(if '.length)}"
|
|
371
|
-
lines <<
|
|
441
|
+
lines << prefix_continuation(hanging, render(args[1], indent + '(if '.length))
|
|
372
442
|
end
|
|
373
443
|
index = 2
|
|
374
444
|
else
|
|
@@ -382,12 +452,12 @@ module Kapusta
|
|
|
382
452
|
if pair
|
|
383
453
|
lines << "#{hanging}#{pair}"
|
|
384
454
|
else
|
|
385
|
-
lines <<
|
|
386
|
-
lines <<
|
|
455
|
+
lines << prefix_continuation(hanging, render(args[index], indent + '(if '.length))
|
|
456
|
+
lines << prefix_continuation(hanging, render(args[index + 1], indent + '(if '.length))
|
|
387
457
|
end
|
|
388
458
|
index += 2
|
|
389
459
|
else
|
|
390
|
-
lines <<
|
|
460
|
+
lines << prefix_continuation(hanging, render(args[index], indent + '(if '.length))
|
|
391
461
|
index += 1
|
|
392
462
|
end
|
|
393
463
|
end
|
|
@@ -395,6 +465,12 @@ module Kapusta
|
|
|
395
465
|
append_suffix(lines, ')')
|
|
396
466
|
end
|
|
397
467
|
|
|
468
|
+
def prefix_continuation(prefix, rendered)
|
|
469
|
+
first_line, *rest = rendered.lines(chomp: true)
|
|
470
|
+
pad = ' ' * prefix.length
|
|
471
|
+
["#{prefix}#{first_line}", *rest.map { |line| "#{pad}#{line}" }].join("\n")
|
|
472
|
+
end
|
|
473
|
+
|
|
398
474
|
def render_case(head, args, indent)
|
|
399
475
|
subject = args.first
|
|
400
476
|
clauses = args.drop(1)
|
|
@@ -505,15 +581,44 @@ module Kapusta
|
|
|
505
581
|
flat = flat_render(vec)
|
|
506
582
|
return flat if !force_expand && flat && fits?(flat, indent) && allow_flat?(vec, top_level:, layout:)
|
|
507
583
|
|
|
508
|
-
if layout == :pairwise && !contains_comments?(vec.items)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
584
|
+
return render_pairwise_vec(vec, indent) if layout == :pairwise && !contains_comments?(vec.items)
|
|
585
|
+
return render_filled_vec(vec, indent) if !contains_comments?(vec.items) && !vec.items.empty?
|
|
586
|
+
|
|
587
|
+
lines = ['[']
|
|
588
|
+
vec.items.each do |item|
|
|
589
|
+
lines << indent_block(render(item, indent + INDENT), INDENT)
|
|
590
|
+
end
|
|
591
|
+
append_suffix(lines, ']')
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def render_filled_vec(vec, indent)
|
|
595
|
+
output_lines = ['[']
|
|
596
|
+
|
|
597
|
+
vec.items.each_with_index do |item, idx|
|
|
598
|
+
if idx.zero?
|
|
599
|
+
item_col = output_lines.last.length
|
|
600
|
+
rendered_lines = render(item, indent + item_col).lines.map(&:chomp)
|
|
601
|
+
output_lines[-1] += rendered_lines.first
|
|
602
|
+
rendered_lines.drop(1).each { |line| output_lines << ((' ' * item_col) + line) }
|
|
603
|
+
next
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
inline_col = output_lines.last.length + 1
|
|
607
|
+
flat = flat_render(item)
|
|
608
|
+
|
|
609
|
+
if flat && indent + inline_col + flat.length <= MAX_WIDTH
|
|
610
|
+
output_lines[-1] += " #{flat}"
|
|
611
|
+
elsif flat && indent + 1 + flat.length <= MAX_WIDTH
|
|
612
|
+
output_lines << " #{flat}"
|
|
613
|
+
else
|
|
614
|
+
rendered_lines = render(item, indent + inline_col).lines.map(&:chomp)
|
|
615
|
+
output_lines[-1] += " #{rendered_lines.first}"
|
|
616
|
+
rendered_lines.drop(1).each { |line| output_lines << ((' ' * inline_col) + line) }
|
|
514
617
|
end
|
|
515
|
-
append_suffix(lines, ']')
|
|
516
618
|
end
|
|
619
|
+
|
|
620
|
+
output_lines[-1] += ']'
|
|
621
|
+
output_lines.join("\n")
|
|
517
622
|
end
|
|
518
623
|
|
|
519
624
|
def render_pairwise_vec(vec, indent)
|
|
@@ -567,33 +672,45 @@ module Kapusta
|
|
|
567
672
|
def render_hash(hash, indent)
|
|
568
673
|
flat = flat_render(hash)
|
|
569
674
|
return flat if flat && fits?(flat, indent)
|
|
675
|
+
return '{}' if hash.entries.empty?
|
|
570
676
|
|
|
571
|
-
|
|
677
|
+
output_lines = []
|
|
572
678
|
|
|
573
|
-
hash.entries.
|
|
679
|
+
hash.entries.each_with_index do |entry, idx|
|
|
574
680
|
if comment?(entry)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
end
|
|
681
|
+
output_lines << "#{idx.zero? ? '{' : ' '}#{render(entry, indent + 1)}"
|
|
682
|
+
next
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
key, value = entry
|
|
686
|
+
first_pair = output_lines.empty?
|
|
687
|
+
if hash_shorthand?(key, value)
|
|
688
|
+
output_lines << "#{first_pair ? '{' : ' '}: #{value.name}"
|
|
689
|
+
next
|
|
585
690
|
end
|
|
691
|
+
|
|
692
|
+
key_str = render_hash_key(key)
|
|
693
|
+
value_col = indent + 1 + key_str.length + 1
|
|
694
|
+
rendered_value = render(value, value_col)
|
|
695
|
+
value_lines = rendered_value.lines(chomp: true)
|
|
696
|
+
|
|
697
|
+
prefix = "#{first_pair ? '{' : ' '}#{key_str} "
|
|
698
|
+
output_lines << "#{prefix}#{value_lines.first}"
|
|
699
|
+
pad = ' ' * prefix.length
|
|
700
|
+
value_lines.drop(1).each { |line| output_lines << "#{pad}#{line}" }
|
|
586
701
|
end
|
|
587
702
|
|
|
588
|
-
|
|
703
|
+
output_lines[-1] = "#{output_lines[-1]}}"
|
|
704
|
+
output_lines.join("\n")
|
|
589
705
|
end
|
|
590
706
|
|
|
591
707
|
def flat_hash_pair(key, value)
|
|
592
|
-
if hash_shorthand?(key, value)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
708
|
+
return ": #{value.name}" if hash_shorthand?(key, value)
|
|
709
|
+
|
|
710
|
+
rendered_value = flat_render(value)
|
|
711
|
+
return unless rendered_value
|
|
712
|
+
|
|
713
|
+
"#{render_hash_key(key)} #{rendered_value}"
|
|
597
714
|
end
|
|
598
715
|
|
|
599
716
|
def render_hash_key(key)
|
|
@@ -642,16 +759,17 @@ module Kapusta
|
|
|
642
759
|
items[0].name == 'hashfn'
|
|
643
760
|
end
|
|
644
761
|
|
|
645
|
-
def allow_flat?(form, top_level
|
|
762
|
+
def allow_flat?(form, top_level: false, layout: nil)
|
|
646
763
|
return false if layout == :pairwise && form.is_a?(Vec) && semantic_items(form.items).length > 2
|
|
647
764
|
return true unless form.is_a?(List)
|
|
765
|
+
return true if !multiline_in_source?(form) && form.respond_to?(:multiline_source)
|
|
648
766
|
|
|
649
767
|
head = list_head(form)
|
|
650
768
|
return true unless head.is_a?(Sym)
|
|
651
769
|
|
|
652
770
|
case head.name
|
|
653
|
-
when 'fn', 'lambda', 'λ', 'when', 'unless', 'for', 'each', 'icollect', 'collect', 'fcollect',
|
|
654
|
-
'faccumulate'
|
|
771
|
+
when 'fn', 'lambda', 'λ', 'macro', 'when', 'unless', 'for', 'each', 'icollect', 'collect', 'fcollect',
|
|
772
|
+
'accumulate', 'faccumulate'
|
|
655
773
|
!top_level
|
|
656
774
|
else
|
|
657
775
|
!%w[let case match try catch finally do -> ->> -?> -?>> doto].include?(head.name)
|
|
@@ -659,14 +777,16 @@ module Kapusta
|
|
|
659
777
|
end
|
|
660
778
|
|
|
661
779
|
def force_multiline_body?(form)
|
|
780
|
+
return force_multiline_body?(form.form) if form.is_a?(Quasiquote)
|
|
662
781
|
return false unless form.is_a?(List)
|
|
782
|
+
return true if multiline_in_source?(form)
|
|
663
783
|
|
|
664
784
|
head = list_head(form)
|
|
665
785
|
return false unless head.is_a?(Sym)
|
|
666
786
|
|
|
667
787
|
case head.name
|
|
668
788
|
when 'if', 'case', 'match', 'let', 'try', 'catch', 'finally', 'do', 'for', '->', '->>', '-?>', '-?>>', 'doto',
|
|
669
|
-
'fn', 'lambda', 'λ'
|
|
789
|
+
'fn', 'lambda', 'λ', 'macro'
|
|
670
790
|
true
|
|
671
791
|
else
|
|
672
792
|
flat = flat_render(form)
|
|
@@ -674,27 +794,43 @@ module Kapusta
|
|
|
674
794
|
end
|
|
675
795
|
end
|
|
676
796
|
|
|
677
|
-
def
|
|
678
|
-
|
|
797
|
+
def multiline_in_source?(form)
|
|
798
|
+
form.respond_to?(:multiline_source) && form.multiline_source
|
|
679
799
|
end
|
|
680
800
|
|
|
681
|
-
def
|
|
682
|
-
|
|
683
|
-
return false unless
|
|
801
|
+
def let_with_multiple_bindings?(form)
|
|
802
|
+
head = list_head(form)
|
|
803
|
+
return false unless head.is_a?(Sym) && head.name == 'let'
|
|
804
|
+
|
|
805
|
+
bindings = semantic_items(form.items)[1]
|
|
806
|
+
return false unless bindings.is_a?(Vec)
|
|
807
|
+
|
|
808
|
+
semantic_items(bindings.items).length > 2
|
|
809
|
+
end
|
|
684
810
|
|
|
811
|
+
def let_with_nested_binding_value?(form)
|
|
685
812
|
head = list_head(form)
|
|
686
|
-
return false unless head.is_a?(Sym)
|
|
813
|
+
return false unless head.is_a?(Sym) && head.name == 'let'
|
|
814
|
+
|
|
815
|
+
bindings = semantic_items(form.items)[1]
|
|
816
|
+
return false unless bindings.is_a?(Vec)
|
|
687
817
|
|
|
688
|
-
|
|
818
|
+
semantic_items(bindings.items).each_slice(2).any? do |_pattern, value|
|
|
819
|
+
value && contains_collection?(value)
|
|
820
|
+
end
|
|
689
821
|
end
|
|
690
822
|
|
|
691
|
-
def
|
|
692
|
-
|
|
823
|
+
def contains_collection?(form)
|
|
824
|
+
case form
|
|
825
|
+
when List then semantic_items(form.items).any? { |item| collection?(item) }
|
|
826
|
+
when Vec then form.items.any? { |item| collection?(item) }
|
|
827
|
+
when HashLit then form.pairs.any? { |k, v| collection?(k) || collection?(v) }
|
|
828
|
+
else false
|
|
829
|
+
end
|
|
830
|
+
end
|
|
693
831
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
items[0].is_a?(Sym) &&
|
|
697
|
-
items[0].name == 'require'
|
|
832
|
+
def collection?(form)
|
|
833
|
+
form.is_a?(List) || form.is_a?(Vec) || form.is_a?(HashLit)
|
|
698
834
|
end
|
|
699
835
|
|
|
700
836
|
def fn_form?(form)
|
|
@@ -784,11 +920,11 @@ module Kapusta
|
|
|
784
920
|
end
|
|
785
921
|
|
|
786
922
|
def contains_comments?(items)
|
|
787
|
-
items.any? { |item|
|
|
923
|
+
items.any? { |item| non_semantic?(item) }
|
|
788
924
|
end
|
|
789
925
|
|
|
790
926
|
def semantic_items(items)
|
|
791
|
-
items.reject { |item|
|
|
927
|
+
items.reject { |item| non_semantic?(item) }
|
|
792
928
|
end
|
|
793
929
|
|
|
794
930
|
def list_head(list)
|