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,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaskellMatch
|
|
4
|
+
# A single compiled pattern, usable on its own:
|
|
5
|
+
#
|
|
6
|
+
# p = HaskellMatch.pattern("Just (x:_)")
|
|
7
|
+
# p.match(Just.new([1, 2])) # => { x: 1 }
|
|
8
|
+
# p.match(Nothing) # => nil
|
|
9
|
+
# p === Just.new([1]) # => true (so it works in case/when)
|
|
10
|
+
class Pattern
|
|
11
|
+
attr_reader :source, :names
|
|
12
|
+
|
|
13
|
+
def initialize(source = nil, scope: Native::GLOBAL_SCOPE, &block)
|
|
14
|
+
if block
|
|
15
|
+
raise ArgumentError, "pass either a pattern string or a block, not both" if source
|
|
16
|
+
|
|
17
|
+
source = PatternAST.render(ClauseBuilder.new(block.binding.receiver).instance_exec(&block))
|
|
18
|
+
end
|
|
19
|
+
raise ArgumentError, "pattern must be a String" unless source.is_a?(String)
|
|
20
|
+
|
|
21
|
+
@source = source.dup.freeze
|
|
22
|
+
@scope = scope
|
|
23
|
+
resolver = block && ClauseBuilder.new(block.binding.receiver).constant_resolver
|
|
24
|
+
@matcher = Compiler.build_matcher(Native::Matcher, "pattern #{source.inspect}", [[source]], [false], 1, scope,
|
|
25
|
+
resolver: resolver)
|
|
26
|
+
@names = @matcher.names.first.map(&:to_sym).freeze
|
|
27
|
+
@exhaustive = @matcher.instance_variable_get(:@missing).empty?
|
|
28
|
+
freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Bindings as a Hash, or nil when the value does not match. Raises
|
|
32
|
+
# {TypeMismatchError} when the value is of the wrong type altogether.
|
|
33
|
+
def match(value)
|
|
34
|
+
result = @matcher.select(value)
|
|
35
|
+
return nil unless result
|
|
36
|
+
|
|
37
|
+
@names.zip(result[1]).to_h
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Like {#match} but raises {MatchError} on failure.
|
|
41
|
+
def match!(value)
|
|
42
|
+
match(value) or raise MatchError, "#{value.inspect} does not match pattern #{@source}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Does the value match? Unlike {#match}, a value of the wrong type is
|
|
46
|
+
# simply a non-match, so the pattern can be used in `case`/`when`.
|
|
47
|
+
def ===(value)
|
|
48
|
+
!@matcher.select(value).nil?
|
|
49
|
+
rescue TypeMismatchError
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Whether the pattern matches every value of its type (irrefutable).
|
|
54
|
+
def irrefutable?
|
|
55
|
+
@exhaustive
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_proc
|
|
59
|
+
method(:match).to_proc
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def inspect
|
|
63
|
+
"#<HaskellMatch::Pattern #{@source}>"
|
|
64
|
+
end
|
|
65
|
+
alias to_s inspect
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class << self
|
|
69
|
+
# `HaskellMatch.pattern("Just (x:_)")` or `HaskellMatch.pattern { Just([x, *_]) }`
|
|
70
|
+
def pattern(source = nil, scope: Native::GLOBAL_SCOPE, &block)
|
|
71
|
+
Pattern.new(source, scope: scope, &block)
|
|
72
|
+
end
|
|
73
|
+
alias [] pattern
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaskellMatch
|
|
4
|
+
# Patterns written in place inside a definition block, without quotes.
|
|
5
|
+
# Each expression builds the pattern tree directly in Ruby and is rendered
|
|
6
|
+
# to Haskell pattern syntax, then compiled exactly like a quoted pattern, so
|
|
7
|
+
# matching, exhaustiveness checking and error messages are identical.
|
|
8
|
+
#
|
|
9
|
+
# on([]) { 0 }
|
|
10
|
+
# on([x, *xs]) { |x, xs| ... } # (x:xs); also cons(x, xs)
|
|
11
|
+
# on(Just(Just(_))) { ... } # or Just[Just[_]]
|
|
12
|
+
# on(Person(name: n)) { |n| n } # Person { name = n }
|
|
13
|
+
# on(Person(name: n, **_)) { |n| n } # Person { name = n, .. }
|
|
14
|
+
# on(tuple(a, b)) { |a, b| a + b } # (a, b)
|
|
15
|
+
# on(as(all, [x, *_])) { |all, x| ... } # all@(x:_)
|
|
16
|
+
# on(lazy(tuple(a, b))) { ... } # ~(a, b)
|
|
17
|
+
# on(0) ... on(1.5) ... on(str("abc")) ... on(char("c")) ... on(:sym) ... on(true)
|
|
18
|
+
#
|
|
19
|
+
# A String given directly to `on` is quoted Haskell pattern syntax; inside a
|
|
20
|
+
# pattern (`Just("abc")`) a Ruby String is a string literal, and `str("abc")`
|
|
21
|
+
# makes one at the top level.
|
|
22
|
+
# Bare lower-case names are variables (use `var(:name)` when the name is a
|
|
23
|
+
# method of the enclosing object or a local variable), `_` is the wildcard.
|
|
24
|
+
# Quoted and in-place patterns may be mixed freely.
|
|
25
|
+
module PatternAST
|
|
26
|
+
class Node
|
|
27
|
+
# `*node` inside an array literal marks the rest of a list.
|
|
28
|
+
def to_a
|
|
29
|
+
[Rest.new(self)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# `**_` inside a record pattern stands for `..`.
|
|
33
|
+
def to_hash
|
|
34
|
+
{ REST_KEY => true }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inspect
|
|
38
|
+
"#<HaskellMatch pattern #{to_haskell}>"
|
|
39
|
+
end
|
|
40
|
+
alias to_s inspect
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
REST_KEY = :"haskell_match.rest"
|
|
44
|
+
|
|
45
|
+
class Var < Node
|
|
46
|
+
attr_reader :name
|
|
47
|
+
|
|
48
|
+
def initialize(name)
|
|
49
|
+
@name = name.to_s
|
|
50
|
+
unless @name.match?(/\A[a-z_][A-Za-z0-9_']*\z/)
|
|
51
|
+
raise DefinitionError, "'#{@name}' is not a valid pattern variable name (lower-case identifier)"
|
|
52
|
+
end
|
|
53
|
+
freeze
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_haskell(_atomic = false)
|
|
57
|
+
name
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class Wild < Node
|
|
62
|
+
def to_haskell(_atomic = false)
|
|
63
|
+
"_"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
WILD = Wild.new.freeze
|
|
68
|
+
|
|
69
|
+
# `*xs` inside an array literal: the rest of the list.
|
|
70
|
+
class Rest < Node
|
|
71
|
+
attr_reader :pattern
|
|
72
|
+
|
|
73
|
+
def initialize(pattern)
|
|
74
|
+
@pattern = pattern
|
|
75
|
+
freeze
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_haskell(_atomic = false)
|
|
79
|
+
raise DefinitionError, "a splat (*rest) may only appear last in a list pattern"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class ConApp < Node
|
|
84
|
+
attr_reader :name, :args
|
|
85
|
+
|
|
86
|
+
def initialize(name, args)
|
|
87
|
+
@name = name.to_s
|
|
88
|
+
@args = args.freeze
|
|
89
|
+
freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_haskell(atomic = false)
|
|
93
|
+
hs = HaskellMatch.symbolic_constructor(name) || name
|
|
94
|
+
return hs if args.empty?
|
|
95
|
+
|
|
96
|
+
s = if hs.start_with?(":") && args.size == 2
|
|
97
|
+
"#{PatternAST.render(args[0], true)} #{hs} #{PatternAST.render(args[1], true)}"
|
|
98
|
+
else
|
|
99
|
+
"#{hs.start_with?(':') ? "(#{hs})" : hs} #{args.map { |a| PatternAST.render(a, true) }.join(' ')}"
|
|
100
|
+
end
|
|
101
|
+
atomic ? "(#{s})" : s
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class Record < Node
|
|
106
|
+
attr_reader :name, :fields, :rest
|
|
107
|
+
|
|
108
|
+
def initialize(name, fields, rest)
|
|
109
|
+
@name = name.to_s
|
|
110
|
+
@fields = fields.freeze
|
|
111
|
+
@rest = rest
|
|
112
|
+
freeze
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_haskell(_atomic = false)
|
|
116
|
+
parts = fields.map { |f, p| "#{f} = #{PatternAST.render(p)}" }
|
|
117
|
+
parts << ".." if rest
|
|
118
|
+
"#{name} {#{parts.join(', ')}}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class Tuple < Node
|
|
123
|
+
attr_reader :items
|
|
124
|
+
|
|
125
|
+
def initialize(items)
|
|
126
|
+
@items = items.freeze
|
|
127
|
+
freeze
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def to_haskell(_atomic = false)
|
|
131
|
+
"(#{items.map { |i| PatternAST.render(i) }.join(', ')})"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class Cons < Node
|
|
136
|
+
attr_reader :head, :tail
|
|
137
|
+
|
|
138
|
+
def initialize(head, tail)
|
|
139
|
+
@head = head
|
|
140
|
+
@tail = tail
|
|
141
|
+
freeze
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def to_haskell(_atomic = false)
|
|
145
|
+
"(#{PatternAST.render(head, true)}:#{PatternAST.render(tail, true)})"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Prefixed < Node
|
|
150
|
+
attr_reader :prefix, :pattern
|
|
151
|
+
|
|
152
|
+
def initialize(prefix, pattern)
|
|
153
|
+
@prefix = prefix
|
|
154
|
+
@pattern = pattern
|
|
155
|
+
freeze
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def to_haskell(_atomic = false)
|
|
159
|
+
"#{prefix}#{PatternAST.render(pattern, true)}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class As < Node
|
|
164
|
+
attr_reader :name, :pattern
|
|
165
|
+
|
|
166
|
+
def initialize(name, pattern)
|
|
167
|
+
@name = name.is_a?(Var) ? name : Var.new(name)
|
|
168
|
+
@pattern = pattern
|
|
169
|
+
freeze
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def to_haskell(_atomic = false)
|
|
173
|
+
"#{name.to_haskell}@#{PatternAST.render(pattern, true)}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class Str < Node
|
|
178
|
+
attr_reader :string
|
|
179
|
+
|
|
180
|
+
def initialize(string)
|
|
181
|
+
@string = string.to_s
|
|
182
|
+
freeze
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def to_haskell(_atomic = false)
|
|
186
|
+
"\"#{PatternAST.escape(string, '"')}\""
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
class Char < Node
|
|
191
|
+
attr_reader :char
|
|
192
|
+
|
|
193
|
+
def initialize(str)
|
|
194
|
+
s = str.to_s
|
|
195
|
+
raise DefinitionError, "char(#{str.inspect}) needs exactly one character" unless s.length == 1
|
|
196
|
+
|
|
197
|
+
@char = s
|
|
198
|
+
freeze
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def to_haskell(_atomic = false)
|
|
202
|
+
"'#{PatternAST.escape(char, "'")}'"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
module_function
|
|
207
|
+
|
|
208
|
+
# Render any supported Ruby object as Haskell pattern text.
|
|
209
|
+
def render(obj, atomic = false)
|
|
210
|
+
case obj
|
|
211
|
+
when Node then obj.to_haskell(atomic)
|
|
212
|
+
when String then "\"#{escape(obj, '"')}\""
|
|
213
|
+
when Symbol then symbol(obj)
|
|
214
|
+
when Integer then obj.negative? && atomic ? "(#{obj})" : obj.to_s
|
|
215
|
+
when Float then float(obj, atomic)
|
|
216
|
+
when true then "True"
|
|
217
|
+
when false then "False"
|
|
218
|
+
when Array then list(obj)
|
|
219
|
+
when Hash then hash_pattern(obj)
|
|
220
|
+
when Constructor then constructor(obj, atomic)
|
|
221
|
+
when Class
|
|
222
|
+
if obj < Data && obj.include?(Constructor)
|
|
223
|
+
obj.constructor_name # the compiler reports the arity error
|
|
224
|
+
else
|
|
225
|
+
unsupported(obj)
|
|
226
|
+
end
|
|
227
|
+
when nil
|
|
228
|
+
raise DefinitionError, "nil is not a Haskell pattern (use _ for a wildcard)"
|
|
229
|
+
else
|
|
230
|
+
unsupported(obj)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def unsupported(obj)
|
|
235
|
+
raise DefinitionError, "#{obj.inspect} (#{obj.class}) cannot be used as a pattern"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def list(items)
|
|
239
|
+
return "[]" if items.empty?
|
|
240
|
+
|
|
241
|
+
if items.last.is_a?(Rest)
|
|
242
|
+
*init, rest = items
|
|
243
|
+
return render(rest.pattern) if init.empty?
|
|
244
|
+
|
|
245
|
+
inner = init.map { |i| render(i, true) } + [render(rest.pattern, true)]
|
|
246
|
+
return "(#{inner.join(':')})"
|
|
247
|
+
end
|
|
248
|
+
if items.any? { |i| i.is_a?(Rest) }
|
|
249
|
+
raise DefinitionError, "a splat (*rest) may only appear last in a list pattern"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
"[#{items.map { |i| render(i) }.join(', ')}]"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# `{name: n, "k" => v}` -> `{name = n, "k" = v}`: a Hash having those keys
|
|
256
|
+
# (Symbol or String), whose values match the sub-patterns; other keys are
|
|
257
|
+
# ignored. `**_` (the record-pattern rest marker) is accepted and means
|
|
258
|
+
# nothing extra, since Hash patterns are always open.
|
|
259
|
+
def hash_pattern(hash)
|
|
260
|
+
parts = hash.filter_map do |k, v|
|
|
261
|
+
next if k == REST_KEY
|
|
262
|
+
|
|
263
|
+
key = case k
|
|
264
|
+
when Symbol then k.to_s.match?(/\A[a-z_][A-Za-z0-9_']*\z/) ? k.to_s : ":#{k.to_s.inspect}"
|
|
265
|
+
when String then k.inspect
|
|
266
|
+
else raise DefinitionError, "Hash pattern keys must be Symbols or Strings (got #{k.inspect})"
|
|
267
|
+
end
|
|
268
|
+
"#{key} = #{render(v)}"
|
|
269
|
+
end
|
|
270
|
+
"{#{parts.join(', ')}}"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def constructor(value, atomic)
|
|
274
|
+
name = value.class.constructor_name
|
|
275
|
+
fields = value.fields
|
|
276
|
+
return name if fields.empty?
|
|
277
|
+
|
|
278
|
+
s = if name.to_s.start_with?(":") && fields.size == 2
|
|
279
|
+
"#{render(fields[0], true)} #{name} #{render(fields[1], true)}"
|
|
280
|
+
else
|
|
281
|
+
"#{name} #{fields.map { |f| render(f, true) }.join(' ')}"
|
|
282
|
+
end
|
|
283
|
+
atomic ? "(#{s})" : s
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def symbol(sym)
|
|
287
|
+
s = sym.to_s
|
|
288
|
+
if s.match?(/\A[A-Za-z_][A-Za-z0-9_]*[?!]?\z/)
|
|
289
|
+
":#{s}"
|
|
290
|
+
else
|
|
291
|
+
":\"#{escape(s, '"')}\""
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def float(f, atomic)
|
|
296
|
+
raise DefinitionError, "#{f} cannot be used as a pattern" unless f.finite?
|
|
297
|
+
|
|
298
|
+
s = f.to_s
|
|
299
|
+
f.negative? && atomic ? "(#{s})" : s
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Escape for the pattern lexer's string/char literals.
|
|
303
|
+
def escape(str, quote)
|
|
304
|
+
str.each_char.map do |c|
|
|
305
|
+
case c
|
|
306
|
+
when "\\" then "\\\\"
|
|
307
|
+
when quote then "\\#{quote}"
|
|
308
|
+
when "\n" then "\\n"
|
|
309
|
+
when "\t" then "\\t"
|
|
310
|
+
when "\r" then "\\r"
|
|
311
|
+
when "\0" then "\\0"
|
|
312
|
+
else
|
|
313
|
+
c.ord < 0x20 ? format("\\x%02x", c.ord) : c
|
|
314
|
+
end
|
|
315
|
+
end.join
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Methods mixed into clause builders so patterns can be written in place.
|
|
319
|
+
module BuilderMethods
|
|
320
|
+
def _
|
|
321
|
+
WILD
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def var(name)
|
|
325
|
+
Var.new(name)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def cons(head, tail)
|
|
329
|
+
Cons.new(head, tail)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def tuple(*items)
|
|
333
|
+
Tuple.new(items)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def unit
|
|
337
|
+
Tuple.new([])
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def as(name, pattern)
|
|
341
|
+
As.new(name, pattern)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def lazy(pattern)
|
|
345
|
+
Prefixed.new("~", pattern)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def bang(pattern)
|
|
349
|
+
Prefixed.new("!", pattern)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def char(str)
|
|
353
|
+
Char.new(str)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# A string literal pattern. Needed only as a whole argument of `on`,
|
|
357
|
+
# where a bare String is read as quoted Haskell pattern syntax; inside
|
|
358
|
+
# a pattern (`Just("abc")`, `[str, *rest]`) Ruby Strings are literals.
|
|
359
|
+
def str(string)
|
|
360
|
+
Str.new(string)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# `Just(x)`, `Person(name: n, age: _)`, `Node(l, v, r)`; a bare
|
|
364
|
+
# lower-case name is a pattern variable.
|
|
365
|
+
def method_missing(name, *args, **kwargs, &blk)
|
|
366
|
+
s = name.to_s
|
|
367
|
+
if s.match?(/\A[A-Z]/) && blk.nil?
|
|
368
|
+
if kwargs.empty?
|
|
369
|
+
ConApp.new(s, args)
|
|
370
|
+
elsif args.empty?
|
|
371
|
+
rest = kwargs.delete(REST_KEY) ? true : false
|
|
372
|
+
Record.new(s, kwargs.map { |k, v| [k.to_s, v] }, rest)
|
|
373
|
+
else
|
|
374
|
+
raise DefinitionError, "#{s}(...) takes either positional or named fields, not both"
|
|
375
|
+
end
|
|
376
|
+
elsif args.empty? && kwargs.empty? && blk.nil? && s.match?(/\A[a-z_][A-Za-z0-9_]*\z/) &&
|
|
377
|
+
!pattern_owner_responds?(name)
|
|
378
|
+
Var.new(s)
|
|
379
|
+
else
|
|
380
|
+
super
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def respond_to_missing?(name, include_private = false)
|
|
385
|
+
name.to_s.match?(/\A[A-Za-z_]/) || super
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Hook: builders with an owner delegate names the owner knows.
|
|
389
|
+
def pattern_owner_responds?(_name)
|
|
390
|
+
false
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|