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.
@@ -114,36 +114,33 @@ module Kapusta
114
114
  raise Error, e.message
115
115
  end
116
116
 
117
- def separator_for(previous, current)
118
- if consecutive_requires?(previous, current) ||
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 comment?(form)
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(previous, current)
144
- return "\n" unless previous[:form] && current[:form]
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
- "[#{form.items.map { |item| flat_render(item) }.join(' ')}]"
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
- "{#{form.pairs.map { |key, value| flat_hash_pair(key, value) }.join(' ')}}"
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
- "(#{form.items.map { |item| flat_render(item) }.join(' ')})"
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
- render_prefix_body_form(head, raw_prefix, raw_body, indent, force_body_multiline: top_level)
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
- if comment?(form)
312
- lines << indent_block(render(form, indent + INDENT), INDENT)
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 << "#{hanging}#{render(args[1], indent + '(if '.length)}"
360
- lines << "#{hanging}#{render(args[2], indent + '(if '.length)}"
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 << "#{hanging}#{render(args[1], indent + '(if '.length)}"
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 << "#{hanging}#{render(args[index], indent + '(if '.length)}"
386
- lines << "#{hanging}#{render(args[index + 1], indent + '(if '.length)}"
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 << "#{hanging}#{render(args[index], indent + '(if '.length)}"
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
- render_pairwise_vec(vec, indent)
510
- else
511
- lines = ['[']
512
- vec.items.each do |item|
513
- lines << indent_block(render(item, indent + INDENT), INDENT)
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
- lines = ['{']
670
+ output_lines = []
572
671
 
573
- hash.entries.each do |entry|
672
+ hash.entries.each_with_index do |entry, idx|
574
673
  if comment?(entry)
575
- lines << indent_block(render(entry, indent + INDENT), INDENT)
576
- else
577
- key, value = entry
578
- pair = flat_hash_pair(key, value)
579
- if pair && fits?(pair, indent + INDENT)
580
- lines << indent_block(pair, INDENT)
581
- else
582
- lines << indent_block(render_hash_key(key), INDENT)
583
- lines << indent_block(render(value, indent + INDENT), INDENT)
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
- append_suffix(lines, '}')
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
- ": #{value.name}"
594
- else
595
- "#{render_hash_key(key)} #{flat_render(value)}"
596
- end
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:, layout:)
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', 'accumulate',
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 consecutive_requires?(previous, current)
678
- require_form?(previous) && require_form?(current)
790
+ def multiline_in_source?(form)
791
+ form.respond_to?(:multiline_source) && form.multiline_source
679
792
  end
680
793
 
681
- def groupable_top_level_form?(form)
682
- return true if require_form?(form)
683
- return false unless form.is_a?(List) && flat_render(form)
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
- !%w[fn module class let].include?(head.name)
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 require_form?(form)
692
- return false unless form.is_a?(List)
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
- items = semantic_items(form.items)
695
- items.length == 2 &&
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| comment?(item) }
916
+ items.any? { |item| non_semantic?(item) }
788
917
  end
789
918
 
790
919
  def semantic_items(items)
791
- items.reject { |item| comment?(item) }
920
+ items.reject { |item| non_semantic?(item) }
792
921
  end
793
922
 
794
923
  def list_head(list)
@@ -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", ','].freeze
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 WHITESPACE.include?(char)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.1'
5
5
  end
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
- kap_path = Kapusta.send(:resolve_kap_relative, path, caller_locations(1, 1).first)
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
- __kapusta_original_require_relative(path)
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