dhallish 0.2.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.
@@ -0,0 +1,498 @@
1
+ require File.join(File.dirname(__FILE__), 'ast.rb')
2
+ require File.join(File.dirname(__FILE__), 'types.rb')
3
+ require File.join(File.dirname(__FILE__), 'utils.rb')
4
+
5
+ grammar DhallishGrammar
6
+
7
+ rule root
8
+ space? exp:expression space? { def to_node() exp.to_node() end }
9
+ end
10
+
11
+ rule expression
12
+ exp:if_then_else_expression { def to_node() exp.to_node() end } /
13
+ exp:let_expression { def to_node() exp.to_node() end } /
14
+ exp:import_alternative_expression { def to_node() exp.to_node() end }
15
+ end
16
+
17
+ rule eol
18
+ "\r"? "\n"
19
+ end
20
+
21
+ ## Comments
22
+ rule single_line_comment
23
+ "--" (!"\n" .)* "\n"
24
+ end
25
+
26
+ rule multi_line_comment
27
+ "{-" multi_line_comment_continue
28
+ end
29
+
30
+ rule multi_line_comment_chunk
31
+ multi_line_comment / (!"{-" .)
32
+ end
33
+
34
+ rule multi_line_comment_continue
35
+ "-}" / multi_line_comment_chunk multi_line_comment_continue
36
+ end
37
+
38
+ rule space
39
+ (" " / "\t" / eol / single_line_comment / multi_line_comment)+
40
+ end
41
+
42
+ ## Number Literals
43
+ rule integer_literal
44
+ ("+" / "-") [0-9]+ { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_i, Dhallish::Types::Integer) end }
45
+ end
46
+
47
+ rule exponent
48
+ "e" ("+" / "-")? [0-9]+
49
+
50
+ end
51
+
52
+ rule double_literal
53
+ ("+" / "-")? [0-9]+ (("." [0-9]+ exponent?) / exponent) { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_f, Dhallish::Types::Double) end }
54
+
55
+ end
56
+
57
+ rule natural_literal
58
+ [0-9]+ { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_i, Dhallish::Types::Natural) end }
59
+ end
60
+
61
+ ## Bools
62
+ rule bool_literal
63
+ "True" { def to_node() Dhallish::Ast::Literal_Node.new(true, Dhallish::Types::Bool) end }
64
+ /
65
+ "False" { def to_node() Dhallish::Ast::Literal_Node.new(false, Dhallish::Types::Bool) end }
66
+ end
67
+
68
+ ## Lists
69
+ rule empty_list_literal
70
+ "[" space? "]" space? ":" space? "List" space type:expression
71
+ {
72
+ def to_node()
73
+ Dhallish::Ast::ListNode.new [], type.to_node()
74
+ end
75
+ }
76
+ end
77
+
78
+ rule non_empty_list_literal
79
+ "[" space? fst:expression tail:(space? "," space? exp:expression)* space? "]" annot:(space? ":" space? "List" space type:expression)?
80
+ {
81
+ def to_node()
82
+ list = []
83
+ list.append fst.to_node()
84
+ tail.elements.each { |node|
85
+ list.append node.exp.to_node()
86
+ }
87
+ if annot.respond_to? :type
88
+ Dhallish::Ast::ListNode.new list, annot.type.to_node()
89
+ else
90
+ Dhallish::Ast::ListNode.new list, nil
91
+ end
92
+ end
93
+ }
94
+ end
95
+
96
+ rule list_literal
97
+ exp:empty_list_literal { def to_node() exp.to_node() end } /
98
+ exp:non_empty_list_literal { def to_node() exp.to_node() end }
99
+ end
100
+
101
+ ## Records
102
+ rule record_type_literal
103
+ "{" space? "}" { def to_node() Dhallish::Ast::RecordTypeNode.new({}) end } /
104
+ "{" space? fstkey:label space? ":" space? fstexp:expression tail:(space? "," space? key:label space? ":" space? exp:expression)* space? "}"
105
+ {
106
+ def to_node()
107
+ data = { fstkey.text_value => fstexp.to_node() }
108
+ tail.elements.each { |node|
109
+ key = node.key.text_value
110
+ assert("no key should apeare multiple times in a record: `#{key}`") { !data.key?(key) }
111
+ data[key] = node.exp.to_node()
112
+ }
113
+ Dhallish::Ast::RecordTypeNode.new data
114
+ end
115
+ }
116
+ end
117
+
118
+ rule record_literal
119
+ "{" space? "=" space? "}" { def to_node() Dhallish::Ast::RecordNode.new({}) end } /
120
+ "{" space? fstkey:label space? "=" space? fstexp:expression tail:(space? "," space? key:label space? "=" space? exp:expression)* space? "}"
121
+ {
122
+ def to_node()
123
+ data = { fstkey.text_value => fstexp.to_node() }
124
+ tail.elements.each { |node|
125
+ key = node.key.text_value
126
+ assert("no key should apeare multiple times in a record: `#{key}`") { !data.key?(key) }
127
+ data[key] = node.exp.to_node()
128
+ }
129
+ Dhallish::Ast::RecordNode.new data
130
+ end
131
+ }
132
+ end
133
+
134
+ ## Strings
135
+ rule text_literal
136
+ '"' tail:(exp:('${' space? innerexp:expression space? '}') / esc:('\"') / any:(!'"' .))* '"'
137
+ {
138
+ def to_node()
139
+ parts = []
140
+ tail.elements.each { |node|
141
+ if node.respond_to?(:exp)
142
+ parts.push node.exp.innerexp.to_node()
143
+ elsif node.respond_to?(:esc)
144
+ parts.push "\""
145
+ else
146
+ parts.push node.any.text_value
147
+ end
148
+ }
149
+ Dhallish::Ast::TextInterpolationNode.new parts
150
+ end
151
+ }
152
+ end
153
+
154
+ ## Optionals
155
+ rule optional_literal
156
+ prefix:("Some" / "None") space exp:or_expression { def to_node() Dhallish::Ast::OptionalNode.new prefix.text_value, exp.to_node() end }
157
+ end
158
+
159
+ ## Type Literals
160
+ rule forall_literal
161
+ ("forall" / "∀") space? "(" space? lb:label space? ":" space? type:expression space? ")" space? ("->" / "→") space? res_type:expression
162
+ {
163
+ def to_node()
164
+ Dhallish::Ast::FunctionType.new(type.to_node(), res_type.to_node(), lb.text_value)
165
+ end
166
+ }
167
+ end
168
+
169
+ ## Union Literals
170
+ rule union_literal
171
+ "<" space? start:(lb:label space? ":" space? type:record_merge_expression space? "|" space?)* lb:label space? "=" space? expr:add_sub_expression tail:(space? "|" space? lb:label space? ":" space? type:record_merge_expression)* space? ">"
172
+ {
173
+ def to_node()
174
+ typed_labels = {}
175
+ start.elements.each { |node|
176
+ typed_labels[node.lb.text_value] = node.type.to_node()
177
+ }
178
+ tail.elements.each { |node|
179
+ typed_labels[node.lb.text_value] = node.type.to_node()
180
+ }
181
+ Dhallish::Ast::UnionLiteral.new(typed_labels, lb.text_value, expr.to_node())
182
+ end
183
+ }
184
+ end
185
+
186
+ rule empty_union_type_literal
187
+ "<" space? ">"
188
+ end
189
+
190
+ rule union_type_literal
191
+ empty_union_type_literal { def to_node() Dhallish::Ast::UnionType.new({}) end } /
192
+ "<" space? lb:label space? ":" space? type:record_merge_expression tail:(space? "|" space? lb:label space? ":" space? type:record_merge_expression)* space? ">"
193
+ {
194
+ def to_node()
195
+ type_map = {lb.text_value => type.to_node()}
196
+ tail.elements.each { |node|
197
+ type_map[node.lb.text_value] = node.type.to_node()
198
+ }
199
+ Dhallish::Ast::UnionType.new(type_map)
200
+ end
201
+ }
202
+
203
+ end
204
+
205
+ ## Rules for operator expressions
206
+ ## rules are reversely ordered by their precedence
207
+ ## e.g. if rule X is over rule Y, the operator associated with rule Y has a stronger precedence than Xs operator
208
+
209
+ rule import_alternative_expression
210
+ exp:annotated_expression tail:(space? "?" space? alternative:annotated_expression)*
211
+ {
212
+ def to_node()
213
+ tail.elements.reduce(exp.to_node()) { |tree, node|
214
+ Dhallish::Ast::Import_Alternative.new tree, node.alternative.to_node()
215
+ }
216
+ end
217
+ }
218
+ end
219
+
220
+ rule annotated_expression
221
+ exp:function_type_expression tail:(space? ":" space? type_expr:function_type_expression)*
222
+ {
223
+ def to_node()
224
+ tail.elements.reduce(exp.to_node()) { |tree, node|
225
+ Dhallish::Ast::TypeAnnotationNode.new tree, node.type_expr.to_node()
226
+ }
227
+ end
228
+ }
229
+ end
230
+
231
+ rule function_type_expression
232
+ arg_type:or_expression tail:(space? ("->" / "→") space? res_type:or_expression)*
233
+ {
234
+ def to_node()
235
+ if tail.elements.size == 0
236
+ arg_type.to_node()
237
+ else
238
+ tree = nil
239
+ tail.elements.reverse.each { |node|
240
+ if tree.nil?
241
+ tree = node.res_type.to_node()
242
+ else
243
+ tree = Dhallish::Ast::FunctionType.new node.res_type.to_node(), tree
244
+ end
245
+ }
246
+ Dhallish::Ast::FunctionType.new arg_type.to_node(), tree
247
+ end
248
+ end
249
+ }
250
+ end
251
+
252
+ rule or_expression
253
+ exp:and_expression tail:(space? "||" space? exp:and_expression)*
254
+ {
255
+ def to_node()
256
+ tail.elements.reduce(exp.to_node()) { |tree, node|
257
+ Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Bool], tree, node.exp.to_node(), "||") { |x, y| x || y }
258
+ }
259
+ end
260
+ }
261
+ end
262
+
263
+ rule and_expression
264
+ exp:comparison_expression tail:(space? "&&" space? exp:comparison_expression)*
265
+ {
266
+ def to_node()
267
+ tail.elements.reduce(exp.to_node()) { |tree, node|
268
+ Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Bool], tree, node.exp.to_node(), "&&") { |x, y| x && y }
269
+ }
270
+ end
271
+ }
272
+ end
273
+
274
+ rule comparison_expression
275
+ exp:add_sub_expression tail:(space? op:("==" / "!=" / "<=" / "<" / ">=" / ">") space? exp:add_sub_expression)*
276
+ {
277
+ def to_node()
278
+ tail.elements.reduce(exp.to_node()) { |tree, node|
279
+ Dhallish::Ast::ComparisonOpNode.new(tree, node.exp.to_node(), node.op.text_value) { |lhs_val, rhs_val| lhs_val.send(node.op.text_value, rhs_val )}
280
+ }
281
+ end
282
+ }
283
+ end
284
+
285
+ rule add_sub_expression
286
+ exp:mult_div_expression tail:(space? op:("+"/"-") space? exp:mult_div_expression)*
287
+ {
288
+ def to_node()
289
+ tail.elements.reduce(exp.to_node()) { |tree, node|
290
+ exp = node.exp.to_node()
291
+ if node.op.text_value == "+"
292
+ Dhallish::Ast::BinaryArithOpNode.new(Dhallish::Types::Numbers, tree, exp, "+") { |x, y| x + y }
293
+ else
294
+ Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Double, Dhallish::Types::Integer], tree, exp, "-") { |x, y| x - y }
295
+ end
296
+ }
297
+ end
298
+ }
299
+ end
300
+
301
+ rule mult_div_expression
302
+ exp:list_concat_expression tail:(space? op:("*"/"/") space? exp:list_concat_expression)*
303
+ {
304
+ def to_node()
305
+ tail.elements.reduce(exp.to_node()) { |tree, node|
306
+ exp = node.exp.to_node()
307
+ if node.op.text_value == "*"
308
+ Dhallish::Ast::BinaryArithOpNode.new(Dhallish::Types::Numbers, tree, exp, "*") { |x, y| x * y }
309
+ else
310
+ Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Double, Dhallish::Types::Integer], tree, exp, "/") { |x, y| x / y }
311
+ end
312
+
313
+ }
314
+ end
315
+ }
316
+ end
317
+
318
+ rule list_concat_expression
319
+ exp:text_append_expression tail:(space? "#" space? exp:text_append_expression)*
320
+ {
321
+ def to_node()
322
+ tail.elements.reduce(exp.to_node()) { |tree, node|
323
+ Dhallish::Ast::ListConcatNode.new(tree, node.exp.to_node())
324
+ }
325
+ end
326
+ }
327
+ end
328
+
329
+ rule text_append_expression
330
+ exp:record_merge_expression tail:(space? "++" space? exp:record_merge_expression)*
331
+ {
332
+ def to_node()
333
+ tail.elements.reduce(exp.to_node()) { |tree, node|
334
+ Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Text], tree, node.exp.to_node(), "+") { |x, y| x + y }
335
+ }
336
+ end
337
+ }
338
+ end
339
+
340
+ rule record_merge_expression
341
+ exp:application_expression tail:(space? op:("//\\\\" / "⩓" / "/\\" / "∧" / "//" / "⫽") space? exp:application_expression)*
342
+ {
343
+ def to_node()
344
+ tail.elements.reduce(exp.to_node()) { |tree, node|
345
+ if node.op.text_value == "//\\\\" or node.op.text_value == "⩓"
346
+ Dhallish::Ast::RecordTypeRecursiveMergeNode.new tree, node.exp.to_node()
347
+ elsif node.op.text_value == "/\\" or node.op.text_value == "∧"
348
+ Dhallish::Ast::RecordRecursiveMergeNode.new tree, node.exp.to_node()
349
+ else
350
+ Dhallish::Ast::RecordNonRecursiveMergeNode.new tree, node.exp.to_node()
351
+ end
352
+ }
353
+ end
354
+ }
355
+ end
356
+
357
+ rule application_expression
358
+ fn:proj_sel_expression tail:(space? exp:(!reserved arg:proj_sel_expression))*
359
+ {
360
+ def to_node()
361
+ tail.elements.reduce(fn.to_node()) { |tree, node|
362
+ Dhallish::Ast::FunctionCallNode.new(tree, node.exp.arg.to_node())
363
+ }
364
+ end
365
+ }
366
+ end
367
+
368
+ rule key_list
369
+ "{" space? "}" { def to_list() [] end } /
370
+ "{" space? head:label tail:(space? "," space? lbl:label)* space? "}"
371
+ {
372
+ def to_list()
373
+ tail.elements.reduce([head.text_value]) { |list, node|
374
+ list.push node.lbl.text_value
375
+ list
376
+ }
377
+ end
378
+ }
379
+ end
380
+
381
+ rule proj_sel_expression
382
+ pe:primitive_expression tail:(space? "." space? sel:(a:label / b:key_list))*
383
+ {
384
+ def to_node()
385
+ tail.elements.reduce(pe.to_node()) { |tree, node|
386
+ if node.sel.respond_to? :a
387
+ Dhallish::Ast::RecordUnionSelector.new tree, node.sel.a.text_value
388
+ else
389
+ Dhallish::Ast::RecordProjection.new tree, node.sel.b.to_list()
390
+ end
391
+ }
392
+ end
393
+ }
394
+ end
395
+
396
+ rule primitive_expression
397
+ exp:bool_literal { def to_node() exp.to_node() end } /
398
+ exp:double_literal { def to_node() exp.to_node() end } /
399
+ exp:natural_literal { def to_node() exp.to_node() end } /
400
+ exp:integer_literal { def to_node() exp.to_node() end } /
401
+ exp:text_literal { def to_node() exp.to_node() end } /
402
+ exp:list_literal { def to_node() exp.to_node() end } /
403
+ exp:function_definition { def to_node() exp.to_node() end } /
404
+ exp:optional_literal { def to_node() exp.to_node() end } /
405
+ exp:forall_literal { def to_node() exp.to_node() end } /
406
+ exp:import_expression { def to_node() exp.to_node() end } /
407
+ exp:union_merge { def to_node() exp.to_node() end } /
408
+ exp:label { def to_node() exp.to_node() end } /
409
+ exp:record_literal { def to_node() exp.to_node() end } /
410
+ exp:record_type_literal { def to_node() exp.to_node() end } /
411
+ exp:union_literal { def to_node() exp.to_node() end } /
412
+ exp:union_type_literal { def to_node() exp.to_node() end } /
413
+ "(" space? exp:expression space? ")" { def to_node() exp.to_node() end } /
414
+ "???" { def to_node() Dhallish::Ast::GetContext.new end }
415
+ end
416
+
417
+ ## End of operator expressions
418
+
419
+ rule if_then_else_expression
420
+ "if" space cond:expression space
421
+ "then" space exp_true:expression space
422
+ "else" space exp_false:expression
423
+ {
424
+ def to_node()
425
+ Dhallish::Ast::IfThenElseNode.new cond.to_node(), exp_true.to_node(), exp_false.to_node()
426
+ end
427
+ }
428
+ end
429
+
430
+ rule label
431
+ ("_" / [a-zA-Z]) ("_" / "-" / "/" / [a-zA-Z0-9])* { def to_node() Dhallish::Ast::VariableNode.new text_value end } /
432
+ "`" lb:(([a-zA-Z0-9] / "-" / "/" / "_" / ":" / "." / "$")+) "`" { def to_node() Dhallish::Ast::VariableNode.new lb.text_value end }
433
+ end
434
+
435
+ rule let_expression
436
+ declarations:("let" space var:label space? annot:(":" space? exp:expression space?)? "=" space? val:expression space)+ "in" space in_expr:expression
437
+ {
438
+ def to_node()
439
+ vars = []
440
+ declarations.elements.each { |node|
441
+ typeannot = nil
442
+ if node.respond_to? :annot and node.annot.respond_to? :exp
443
+ typeannot = node.annot.exp.to_node()
444
+ end
445
+ vars.append [node.var.text_value, typeannot, node.val.to_node()]
446
+ }
447
+ Dhallish::Ast::LetInNode.new vars, in_expr.to_node()
448
+ end
449
+ }
450
+ end
451
+
452
+ rule function_definition
453
+ ("\\" / "λ") space? "(" space? lb:label space? ":" space? type_exp:expression space? ")" space? ("->" / "→") space? exp:expression
454
+ {
455
+ def to_node()
456
+ Dhallish::Ast::FunctionDefinitionNode.new lb.text_value, type_exp.to_node(), exp.to_node()
457
+ end
458
+ }
459
+ end
460
+
461
+ rule forbidden_label
462
+ reserved / "Text" / "Bool" / "Optional" / "None" / "Some" / "True" / "False" / "Type" /
463
+ "Natural" ("/" ("show" / "toInteger" / "toDouble"))? /
464
+ "Integer" ("/" ("show" / "toNatural" / "toDouble"))? /
465
+ "Double" ("/" ("show" / "toNatural" / "toInteger"))? /
466
+ "List" ("/" ("length" / "reduce" / "head" / "tail"))? /
467
+ "Optional" ("/" ("fold"))?
468
+ end
469
+
470
+ rule reserved
471
+ "if" / "then" / "else" / "let" / "in" / "as" / "?"
472
+ end
473
+
474
+ rule import_expression
475
+ prefix:("../" / "./" / "~/" / "env:" / "http:" / "https:") src:import_source as_type:(space "as" space import_type:"Text")?
476
+ {
477
+ def to_node()
478
+ import_as_text = (as_type.respond_to? :import_type and as_type.import_type.text_value == "Text")
479
+ Dhallish::Ast::Import.new prefix.text_value, src.text_value, import_as_text
480
+ end
481
+ }
482
+ end
483
+
484
+ rule import_source
485
+ # can be an environment variable, url or path
486
+ (!([\s] / "\\" / "<" / ">" / "?" / "," / "[" / "]" / "{" / "}" / "#" / "(" / ")") .)*
487
+ end
488
+
489
+ rule union_merge
490
+ "merge" space? handler:primitive_expression space? union:primitive_expression
491
+ {
492
+ def to_node()
493
+ Dhallish::Ast::UnionMerge.new(handler.to_node(), union.to_node())
494
+ end
495
+ }
496
+ end
497
+
498
+ end