atomy 0.1.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.
Files changed (99) hide show
  1. data/COPYING +30 -0
  2. data/README.md +1 -0
  3. data/bin/atomy +134 -0
  4. data/kernel/block.ay +30 -0
  5. data/kernel/boot.ay +10 -0
  6. data/kernel/comparison.ay +61 -0
  7. data/kernel/concurrency.ay +84 -0
  8. data/kernel/condition.ay +277 -0
  9. data/kernel/control-flow.ay +222 -0
  10. data/kernel/cosmetics.ay +3 -0
  11. data/kernel/data-delta.ay +105 -0
  12. data/kernel/data.ay +56 -0
  13. data/kernel/define.ay +93 -0
  14. data/kernel/doc.ay +453 -0
  15. data/kernel/documentation.ay +135 -0
  16. data/kernel/dynamic.ay +42 -0
  17. data/kernel/errors.ay +6 -0
  18. data/kernel/format.ay +13 -0
  19. data/kernel/format/data.ay +89 -0
  20. data/kernel/format/formatter.ay +345 -0
  21. data/kernel/format/parser.ay +13 -0
  22. data/kernel/hashes.ay +39 -0
  23. data/kernel/io.ay +244 -0
  24. data/kernel/namespaces.ay +63 -0
  25. data/kernel/node.ay +48 -0
  26. data/kernel/operators.ay +28 -0
  27. data/kernel/particles.ay +53 -0
  28. data/kernel/patterns.ay +256 -0
  29. data/kernel/precision.ay +148 -0
  30. data/kernel/pretty.ay +283 -0
  31. data/kernel/repl.ay +140 -0
  32. data/kernel/therie.ay +204 -0
  33. data/lib/ast/binary_send.rb +44 -0
  34. data/lib/ast/block.rb +268 -0
  35. data/lib/ast/constant.rb +88 -0
  36. data/lib/ast/internal/assign.rb +19 -0
  37. data/lib/ast/internal/block_pass.rb +21 -0
  38. data/lib/ast/internal/catch.rb +247 -0
  39. data/lib/ast/internal/class.rb +30 -0
  40. data/lib/ast/internal/class_variable.rb +23 -0
  41. data/lib/ast/internal/define.rb +174 -0
  42. data/lib/ast/internal/ensure.rb +135 -0
  43. data/lib/ast/internal/file.rb +14 -0
  44. data/lib/ast/internal/global_variable.rb +20 -0
  45. data/lib/ast/internal/if_then_else.rb +24 -0
  46. data/lib/ast/internal/instance_variable.rb +17 -0
  47. data/lib/ast/internal/let_macro.rb +35 -0
  48. data/lib/ast/internal/macro_quote.rb +23 -0
  49. data/lib/ast/internal/match.rb +53 -0
  50. data/lib/ast/internal/module.rb +30 -0
  51. data/lib/ast/internal/pattern.rb +17 -0
  52. data/lib/ast/internal/return.rb +29 -0
  53. data/lib/ast/internal/set.rb +19 -0
  54. data/lib/ast/internal/singleton_class.rb +18 -0
  55. data/lib/ast/internal/splat.rb +14 -0
  56. data/lib/ast/internal/when.rb +24 -0
  57. data/lib/ast/list.rb +25 -0
  58. data/lib/ast/macro.rb +37 -0
  59. data/lib/ast/node.rb +599 -0
  60. data/lib/ast/operator.rb +21 -0
  61. data/lib/ast/particle.rb +13 -0
  62. data/lib/ast/primitive.rb +20 -0
  63. data/lib/ast/quasi_quote.rb +20 -0
  64. data/lib/ast/quote.rb +13 -0
  65. data/lib/ast/send.rb +104 -0
  66. data/lib/ast/splice.rb +32 -0
  67. data/lib/ast/string.rb +23 -0
  68. data/lib/ast/unary.rb +44 -0
  69. data/lib/ast/unquote.rb +45 -0
  70. data/lib/ast/variable.rb +64 -0
  71. data/lib/atomy.kpeg.rb +3995 -0
  72. data/lib/code_loader.rb +137 -0
  73. data/lib/compiler/compiler.rb +155 -0
  74. data/lib/compiler/stages.rb +81 -0
  75. data/lib/formatter.kpeg.rb +1394 -0
  76. data/lib/macros.rb +317 -0
  77. data/lib/method.rb +261 -0
  78. data/lib/namespace.rb +236 -0
  79. data/lib/parser.rb +28 -0
  80. data/lib/patterns.rb +276 -0
  81. data/lib/patterns/any.rb +21 -0
  82. data/lib/patterns/attribute.rb +59 -0
  83. data/lib/patterns/block_pass.rb +54 -0
  84. data/lib/patterns/constant.rb +33 -0
  85. data/lib/patterns/default.rb +44 -0
  86. data/lib/patterns/head_tail.rb +63 -0
  87. data/lib/patterns/list.rb +77 -0
  88. data/lib/patterns/match.rb +45 -0
  89. data/lib/patterns/named.rb +55 -0
  90. data/lib/patterns/named_class.rb +46 -0
  91. data/lib/patterns/named_global.rb +46 -0
  92. data/lib/patterns/named_instance.rb +46 -0
  93. data/lib/patterns/particle.rb +29 -0
  94. data/lib/patterns/quasi_quote.rb +184 -0
  95. data/lib/patterns/quote.rb +33 -0
  96. data/lib/patterns/singleton_class.rb +31 -0
  97. data/lib/patterns/splat.rb +57 -0
  98. data/lib/util.rb +37 -0
  99. metadata +164 -0
@@ -0,0 +1,135 @@
1
+ namespace(atomy/documentation)
2
+
3
+ in-namespace(atomy):
4
+ symbols(pretty, show)
5
+
6
+ for-macro:
7
+ escape(x) :=
8
+ x gsub(r"[\{\}\\]", raw"\\\0")
9
+
10
+ indent(x, n = 1) :=
11
+ x split("\n") collect [x] { (" " * n * 2) + x } join("\n")
12
+
13
+ str-expr(e) :=
14
+ escape $:
15
+ e match:
16
+ String -> e
17
+ Atomy::AST::String -> e value
18
+ _ -> e show
19
+
20
+ to-thumb(x: Atomy::AST::Macro) :=
21
+ indent(escape(x pattern show))
22
+ to-thumb(`(~meth := ~_)) :=
23
+ indent(escape(meth show))
24
+ to-thumb(d: `(data(Object))) :=
25
+ let(atomy/pretty/multiline? = true):
26
+ indent(escape(d block body show))
27
+ to-thumb(d: `(data(~r))) :=
28
+ let(atomy/pretty/multiline? = true):
29
+ indent(escape(`(~r ~(d block)) show))
30
+ to-thumb(`(dynamic(~n, ~r))) :=
31
+ indent(`^~n show + "\n> " + r show)
32
+ to-thumb(`(class(~n))) :=
33
+ indent(n show + "\n> Class")
34
+ to-thumb(`(module(~n))) :=
35
+ indent(n show + "\n> Module")
36
+ to-thumb(x) := str-expr(x)
37
+
38
+ to-spec([]) := ""
39
+ to-spec(b) :=
40
+ "\n" + indent $:
41
+ b collect [e] {
42
+ e match:
43
+ `(=> ~x) ->
44
+ "> " + str-expr(x)
45
+
46
+ _ ->
47
+ "| " + str-expr(e)
48
+ } join("\n")
49
+
50
+ to-examples(b) :=
51
+ b collect [e] {
52
+ " " + str-expr(e)
53
+ } join("\n")
54
+
55
+ export-to(atomy):
56
+ -- TODO: shoudln't need to export this; not being resolved in #section
57
+ macro(for-docs(&body)):
58
+ `(when(^documentation):
59
+ ^documentation << ~(body block-body))
60
+
61
+ macro-quoter(doc) [c]:
62
+ for-docs:
63
+ c + "\n\n"
64
+
65
+ c
66
+
67
+ macro-quoter(title) [c]:
68
+ for-docs:
69
+ "\\title{" + escape(c) + "}\n\n"
70
+
71
+ c
72
+
73
+ macro(section(name, &body)):
74
+ `(do:
75
+ evaluate-when(compile):
76
+ for-docs: "\\section{" + ~name + "}{\n\\style{Atomy}\n\n"
77
+
78
+ res = ~(body caller)
79
+
80
+ evaluate-when(compile):
81
+ for-docs: "}"
82
+
83
+ res)
84
+
85
+ macro(docs spec(&s) for(&code)):
86
+ for-docs:
87
+ [ "\\define{"
88
+ "\n" + to-thumb(code contents first)
89
+ to-spec(s contents)
90
+ "\n}{"
91
+ docs contents
92
+ "}\n\n"
93
+ ] join
94
+
95
+ code block-body
96
+
97
+ macro(docs for(&code)):
98
+ for-docs:
99
+ [ "\\define{"
100
+ "\n" + to-thumb(code contents first)
101
+ "\n}{"
102
+ docs contents
103
+ "}\n\n"
104
+ ] join
105
+
106
+ code block-body
107
+
108
+ macro(docs spec(&s) for(&code) examples(&es)):
109
+ for-docs:
110
+ [ "\\define{"
111
+ "\n" + to-thumb(code contents first)
112
+ to-spec(s contents)
113
+ "\n}{"
114
+ docs contents
115
+ "\n \\examples{"
116
+ "\n" + to-examples(es contents)
117
+ "\n }"
118
+ "\n}\n\n"
119
+ ] join
120
+
121
+ code block-body
122
+
123
+ macro(docs for(&code) examples(&es)):
124
+ for-docs:
125
+ [ "\\define{"
126
+ "\n" + to-thumb(code contents first)
127
+ "\n}{"
128
+ docs contents
129
+ "\n \\examples{"
130
+ "\n" + to-examples(es contents)
131
+ "\n }"
132
+ "\n}\n\n"
133
+ ] join
134
+
135
+ code block-body
data/kernel/dynamic.ay ADDED
@@ -0,0 +1,42 @@
1
+ Atomy::DYNAMIC_ROOTS = Hash new
2
+
3
+ for-macro:
4
+ key(n) :=
5
+ Atomy::AST::Variable new $:
6
+ n line
7
+ "atomy_" + n resolve message-name
8
+
9
+ dynvar(x) :=
10
+ `(Thread current [#~key(x)] || Atomy::DYNAMIC_ROOTS [#~x])
11
+
12
+ set-dynvar(n, v) :=
13
+ `(Thread current [#~key(n)] = ~v)
14
+
15
+ macro(dynamic(n, root)):
16
+ where = Atomy::Namespace define-target
17
+
18
+ Atomy::Namespace register(n namespace-symbol, where)
19
+
20
+ Atomy::CodeLoader when-load <<
21
+ [`(Atomy::Namespace register(~(n namespace-symbol), ~where)), true]
22
+
23
+ `(Atomy::DYNAMIC_ROOTS [#~n] = ~root)
24
+
25
+ macro(^x): dynvar(x)
26
+
27
+ macro(let(*bindings, &body)):
28
+ tmps = names(bindings size)
29
+ save = []
30
+ set = []
31
+ restore = []
32
+
33
+ bindings zip(tmps) [[`(~n = ~v), tmp]]:
34
+ save << `(~tmp = ~(dynvar(n)))
35
+ set << set-dynvar(n, v)
36
+ restore << set-dynvar(n, tmp)
37
+
38
+ `(do:
39
+ ~*save
40
+ { ~*set
41
+ ~(body caller)
42
+ } ensuring: ~*restore)
data/kernel/errors.ay ADDED
@@ -0,0 +1,6 @@
1
+ MethodFail message :=
2
+ ("method '" + @method-name to-s + "' did not understand " +
3
+ "its arguments (non-exhaustive patterns)")
4
+
5
+ PatternMismatch message :=
6
+ "irrefutable pattern failed for " + @pattern to-s
data/kernel/format.ay ADDED
@@ -0,0 +1,13 @@
1
+ namespace(atomy/format)
2
+
3
+ base = File expand-path("../", _FILE)
4
+
5
+ import(base + "/format/data")
6
+ import(base + "/format/formatter")
7
+ import(base + "/format/parser")
8
+
9
+ symbols(parse)
10
+
11
+ export-to(atomy):
12
+ macro-quoter(f) [c]:
13
+ Atomy::Format::Parser parse(c)
@@ -0,0 +1,89 @@
1
+ namespace(atomy/format)
2
+
3
+ macro(ast(root, &nodes)):
4
+ parent = root || 'Atomy::AST::Node
5
+ cs = nodes contents map [e]:
6
+ e match:
7
+ Atomy::AST::Send -> do:
8
+ name = Atomy::AST::Constant new(0, e method-name)
9
+
10
+ children = []
11
+ attributes = []
12
+
13
+ e arguments each [a]:
14
+ a match:
15
+ `[@~name] ->
16
+ attributes << `[#~name]
17
+
18
+ `@~name ->
19
+ attributes << `#~name
20
+
21
+ _ ->
22
+ children << a
23
+
24
+ `(class(~name < ~parent):
25
+ children(~*children)
26
+ attributes(~*attributes)
27
+ generate)
28
+
29
+ _ ->
30
+ `(class(~e < ~parent):
31
+ generate)
32
+
33
+ when(root):
34
+ cs unshift(`(class(~root < Atomy::AST::Node) {}))
35
+
36
+ `(do: ~*cs)
37
+
38
+ module(Atomy::Format):
39
+ ast(Segment):
40
+ Chunk([#flags], @text)
41
+ String([#flags])
42
+ Decimal([#flags])
43
+ Hex([#flags])
44
+ Octal([#flags])
45
+ Binary([#flags])
46
+ Radix([#flags])
47
+ Float([#flags])
48
+ Exponent([#flags])
49
+ General([#flags])
50
+ Character([#flags])
51
+ Any([#flags])
52
+ Pluralize(#singular, [#flags], #plural?)
53
+ Lowercase(#content, [#flags])
54
+ Capitalize(#content, [#flags])
55
+ Uppercase(#content, [#flags])
56
+ Justify([#segments], [#flags])
57
+ Skip([#flags])
58
+ Indirection([#flags])
59
+ Iterate(#content, [#flags])
60
+ Break([#flags])
61
+ Conditional([#branches], [#flags], #default?)
62
+
63
+ ast(Flag):
64
+ Number(@value)
65
+ Symbol(@character)
66
+ ZeroPad
67
+ Precision(@value)
68
+
69
+ ast:
70
+ Formatter([#segments])
71
+
72
+ Segment symbol?(m) :=
73
+ @flags any? [f]:
74
+ f match:
75
+ Symbol -> f character == m
76
+ _ -> false
77
+
78
+ Segment precision := do:
79
+ @flags each [f]:
80
+ when(f is-a?(Precision)):
81
+ return(f value)
82
+
83
+ nil
84
+
85
+ Segment zero-pad? :=
86
+ @flags any? [f]:
87
+ f match:
88
+ ZeroPad -> true
89
+ _ -> false
@@ -0,0 +1,345 @@
1
+ namespace(atomy/format)
2
+
3
+ module(Atomy::Format):
4
+ class(Formatter):
5
+ attr-accessor(#position)
6
+
7
+ export:
8
+ initialize(@line, @segments) :=
9
+ reset!
10
+
11
+ bytecode(g) := do:
12
+ pos(g)
13
+ construct(g)
14
+
15
+ reset! := do:
16
+ @input = []
17
+ @output = ""
18
+ @position = 0
19
+ @stop? = false
20
+ @iterating = []
21
+
22
+ scan(*@input) := do:
23
+ @segments each [s]:
24
+ s match:
25
+ Break ? symbol?(".") -> do:
26
+ when(@iterating empty?):
27
+ @stop? = true
28
+ break
29
+
30
+ Break ->
31
+ when(next-inputs empty?):
32
+ break
33
+
34
+ _ -> process(s)
35
+
36
+ @output
37
+
38
+ export-to(atomy):
39
+ format(*inputs) := do:
40
+ res = scan(*inputs)
41
+ reset!
42
+ res
43
+
44
+ -- TODO: use alias-method
45
+ self [*inputs] := format(*inputs)
46
+
47
+ peek-input := @input [@position]
48
+
49
+ next-input := do:
50
+ v = peek-input
51
+ @position += 1
52
+ v
53
+
54
+ next-inputs := @input [@position .. -1] || []
55
+
56
+ process(c: Chunk) :=
57
+ @output << c text
58
+
59
+ process(s: String) :=
60
+ @output << justified(s, next-input to-s, true)
61
+
62
+ process(i: Decimal) :=
63
+ @output << integer(i, 10)
64
+
65
+ process(i: Hex) :=
66
+ @output << integer(i, 16)
67
+
68
+ process(i: Octal) :=
69
+ @output << integer(i, 8)
70
+
71
+ process(i: Binary) :=
72
+ @output << integer(i, 2)
73
+
74
+ process(i: Radix) :=
75
+ @output << integer(i, i precision)
76
+
77
+ process(f: Float) :=
78
+ @output << float(f, "f")
79
+
80
+ process(f: Exponent) :=
81
+ @output << float(f, "e")
82
+
83
+ process(f: General) :=
84
+ @output << float(f, "g")
85
+
86
+ process(c: Character) :=
87
+ @output << char(c, next-input)
88
+
89
+ process(a: Any) :=
90
+ @output << justified(a, next-input show, true)
91
+
92
+ process(p: Pluralize) := do:
93
+ word = sub-format(p singular)
94
+ num =
95
+ if(p symbol?(">"))
96
+ then: peek-input
97
+ else: next-input
98
+
99
+ condition:
100
+ num == 1 ->
101
+ @output << word
102
+
103
+ p plural ->
104
+ @output << sub-format(p plural)
105
+
106
+ otherwise ->
107
+ @output << pluralize(word)
108
+
109
+ process(l: Lowercase) :=
110
+ @output << sub-format(l content) downcase
111
+
112
+ process(c: Capitalize) := do:
113
+ words = sub-format(c content) split(" ")
114
+ number(c, words size) times [n]:
115
+ unless(n == 0):
116
+ @output << " "
117
+
118
+ @output << words shift capitalize
119
+
120
+ unless(words empty?):
121
+ @output << " " + words join(" ")
122
+
123
+ process(u: Uppercase) :=
124
+ @output << sub-format(u content) upcase
125
+
126
+ process(s: Skip) :=
127
+ if(s symbol?("<"))
128
+ then: @position -= number(s)
129
+ else: @position += number(s)
130
+
131
+ process(i: Indirection) :=
132
+ if(i symbol?("*"))
133
+ then:
134
+ @output << sub-format(next-input)
135
+ else:
136
+ @output << next-input format(*next-input)
137
+
138
+ process(i: Iterate) := do:
139
+ splat? = i symbol?("*")
140
+ sub? = i symbol?(".")
141
+ always-run? = i symbol?("+")
142
+ iterations = number(i, nil)
143
+
144
+ inputs =
145
+ if(splat?)
146
+ then: next-inputs
147
+ else: next-input
148
+
149
+ before = [@input, @position]
150
+
151
+ when(inputs empty? && always-run? &&
152
+ iterations != 0):
153
+ @output << sub-format(i content)
154
+ return(nil)
155
+
156
+ iterations match:
157
+ nil ->
158
+ if(sub?)
159
+ then:
160
+ @iterating = inputs
161
+ inputs each [is]:
162
+ @output << i content format(*is)
163
+ else:
164
+ @input = inputs
165
+ @position = 0
166
+ iterate(i content)
167
+ n -> do:
168
+ @input = inputs
169
+ @position = 0
170
+ iterate-max(n, i content)
171
+
172
+ if(splat?)
173
+ then: @position = @input size
174
+ else: [@input, @position] = before
175
+
176
+ process(c: Conditional) :=
177
+ [c symbol?("?"), c branches] match:
178
+ [true, t . (f . _)] ->
179
+ @output << sub-format(if(next-input) then: t; else: f)
180
+
181
+ [true, [t]] ->
182
+ when(next-input):
183
+ @output << sub-format(t)
184
+
185
+ _ -> do:
186
+ n = next-number(c)
187
+ if(n >= c branches size)
188
+ then:
189
+ when(c default):
190
+ @output << sub-format(c default)
191
+ else:
192
+ @output << sub-format(c branches [n])
193
+
194
+ process(j: Justify) :=
195
+ @output <<
196
+ justify(j, j segments collect [s]: sub-format(s))
197
+
198
+ process(x) := raise("todo formatting: " + x inspect)
199
+
200
+
201
+ pluralize(s) :=
202
+ condition:
203
+ s =~ r"o$"(i) ->
204
+ s + "es"
205
+
206
+ s =~ r"[aeiou]$"(i) ->
207
+ s + "s"
208
+
209
+ s =~ r"(?<root>.+[aeiou])y$"(i) ->
210
+ s + "s"
211
+
212
+ s =~ r"(lay-by|stand-by)$"(i) ->
213
+ s + "s"
214
+
215
+ s =~ r"(.+)y$"(i) ->
216
+ $1 + "es"
217
+
218
+ s =~ r"(.+)us$"(i) ->
219
+ $1 + ""
220
+
221
+ s =~ r"(.+)sis$"(i) ->
222
+ $1 + "es"
223
+
224
+ s =~ r"(.+)(ex|ix)$"(i) ->
225
+ $1 + "ces"
226
+
227
+ s =~ r"(.+)(ss|sh|ch|dge)$"(i) ->
228
+ s + "es"
229
+
230
+ otherwise ->
231
+ s + "s"
232
+
233
+
234
+ iterate(f) :=
235
+ until(next-inputs empty? || @stop?):
236
+ @output << sub-format(f)
237
+
238
+ iterate-max(max, f) :=
239
+ max times:
240
+ when(next-inputs empty? || @stop?):
241
+ break
242
+
243
+ @output << sub-format(f)
244
+
245
+ next-number(s) :=
246
+ number(s, nil) || next-input
247
+
248
+ char(c, n: Integer) := justified(c, n chr, true)
249
+ char(c, s) := justified(c, s to-s [0, 1], true)
250
+
251
+ integer(i, base) := justified(i, Integer(next-input) to-s(base))
252
+
253
+ float(f, x) := do:
254
+ format =
255
+ if(f precision)
256
+ then: "%." + f precision to-s + x
257
+ else: "%" + x
258
+
259
+ justified(f, sprintf(format, Float(next-input)))
260
+
261
+ sub-format(sub) := do:
262
+ sub position = @position
263
+ out = sub scan(*@input)
264
+ @position = sub position
265
+ sub reset!
266
+ out
267
+
268
+ number(s, default = 1) := do:
269
+ s flags each [f]:
270
+ when(f is-a?(Number)):
271
+ return(f value || next-inputs size)
272
+
273
+ default
274
+
275
+ justified(j, s, left? = false) :=
276
+ number(j, nil) match:
277
+ nil -> s
278
+ w -> do:
279
+ padding = if(j zero-pad?) then: "0"; else: " "
280
+
281
+ condition:
282
+ j symbol?("=") || j symbol?("<") && j symbol?(">") ->
283
+ s center(w, padding)
284
+
285
+ left? && !(j symbol?(">")) || j symbol?("<") ->
286
+ s ljust(w, padding)
287
+
288
+ otherwise ->
289
+ s rjust(w, padding)
290
+
291
+ justify(j, [s]) := justified(j, s)
292
+ justify(j, ss) :=
293
+ number(j, nil) match:
294
+ nil -> ss join
295
+ to -> justify-to(j, to, ss)
296
+
297
+ justify-to(_, _, []) := ""
298
+ justify-to(j, to, all: s . ss) := do:
299
+ needed = to - all collect(&#size) inject(*#"+")
300
+
301
+ spacings =
302
+ condition:
303
+ j symbol?("<") && j symbol?(">") || j symbol?("=") ->
304
+ all size + 1
305
+
306
+ j symbol?("<") ->
307
+ all size
308
+
309
+ j symbol?(">") ->
310
+ all size
311
+
312
+ otherwise ->
313
+ all size - 1
314
+
315
+ naive-average = needed / spacings
316
+
317
+ average =
318
+ if(needed - naive-average * spacings >= spacings - 1)
319
+ then: naive-average + 1
320
+ else: naive-average
321
+
322
+ condition:
323
+ j symbol?("<") || j symbol?("=") ->
324
+ [ " " * naive-average
325
+ s
326
+ " " * average
327
+ spaced(j, average, needed - naive-average - average, ss)
328
+ ] join
329
+
330
+ otherwise ->
331
+ [ s
332
+ " " * naive-average
333
+ spaced(j, average, needed - naive-average, ss)
334
+ ] join
335
+
336
+ spaced(_, _, _, []) := ""
337
+ spaced(j, _, left, [s]) :=
338
+ if(j symbol?(">") || j symbol?("="))
339
+ then: s + " " * left
340
+ else: " " * left + s
341
+ spaced(j, average, left, s . ss) :=
342
+ [ s
343
+ " " * average
344
+ spaced(j, average, left - average, ss)
345
+ ] join