atomy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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