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/dhallish.rb ADDED
@@ -0,0 +1,70 @@
1
+
2
+ require 'treetop'
3
+ require_relative 'utils.rb'
4
+ require_relative 'types.rb'
5
+ require_relative 'stdlib.rb'
6
+
7
+ DIR = File.dirname(__FILE__)
8
+ if File.exists?(DIR + '/DhallishGrammar.rb') and File.mtime(DIR + '/DhallishGrammar.rb') >= File.mtime(DIR + '/DhallishGrammar.treetop')
9
+ require_relative 'DhallishGrammar.rb'
10
+ elsif File.writable?(DIR) and does_cmd_exist?('tt') and system("tt '#{DIR + '/DhallishGrammar.treetop'}'")
11
+ require_relative 'DhallishGrammar.rb'
12
+ else
13
+ Treetop.load File.join(DIR, 'DhallishGrammar.treetop')
14
+ end
15
+
16
+ module Dhallish
17
+
18
+ @@parser = DhallishGrammarParser.new
19
+
20
+ def empty_context(basedir=Dir.pwd)
21
+ ctx = { "<#TYPES#>" => Context.new, "<#VALS#>" => Context.new }
22
+ ctx["<#TYPES#>"]["<#DIR#>"] = basedir
23
+ fill_context(ctx["<#VALS#>"], ctx["<#TYPES#>"])
24
+ ctx
25
+ end
26
+ module_function :empty_context
27
+
28
+ def evaluate(dhallcode, ctx=nil, expected_type=nil)
29
+ if ctx.nil?; ctx = empty_context() end
30
+
31
+ rawast = @@parser.parse dhallcode
32
+ if rawast.nil?
33
+ raise DhallError, "#{@@parser.failure_reason} (line/column: #{@@parser.failure_line}:#{@@parser.failure_column})"
34
+ end
35
+
36
+ ast = rawast.to_node
37
+
38
+ type = ast.compute_type ctx["<#TYPES#>"]
39
+ res = ast.evaluate ctx["<#VALS#>"]
40
+
41
+ if !expected_type.nil?
42
+ if !(type == expected_type)
43
+ raise DhallError, "expression return type missmatch: expected `#{expected_type}`, got: `#{type}`"
44
+ else
45
+ res
46
+ end
47
+ else
48
+ [res, type]
49
+ end
50
+ end
51
+ module_function :evaluate
52
+
53
+ def create_ctx(dhallcode, basedir=Dir.pwd)
54
+ empty_ctx = empty_context(basedir)
55
+ ctx, type = evaluate(dhallcode, empty_ctx)
56
+ if type != Types::Record.new({}) or ctx["<#TYPES#>"].nil? or ctx["<#VALS#>"].nil?
57
+ raise DhallError, "return type of dhallcode supplied to `create_ctx` did not return a context (but something of type `#{type}`). use `???`"
58
+ end
59
+ ctx
60
+ end
61
+ module_function :create_ctx
62
+
63
+ def create_ctx_from_file(dhallfile, basedir=nil)
64
+ if basedir.nil?; basedir = File.dirname(dhallfile) end
65
+ file = File.open(dhallfile)
66
+ res = create_ctx(file.read, basedir)
67
+ file.close
68
+ res
69
+ end
70
+ end
data/lib/stdlib.rb ADDED
@@ -0,0 +1,268 @@
1
+ require_relative './types.rb'
2
+ require_relative './utils.rb'
3
+
4
+ module Dhallish
5
+
6
+ # Utility function to create dhallish function types:
7
+ # Use [:name] to introduce a new unresolved type and write
8
+ # :name to use it. Look at `fill_context` for examples.
9
+ def make_fn_type(*args, rettype)
10
+ if Types::not_a_type?(rettype); rettype = Types::Unresolved.new(rettype) end
11
+ args.reverse.reduce(rettype) { |rettype, arg|
12
+ argtype = arg
13
+ name = nil
14
+ if arg.is_a? Array
15
+ name = arg[0]
16
+ argtype = Types::Type.new(Types::Unresolved.new(name))
17
+ end
18
+ if Types::not_a_type?(argtype); argtype = Types::Unresolved.new(argtype) end
19
+ Types::Function.new(argtype, rettype, name)
20
+ }
21
+ end
22
+ module_function :make_fn_type
23
+
24
+ def fill_context(globalctx, types)
25
+
26
+ # Types:
27
+ globalctx["Natural"] = Types::Natural
28
+ globalctx["Integer"] = Types::Integer
29
+ globalctx["Double"] = Types::Double
30
+ globalctx["Bool"] = Types::Bool
31
+ globalctx["Text"] = Types::Text
32
+ globalctx["Type"] = Types::Type.new
33
+
34
+ types["Natural"] = Types::Type.new(Types::Natural)
35
+ types["Integer"] = Types::Type.new(Types::Integer)
36
+ types["Double"] = Types::Type.new(Types::Double)
37
+ types["Bool"] = Types::Type.new(Types::Bool)
38
+ types["Text"] = Types::Type.new(Types::Text)
39
+ types["Type"] = Types::Type.new(Types::Type.new)
40
+
41
+ # Shows:
42
+ globalctx["Natural/show"] = BuiltinFunction.new { |arg| arg.to_s }
43
+ types["Natural/show"] = Types::Function.new(Types::Natural, Types::Text)
44
+
45
+ globalctx["Integer/show"] = BuiltinFunction.new { |arg| arg.to_s }
46
+ types["Integer/show"] = Types::Function.new(Types::Integer, Types::Text)
47
+
48
+ globalctx["Double/show"] = BuiltinFunction.new { |arg| arg.to_s }
49
+ types["Double/show"] = Types::Function.new(Types::Double, Types::Text)
50
+
51
+ globalctx["Text/show"] = BuiltinFunction.new { |arg| arg }
52
+ types["Text/show"] = Types::Function.new(Types::Text, Types::Text)
53
+
54
+ # Casts:
55
+ natToInt = BuiltinFunction.new { |arg| arg }
56
+ globalctx["Natural/toInteger"] = natToInt
57
+ types["Natural/toInteger"] = Types::Function.new(Types::Natural, Types::Integer)
58
+
59
+ natToDou = BuiltinFunction.new { |arg| arg.to_f }
60
+ globalctx["Natural/toDouble"] = natToDou
61
+ types["Natural/toDouble"] = Types::Function.new(Types::Natural, Types::Double)
62
+
63
+
64
+ intToNat = BuiltinFunction.new { |arg| arg.abs }
65
+ globalctx["Integer/toNatural"] = intToNat
66
+ types["Integer/toNatural"] = Types::Function.new(Types::Integer, Types::Natural)
67
+
68
+ intToDou = BuiltinFunction.new { |arg| arg.to_f }
69
+ globalctx["Integer/toDouble"] = intToDou
70
+ types["Integer/toDouble"] = Types::Function.new(Types::Integer, Types::Double)
71
+
72
+
73
+ douToNat = BuiltinFunction.new { |arg| arg.round.abs }
74
+ globalctx["Double/toNatural"] = douToNat
75
+ types["Double/toNatural"] = Types::Function.new(Types::Double, Types::Natural)
76
+
77
+ douToInt = BuiltinFunction.new { |arg| arg.round }
78
+ globalctx["Double/toInteger"] = douToInt
79
+ types["Double/toInteger"] = Types::Function.new(Types::Double, Types::Integer)
80
+
81
+
82
+ # Lists:
83
+ globalctx["List"] = BuiltinFunction.new { |a| Types::List.new(a) }
84
+ types["List"] = make_fn_type([:list_a], Types::Type.new(Types::List.new(Types::Unresolved.new(:list_a))))
85
+
86
+ list_length_type = make_fn_type([:list_len_a], Types::List.new(Types::Unresolved.new(:list_len_a)), Types::Natural)
87
+ globalctx["List/length"] = BuiltinFunction.new{ |a|
88
+ BuiltinFunction.new { |list|
89
+ list.length
90
+ }
91
+ }
92
+ types["List/length"] = list_length_type
93
+
94
+ list_fold_type = make_fn_type(
95
+ [:list_fold_a],
96
+ Types::List.new(Types::Unresolved.new(:list_fold_a)),
97
+ [:list_fold_b],
98
+ make_fn_type(:list_fold_a, :list_fold_b, :list_fold_b),
99
+ :list_fold_b, :list_fold_b)
100
+ globalctx["List/fold"] = BuiltinFunction.new { |a|
101
+ BuiltinFunction.new { |list|
102
+ BuiltinFunction.new { |b|
103
+ BuiltinFunction.new { |f|
104
+ BuiltinFunction.new { |x|
105
+ acc = x
106
+ list.reverse_each { |elm| acc = f.call(elm).call(acc) }
107
+ acc
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ types["List/fold"] = list_fold_type
114
+
115
+ list_head_type = make_fn_type(
116
+ [:list_head_a],
117
+ Types::List.new(Types::Unresolved.new(:list_head_a)),
118
+ Types::Optional.new(Types::Unresolved.new(:list_head_a)))
119
+ globalctx["List/head"] = BuiltinFunction.new { |a|
120
+ BuiltinFunction.new { |list|
121
+ list.first
122
+ }
123
+ }
124
+ types["List/head"] = list_head_type
125
+
126
+ list_last_type = make_fn_type(
127
+ [:list_last_a],
128
+ Types::List.new(Types::Unresolved.new(:list_last_a)),
129
+ Types::Optional.new(Types::Unresolved.new(:list_last_a)))
130
+ globalctx["List/last"] = BuiltinFunction.new { |a|
131
+ BuiltinFunction.new { |list|
132
+ list.last
133
+ }
134
+ }
135
+ types["List/last"] = list_last_type
136
+
137
+ list_tail_type = make_fn_type(
138
+ [:list_tail_a],
139
+ Types::List.new(Types::Unresolved.new(:list_tail_a)),
140
+ Types::List.new(Types::Unresolved.new(:list_tail_a)))
141
+ globalctx["List/tail"] = BuiltinFunction.new { |a|
142
+ BuiltinFunction.new { |list|
143
+ if list.empty?
144
+ []
145
+ else
146
+ list[1..list.length]
147
+ end
148
+ }
149
+ }
150
+ types["List/tail"] = list_tail_type
151
+
152
+ types["List/reverse"] = make_fn_type(
153
+ [:list_reverse_a],
154
+ Types::List.new(Types::Unresolved.new(:list_reverse_a)),
155
+ Types::List.new(Types::Unresolved.new(:list_reverse_a)))
156
+ globalctx["List/reverse"] = BuiltinFunction.new { |a|
157
+ BuiltinFunction.new { |list| list.reverse }
158
+ }
159
+
160
+ types["List/build"] = make_fn_type(
161
+ [:list_build_a],
162
+ make_fn_type(
163
+ [:list_build_b],
164
+ make_fn_type(:list_build_a, :list_build_b, :list_build_b), :list_build_b, :list_build_b),
165
+ Types::List.new(Types::Unresolved.new(:list_build_a)))
166
+ globalctx["List/build"] = BuiltinFunction.new { |a|
167
+ BuiltinFunction.new { |f|
168
+ cons = BuiltinFunction.new { |x|
169
+ BuiltinFunction.new { |list| [x] + list }
170
+ }
171
+ f.call(Types::List.new(a)).call(cons).call([])
172
+ }
173
+ }
174
+
175
+ types["List/indexed"] = make_fn_type(
176
+ [:list_indexed_a],
177
+ Types::List.new(Types::Unresolved.new(:list_indexed_a)),
178
+ Types::List.new(Types::Record.new({
179
+ "index" => Types::Natural,
180
+ "value" => Types::Unresolved.new(:list_indexed_a)})))
181
+ globalctx["List/indexed"] = BuiltinFunction.new { |a|
182
+ BuiltinFunction.new { |list|
183
+ list.map.with_index { |val, idx|
184
+ { "index" => idx, "value" => val }
185
+ }
186
+ }
187
+ }
188
+
189
+ # Optionals:
190
+ globalctx["Optional"] = BuiltinFunction.new { |a|
191
+ Types::Optional.new(a)
192
+ }
193
+ types["Optional"] = make_fn_type([:opt_a], Types::Type.new(Types::Optional.new(Types::Unresolved.new(:opt_a))))
194
+
195
+ optional_fold_type = make_fn_type(
196
+ [:opt_fold_a],
197
+ Types::Optional.new(Types::Unresolved.new(:opt_fold_a)),
198
+ [:opt_fold_b],
199
+ make_fn_type(:opt_fold_a, :opt_fold_b),
200
+ :opt_fold_b, :opt_fold_b)
201
+ globalctx["Optional/fold"] = BuiltinFunction.new { |a|
202
+ BuiltinFunction.new { |opt|
203
+ BuiltinFunction.new { |b|
204
+ BuiltinFunction.new { |f|
205
+ BuiltinFunction.new { |x|
206
+ if opt.nil?
207
+ x
208
+ else
209
+ f.call(opt)
210
+ end
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ types["Optional/fold"] = optional_fold_type
217
+
218
+ types["Optional/build"] = make_fn_type(
219
+ [:opt_build_a],
220
+ make_fn_type([:opt_build_b],
221
+ make_fn_type(:opt_build_a, :opt_build_b), :opt_build_b, :opt_build_b),
222
+ Types::Optional.new(Types::Unresolved.new(:opt_build_a)))
223
+ globalctx["Optional/build"] = BuiltinFunction.new { |a|
224
+ BuiltinFunction.new { |f|
225
+ just = BuiltinFunction.new { |x| x }
226
+ f.call(Types::Optional.new(a)).call(just).call(nil)
227
+ }
228
+ }
229
+
230
+
231
+ # Naturals:
232
+ natural_fold_type = make_fn_type(
233
+ Types::Natural,
234
+ [:nat_fold_a],
235
+ make_fn_type(:nat_fold_a, :nat_fold_a),
236
+ :nat_fold_a, :nat_fold_a)
237
+ globalctx["Natural/fold"] = BuiltinFunction.new { |n|
238
+ BuiltinFunction.new { |a|
239
+ BuiltinFunction.new { |succ|
240
+ BuiltinFunction.new { |zero|
241
+ res = zero
242
+ n.times { res = succ.call(res) }
243
+ res
244
+ }
245
+ }
246
+ }
247
+ }
248
+ types["Natural/fold"] = natural_fold_type
249
+
250
+ types["Natural/even"] = make_fn_type(Types::Natural, Types::Bool)
251
+ globalctx["Natural/even"] = BuiltinFunction.new { |n| n % 2 == 0 }
252
+
253
+ types["Natural/odd"] = make_fn_type(Types::Natural, Types::Bool)
254
+ globalctx["Natural/odd"] = BuiltinFunction.new { |n| n % 2 == 1 }
255
+
256
+ types["Natural/isZero"] = make_fn_type(Types::Natural, Types::Bool)
257
+ globalctx["Natural/isZero"] = BuiltinFunction.new { |n| n == 0 }
258
+
259
+ globalctx["Natural/build"] = BuiltinFunction.new { |f|
260
+ zero = 0
261
+ succ = BuiltinFunction.new { |n| n + 1 }
262
+ f.call(Types::Natural).call(succ).call(zero)
263
+ }
264
+ types["Natural/build"] = make_fn_type(make_fn_type([:nat_build_a], make_fn_type(:nat_build_a, :nat_build_a), :nat_build_a, :nat_build_a), Types::Natural)
265
+
266
+ end
267
+ module_function :fill_context
268
+ end