dhallish 0.2.0

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