dhallish 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/dhallish +65 -0
- data/lib/DhallishGrammar.rb +7338 -0
- data/lib/DhallishGrammar.treetop +498 -0
- data/lib/ast.rb +836 -0
- data/lib/dhallish.rb +70 -0
- data/lib/stdlib.rb +268 -0
- data/lib/types.rb +440 -0
- data/lib/utils.rb +46 -0
- metadata +68 -0
data/lib/types.rb
ADDED
@@ -0,0 +1,440 @@
|
|
1
|
+
module Dhallish
|
2
|
+
module Types
|
3
|
+
Natural = :Natural
|
4
|
+
Integer = :Integer
|
5
|
+
Double = :Double
|
6
|
+
Bool = :Bool
|
7
|
+
Text = :Text
|
8
|
+
class Type
|
9
|
+
# metadata contains information about that type, for example if
|
10
|
+
# its a list type. This is important for the static type checks.
|
11
|
+
attr_accessor :metadata
|
12
|
+
def initialize(metadata=nil)
|
13
|
+
@metadata = metadata
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(otype)
|
17
|
+
if !otype.is_a? Type
|
18
|
+
false
|
19
|
+
elsif @metadata != nil && otype.metadata != nil
|
20
|
+
@metadata == otype.metadata
|
21
|
+
else
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def to_s()
|
26
|
+
if @metadata.nil? or @metadata.is_a? Unresolved
|
27
|
+
"Type"
|
28
|
+
else
|
29
|
+
"Type(#{@metadata.to_s})"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class Optional
|
34
|
+
# Type of Optional
|
35
|
+
attr_accessor :type
|
36
|
+
def initialize(type)
|
37
|
+
@type = type
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(otype) otype.is_a? Optional and otype.type == @type end
|
41
|
+
def to_s() "Optional #{@type.to_s}" end
|
42
|
+
end
|
43
|
+
class List
|
44
|
+
# Type of List elements
|
45
|
+
attr_accessor :type
|
46
|
+
def initialize(type)
|
47
|
+
@type = type
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(otype) otype.is_a? List and otype.type == @type end
|
51
|
+
def to_s() "List #{@type.to_s}" end
|
52
|
+
end
|
53
|
+
class Record
|
54
|
+
# Hash: keyname -> type
|
55
|
+
attr_accessor :types
|
56
|
+
def initialize(types)
|
57
|
+
@types = types
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(otype)
|
61
|
+
if !otype.is_a? Record or @types.size != otype.types.size
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
@types.keys.reduce(true) { |isequal, key|
|
66
|
+
if !isequal or !(otype.types.key? key)
|
67
|
+
false
|
68
|
+
else
|
69
|
+
otype.types[key] == @types[key]
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s()
|
75
|
+
"{ #{@types.keys.map { |key|
|
76
|
+
"#{key}: #{@types[key].to_s}"
|
77
|
+
}.join(", ")} }"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
class Function
|
81
|
+
# Type of Function that takes argtype and returns restype
|
82
|
+
attr_accessor :argtype
|
83
|
+
attr_accessor :restype
|
84
|
+
attr_accessor :unres
|
85
|
+
|
86
|
+
def initialize(argtype, restype, unres=nil)
|
87
|
+
@argtype = argtype
|
88
|
+
@restype = restype
|
89
|
+
if !unres.nil?
|
90
|
+
if !unres.is_a? Symbol
|
91
|
+
@unres = unres.to_sym
|
92
|
+
else
|
93
|
+
@unres = unres
|
94
|
+
end
|
95
|
+
else
|
96
|
+
@unres = nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def ==(otype)
|
101
|
+
if !(otype.is_a? Function) or !(otype.argtype == @argtype)
|
102
|
+
false
|
103
|
+
elsif !@restype.nil? and !otype.restype.nil?
|
104
|
+
@restype == otype.restype
|
105
|
+
else
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s()
|
111
|
+
if !@restype.nil? and !@unres.nil?
|
112
|
+
"∀(#{@unres}: #{@argtype.to_s}) -> #{@restype.to_s}"
|
113
|
+
elsif !@restype.nil?
|
114
|
+
"∀(#{@argtype.to_s}) -> #{@restype.to_s}"
|
115
|
+
else
|
116
|
+
"∀(#{@argtype.to_s}) -> ?"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
class Unresolved
|
121
|
+
attr_accessor :name
|
122
|
+
def initialize(name)
|
123
|
+
@name = name.to_sym
|
124
|
+
end
|
125
|
+
def ==(otype) otype.is_a? Unresolved and otype.name == @name end
|
126
|
+
def to_s() "#{@name}" end
|
127
|
+
end
|
128
|
+
class Union
|
129
|
+
# types: label -> Type
|
130
|
+
attr_accessor :types
|
131
|
+
def initialize(types)
|
132
|
+
@types = types
|
133
|
+
end
|
134
|
+
|
135
|
+
def ==(otype)
|
136
|
+
if !otype.is_a? Union
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
140
|
+
@types.keys.reduce(true) { |isequal, key|
|
141
|
+
isequal and (otype.types.include? key and @types[key] == otype.types[key])
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_s()
|
146
|
+
"< #{@types.keys.map { |key|
|
147
|
+
"#{key}: #{@types[key].to_s}"
|
148
|
+
}.join(" | ")} >"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def resolve(orgtype, name, newtype)
|
153
|
+
case orgtype
|
154
|
+
when Type
|
155
|
+
Type.new(resolve(orgtype.metadata, name, newtype))
|
156
|
+
when Unresolved
|
157
|
+
if name == orgtype.name
|
158
|
+
newtype
|
159
|
+
else
|
160
|
+
orgtype
|
161
|
+
end
|
162
|
+
when Function
|
163
|
+
if orgtype.restype.nil?
|
164
|
+
Function.new(resolve(orgtype.argtype, name, newtype), nil, orgtype.unres)
|
165
|
+
else
|
166
|
+
restype = orgtype.restype
|
167
|
+
argtype = orgtype.argtype
|
168
|
+
if orgtype.unres.nil? or orgtype.unres != name
|
169
|
+
restype = resolve(orgtype.restype, name, newtype)
|
170
|
+
argtype = resolve(orgtype.argtype, name, newtype)
|
171
|
+
end
|
172
|
+
Function.new(argtype, restype, orgtype.unres)
|
173
|
+
end
|
174
|
+
when Optional
|
175
|
+
Optional.new(resolve(orgtype.type, name, newtype))
|
176
|
+
when List
|
177
|
+
List.new(resolve(orgtype.type, name, newtype))
|
178
|
+
when Record
|
179
|
+
Record.new(orgtype.types.map{ |key, val| [key, resolve(val, name, newtype)] }.to_h)
|
180
|
+
when Union
|
181
|
+
Union.new(orgtype.types.map{ |key, val| [key, resolve(val, name, newtype)] }.to_h)
|
182
|
+
else
|
183
|
+
orgtype
|
184
|
+
end
|
185
|
+
end
|
186
|
+
module_function :resolve
|
187
|
+
|
188
|
+
def unification(a, b, mapping={})
|
189
|
+
case a
|
190
|
+
when Type
|
191
|
+
if b.is_a? Type
|
192
|
+
unification(a.metadata, b.metadata, mapping)
|
193
|
+
else
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
when Unresolved
|
197
|
+
if b.is_a? Unresolved
|
198
|
+
if mapping[a.name] == nil
|
199
|
+
mapping[a.name] = b.name
|
200
|
+
mapping
|
201
|
+
elsif mapping[a.name] != b.name
|
202
|
+
nil
|
203
|
+
else
|
204
|
+
mapping
|
205
|
+
end
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
when Function
|
210
|
+
if b.is_a? Function
|
211
|
+
if unification(a.argtype, b.argtype, mapping).nil?
|
212
|
+
nil
|
213
|
+
else
|
214
|
+
arestype = a.restype
|
215
|
+
brestype = b.restype
|
216
|
+
if !a.unres.nil?; arestype = resolve(a.restype, a.unres, Unresolved.new(get_new_sym())) end
|
217
|
+
if !b.unres.nil?; brestype = resolve(b.restype, b.unres, Unresolved.new(get_new_sym())) end
|
218
|
+
unification(arestype, brestype, mapping)
|
219
|
+
end
|
220
|
+
else
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
when Optional
|
224
|
+
if b.is_a? Optional
|
225
|
+
unification(a.type, b.type, mapping)
|
226
|
+
else
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
when List
|
230
|
+
if b.is_a? List
|
231
|
+
unification(a.type, b.type, mapping)
|
232
|
+
else
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
when Record
|
236
|
+
if b.is_a? Record and a.types.keys.length == b.types.keys.length
|
237
|
+
for key in a.types.keys
|
238
|
+
aelm = a.types[key]
|
239
|
+
belm = b.types[key]
|
240
|
+
if belm.nil?; return nil end
|
241
|
+
ret = unification(aelm, belm, mapping)
|
242
|
+
if ret.nil?; return nil end
|
243
|
+
end
|
244
|
+
mapping
|
245
|
+
else
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
when Union
|
249
|
+
if b.is_a? Union and a.types.keys.length == b.types.keys.length
|
250
|
+
for key in a.types.keys
|
251
|
+
aelm = a.types[key]
|
252
|
+
belm = b.types[key]
|
253
|
+
if belm.nil?; return nil end
|
254
|
+
ret = unification(aelm, belm, mapping)
|
255
|
+
if ret.nil?; return nil end
|
256
|
+
end
|
257
|
+
mapping
|
258
|
+
else
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
else
|
262
|
+
if a == b
|
263
|
+
mapping
|
264
|
+
else
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
module_function :unification
|
270
|
+
|
271
|
+
def not_a_type?(x)
|
272
|
+
if x.is_a? Symbol
|
273
|
+
x != Natural and x != Integer and x != Double and x != Bool and x != Text
|
274
|
+
else
|
275
|
+
!x.is_a? Optional and !x.is_a? List and !x.is_a? Record and !x.is_a? Function and !x.is_a? Unresolved and !x.is_a? Type and !x.is_a? Union
|
276
|
+
end
|
277
|
+
end
|
278
|
+
module_function :not_a_type?
|
279
|
+
|
280
|
+
def is_a_type?(x)
|
281
|
+
!not_a_type?(x)
|
282
|
+
end
|
283
|
+
module_function :is_a_type?
|
284
|
+
|
285
|
+
Numbers = [Natural, Integer, Double]
|
286
|
+
TextList = List.new(Text)
|
287
|
+
NaturalList = List.new(Natural)
|
288
|
+
IntegerList = List.new(Integer)
|
289
|
+
DoubleList = List.new(Double)
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_json(dhallval)
|
293
|
+
case dhallval
|
294
|
+
when Integer
|
295
|
+
dhallval.to_s
|
296
|
+
when Float
|
297
|
+
dhallval.to_s
|
298
|
+
when String
|
299
|
+
"\"#{escape_str(dhallval)}\""
|
300
|
+
when TrueClass
|
301
|
+
"true"
|
302
|
+
when FalseClass
|
303
|
+
"false"
|
304
|
+
when Array
|
305
|
+
"[#{ dhallval.map{ |val| to_json(val) }.join(", ") }]"
|
306
|
+
when Hash
|
307
|
+
"{#{ dhallval.map{ |key, val| "\"#{escape_str(key)}\": #{ to_json(val) }" }.join(", ") }}"
|
308
|
+
when nil
|
309
|
+
"null"
|
310
|
+
else
|
311
|
+
if Types::is_a_type? dhallval
|
312
|
+
"\"#{dhallval.to_s}\""
|
313
|
+
else
|
314
|
+
"\"<#{escape_str(dhallval.class.to_s)}##{dhallval.hash.abs.to_s(16)}>\""
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
module_function :to_json
|
319
|
+
|
320
|
+
# To be used as Dhallish::Value.val for dhall-defined functions
|
321
|
+
class Function
|
322
|
+
attr_accessor :argname
|
323
|
+
attr_accessor :ast
|
324
|
+
attr_accessor :ctx
|
325
|
+
def initialize(argname, ast, ctx)
|
326
|
+
@argname = argname
|
327
|
+
@ast = ast
|
328
|
+
@ctx = ctx
|
329
|
+
end
|
330
|
+
|
331
|
+
def call(arg)
|
332
|
+
newctx = Context.new ctx
|
333
|
+
newctx[@argname] = arg
|
334
|
+
@ast.evaluate newctx
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class Union
|
339
|
+
attr_accessor :init_label
|
340
|
+
attr_accessor :init_val
|
341
|
+
attr_accessor :type
|
342
|
+
|
343
|
+
def initialize(init_label, init_val, type)
|
344
|
+
@init_label = init_label
|
345
|
+
@init_val = init_val
|
346
|
+
@type = type
|
347
|
+
end
|
348
|
+
|
349
|
+
def select(label)
|
350
|
+
if label == @init_label
|
351
|
+
@init_val
|
352
|
+
else
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# To be used as Dhallish::Value.val for ruby-defined functions
|
359
|
+
class BuiltinFunction
|
360
|
+
attr_accessor :block
|
361
|
+
def initialize(&block)
|
362
|
+
@block = block
|
363
|
+
end
|
364
|
+
|
365
|
+
def call(arg) @block.call arg end
|
366
|
+
end
|
367
|
+
|
368
|
+
class Context
|
369
|
+
def initialize(outercontext=nil)
|
370
|
+
@outercontext = outercontext
|
371
|
+
@scope = {}
|
372
|
+
end
|
373
|
+
|
374
|
+
def [](varname)
|
375
|
+
val = @scope[varname]
|
376
|
+
if val.nil? && !@outercontext.nil?
|
377
|
+
@outercontext[varname]
|
378
|
+
else
|
379
|
+
val
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def []=(varname, value)
|
384
|
+
@scope[varname] = value
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def mergeRecordTypes(a, b)
|
389
|
+
assert("records expected") { a.is_a? Types::Record and b.is_a? Types::Record }
|
390
|
+
|
391
|
+
merged = a.types.clone
|
392
|
+
b.types.each { |key, type|
|
393
|
+
if merged.key? key
|
394
|
+
if type.is_a? Types::Record and merged[key].is_a? Types::Record
|
395
|
+
merged[key] = mergeRecordTypes(merged[key], type)
|
396
|
+
else
|
397
|
+
raise DhallError, "key `#{key}` apeares in left and right side of `//\\\\` with different types"
|
398
|
+
end
|
399
|
+
else
|
400
|
+
merged[key] = type
|
401
|
+
end
|
402
|
+
}
|
403
|
+
|
404
|
+
Types::Record.new merged
|
405
|
+
end
|
406
|
+
module_function :mergeRecordTypes
|
407
|
+
|
408
|
+
# TODO: .val weg!
|
409
|
+
def mergeRecordsRecursively(a, b)
|
410
|
+
merged = a.clone
|
411
|
+
b.each { |key, val|
|
412
|
+
if merged.key? key
|
413
|
+
if val.is_a? Hash and merged[key].is_a? Hash
|
414
|
+
merged[key] = mergeRecordsRecursively(merged[key], val)
|
415
|
+
else
|
416
|
+
raise DhallError, "key `#{key}` apeares in left and right side of `/\\` (should not happen becouse of static type checks)"
|
417
|
+
end
|
418
|
+
else
|
419
|
+
merged[key] = val
|
420
|
+
end
|
421
|
+
}
|
422
|
+
merged
|
423
|
+
end
|
424
|
+
module_function :mergeRecordsRecursively
|
425
|
+
|
426
|
+
def mergeRecordTypesPrefereRight(a, b)
|
427
|
+
assert("expecting records for `//`") { a.is_a? Types::Record and b.is_a? Types::Record }
|
428
|
+
mergedTypes = a.types.clone
|
429
|
+
b.types.each { |key, type| mergedTypes[key] = type }
|
430
|
+
Types::Record.new(mergedTypes)
|
431
|
+
end
|
432
|
+
module_function :mergeRecordTypesPrefereRight
|
433
|
+
|
434
|
+
def mergeRecordsPrefereRight(a, b)
|
435
|
+
mergedVals = a.clone
|
436
|
+
b.each { |key, val| mergedVals[key] = val }
|
437
|
+
mergedVals
|
438
|
+
end
|
439
|
+
module_function :mergeRecordsPrefereRight
|
440
|
+
end
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
class AssertionError < RuntimeError
|
3
|
+
def initialize(msg = "Help!")
|
4
|
+
super(msg)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def assert(msg, &block) raise AssertionError, msg unless yield end
|
9
|
+
|
10
|
+
class DhallError < StandardError
|
11
|
+
def initialize(msg)
|
12
|
+
super(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
$dhallish_internal_global_counter = 0
|
17
|
+
def get_new_sym()
|
18
|
+
$dhallish_internal_global_counter += 1
|
19
|
+
("__newsym_#{$dhallish_internal_global_counter}").to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def escape_str(str)
|
23
|
+
str.gsub("\n", "\\n").gsub("\"", "\\\"")
|
24
|
+
end
|
25
|
+
|
26
|
+
def does_cmd_exist?(cmd)
|
27
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each { |path|
|
28
|
+
exe = File.join(path, cmd)
|
29
|
+
if File.executable?(exe) && !File.directory?(exe)
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
}
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
def resolve_map(map, from_name, to_name)
|
37
|
+
resmap = {}
|
38
|
+
map.each { |key, val|
|
39
|
+
reskey = key
|
40
|
+
resval = val
|
41
|
+
if reskey == from_name; reskey = to_name; end
|
42
|
+
if resval == from_name; resval = to_name; end
|
43
|
+
resmap[reskey] = resval
|
44
|
+
}
|
45
|
+
resmap
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dhallish
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lou Knauer <lou.knauer@fau.de>
|
8
|
+
- Philipp Panzer <philipp.panzer@fau.de>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-03-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: treetop
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.6.10
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.6.10
|
28
|
+
description: A Ruby Implementation of a Dhall-like Language
|
29
|
+
email:
|
30
|
+
executables:
|
31
|
+
- dhallish
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- "./bin/dhallish"
|
36
|
+
- "./lib/DhallishGrammar.rb"
|
37
|
+
- "./lib/DhallishGrammar.treetop"
|
38
|
+
- "./lib/ast.rb"
|
39
|
+
- "./lib/dhallish.rb"
|
40
|
+
- "./lib/stdlib.rb"
|
41
|
+
- "./lib/types.rb"
|
42
|
+
- "./lib/utils.rb"
|
43
|
+
- bin/dhallish
|
44
|
+
homepage: https://gitlab.cs.fau.de/basst/ruby-dhallish
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '2.4'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.7.6
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: A Ruby Implementation of a Dhall-like Language
|
68
|
+
test_files: []
|