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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. 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