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.
- 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: []
|