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