fear 1.2.0 → 2.0.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 +4 -4
- data/.github/dependabot.yml +27 -0
- data/.github/workflows/rubocop.yml +2 -2
- data/.github/workflows/spec.yml +1 -1
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +53 -56
- data/README.md +54 -186
- data/Rakefile +0 -21
- data/examples/pattern_extracting.rb +4 -4
- data/fear.gemspec +2 -4
- data/lib/fear/either.rb +8 -4
- data/lib/fear/either_api.rb +2 -0
- data/lib/fear/either_pattern_match.rb +7 -8
- data/lib/fear/failure.rb +0 -9
- data/lib/fear/failure_pattern_match.rb +2 -0
- data/lib/fear/for_api.rb +2 -0
- data/lib/fear/future.rb +12 -20
- data/lib/fear/future_api.rb +13 -2
- data/lib/fear/left.rb +0 -9
- data/lib/fear/none.rb +7 -9
- data/lib/fear/option.rb +5 -1
- data/lib/fear/option_api.rb +2 -0
- data/lib/fear/option_pattern_match.rb +6 -4
- data/lib/fear/partial_function/empty.rb +2 -0
- data/lib/fear/partial_function/guard.rb +4 -4
- data/lib/fear/partial_function/or_else.rb +2 -0
- data/lib/fear/partial_function.rb +9 -8
- data/lib/fear/pattern_match.rb +0 -10
- data/lib/fear/pattern_matching_api.rb +3 -28
- data/lib/fear/promise.rb +3 -9
- data/lib/fear/right.rb +0 -10
- data/lib/fear/right_biased.rb +1 -1
- data/lib/fear/right_pattern_match.rb +2 -0
- data/lib/fear/some.rb +7 -10
- data/lib/fear/struct.rb +3 -14
- data/lib/fear/success.rb +0 -9
- data/lib/fear/success_pattern_match.rb +2 -0
- data/lib/fear/try.rb +6 -2
- data/lib/fear/try_api.rb +2 -0
- data/lib/fear/try_pattern_match.rb +7 -8
- data/lib/fear/utils.rb +0 -3
- data/lib/fear/version.rb +1 -1
- data/lib/fear.rb +8 -42
- data/spec/fear/awaitable_spec.rb +2 -0
- data/spec/fear/either_spec.rb +26 -0
- data/spec/fear/failure_spec.rb +8 -23
- data/spec/fear/for/mixin_spec.rb +15 -0
- data/spec/fear/future_spec.rb +17 -2
- data/spec/fear/guard_spec.rb +110 -0
- data/spec/fear/left_spec.rb +7 -22
- data/spec/fear/none_spec.rb +11 -17
- data/spec/fear/option_spec.rb +15 -1
- data/spec/fear/partial_function/any_spec.rb +25 -0
- data/spec/fear/partial_function_spec.rb +2 -24
- data/spec/fear/pattern_match_spec.rb +0 -34
- data/spec/fear/promise_spec.rb +4 -6
- data/spec/fear/right_spec.rb +0 -22
- data/spec/fear/some_spec.rb +10 -22
- data/spec/fear/success_spec.rb +0 -22
- data/spec/fear/try/mixin_spec.rb +14 -0
- data/spec/fear/try_api_spec.rb +23 -0
- data/spec/struct_spec.rb +1 -33
- metadata +18 -80
- data/examples/pattern_extracting_ruby2.7.rb +0 -15
- data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -10
- data/lib/fear/extractor/any_matcher.rb +0 -17
- data/lib/fear/extractor/array_head_matcher.rb +0 -36
- data/lib/fear/extractor/array_matcher.rb +0 -40
- data/lib/fear/extractor/array_splat_matcher.rb +0 -16
- data/lib/fear/extractor/empty_list_matcher.rb +0 -20
- data/lib/fear/extractor/extractor_matcher.rb +0 -44
- data/lib/fear/extractor/grammar.rb +0 -203
- data/lib/fear/extractor/grammar.treetop +0 -129
- data/lib/fear/extractor/identifier_matcher.rb +0 -18
- data/lib/fear/extractor/matcher/and.rb +0 -38
- data/lib/fear/extractor/matcher.rb +0 -53
- data/lib/fear/extractor/named_array_splat_matcher.rb +0 -17
- data/lib/fear/extractor/pattern.rb +0 -58
- data/lib/fear/extractor/typed_identifier_matcher.rb +0 -26
- data/lib/fear/extractor/value_matcher.rb +0 -19
- data/lib/fear/extractor.rb +0 -112
- data/lib/fear/extractor_api.rb +0 -35
- data/spec/fear/extractor/array_matcher_spec.rb +0 -230
- data/spec/fear/extractor/extractor_matcher_spec.rb +0 -153
- data/spec/fear/extractor/grammar_array_spec.rb +0 -25
- data/spec/fear/extractor/identified_matcher_spec.rb +0 -49
- data/spec/fear/extractor/identifier_matcher_spec.rb +0 -68
- data/spec/fear/extractor/pattern_spec.rb +0 -34
- data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -64
- data/spec/fear/extractor/value_matcher_number_spec.rb +0 -79
- data/spec/fear/extractor/value_matcher_string_spec.rb +0 -88
- data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -71
- data/spec/fear/extractor_api_spec.rb +0 -115
- data/spec/fear/extractor_spec.rb +0 -61
@@ -1,203 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Fear
|
4
|
-
module Extractor
|
5
|
-
# This module contains AST nodes for GrammarParser
|
6
|
-
# generated by +treetop+. The sole purpose of them all is to
|
7
|
-
# generate matchers
|
8
|
-
module Grammar
|
9
|
-
class Node < Treetop::Runtime::SyntaxNode
|
10
|
-
end
|
11
|
-
|
12
|
-
class EmptyArray < Node
|
13
|
-
def to_matcher
|
14
|
-
EmptyListMatcher.new(node: self)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class ArrayLiteral < Node
|
19
|
-
def to_matcher
|
20
|
-
elements[1].to_matcher
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class NonEmptyArray < Node
|
25
|
-
def to_matcher
|
26
|
-
head, tail = elements
|
27
|
-
ArrayMatcher.new(head: head.to_matcher, tail: tail.to_matcher, node: self)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class ArrayTail < Node
|
32
|
-
def to_matcher
|
33
|
-
elements[1].to_matcher
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class ArrayHead < Node
|
38
|
-
def to_matcher
|
39
|
-
ArrayHeadMatcher.new(matcher: elements[1].to_matcher, node: elements[1])
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class ArraySplat < Node
|
44
|
-
def to_matcher
|
45
|
-
elements[1].to_matcher
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class AnonymousArraySplat < Node
|
50
|
-
def to_matcher
|
51
|
-
AnonymousArraySplatMatcher.new(node: self)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class FloatLiteral < Node
|
56
|
-
def to_matcher
|
57
|
-
ValueMatcher.new(value: value, node: self)
|
58
|
-
end
|
59
|
-
|
60
|
-
def value
|
61
|
-
Float(text_value)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class IntegerLiteral < Node
|
66
|
-
def to_matcher
|
67
|
-
ValueMatcher.new(value: value, node: self)
|
68
|
-
end
|
69
|
-
|
70
|
-
def value
|
71
|
-
Integer(text_value, 10)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class StringLiteral < Node
|
76
|
-
def to_matcher
|
77
|
-
ValueMatcher.new(value: value, node: self)
|
78
|
-
end
|
79
|
-
|
80
|
-
def value
|
81
|
-
elements[1].text_value
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
require "yaml"
|
86
|
-
|
87
|
-
class DoubleQuotedStringLiteral < StringLiteral
|
88
|
-
def to_matcher
|
89
|
-
ValueMatcher.new(value: value, node: self)
|
90
|
-
end
|
91
|
-
|
92
|
-
def value
|
93
|
-
YAML.safe_load(%(---\n"#{super}"\n))
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
class SymbolLiteral < Node
|
98
|
-
def to_matcher
|
99
|
-
ValueMatcher.new(value: value, node: self)
|
100
|
-
end
|
101
|
-
|
102
|
-
def value
|
103
|
-
elements[1].value.to_sym
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class TrueLiteral < Node
|
108
|
-
def to_matcher
|
109
|
-
ValueMatcher.new(value: true, node: self)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class FalseLiteral < Node
|
114
|
-
def to_matcher
|
115
|
-
ValueMatcher.new(value: false, node: self)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
class NilLiteral < Node
|
120
|
-
def to_matcher
|
121
|
-
ValueMatcher.new(value: nil, node: self)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class AnyIdentifier < Node
|
126
|
-
def to_matcher
|
127
|
-
AnyMatcher.new(node: self)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
class Identifier < Node
|
132
|
-
def to_matcher
|
133
|
-
IdentifierMatcher.new(name: value, node: self)
|
134
|
-
end
|
135
|
-
|
136
|
-
def value
|
137
|
-
text_value.to_sym
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
class NamedArraySplat < Node
|
142
|
-
def to_matcher
|
143
|
-
NamedArraySplatMatcher.new(name: name, node: self)
|
144
|
-
end
|
145
|
-
|
146
|
-
def name
|
147
|
-
text_value[1..-1].to_sym
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
class TypeLiteral < Node
|
152
|
-
def to_matcher
|
153
|
-
ValueMatcher.new(value: value, node: self)
|
154
|
-
end
|
155
|
-
|
156
|
-
def value
|
157
|
-
Object.const_get(text_value)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
class TypedIdentifier < Node
|
162
|
-
def to_matcher
|
163
|
-
identifier, type = elements.values_at(0, 2)
|
164
|
-
type.to_matcher.and(identifier.to_matcher)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
class IdentifiedMatcher < Node
|
169
|
-
def to_matcher
|
170
|
-
identifier, matcher = elements.values_at(0, -1)
|
171
|
-
identifier.to_matcher.and(matcher.to_matcher)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
class ExtractorLiteral < Node
|
176
|
-
def to_matcher
|
177
|
-
ExtractorMatcher.new(
|
178
|
-
name: extractor_name,
|
179
|
-
arguments_matcher: extractor_arguments,
|
180
|
-
node: self,
|
181
|
-
)
|
182
|
-
end
|
183
|
-
|
184
|
-
private def extractor_name
|
185
|
-
name = elements[0].text_value
|
186
|
-
if Object.const_defined?(name)
|
187
|
-
Object.const_get(name)
|
188
|
-
else
|
189
|
-
name
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
private def extractor_arguments
|
194
|
-
if elements[2].empty?
|
195
|
-
EmptyListMatcher.new(node: self)
|
196
|
-
else
|
197
|
-
elements[2].to_matcher
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
@@ -1,129 +0,0 @@
|
|
1
|
-
# PEG grammar for pattern matching and extracting
|
2
|
-
|
3
|
-
module Fear::Extractor
|
4
|
-
grammar Grammar
|
5
|
-
rule matcher
|
6
|
-
identified_matcher / anonymous_matcher
|
7
|
-
end
|
8
|
-
|
9
|
-
rule identified_matcher
|
10
|
-
identifier space '@' space anonymous_matcher <IdentifiedMatcher>
|
11
|
-
end
|
12
|
-
|
13
|
-
rule anonymous_matcher
|
14
|
-
array / literal / identifier / extractor / type
|
15
|
-
end
|
16
|
-
|
17
|
-
rule array
|
18
|
-
'[' (non_empty_array / array_splat / empty_array) ']' <ArrayLiteral>
|
19
|
-
end
|
20
|
-
|
21
|
-
rule empty_array
|
22
|
-
'' <EmptyArray>
|
23
|
-
end
|
24
|
-
|
25
|
-
rule non_empty_array
|
26
|
-
array_head (array_tail / empty_array) <NonEmptyArray>
|
27
|
-
end
|
28
|
-
|
29
|
-
rule array_tail
|
30
|
-
',' (non_empty_array / array_splat) <ArrayTail>
|
31
|
-
end
|
32
|
-
|
33
|
-
rule array_splat
|
34
|
-
space? (named_array_splat / anonymous_array_splat) space? <ArraySplat>
|
35
|
-
end
|
36
|
-
|
37
|
-
rule array_splat_identifier
|
38
|
-
space? anonymous_array_splat space? <ArraySplat>
|
39
|
-
end
|
40
|
-
|
41
|
-
rule anonymous_array_splat
|
42
|
-
'*' '_'? <AnonymousArraySplat>
|
43
|
-
end
|
44
|
-
|
45
|
-
rule named_array_splat
|
46
|
-
'*' [a-z] [a-z0-9_]* <NamedArraySplat>
|
47
|
-
end
|
48
|
-
|
49
|
-
rule array_head
|
50
|
-
space? anonymous_matcher space? <ArrayHead>
|
51
|
-
end
|
52
|
-
|
53
|
-
rule space
|
54
|
-
[\s]+
|
55
|
-
end
|
56
|
-
|
57
|
-
rule any
|
58
|
-
'_' <AnyIdentifier>
|
59
|
-
end
|
60
|
-
|
61
|
-
rule variable_identifier
|
62
|
-
[a-z_] [a-z0-9_]* <Identifier>
|
63
|
-
end
|
64
|
-
|
65
|
-
rule identifier
|
66
|
-
typed_identifier / any / variable_identifier
|
67
|
-
end
|
68
|
-
|
69
|
-
rule typed_identifier
|
70
|
-
(any / variable_identifier) ' : ' type <TypedIdentifier>
|
71
|
-
end
|
72
|
-
|
73
|
-
rule literal
|
74
|
-
nil / true / false / number / string / symbol
|
75
|
-
end
|
76
|
-
|
77
|
-
rule number
|
78
|
-
float / integer
|
79
|
-
end
|
80
|
-
|
81
|
-
rule integer
|
82
|
-
('+' / '-')? [0-9]+ <IntegerLiteral>
|
83
|
-
end
|
84
|
-
|
85
|
-
rule float
|
86
|
-
('+' / '-')? [0-9]+ '.' [0-9]+ <FloatLiteral>
|
87
|
-
end
|
88
|
-
|
89
|
-
rule symbol
|
90
|
-
':' (string / any / variable_identifier) <SymbolLiteral>
|
91
|
-
end
|
92
|
-
|
93
|
-
rule string
|
94
|
-
double_quoted_string / single_quoted_string
|
95
|
-
end
|
96
|
-
|
97
|
-
rule double_quoted_string
|
98
|
-
'"' ([^"\\] / '\\' . )* '"' <DoubleQuotedStringLiteral>
|
99
|
-
end
|
100
|
-
|
101
|
-
rule single_quoted_string
|
102
|
-
"'" ([^'\\] / '\\' . )* "'" <StringLiteral>
|
103
|
-
end
|
104
|
-
|
105
|
-
rule true
|
106
|
-
'true' <TrueLiteral>
|
107
|
-
end
|
108
|
-
|
109
|
-
rule false
|
110
|
-
'false' <FalseLiteral>
|
111
|
-
end
|
112
|
-
|
113
|
-
rule nil
|
114
|
-
'nil' <NilLiteral>
|
115
|
-
end
|
116
|
-
|
117
|
-
rule extractor
|
118
|
-
type '(' non_empty_array? ')' <ExtractorLiteral>
|
119
|
-
end
|
120
|
-
|
121
|
-
rule type
|
122
|
-
constant ('::' constant)* <TypeLiteral>
|
123
|
-
end
|
124
|
-
|
125
|
-
rule constant
|
126
|
-
[A-Z] [a-zA-Z0-9_]*
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Fear
|
4
|
-
module Extractor
|
5
|
-
class IdentifierMatcher < Matcher
|
6
|
-
# @!attribute name
|
7
|
-
# @return [Types::Strict::Symbol]
|
8
|
-
|
9
|
-
def defined_at?(_)
|
10
|
-
true
|
11
|
-
end
|
12
|
-
|
13
|
-
def bindings(other)
|
14
|
-
{ name => other }
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ostruct"
|
4
|
-
|
5
|
-
module Fear
|
6
|
-
module Extractor
|
7
|
-
class Matcher < OpenStruct
|
8
|
-
# Combine two matchers, so both should pass
|
9
|
-
class And < Matcher
|
10
|
-
def initialize(matcher1, matcher2)
|
11
|
-
@matcher1 = matcher1
|
12
|
-
@matcher2 = matcher2
|
13
|
-
end
|
14
|
-
attr_reader :matcher1, :matcher2
|
15
|
-
|
16
|
-
def defined_at?(arg)
|
17
|
-
matcher1.defined_at?(arg) && matcher2.defined_at?(arg)
|
18
|
-
end
|
19
|
-
|
20
|
-
def bindings(arg)
|
21
|
-
matcher1.bindings(arg).merge(matcher2.bindings(arg))
|
22
|
-
end
|
23
|
-
|
24
|
-
def failure_reason(arg)
|
25
|
-
if matcher1.defined_at?(arg)
|
26
|
-
if matcher2.defined_at?(arg)
|
27
|
-
Fear.none
|
28
|
-
else
|
29
|
-
matcher2.failure_reason(arg)
|
30
|
-
end
|
31
|
-
else
|
32
|
-
matcher1.failure_reason(arg)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ostruct"
|
4
|
-
|
5
|
-
module Fear
|
6
|
-
module Extractor
|
7
|
-
# @abstract abstract matcher to inherit from.
|
8
|
-
class Matcher < OpenStruct
|
9
|
-
autoload :And, "fear/extractor/matcher/and"
|
10
|
-
|
11
|
-
# @param node [Fear::Extractor::Grammar::Node]
|
12
|
-
def initialize(node:, **attributes)
|
13
|
-
@input = node.input
|
14
|
-
@input_position = node.interval.first
|
15
|
-
super(attributes)
|
16
|
-
end
|
17
|
-
attr_reader :input_position, :input
|
18
|
-
private :input
|
19
|
-
private :input_position
|
20
|
-
|
21
|
-
def call(arg)
|
22
|
-
call_or_else(arg, &PartialFunction::EMPTY)
|
23
|
-
end
|
24
|
-
|
25
|
-
def and(other)
|
26
|
-
And.new(self, other)
|
27
|
-
end
|
28
|
-
|
29
|
-
# @param arg [any]
|
30
|
-
# @yield [arg] if function not defined
|
31
|
-
def call_or_else(arg)
|
32
|
-
if defined_at?(arg)
|
33
|
-
bindings(arg)
|
34
|
-
else
|
35
|
-
yield arg
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Shows why matcher has failed. Use it for debugging.
|
40
|
-
# @example
|
41
|
-
# Fear['[1, 2, _]'].failure_reason([1, 3, 4])
|
42
|
-
# # it will show that the second element hasn't match
|
43
|
-
#
|
44
|
-
def failure_reason(other)
|
45
|
-
if defined_at?(other)
|
46
|
-
Fear.none
|
47
|
-
else
|
48
|
-
Fear.some("Expected `#{other.inspect}` to match:\n#{input}\n#{"~" * input_position}^")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Fear
|
4
|
-
module Extractor
|
5
|
-
# Match against array splat, and capture rest of an array
|
6
|
-
# E.g. +[1, 2, *tail]+
|
7
|
-
#
|
8
|
-
class NamedArraySplatMatcher < ArraySplatMatcher
|
9
|
-
# @!attribute name
|
10
|
-
# @return [Types::Strict::Symbol]
|
11
|
-
|
12
|
-
def bindings(other)
|
13
|
-
{ name => other }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "lru_redux"
|
4
|
-
|
5
|
-
module Fear
|
6
|
-
module Extractor
|
7
|
-
# Parse pattern. Used within +Fear[]+
|
8
|
-
class Pattern
|
9
|
-
DEFAULT_PATTERN_CACHE_SIZE = 10_000
|
10
|
-
private_constant :DEFAULT_PATTERN_CACHE_SIZE
|
11
|
-
@pattern_cache = LruRedux::Cache.new(ENV.fetch("FEAR_PATTERNS_CACHE_SIZE", DEFAULT_PATTERN_CACHE_SIZE))
|
12
|
-
|
13
|
-
class << self
|
14
|
-
attr_reader :pattern_cache
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(pattern)
|
18
|
-
@matcher = compile_pattern(pattern)
|
19
|
-
end
|
20
|
-
attr_reader :matcher
|
21
|
-
private :matcher
|
22
|
-
|
23
|
-
private def compile_pattern(pattern)
|
24
|
-
self.class.pattern_cache.getset(pattern) do
|
25
|
-
compile_pattern_without_cache(pattern)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private def compile_pattern_without_cache(pattern)
|
30
|
-
parser = Extractor::GrammarParser.new
|
31
|
-
if (result = parser.parse(pattern))
|
32
|
-
result.to_matcher
|
33
|
-
else
|
34
|
-
raise PatternSyntaxError, syntax_error_message(parser, pattern)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def ===(other)
|
39
|
-
matcher.defined_at?(other)
|
40
|
-
end
|
41
|
-
|
42
|
-
def and_then(other)
|
43
|
-
Fear::PartialFunction::Combined.new(matcher, other)
|
44
|
-
end
|
45
|
-
|
46
|
-
def failure_reason(other)
|
47
|
-
matcher.failure_reason(other).get_or_else { "It matches" }
|
48
|
-
end
|
49
|
-
|
50
|
-
private def syntax_error_message(parser, pattern)
|
51
|
-
parser.failure_reason =~ /^(Expected .+) after/m
|
52
|
-
"#{Regexp.last_match(1).gsub("\n", "$NEWLINE")}:\n" +
|
53
|
-
pattern.split("\n")[parser.failure_line - 1] + "\n" \
|
54
|
-
"#{"~" * (parser.failure_column - 1)}^\n"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Fear
|
4
|
-
module Extractor
|
5
|
-
# Match and capture identifier with specific type. E.g. +foo : Integer+
|
6
|
-
#
|
7
|
-
class TypedIdentifierMatcher < Matcher
|
8
|
-
# @!attribute identifier
|
9
|
-
# @return [IdentifierMatcher]
|
10
|
-
# @!attribute type
|
11
|
-
# @return [ValueMatcher]
|
12
|
-
|
13
|
-
def defined_at?(other)
|
14
|
-
type.defined_at?(other)
|
15
|
-
end
|
16
|
-
|
17
|
-
def bindings(other)
|
18
|
-
{ identifier.name => other }
|
19
|
-
end
|
20
|
-
|
21
|
-
def failure_reason(other)
|
22
|
-
type.failure_reason(other)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Fear
|
4
|
-
module Extractor
|
5
|
-
# Match against values -- true, false, 1, "foo" etc.
|
6
|
-
class ValueMatcher < Matcher
|
7
|
-
# @!attribute value
|
8
|
-
# @return [Any]
|
9
|
-
|
10
|
-
def defined_at?(arg)
|
11
|
-
value === arg
|
12
|
-
end
|
13
|
-
|
14
|
-
def bindings(_)
|
15
|
-
Utils::EMPTY_HASH
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/fear/extractor.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "treetop"
|
4
|
-
require "fear/extractor/grammar"
|
5
|
-
Treetop.load File.expand_path("extractor/grammar.treetop", __dir__)
|
6
|
-
|
7
|
-
module Fear
|
8
|
-
# @api private
|
9
|
-
module Extractor
|
10
|
-
autoload :Pattern, "fear/extractor/pattern"
|
11
|
-
autoload :Matcher, "fear/extractor/matcher"
|
12
|
-
|
13
|
-
autoload :AnonymousArraySplatMatcher, "fear/extractor/anonymous_array_splat_matcher"
|
14
|
-
autoload :AnyMatcher, "fear/extractor/any_matcher"
|
15
|
-
autoload :ArrayHeadMatcher, "fear/extractor/array_head_matcher"
|
16
|
-
autoload :ArrayMatcher, "fear/extractor/array_matcher"
|
17
|
-
autoload :ArraySplatMatcher, "fear/extractor/array_splat_matcher"
|
18
|
-
autoload :EmptyListMatcher, "fear/extractor/empty_list_matcher"
|
19
|
-
autoload :ExtractorMatcher, "fear/extractor/extractor_matcher"
|
20
|
-
autoload :IdentifierMatcher, "fear/extractor/identifier_matcher"
|
21
|
-
autoload :NamedArraySplatMatcher, "fear/extractor/named_array_splat_matcher"
|
22
|
-
autoload :TypedIdentifierMatcher, "fear/extractor/typed_identifier_matcher"
|
23
|
-
autoload :ValueMatcher, "fear/extractor/value_matcher"
|
24
|
-
|
25
|
-
ExtractorNotFound = Class.new(Error)
|
26
|
-
public_constant :ExtractorNotFound
|
27
|
-
|
28
|
-
@mutex = Mutex.new
|
29
|
-
@registry = PartialFunction::EMPTY
|
30
|
-
|
31
|
-
EXTRACTOR_NOT_FOUND = proc do |klass|
|
32
|
-
raise ExtractorNotFound, "could not find extractor for " + klass.inspect
|
33
|
-
end
|
34
|
-
private_constant :EXTRACTOR_NOT_FOUND
|
35
|
-
|
36
|
-
class << self
|
37
|
-
# @param klass [Class, String]
|
38
|
-
# @api private
|
39
|
-
def find_extractor(klass)
|
40
|
-
@registry.call_or_else(klass, &EXTRACTOR_NOT_FOUND)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Register extractor for given class
|
44
|
-
# @!method register_extractor(*names, extractor)
|
45
|
-
# @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
|
46
|
-
# @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
|
47
|
-
# of extracted value('s)
|
48
|
-
#
|
49
|
-
# @example
|
50
|
-
# register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
|
51
|
-
#
|
52
|
-
# register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
|
53
|
-
#
|
54
|
-
# @example registering an alias. Alias should be CamelCased string
|
55
|
-
# register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
|
56
|
-
#
|
57
|
-
# # no you can extract Fear::Some's using Some alias
|
58
|
-
# m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
|
59
|
-
#
|
60
|
-
def register_extractor(*args)
|
61
|
-
*keys, extractor = *args
|
62
|
-
|
63
|
-
@mutex.synchronize do
|
64
|
-
keys.uniq.each do |key|
|
65
|
-
@registry = BUILD_EXTRACTOR.(key, extractor).or_else(@registry)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
BUILD_EXTRACTOR = proc do |key, extractor|
|
72
|
-
Fear.matcher do |m|
|
73
|
-
case key
|
74
|
-
when String
|
75
|
-
m.case(Module, ->(lookup) { lookup.to_s == key }) { extractor }
|
76
|
-
m.case(String, key) { extractor }
|
77
|
-
when Module
|
78
|
-
m.case(Module, ->(lookup) { lookup <= key }) { extractor }
|
79
|
-
m.case(String, key.to_s) { extractor }
|
80
|
-
else
|
81
|
-
m.case(key) { extractor } # may it be useful to register other types of keys? lambda?
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Multiple arguments extractor example
|
88
|
-
register_extractor("Date", proc do |other|
|
89
|
-
if other.class.name == "Date"
|
90
|
-
Fear.some([other.year, other.month, other.day])
|
91
|
-
else
|
92
|
-
Fear.none
|
93
|
-
end
|
94
|
-
end)
|
95
|
-
register_extractor(::Struct, Fear.case(::Struct, &:to_a).lift)
|
96
|
-
# No argument boolean extractor example
|
97
|
-
register_extractor("IsEven", proc do |int|
|
98
|
-
if int.is_a?(Integer) && int.even?
|
99
|
-
Fear.some([])
|
100
|
-
else
|
101
|
-
Fear.none
|
102
|
-
end
|
103
|
-
end)
|
104
|
-
# Single argument extractor example
|
105
|
-
register_extractor("Fear::Some", "Some", Some::EXTRACTOR)
|
106
|
-
register_extractor("Fear::None", "None", NoneClass::EXTRACTOR)
|
107
|
-
register_extractor("Fear::Right", "Right", Right::EXTRACTOR)
|
108
|
-
register_extractor("Fear::Left", "Left", Left::EXTRACTOR)
|
109
|
-
register_extractor("Fear::Success", "Success", Success::EXTRACTOR)
|
110
|
-
register_extractor("Fear::Failure", "Failure", Failure::EXTRACTOR)
|
111
|
-
end
|
112
|
-
end
|