haskell_match 0.1.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/CHANGELOG.md +98 -0
- data/LICENSE-APACHE +202 -0
- data/LICENSE-MIT +21 -0
- data/README.md +1484 -0
- data/ext/haskell_match/Cargo.lock +33 -0
- data/ext/haskell_match/Cargo.toml +22 -0
- data/ext/haskell_match/extconf.rb +41 -0
- data/ext/haskell_match/src/core/ast.rs +190 -0
- data/ext/haskell_match/src/core/error.rs +52 -0
- data/ext/haskell_match/src/core/exhaust.rs +699 -0
- data/ext/haskell_match/src/core/hs/ast.rs +256 -0
- data/ext/haskell_match/src/core/hs/json.rs +225 -0
- data/ext/haskell_match/src/core/hs/layout.rs +346 -0
- data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
- data/ext/haskell_match/src/core/hs/mod.rs +14 -0
- data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
- data/ext/haskell_match/src/core/lexer.rs +590 -0
- data/ext/haskell_match/src/core/mod.rs +19 -0
- data/ext/haskell_match/src/core/parser.rs +1116 -0
- data/ext/haskell_match/src/core/pretty.rs +373 -0
- data/ext/haskell_match/src/core/resolve.rs +336 -0
- data/ext/haskell_match/src/core/tree.rs +921 -0
- data/ext/haskell_match/src/core/typecheck.rs +226 -0
- data/ext/haskell_match/src/core/types.rs +404 -0
- data/ext/haskell_match/src/lib.rs +19 -0
- data/ext/haskell_match/src/ruby/mod.rs +1195 -0
- data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
- data/lib/haskell_match/binding_plan.rb +84 -0
- data/lib/haskell_match/case_of.rb +71 -0
- data/lib/haskell_match/clauses.rb +354 -0
- data/lib/haskell_match/data.rb +417 -0
- data/lib/haskell_match/deep_call.rb +98 -0
- data/lib/haskell_match/deriving.rb +130 -0
- data/lib/haskell_match/dsl.rb +71 -0
- data/lib/haskell_match/errors.rb +85 -0
- data/lib/haskell_match/field_types.rb +140 -0
- data/lib/haskell_match/function.rb +240 -0
- data/lib/haskell_match/haskell/compiler.rb +961 -0
- data/lib/haskell_match/haskell.rb +326 -0
- data/lib/haskell_match/inspect.rb +45 -0
- data/lib/haskell_match/lazy_list.rb +210 -0
- data/lib/haskell_match/native_loader.rb +64 -0
- data/lib/haskell_match/pattern.rb +75 -0
- data/lib/haskell_match/pattern_ast.rb +394 -0
- data/lib/haskell_match/prelude.rb +448 -0
- data/lib/haskell_match/scope.rb +44 -0
- data/lib/haskell_match/version.rb +5 -0
- data/lib/haskell_match.rb +41 -0
- metadata +124 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module HaskellMatch
|
|
6
|
+
module Haskell
|
|
7
|
+
# Compiles the JSON AST produced by the native parser into Ruby source
|
|
8
|
+
# that is evaluated in the host module.
|
|
9
|
+
#
|
|
10
|
+
# * Each top-level function becomes a `HaskellMatch.fn` held in an
|
|
11
|
+
# instance variable of the module (`@hs_name`), plus a singleton method
|
|
12
|
+
# (`Mod.name` and its snake_case alias) for callers in Ruby.
|
|
13
|
+
# * Values (`x = expr`) become memoised singleton methods.
|
|
14
|
+
# * `where`/`let` functions, lambdas with refutable patterns and `case`
|
|
15
|
+
# expressions are lambda-lifted into hidden top-level functions whose
|
|
16
|
+
# leading parameters are their free variables, so every pattern match
|
|
17
|
+
# is compiled exactly once.
|
|
18
|
+
# * Saturated calls to known functions are direct; a function used as a
|
|
19
|
+
# value is a curried Proc. Calls in tail position compile to `tail`,
|
|
20
|
+
# so Haskell loops run in constant space.
|
|
21
|
+
class Compiler
|
|
22
|
+
Scope = Struct.new(:vars, :funs, :parent) do
|
|
23
|
+
# vars: haskell name => ruby expression (local or param name)
|
|
24
|
+
# funs: haskell name => { ivar:, arity:, free: [haskell names] }
|
|
25
|
+
def lookup_var(name)
|
|
26
|
+
return vars[name] if vars.key?(name)
|
|
27
|
+
|
|
28
|
+
parent&.lookup_var(name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def lookup_fun(name)
|
|
32
|
+
return funs[name] if funs.key?(name)
|
|
33
|
+
|
|
34
|
+
parent&.lookup_fun(name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shadowed?(name)
|
|
38
|
+
vars.key?(name) || funs.key?(name) || (parent&.shadowed?(name) || false)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
BINOPS = {
|
|
43
|
+
"+" => "+", "-" => "-", "*" => "*", "==" => "==", "/=" => "!=",
|
|
44
|
+
"<" => "<", "<=" => "<=", ">" => ">", ">=" => ">=", "&&" => "&&", "||" => "||", "^" => "**"
|
|
45
|
+
}.freeze
|
|
46
|
+
PRELUDE_BINOPS = {
|
|
47
|
+
"/" => "fdiv", "**" => "powf", "++" => "append", ":" => "cons", "!!" => "index", "." => "compose"
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
attr_reader :source_name, :lines, :lifted_lines
|
|
51
|
+
|
|
52
|
+
def initialize(ast, host, source_name: "haskell", exhaustive: HaskellMatch.exhaustive, line_offset: 0,
|
|
53
|
+
scope: Native::GLOBAL_SCOPE)
|
|
54
|
+
@ast = ast
|
|
55
|
+
@line_offset = line_offset
|
|
56
|
+
@scope = scope
|
|
57
|
+
@host = host
|
|
58
|
+
@source_name = source_name
|
|
59
|
+
@exhaustive = exhaustive
|
|
60
|
+
@lifted = [] # generated definitions (strings), in order
|
|
61
|
+
@counter = 0
|
|
62
|
+
@top = {} # name => { ivar:, arity: } for top-level functions
|
|
63
|
+
@values = {} # name => method name for top-level values
|
|
64
|
+
@con_arity = {} # constructor name => arity
|
|
65
|
+
@local_cons = [] # constructors declared by this module
|
|
66
|
+
@local_types = [] # type names declared by this module
|
|
67
|
+
@lines = {} # function name => source line of its first equation
|
|
68
|
+
@lifted_lines = {} # the same for where/let-bound functions
|
|
69
|
+
# functions and values the host already has (imports, or an earlier
|
|
70
|
+
# `haskell` call on the same module) are callable like local ones
|
|
71
|
+
imports = host.respond_to?(:haskell_imports) ? host.haskell_imports : {}
|
|
72
|
+
if host.respond_to?(:haskell_functions)
|
|
73
|
+
host.haskell_functions.each do |n, f|
|
|
74
|
+
@top[n] = { ivar: "@__haskell_functions__[#{rb_str(n)}]", arity: f.arity }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
imports.each { |n, kind| @values[n] = n if kind == :value && !@top.key?(n) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Generate the Ruby source for the module.
|
|
81
|
+
def generate
|
|
82
|
+
decls = @ast.fetch("decls")
|
|
83
|
+
out = []
|
|
84
|
+
# data declarations first: patterns need the constructors
|
|
85
|
+
decls.each do |d|
|
|
86
|
+
next unless d["kind"] == "data"
|
|
87
|
+
|
|
88
|
+
out << data_decl(d)
|
|
89
|
+
end
|
|
90
|
+
decls.each do |d|
|
|
91
|
+
case d["kind"]
|
|
92
|
+
when "fun"
|
|
93
|
+
@top[d["name"]] = { ivar: ivar_for(d["name"]), arity: d["arity"] }
|
|
94
|
+
@lines[d["name"]] ||= ln(d["equations"].first&.fetch("line", nil))
|
|
95
|
+
when "bind"
|
|
96
|
+
pat = d["pat"]
|
|
97
|
+
unless pat["vars"].size == 1 && pat["text"] == mangle(pat["vars"][0])
|
|
98
|
+
raise DefinitionError, "#{@source_name}:#{ln(d['line'])}: only simple names can be bound at top level (got #{pat['text']})"
|
|
99
|
+
end
|
|
100
|
+
@values[pat["vars"][0]] = "hs_value_#{rb_ident(pat['vars'][0])}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
body = []
|
|
104
|
+
decls.each do |d|
|
|
105
|
+
case d["kind"]
|
|
106
|
+
when "fun" then body << function(d)
|
|
107
|
+
when "bind" then body << value(d)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
table = @top.map { |n, info| "#{rb_str(n)} => #{info[:ivar]}" }.join(", ")
|
|
111
|
+
body << "(@__haskell_functions__ ||= {}).merge!({ #{table} })"
|
|
112
|
+
values = @values.map { |n, m| "#{rb_str(n)} => #{m.to_sym.inspect}" }.join(", ")
|
|
113
|
+
body << "(@__haskell_values__ ||= {}).merge!({ #{values} })"
|
|
114
|
+
body << "(@__haskell_types__ ||= []).concat(#{@local_types.inspect}).uniq!"
|
|
115
|
+
body << "@__haskell_exports__ = #{@ast['exports'].inspect}" if @ast["exports"]
|
|
116
|
+
(out + @lifted + body).join("\n")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# A Haskell source line as a line of the enclosing file.
|
|
122
|
+
def ln(line)
|
|
123
|
+
line && line + @line_offset
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def mangle(name)
|
|
127
|
+
"hs_" + name.gsub("'", "_q")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def fresh(prefix)
|
|
131
|
+
@counter += 1
|
|
132
|
+
"#{prefix}_#{@counter}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def ivar_for(name)
|
|
136
|
+
"@hs_fn_#{rb_ident(name)}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
SYMBOL_WORDS = {
|
|
140
|
+
":" => "colon", "+" => "plus", "-" => "minus", "*" => "star", "/" => "slash", "<" => "lt", ">" => "gt",
|
|
141
|
+
"=" => "eq", "!" => "bang", "@" => "at", "#" => "hash", "$" => "dollar", "%" => "percent", "&" => "amp",
|
|
142
|
+
"^" => "caret", "|" => "bar", "~" => "tilde", "?" => "query", "." => "dot", "\\" => "backslash"
|
|
143
|
+
}.freeze
|
|
144
|
+
|
|
145
|
+
# A Haskell function or operator name as a Ruby identifier fragment.
|
|
146
|
+
def rb_ident(name)
|
|
147
|
+
return name.gsub("'", "_q") if name.match?(/\A[A-Za-z_]/)
|
|
148
|
+
|
|
149
|
+
"op_" + name.chars.map { |c| SYMBOL_WORDS.fetch(c) { "u#{c.ord}" } }.join("_")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def symbolic?(name)
|
|
153
|
+
!name.match?(/\A[A-Za-z_]/)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# A user-defined function or operator visible from `scope`.
|
|
157
|
+
def user_function?(name, scope)
|
|
158
|
+
!scope.lookup_fun(name).nil? || @top.key?(name) || @values.key?(name)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def snake(name)
|
|
162
|
+
name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.gsub("'", "_prime")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def rb_str(s)
|
|
166
|
+
s.inspect
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# ------------------------------------------------------------ declarations
|
|
170
|
+
|
|
171
|
+
def data_decl(d)
|
|
172
|
+
@local_types << d["name"]
|
|
173
|
+
selectors = []
|
|
174
|
+
cons = d["cons"].map do |c|
|
|
175
|
+
@con_arity[c["name"]] = c["arity"]
|
|
176
|
+
@local_cons << c["name"]
|
|
177
|
+
selectors |= c["fields"] if c["fields"]
|
|
178
|
+
shown = c["name"].start_with?(":") ? "(#{c['name']})" : c["name"]
|
|
179
|
+
if c["fields"]
|
|
180
|
+
"#{shown} { #{c['fields'].map { |f| "#{f} :: T" }.join(', ')} }"
|
|
181
|
+
else
|
|
182
|
+
([shown] + Array.new(c["arity"], "t")).join(" ")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
deriving = d["deriving"].empty? ? "" : " deriving (#{d['deriving'].join(', ')})"
|
|
186
|
+
decl = "#{([d['name']] + d['tyvars']).join(' ')} = #{cons.join(' | ')}#{deriving}"
|
|
187
|
+
lines = ["include HaskellMatch.data(#{rb_str(decl)}, under: self, scope: haskell_scope)"]
|
|
188
|
+
# record fields are selector functions, as in Haskell
|
|
189
|
+
selectors.each do |f|
|
|
190
|
+
lines << "define_singleton_method(#{f.to_sym.inspect}) { |v| v.public_send(#{f.to_sym.inspect}) } unless singleton_class.method_defined?(#{f.to_sym.inspect})"
|
|
191
|
+
end
|
|
192
|
+
lines.join("\n")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def con_arity(name)
|
|
196
|
+
return @con_arity[name] if @con_arity.key?(name)
|
|
197
|
+
return 0 if %w[True False []].include?(name)
|
|
198
|
+
|
|
199
|
+
info = Native.constructor_info(name, @scope)
|
|
200
|
+
@con_arity[name] = info && info[1]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def function(d)
|
|
204
|
+
name = d["name"]
|
|
205
|
+
ivar = @top[name][:ivar]
|
|
206
|
+
scope = Scope.new({}, {}, nil)
|
|
207
|
+
clauses = equations(d["equations"], scope, d["arity"], name)
|
|
208
|
+
<<~RUBY
|
|
209
|
+
#{ivar} = HaskellMatch.fn(#{rb_str(name)}, exhaustive: #{@exhaustive.inspect}, scope: haskell_scope) do |m|
|
|
210
|
+
#{clauses}
|
|
211
|
+
end
|
|
212
|
+
define_singleton_method(#{name.to_sym.inspect}) { |*a| HaskellMatch::Haskell.apply(#{ivar}, #{d['arity']}, a) }
|
|
213
|
+
#{snake(name) == name ? '' : "singleton_class.alias_method(#{snake(name).to_sym.inspect}, #{name.to_sym.inspect})"}
|
|
214
|
+
RUBY
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def value(d)
|
|
218
|
+
name = d["pat"]["vars"][0]
|
|
219
|
+
meth = @values[name]
|
|
220
|
+
scope = Scope.new({}, {}, nil)
|
|
221
|
+
rhs = rhs_code(d["rhs"], d["where"], scope, tail: false, line: d["line"])
|
|
222
|
+
<<~RUBY
|
|
223
|
+
define_singleton_method(#{meth.to_sym.inspect}) { @#{meth} ||= begin; #{rhs}; end }
|
|
224
|
+
define_singleton_method(#{name.to_sym.inspect}) { |*a| a.inject(#{meth}) { |f, x| f.(x) } }
|
|
225
|
+
#{snake(name) == name ? '' : "singleton_class.alias_method(#{snake(name).to_sym.inspect}, #{name.to_sym.inspect})"}
|
|
226
|
+
RUBY
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Clauses for a list of equations (all of the same arity).
|
|
230
|
+
def equations(eqs, scope, arity, fname, prefix_params: [])
|
|
231
|
+
eqs.map do |eq|
|
|
232
|
+
pats = eq["pats"]
|
|
233
|
+
vars = pats.flat_map { |p| p["vars"] }
|
|
234
|
+
dup = vars.detect { |v| vars.count(v) > 1 }
|
|
235
|
+
raise DuplicateVariableError, "#{@source_name}:#{ln(eq['line'])}: conflicting definitions for '#{dup}' in '#{fname}'" if dup
|
|
236
|
+
|
|
237
|
+
inner = Scope.new({}, {}, scope)
|
|
238
|
+
(prefix_params + vars).each { |v| inner.vars[v] = mangle(v) }
|
|
239
|
+
pat_texts = prefix_params.map { |v| rb_str(mangle(v)) } + pats.map { |p| rb_str(p["text"]) }
|
|
240
|
+
params = (prefix_params + vars).map { |v| mangle(v) }
|
|
241
|
+
param_list = params.empty? ? "" : "|#{params.join(', ')}|"
|
|
242
|
+
clauses_for_rhs(eq["rhs"], eq["where"], inner, pat_texts, param_list, ln(eq["line"]))
|
|
243
|
+
end.join("\n")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# One or more `m.on(...)` lines for an equation's right-hand side.
|
|
247
|
+
def clauses_for_rhs(rhs, wheres, scope, pat_texts, param_list, line)
|
|
248
|
+
where_code = where_bindings(wheres, scope)
|
|
249
|
+
loc = line ? ", location: [#{rb_str(@source_name)}, #{line}]" : ""
|
|
250
|
+
if rhs.key?("body")
|
|
251
|
+
body = expr(rhs["body"], scope, tail: true)
|
|
252
|
+
" m.on(#{pat_texts.join(', ')}#{loc}) { #{param_list} #{where_code}#{body} }"
|
|
253
|
+
else
|
|
254
|
+
rhs["guards"].map do |(quals, e)|
|
|
255
|
+
if otherwise?(quals)
|
|
256
|
+
body = expr(e, scope, tail: true)
|
|
257
|
+
" m.on(#{pat_texts.join(', ')}#{loc}) { #{param_list} #{where_code}#{body} }"
|
|
258
|
+
elsif simple_guards?(quals)
|
|
259
|
+
body = expr(e, scope, tail: true)
|
|
260
|
+
guard = quals.map { |q| "(#{expr(q[1], scope, tail: false)})" }.join(" && ")
|
|
261
|
+
" m.on(#{pat_texts.join(', ')}, guard: ->(#{param_list.delete('|')}) { #{where_code}#{guard} }#{loc}) { #{param_list} #{where_code}#{body} }"
|
|
262
|
+
else
|
|
263
|
+
# pattern guards / let guards: the guard lambda runs the
|
|
264
|
+
# qualifiers for their truth; the body runs them again for
|
|
265
|
+
# their bindings (Haskell code is pure, so this is sound).
|
|
266
|
+
gscope = Scope.new({}, {}, scope)
|
|
267
|
+
guard = qual_chain(quals, gscope) { "true" }
|
|
268
|
+
bscope = Scope.new({}, {}, scope)
|
|
269
|
+
body = qual_chain(quals, bscope) { expr(e, bscope, tail: true) }
|
|
270
|
+
" m.on(#{pat_texts.join(', ')}, guard: ->(#{param_list.delete('|')}) { #{where_code}#{guard} }#{loc}) { #{param_list} #{where_code}#{body} }"
|
|
271
|
+
end
|
|
272
|
+
end.join("\n")
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# `| otherwise` / `| True`: an unconditional alternative.
|
|
277
|
+
def otherwise?(quals)
|
|
278
|
+
return false unless quals.size == 1 && quals[0][0] == "guard"
|
|
279
|
+
|
|
280
|
+
g = quals[0][1]
|
|
281
|
+
(g[0] == "var" && g[1] == "otherwise") || (g[0] == "con" && g[1] == "True")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def simple_guards?(quals)
|
|
285
|
+
quals.all? { |q| q[0] == "guard" }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Qualifiers (boolean guards, `pat <- e` generators binding variables,
|
|
289
|
+
# `let` bindings) as nested Ruby expressions ending in `final`; a
|
|
290
|
+
# failing guard or pattern yields `false`.
|
|
291
|
+
def qual_chain(quals, scope, &final)
|
|
292
|
+
return final.call if quals.empty?
|
|
293
|
+
|
|
294
|
+
q, *rest = quals
|
|
295
|
+
case q[0]
|
|
296
|
+
when "guard"
|
|
297
|
+
"((#{expr(q[1], scope, tail: false)}) ? (#{qual_chain(rest, scope, &final)}) : false)"
|
|
298
|
+
when "gen"
|
|
299
|
+
src = expr(q[2], scope, tail: false)
|
|
300
|
+
pat = q[1]
|
|
301
|
+
if pat["vars"].size == 1 && pat["text"] == mangle(pat["vars"][0])
|
|
302
|
+
name = fresh(mangle(pat["vars"][0]))
|
|
303
|
+
scope.vars[pat["vars"][0]] = name
|
|
304
|
+
"(#{name} = #{src}; #{qual_chain(rest, scope, &final)})"
|
|
305
|
+
else
|
|
306
|
+
matcher = "@hs_pat_#{fresh('guard')}"
|
|
307
|
+
@lifted << "#{matcher} = HaskellMatch.pattern(#{rb_str(pat['text'])}, scope: haskell_scope)"
|
|
308
|
+
binds = fresh("hs_b")
|
|
309
|
+
pat["vars"].each { |v| scope.vars[v] = "#{binds}[#{mangle(v).to_sym.inspect}]" }
|
|
310
|
+
"((#{binds} = #{matcher}.match(#{src})) ? (#{qual_chain(rest, scope, &final)}) : false)"
|
|
311
|
+
end
|
|
312
|
+
when "let"
|
|
313
|
+
"(#{where_bindings(q[1], scope)}#{qual_chain(rest, scope, &final)})"
|
|
314
|
+
else
|
|
315
|
+
raise DefinitionError, "#{@source_name}: unknown qualifier #{q[0]}"
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Guarded alternatives as one expression: `[quals, e]` arms tried in
|
|
320
|
+
# order, `fallback` when none applies.
|
|
321
|
+
def guard_chain(arms, scope, tail, fallback)
|
|
322
|
+
chain = arms.map do |(quals, e)|
|
|
323
|
+
if otherwise?(quals)
|
|
324
|
+
"true ? (#{expr(e, scope, tail: tail)}) : "
|
|
325
|
+
elsif simple_guards?(quals)
|
|
326
|
+
conds = quals.map { |q| "(#{expr(q[1], scope, tail: false)})" }.join(" && ")
|
|
327
|
+
"(#{conds}) ? (#{expr(e, scope, tail: tail)}) : "
|
|
328
|
+
else
|
|
329
|
+
s = Scope.new({}, {}, scope)
|
|
330
|
+
tmp = fresh("hs_g")
|
|
331
|
+
"((#{tmp} = #{qual_chain(quals, s) { "[#{expr(e, s, tail: tail)}]" }})) ? (#{tmp}[0]) : "
|
|
332
|
+
end
|
|
333
|
+
end.join
|
|
334
|
+
"(#{chain}#{fallback})"
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Right-hand side as a single Ruby expression (for values and lifted
|
|
338
|
+
# case alternatives without their own clauses).
|
|
339
|
+
def rhs_code(rhs, wheres, scope, tail:, line:)
|
|
340
|
+
where_code = where_bindings(wheres, scope)
|
|
341
|
+
if rhs.key?("body")
|
|
342
|
+
"#{where_code}#{expr(rhs['body'], scope, tail: tail)}"
|
|
343
|
+
else
|
|
344
|
+
fallback = "raise(HaskellMatch::Prelude::HaskellError, #{rb_str("#{@source_name}:#{line}: non-exhaustive guards")})"
|
|
345
|
+
"#{where_code}#{guard_chain(rhs['guards'], scope, tail, fallback)}"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# `where` / `let` declarations: functions are lifted, values become
|
|
350
|
+
# local assignments (strict, in source order). Returns Ruby code to
|
|
351
|
+
# prepend to the body, and extends `scope`.
|
|
352
|
+
def where_bindings(decls, scope)
|
|
353
|
+
return "" if decls.nil? || decls.empty?
|
|
354
|
+
|
|
355
|
+
# register functions first so values and functions can refer to them
|
|
356
|
+
funs = decls.select { |d| d["kind"] == "fun" }
|
|
357
|
+
values = decls.select { |d| d["kind"] == "bind" }
|
|
358
|
+
decls.each do |d|
|
|
359
|
+
next if %w[fun bind sig].include?(d["kind"])
|
|
360
|
+
|
|
361
|
+
raise DefinitionError, "#{@source_name}:#{ln(d['line'])}: data declarations are only allowed at top level"
|
|
362
|
+
end
|
|
363
|
+
lift_functions(funs, scope)
|
|
364
|
+
code = +""
|
|
365
|
+
order_values(values).each do |d|
|
|
366
|
+
pat = d["pat"]
|
|
367
|
+
if pat["vars"].size == 1 && pat["text"] == mangle(pat["vars"][0])
|
|
368
|
+
name = pat["vars"][0]
|
|
369
|
+
local = fresh(mangle(name))
|
|
370
|
+
rhs = rhs_code(d["rhs"], d["where"], scope, tail: false, line: d["line"])
|
|
371
|
+
scope.vars[name] = local
|
|
372
|
+
code << "#{local} = (#{rhs}); "
|
|
373
|
+
else
|
|
374
|
+
# pattern binding: destructure through a single-clause match
|
|
375
|
+
tmp = fresh("hs_pb")
|
|
376
|
+
rhs = rhs_code(d["rhs"], d["where"], scope, tail: false, line: d["line"])
|
|
377
|
+
code << "#{tmp} = HaskellMatch.pattern(#{rb_str(pat['text'])}, scope: haskell_scope).match!(#{rhs}); "
|
|
378
|
+
pat["vars"].each do |v|
|
|
379
|
+
local = fresh(mangle(v))
|
|
380
|
+
scope.vars[v] = local
|
|
381
|
+
code << "#{local} = #{tmp}[#{mangle(v).to_sym.inspect}]; "
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
code
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Haskell's `where`/`let` values may refer to one another in any order
|
|
389
|
+
# (they are lazy); Ruby locals are strict, so emit each value after the
|
|
390
|
+
# sibling values it mentions. A genuine cycle keeps declaration order.
|
|
391
|
+
def order_values(values)
|
|
392
|
+
names = values.flat_map { |d| d["pat"]["vars"] }
|
|
393
|
+
deps = values.map do |d|
|
|
394
|
+
refs = []
|
|
395
|
+
collect_refs(d["rhs"], d["where"], [], refs)
|
|
396
|
+
(refs & names) - d["pat"]["vars"]
|
|
397
|
+
end
|
|
398
|
+
done = []
|
|
399
|
+
out = []
|
|
400
|
+
remaining = values.each_index.to_a
|
|
401
|
+
until remaining.empty?
|
|
402
|
+
i = remaining.find { |j| (deps[j] - done).empty? } || remaining.first
|
|
403
|
+
remaining.delete(i)
|
|
404
|
+
out << values[i]
|
|
405
|
+
done.concat(values[i]["pat"]["vars"])
|
|
406
|
+
end
|
|
407
|
+
out
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Lambda-lift a group of local functions (mutually recursive allowed).
|
|
411
|
+
def lift_functions(funs, scope)
|
|
412
|
+
return if funs.empty?
|
|
413
|
+
|
|
414
|
+
names = funs.map { |d| d["name"] }
|
|
415
|
+
# free variables of each function, excluding its own params and
|
|
416
|
+
# sibling names; then close over siblings' free variables
|
|
417
|
+
free = {}
|
|
418
|
+
funs.each do |d|
|
|
419
|
+
fv = []
|
|
420
|
+
d["equations"].each do |eq|
|
|
421
|
+
bound = eq["pats"].flat_map { |p| p["vars"] }
|
|
422
|
+
collect_free(eq["rhs"], eq["where"], bound + names, scope, fv)
|
|
423
|
+
end
|
|
424
|
+
free[d["name"]] = fv.uniq
|
|
425
|
+
end
|
|
426
|
+
loop do
|
|
427
|
+
changed = false
|
|
428
|
+
funs.each do |d|
|
|
429
|
+
refs = []
|
|
430
|
+
d["equations"].each do |eq|
|
|
431
|
+
bound = eq["pats"].flat_map { |p| p["vars"] }
|
|
432
|
+
collect_refs(eq["rhs"], eq["where"], bound, refs)
|
|
433
|
+
end
|
|
434
|
+
refs.each do |r|
|
|
435
|
+
next unless names.include?(r) && r != d["name"]
|
|
436
|
+
|
|
437
|
+
merged = (free[d["name"]] + free[r]).uniq
|
|
438
|
+
if merged != free[d["name"]]
|
|
439
|
+
free[d["name"]] = merged
|
|
440
|
+
changed = true
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
break unless changed
|
|
445
|
+
end
|
|
446
|
+
funs.each do |d|
|
|
447
|
+
ivar = "@hs_lift_#{fresh(rb_ident(d['name']))}"
|
|
448
|
+
scope.funs[d["name"]] = { ivar: ivar, arity: d["arity"], free: free[d["name"]] }
|
|
449
|
+
end
|
|
450
|
+
funs.each do |d|
|
|
451
|
+
info = scope.funs[d["name"]]
|
|
452
|
+
@lifted_lines[d["name"]] ||= ln(d["equations"].first&.fetch("line", nil))
|
|
453
|
+
inner = Scope.new({}, {}, scope)
|
|
454
|
+
clauses = equations(d["equations"], inner, d["arity"], d["name"], prefix_params: info[:free])
|
|
455
|
+
@lifted << <<~RUBY
|
|
456
|
+
#{info[:ivar]} = HaskellMatch.fn(#{rb_str(d['name'])}, exhaustive: #{@exhaustive.inspect}, scope: haskell_scope) do |m|
|
|
457
|
+
#{clauses}
|
|
458
|
+
end
|
|
459
|
+
RUBY
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# ------------------------------------------------------- free variables
|
|
464
|
+
|
|
465
|
+
# Haskell variables referenced in an rhs that are bound in `scope` as
|
|
466
|
+
# local vars/funs (i.e. would need to be passed to a lifted function).
|
|
467
|
+
def collect_free(rhs, wheres, bound, scope, out)
|
|
468
|
+
where_bound = (wheres || []).flat_map { |d| d["kind"] == "fun" ? [d["name"]] : d["kind"] == "bind" ? d["pat"]["vars"] : [] }
|
|
469
|
+
if rhs.key?("body")
|
|
470
|
+
free_in(rhs["body"], bound + where_bound, scope, out)
|
|
471
|
+
else
|
|
472
|
+
rhs["guards"].each { |(quals, e)| free_in_quals(quals, e, bound + where_bound, scope, out) }
|
|
473
|
+
end
|
|
474
|
+
(wheres || []).each do |d|
|
|
475
|
+
case d["kind"]
|
|
476
|
+
when "fun"
|
|
477
|
+
d["equations"].each do |eq|
|
|
478
|
+
collect_free(eq["rhs"], eq["where"], bound + where_bound + eq["pats"].flat_map { |p| p["vars"] }, scope, out)
|
|
479
|
+
end
|
|
480
|
+
when "bind"
|
|
481
|
+
collect_free(d["rhs"], d["where"], bound + where_bound, scope, out)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Free variables of qualifiers followed by `body`; generator patterns
|
|
487
|
+
# and let bindings bind names for what follows them.
|
|
488
|
+
def free_in_quals(quals, body, bound, scope, out)
|
|
489
|
+
b = bound.dup
|
|
490
|
+
quals.each do |q|
|
|
491
|
+
case q[0]
|
|
492
|
+
when "guard" then free_in(q[1], b, scope, out)
|
|
493
|
+
when "gen"
|
|
494
|
+
free_in(q[2], b, scope, out)
|
|
495
|
+
b += q[1]["vars"]
|
|
496
|
+
when "let"
|
|
497
|
+
names = q[1].flat_map { |d| d["kind"] == "fun" ? [d["name"]] : d["kind"] == "bind" ? d["pat"]["vars"] : [] }
|
|
498
|
+
q[1].each do |d|
|
|
499
|
+
case d["kind"]
|
|
500
|
+
when "fun"
|
|
501
|
+
d["equations"].each do |eq|
|
|
502
|
+
collect_free(eq["rhs"], eq["where"], b + names + eq["pats"].flat_map { |p| p["vars"] }, scope, out)
|
|
503
|
+
end
|
|
504
|
+
when "bind" then collect_free(d["rhs"], d["where"], b + names, scope, out)
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
b += names
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
free_in(body, b, scope, out)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def free_in(e, bound, scope, out)
|
|
514
|
+
case e[0]
|
|
515
|
+
when "var"
|
|
516
|
+
name = e[1]
|
|
517
|
+
return if bound.include?(name)
|
|
518
|
+
|
|
519
|
+
if scope.lookup_var(name)
|
|
520
|
+
out << name unless out.include?(name)
|
|
521
|
+
elsif (f = scope.lookup_fun(name))
|
|
522
|
+
f[:free].each { |v| out << v unless out.include?(v) || bound.include?(v) }
|
|
523
|
+
end
|
|
524
|
+
when "con", "lit" then nil
|
|
525
|
+
when "opfun"
|
|
526
|
+
free_in(["var", e[1]], bound, scope, out) if user_function?(e[1], scope) || scope.lookup_var(e[1])
|
|
527
|
+
when "multiif"
|
|
528
|
+
e[1].each { |(quals, body)| free_in_quals(quals, body, bound, scope, out) }
|
|
529
|
+
when "reccon" then e[2].each { |(_, v)| free_in(v, bound, scope, out) }
|
|
530
|
+
when "recupd"
|
|
531
|
+
free_in(e[1], bound, scope, out)
|
|
532
|
+
e[2].each { |(_, v)| free_in(v, bound, scope, out) }
|
|
533
|
+
when "app"
|
|
534
|
+
free_in(e[1], bound, scope, out)
|
|
535
|
+
e[2].each { |a| free_in(a, bound, scope, out) }
|
|
536
|
+
when "op"
|
|
537
|
+
free_in(["var", e[1]], bound, scope, out) if user_function?(e[1], scope)
|
|
538
|
+
free_in(e[2], bound, scope, out)
|
|
539
|
+
free_in(e[3], bound, scope, out)
|
|
540
|
+
when "neg" then free_in(e[1], bound, scope, out)
|
|
541
|
+
when "if" then e[1..3].each { |x| free_in(x, bound, scope, out) }
|
|
542
|
+
when "case"
|
|
543
|
+
free_in(e[1], bound, scope, out)
|
|
544
|
+
e[2].each do |alt|
|
|
545
|
+
collect_free(alt["rhs"], alt["where"], bound + alt["pat"]["vars"], scope, out)
|
|
546
|
+
end
|
|
547
|
+
when "let"
|
|
548
|
+
inner = bound + e[1].flat_map { |d| d["kind"] == "fun" ? [d["name"]] : d["kind"] == "bind" ? d["pat"]["vars"] : [] }
|
|
549
|
+
e[1].each do |d|
|
|
550
|
+
case d["kind"]
|
|
551
|
+
when "fun" then d["equations"].each { |eq| collect_free(eq["rhs"], eq["where"], inner + eq["pats"].flat_map { |p| p["vars"] }, scope, out) }
|
|
552
|
+
when "bind" then collect_free(d["rhs"], d["where"], inner, scope, out)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
free_in(e[2], inner, scope, out)
|
|
556
|
+
when "lam"
|
|
557
|
+
free_in(e[2], bound + e[1].flat_map { |p| p["vars"] }, scope, out)
|
|
558
|
+
when "list", "tuple" then e[1].each { |x| free_in(x, bound, scope, out) }
|
|
559
|
+
when "range"
|
|
560
|
+
free_in(e[1], bound, scope, out)
|
|
561
|
+
free_in(e[2], bound, scope, out) if e[2]
|
|
562
|
+
free_in(e[3], bound, scope, out) if e[3]
|
|
563
|
+
when "comp"
|
|
564
|
+
inner = bound.dup
|
|
565
|
+
e[2].each do |q|
|
|
566
|
+
case q[0]
|
|
567
|
+
when "gen"
|
|
568
|
+
free_in(q[2], inner, scope, out)
|
|
569
|
+
inner += q[1]["vars"]
|
|
570
|
+
when "guard" then free_in(q[1], inner, scope, out)
|
|
571
|
+
when "let"
|
|
572
|
+
inner += q[1].flat_map { |d| d["kind"] == "bind" ? d["pat"]["vars"] : [d["name"]] }
|
|
573
|
+
q[1].each { |d| collect_free(d["rhs"], d["where"], inner, scope, out) if d["kind"] == "bind" }
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
free_in(e[1], inner, scope, out)
|
|
577
|
+
when "section_l", "section_r" then free_in(e[2], bound, scope, out)
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# All variable names referenced (for sibling closure), ignoring scope.
|
|
582
|
+
def collect_refs(rhs, wheres, bound, out)
|
|
583
|
+
if rhs.key?("body")
|
|
584
|
+
refs_in(rhs["body"], out)
|
|
585
|
+
else
|
|
586
|
+
rhs["guards"].each { |(quals, e)| refs_in_quals(quals, e, bound, out) }
|
|
587
|
+
end
|
|
588
|
+
(wheres || []).each do |d|
|
|
589
|
+
case d["kind"]
|
|
590
|
+
when "fun" then d["equations"].each { |eq| collect_refs(eq["rhs"], eq["where"], bound, out) }
|
|
591
|
+
when "bind" then collect_refs(d["rhs"], d["where"], bound, out)
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def refs_in_quals(quals, body, bound, out)
|
|
597
|
+
quals.each do |q|
|
|
598
|
+
case q[0]
|
|
599
|
+
when "guard" then refs_in(q[1], out)
|
|
600
|
+
when "gen" then refs_in(q[2], out)
|
|
601
|
+
when "let" then collect_refs({ "body" => ["lit", ["int", "0"]] }, q[1], bound, out)
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
refs_in(body, out)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def refs_in(e, out)
|
|
608
|
+
case e[0]
|
|
609
|
+
when "var" then out << e[1]
|
|
610
|
+
when "op"
|
|
611
|
+
out << e[1]
|
|
612
|
+
[e[2], e[3]].each { |x| refs_in(x, out) }
|
|
613
|
+
when "opfun" then out << e[1]
|
|
614
|
+
when "multiif" then e[1].each { |(quals, body)| refs_in_quals(quals, body, [], out) }
|
|
615
|
+
when "reccon" then e[2].each { |(_, v)| refs_in(v, out) }
|
|
616
|
+
when "recupd"
|
|
617
|
+
refs_in(e[1], out)
|
|
618
|
+
e[2].each { |(_, v)| refs_in(v, out) }
|
|
619
|
+
when "app" then (e[2] + [e[1]]).each { |x| refs_in(x, out) }
|
|
620
|
+
when "neg", "section_l", "section_r" then refs_in(e[-1], out)
|
|
621
|
+
when "if", "list", "tuple" then e[1..].flatten(1).each { |x| refs_in(x, out) if x.is_a?(Array) && x[0].is_a?(String) }
|
|
622
|
+
when "case"
|
|
623
|
+
refs_in(e[1], out)
|
|
624
|
+
e[2].each { |alt| collect_refs(alt["rhs"], alt["where"], [], out) }
|
|
625
|
+
when "let"
|
|
626
|
+
e[1].each { |d| collect_refs(d["rhs"], d["where"], [], out) if d.key?("rhs") }
|
|
627
|
+
e[1].each { |d| d["equations"].each { |eq| collect_refs(eq["rhs"], eq["where"], [], out) } if d["kind"] == "fun" }
|
|
628
|
+
refs_in(e[2], out)
|
|
629
|
+
when "lam" then refs_in(e[2], out)
|
|
630
|
+
when "range" then e[1..3].compact.each { |x| refs_in(x, out) }
|
|
631
|
+
when "comp"
|
|
632
|
+
refs_in(e[1], out)
|
|
633
|
+
e[2].each { |q| q[1..].each { |x| refs_in(x, out) if x.is_a?(Array) && x[0].is_a?(String) } }
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# ------------------------------------------------------------ expressions
|
|
638
|
+
|
|
639
|
+
# `tail`: this expression's value is the enclosing clause body's value.
|
|
640
|
+
def expr(e, scope, tail:)
|
|
641
|
+
case e[0]
|
|
642
|
+
when "var" then var(e[1], scope)
|
|
643
|
+
when "con" then con_value(e[1])
|
|
644
|
+
when "lit" then literal(e[1])
|
|
645
|
+
when "app" then application(e[1], e[2], scope, tail)
|
|
646
|
+
when "op" then binop(e[1], e[2], e[3], scope, tail)
|
|
647
|
+
when "neg" then "(-(#{expr(e[1], scope, tail: false)}))"
|
|
648
|
+
when "if"
|
|
649
|
+
"((#{expr(e[1], scope, tail: false)}) ? (#{expr(e[2], scope, tail: tail)}) : (#{expr(e[3], scope, tail: tail)}))"
|
|
650
|
+
when "case" then case_expr(e[1], e[2], scope, tail)
|
|
651
|
+
when "let" then let_expr(e[1], e[2], scope, tail)
|
|
652
|
+
when "lam" then lambda_expr(e[1], e[2], scope)
|
|
653
|
+
when "list" then "[#{e[1].map { |x| expr(x, scope, tail: false) }.join(', ')}]"
|
|
654
|
+
when "tuple" then "[#{e[1].map { |x| expr(x, scope, tail: false) }.join(', ')}]"
|
|
655
|
+
when "range"
|
|
656
|
+
args = [expr(e[1], scope, tail: false)]
|
|
657
|
+
args << (e[2] ? expr(e[2], scope, tail: false) : "nil")
|
|
658
|
+
args << expr(e[3], scope, tail: false) if e[3]
|
|
659
|
+
"HaskellMatch::Prelude.range(#{args.join(', ')})"
|
|
660
|
+
when "comp" then comprehension(e[1], e[2], scope)
|
|
661
|
+
when "multiif"
|
|
662
|
+
fallback = "raise(HaskellMatch::Prelude::HaskellError, #{rb_str("#{@source_name}: non-exhaustive guards in multi-way if")})"
|
|
663
|
+
guard_chain(e[1], scope, tail, fallback)
|
|
664
|
+
when "reccon"
|
|
665
|
+
name = e[1]
|
|
666
|
+
raise UnknownConstructorError, "#{@source_name}: not in scope: data constructor '#{name}'" if con_arity(name).nil?
|
|
667
|
+
|
|
668
|
+
fields = e[2].map { |(f, v)| "#{f.to_sym.inspect} => #{expr(v, scope, tail: false)}" }
|
|
669
|
+
"#{con_path(name)}.new(#{fields.join(', ')})"
|
|
670
|
+
when "recupd"
|
|
671
|
+
fields = e[2].map { |(f, v)| "#{f.to_sym.inspect} => #{expr(v, scope, tail: false)}" }
|
|
672
|
+
"#{expr(e[1], scope, tail: false)}.with(#{fields.join(', ')})"
|
|
673
|
+
when "section_l"
|
|
674
|
+
# (e op) = \y -> e op y
|
|
675
|
+
y = fresh("hs_sec")
|
|
676
|
+
inner = Scope.new({ "__sec" => y }, {}, scope)
|
|
677
|
+
"->(#{y}) { #{binop(e[1], e[2], ['var', '__sec'], inner, false)} }"
|
|
678
|
+
when "section_r"
|
|
679
|
+
x = fresh("hs_sec")
|
|
680
|
+
inner = Scope.new({ "__sec" => x }, {}, scope)
|
|
681
|
+
"->(#{x}) { #{binop(e[1], ['var', '__sec'], e[2], inner, false)} }"
|
|
682
|
+
when "opfun" then op_value(e[1], scope)
|
|
683
|
+
else raise DefinitionError, "#{@source_name}: unknown expression node #{e[0]}"
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def literal(l)
|
|
688
|
+
case l[0]
|
|
689
|
+
when "int" then l[1]
|
|
690
|
+
when "float" then l[1]
|
|
691
|
+
when "char", "str" then rb_str(l[1])
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# A variable used as a value.
|
|
696
|
+
def var(name, scope)
|
|
697
|
+
if (local = scope.lookup_var(name))
|
|
698
|
+
return local
|
|
699
|
+
end
|
|
700
|
+
if (f = scope.lookup_fun(name))
|
|
701
|
+
return curried_call(f[:ivar], f[:free].map { |v| scope.lookup_var(v) || mangle(v) }, f[:arity] + f[:free].size)
|
|
702
|
+
end
|
|
703
|
+
if @top.key?(name)
|
|
704
|
+
info = @top[name]
|
|
705
|
+
return info[:arity] == 1 ? "#{info[:ivar]}.to_proc" : "#{info[:ivar]}.curried"
|
|
706
|
+
end
|
|
707
|
+
return @values[name] if @values.key?(name)
|
|
708
|
+
if Prelude.known?(name.to_sym)
|
|
709
|
+
return Prelude.arity(name.to_sym).zero? ? "HaskellMatch::Prelude.#{name}" : "HaskellMatch::Prelude.curried(#{name.to_sym.inspect})"
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# foreign: a Ruby method of the host module
|
|
713
|
+
"method(#{name.to_sym.inspect}).to_proc"
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def curried_call(ivar, pre_args, total_arity)
|
|
717
|
+
base = total_arity == 1 ? "#{ivar}.to_proc" : "#{ivar}.curried"
|
|
718
|
+
pre_args.inject(base) { |acc, a| "#{acc}.(#{a})" }
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def con_value(name)
|
|
722
|
+
arity = con_arity(name)
|
|
723
|
+
raise UnknownConstructorError, "#{@source_name}: not in scope: data constructor '#{name}'" if arity.nil?
|
|
724
|
+
|
|
725
|
+
path = con_path(name)
|
|
726
|
+
return path if arity.zero?
|
|
727
|
+
|
|
728
|
+
arity == 1 ? "#{path}.to_proc" : "#{path}.method(:new).to_proc.curry(#{arity})"
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
# Constructors declared by this module are constants of the host module;
|
|
732
|
+
# anything else (Prelude types, `HaskellMatch.data` declared elsewhere)
|
|
733
|
+
# is referenced by the permanent class path of its registration.
|
|
734
|
+
def con_path(name)
|
|
735
|
+
return name.downcase if %w[True False].include?(name)
|
|
736
|
+
return "[]" if name == "[]"
|
|
737
|
+
return HaskellMatch.constructor_constant(name) if @con_arity.key?(name) && @local_cons.include?(name)
|
|
738
|
+
|
|
739
|
+
reg = HaskellMatch.constructor(name, @scope)
|
|
740
|
+
raise UnknownConstructorError, "#{@source_name}: not in scope: data constructor '#{name}'" if reg.nil?
|
|
741
|
+
|
|
742
|
+
(reg.is_a?(Class) ? reg : reg.class).name
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
# `(op)` as a value: a constructor, a user-defined function or
|
|
746
|
+
# operator, or a Prelude operator.
|
|
747
|
+
def op_value(op, scope)
|
|
748
|
+
return con_value(op) if op.start_with?(":") && op != ":"
|
|
749
|
+
return var(op, scope) if user_function?(op, scope) || !symbolic?(op)
|
|
750
|
+
|
|
751
|
+
"HaskellMatch::Prelude.op(#{rb_str(op)})"
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def application(f, args, scope, tail)
|
|
755
|
+
arg_code = args.map { |a| expr(a, scope, tail: false) }
|
|
756
|
+
if f[0] == "var"
|
|
757
|
+
name = f[1]
|
|
758
|
+
if scope.lookup_var(name).nil?
|
|
759
|
+
if (lf = scope.lookup_fun(name))
|
|
760
|
+
pre = lf[:free].map { |v| scope.lookup_var(v) || mangle(v) }
|
|
761
|
+
return known_call(lf[:ivar], lf[:arity], pre + arg_code, tail, pre.size)
|
|
762
|
+
end
|
|
763
|
+
if @top.key?(name)
|
|
764
|
+
info = @top[name]
|
|
765
|
+
return known_call(info[:ivar], info[:arity], arg_code, tail, 0)
|
|
766
|
+
end
|
|
767
|
+
if !@values.key?(name) && Prelude.known?(name.to_sym)
|
|
768
|
+
arity = Prelude.arity(name.to_sym)
|
|
769
|
+
if arity.positive? && arg_code.size >= arity
|
|
770
|
+
direct = "HaskellMatch::Prelude.#{name}(#{arg_code.first(arity).join(', ')})"
|
|
771
|
+
return apply_rest(direct, arg_code.drop(arity))
|
|
772
|
+
end
|
|
773
|
+
return apply_rest("HaskellMatch::Prelude.curried(#{name.to_sym.inspect})", arg_code)
|
|
774
|
+
end
|
|
775
|
+
if !@values.key?(name) && !scope.shadowed?(name) && !Prelude.known?(name.to_sym)
|
|
776
|
+
# foreign Ruby method on the host module
|
|
777
|
+
return "__send__(#{name.to_sym.inspect}, #{arg_code.join(', ')})" if symbolic?(name)
|
|
778
|
+
|
|
779
|
+
return "#{name}(#{arg_code.join(', ')})"
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
elsif f[0] == "con"
|
|
783
|
+
name = f[1]
|
|
784
|
+
arity = con_arity(name)
|
|
785
|
+
raise UnknownConstructorError, "#{@source_name}: not in scope: data constructor '#{name}'" if arity.nil?
|
|
786
|
+
|
|
787
|
+
if arg_code.size >= arity && arity.positive?
|
|
788
|
+
return apply_rest("#{con_path(name)}.new(#{arg_code.first(arity).join(', ')})", arg_code.drop(arity))
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
apply_rest(expr(f, scope, tail: false), arg_code)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
# Call a Function held in `ivar` with `arity` own parameters (plus
|
|
795
|
+
# `pre` leading free-variable arguments already included in `args`).
|
|
796
|
+
def known_call(ivar, arity, args, tail, pre)
|
|
797
|
+
total = arity + pre
|
|
798
|
+
if args.size == total
|
|
799
|
+
tail ? "#{ivar}.tail(#{args.join(', ')})" : "#{ivar}.(#{args.join(', ')})"
|
|
800
|
+
elsif args.size > total
|
|
801
|
+
apply_rest("#{ivar}.(#{args.first(total).join(', ')})", args.drop(total))
|
|
802
|
+
else
|
|
803
|
+
curried_call(ivar, args, total)
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# An expression whose evaluation is immediate and cannot recurse.
|
|
808
|
+
def trivial?(e)
|
|
809
|
+
case e[0]
|
|
810
|
+
when "var", "lit", "con" then true
|
|
811
|
+
when "list", "tuple" then e[1].all? { |x| trivial?(x) }
|
|
812
|
+
when "neg" then trivial?(e[1])
|
|
813
|
+
else false
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
def apply_rest(code, rest)
|
|
818
|
+
rest.inject(code) { |acc, a| "#{acc}.(#{a})" }
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def binop(op, l, r, scope, tail)
|
|
822
|
+
# an infix constructor builds a value; a user-defined operator or a
|
|
823
|
+
# backticked function is an ordinary call
|
|
824
|
+
return application(["con", op], [l, r], scope, tail) if op.start_with?(":") && op != ":"
|
|
825
|
+
return application(["var", op], [l, r], scope, tail) if user_function?(op, scope)
|
|
826
|
+
return application(["var", op], [l, r], scope, tail) if !symbolic?(op) && op != "seq"
|
|
827
|
+
|
|
828
|
+
if op == "$"
|
|
829
|
+
# f $ x == f x
|
|
830
|
+
return application(l, [r], scope, tail) if l[0] == "var" || l[0] == "con"
|
|
831
|
+
return application(l[1], l[2] + [r], scope, tail) if l[0] == "app"
|
|
832
|
+
|
|
833
|
+
return "#{expr(l, scope, tail: false)}.(#{expr(r, scope, tail: false)})"
|
|
834
|
+
end
|
|
835
|
+
if op == "seq"
|
|
836
|
+
return "(#{expr(l, scope, tail: false)}; #{expr(r, scope, tail: tail)})"
|
|
837
|
+
end
|
|
838
|
+
if op == ":" && !trivial?(r)
|
|
839
|
+
# Haskell's `:` does not evaluate its tail; a tail that is a
|
|
840
|
+
# computation (not a variable or literal) becomes a thunk so that
|
|
841
|
+
# `p : sieve xs` and `fibs = 0 : 1 : zipWith (+) fibs (tail fibs)`
|
|
842
|
+
# build infinite lists instead of looping.
|
|
843
|
+
return "HaskellMatch::LazyList.lazy_cons(#{expr(l, scope, tail: false)}) { #{expr(r, scope, tail: false)} }"
|
|
844
|
+
end
|
|
845
|
+
binop_code(op, expr(l, scope, tail: false), expr(r, scope, tail: false))
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
def binop_code(op, lc, rc)
|
|
849
|
+
if BINOPS.key?(op)
|
|
850
|
+
"(#{lc} #{BINOPS[op]} #{rc})"
|
|
851
|
+
elsif PRELUDE_BINOPS.key?(op)
|
|
852
|
+
"HaskellMatch::Prelude.#{PRELUDE_BINOPS[op]}(#{lc}, #{rc})"
|
|
853
|
+
elsif op == "$"
|
|
854
|
+
"#{lc}.(#{rc})"
|
|
855
|
+
elsif Prelude.known?(op.to_sym) && Prelude.arity(op.to_sym) == 2
|
|
856
|
+
"HaskellMatch::Prelude.#{op}(#{lc}, #{rc})"
|
|
857
|
+
else
|
|
858
|
+
raise DefinitionError, "#{@source_name}: operator '#{op}' is not supported"
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
# `case` is lifted into a function whose arguments are the free
|
|
863
|
+
# variables of the alternatives followed by the scrutinee.
|
|
864
|
+
def case_expr(scrut, alts, scope, tail)
|
|
865
|
+
free = []
|
|
866
|
+
alts.each { |alt| collect_free(alt["rhs"], alt["where"], alt["pat"]["vars"], scope, free) }
|
|
867
|
+
ivar = "@hs_lift_#{fresh('case')}"
|
|
868
|
+
inner_scope = Scope.new({}, {}, scope)
|
|
869
|
+
clauses = alts.map do |alt|
|
|
870
|
+
vars = alt["pat"]["vars"]
|
|
871
|
+
dup = (free + vars).detect { |v| (free + vars).count(v) > 1 }
|
|
872
|
+
if dup
|
|
873
|
+
# a pattern variable shadowing a captured one: rename the capture
|
|
874
|
+
raise DefinitionError, "#{@source_name}:#{ln(alt['line'])}: '#{dup}' is both captured and bound in a case alternative; rename one"
|
|
875
|
+
end
|
|
876
|
+
s = Scope.new({}, {}, inner_scope)
|
|
877
|
+
(free + vars).each { |v| s.vars[v] = mangle(v) }
|
|
878
|
+
params = (free + vars).map { |v| mangle(v) }
|
|
879
|
+
param_list = params.empty? ? "" : "|#{params.join(', ')}|"
|
|
880
|
+
pat_texts = free.map { |v| rb_str(mangle(v)) } + [rb_str(alt["pat"]["text"])]
|
|
881
|
+
clauses_for_rhs(alt["rhs"], alt["where"], s, pat_texts, param_list, ln(alt["line"]))
|
|
882
|
+
end.join("\n")
|
|
883
|
+
@lifted << <<~RUBY
|
|
884
|
+
#{ivar} = HaskellMatch.fn("case", exhaustive: #{@exhaustive.inspect}, scope: haskell_scope) do |m|
|
|
885
|
+
#{clauses}
|
|
886
|
+
end
|
|
887
|
+
RUBY
|
|
888
|
+
args = free.map { |v| scope.lookup_var(v) || mangle(v) } + [expr(scrut, scope, tail: false)]
|
|
889
|
+
tail ? "#{ivar}.tail(#{args.join(', ')})" : "#{ivar}.(#{args.join(', ')})"
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def let_expr(decls, body, scope, tail)
|
|
893
|
+
inner = Scope.new({}, {}, scope)
|
|
894
|
+
code = where_bindings(decls, inner)
|
|
895
|
+
"(#{code}#{expr(body, inner, tail: tail)})"
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Lambdas with only variable patterns are curried Ruby lambdas; others
|
|
899
|
+
# are lifted into a function and partially applied to their free vars.
|
|
900
|
+
def lambda_expr(pats, body, scope)
|
|
901
|
+
if pats.all? { |p| p["vars"].size == 1 && p["text"] == mangle(p["vars"][0]) }
|
|
902
|
+
inner = Scope.new({}, {}, scope)
|
|
903
|
+
names = pats.map { |p| fresh(mangle(p["vars"][0])) }
|
|
904
|
+
pats.each_with_index { |p, i| inner.vars[p["vars"][0]] = names[i] }
|
|
905
|
+
b = expr(body, inner, tail: false)
|
|
906
|
+
return names.reverse.inject(b) { |acc, n| "->(#{n}) { #{acc} }" }
|
|
907
|
+
end
|
|
908
|
+
free = []
|
|
909
|
+
bound = pats.flat_map { |p| p["vars"] }
|
|
910
|
+
free_in(body, bound, scope, free)
|
|
911
|
+
ivar = "@hs_lift_#{fresh('lambda')}"
|
|
912
|
+
s = Scope.new({}, {}, scope)
|
|
913
|
+
(free + bound).each { |v| s.vars[v] = mangle(v) }
|
|
914
|
+
params = (free + bound).map { |v| mangle(v) }
|
|
915
|
+
pat_texts = free.map { |v| rb_str(mangle(v)) } + pats.map { |p| rb_str(p["text"]) }
|
|
916
|
+
@lifted << <<~RUBY
|
|
917
|
+
#{ivar} = HaskellMatch.fn("lambda", exhaustive: #{@exhaustive.inspect}, scope: haskell_scope) do |m|
|
|
918
|
+
m.on(#{pat_texts.join(', ')}) { |#{params.join(', ')}| #{expr(body, s, tail: true)} }
|
|
919
|
+
end
|
|
920
|
+
RUBY
|
|
921
|
+
curried_call(ivar, free.map { |v| scope.lookup_var(v) || mangle(v) }, free.size + pats.size)
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def comprehension(body, quals, scope)
|
|
925
|
+
inner = Scope.new({}, {}, scope)
|
|
926
|
+
code = comp_quals(quals, body, inner)
|
|
927
|
+
"(HaskellMatch::Prelude.comp_begin; HaskellMatch::Prelude.comp_finish(#{code}))"
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
def comp_quals(quals, body, scope)
|
|
931
|
+
if quals.empty?
|
|
932
|
+
return "[#{expr(body, scope, tail: false)}].lazy"
|
|
933
|
+
end
|
|
934
|
+
q, *rest = quals
|
|
935
|
+
case q[0]
|
|
936
|
+
when "gen"
|
|
937
|
+
src = expr(q[2], scope, tail: false)
|
|
938
|
+
pat = q[1]
|
|
939
|
+
if pat["vars"].size == 1 && pat["text"] == mangle(pat["vars"][0])
|
|
940
|
+
name = fresh(mangle(pat["vars"][0]))
|
|
941
|
+
scope.vars[pat["vars"][0]] = name
|
|
942
|
+
"HaskellMatch::Prelude.gen(#{src}).flat_map { |#{name}| #{comp_quals(rest, body, scope)} }"
|
|
943
|
+
else
|
|
944
|
+
# refutable pattern: elements that do not match are skipped
|
|
945
|
+
matcher = "@hs_pat_#{fresh('comp')}"
|
|
946
|
+
@lifted << "#{matcher} = HaskellMatch.pattern(#{rb_str(pat['text'])}, scope: haskell_scope)"
|
|
947
|
+
el = fresh("hs_el")
|
|
948
|
+
binds = fresh("hs_b")
|
|
949
|
+
pat["vars"].each { |v| scope.vars[v] = "#{binds}[#{mangle(v).to_sym.inspect}]" }
|
|
950
|
+
"HaskellMatch::Prelude.gen(#{src}).flat_map { |#{el}| (#{binds} = #{matcher}.match(#{el})) ? (#{comp_quals(rest, body, scope)}) : [].lazy }"
|
|
951
|
+
end
|
|
952
|
+
when "guard"
|
|
953
|
+
"((#{expr(q[1], scope, tail: false)}) ? (#{comp_quals(rest, body, scope)}) : [].lazy)"
|
|
954
|
+
when "let"
|
|
955
|
+
code = where_bindings(q[1], scope)
|
|
956
|
+
"(#{code}#{comp_quals(rest, body, scope)})"
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
end
|