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/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
|