mutant 0.11.10 → 0.11.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 163279d053e71f375fc6854b89e1ca0730d7de6a1cf883772097a635067fdc92
4
- data.tar.gz: c943e8ad5544a90d967ac9c580557a1e41997de4a2363cffe829710f0a71c8ae
3
+ metadata.gz: 6aa7e42cfe0d8ed12281745fc607d4d933fbb03af10b2bb41c41e6df3765ef89
4
+ data.tar.gz: 2a4a940e72bf2381816cfc0522f236166df7c2f649bd4da6ee8c2f41a6851c33
5
5
  SHA512:
6
- metadata.gz: 1a130b60a8e53fb84d6b95897bc2749007df58d114b8201ac409eb40f203ee9f90804df51f49083c345784d420335eaeaa884299e0c1d37634be6d72875b5580
7
- data.tar.gz: 18845045b045a1692c5574c092f7035560cab6267a8bd2a904266a69919630df59a47e0cc0d6edfc32717c834824b1b8d698da979aa7480359d363c1df9f1c1f
6
+ metadata.gz: 9ffa1cf3efbe20ba6ac20d7aee9765cbb8833a2934f2d1b874ec800673582c709e76bcbdb6a026191bd751a9f30726720c5f0f81b09e0999805608e22d775dc7
7
+ data.tar.gz: b4a2cf6f0fc16056e8f92b306828c4e5578f0b1408770b0395b4874ff887a3f1cbe5023613694493736bcdede6c906dfaed06c1b73e78f2b8b5821b2fb61457b
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ class Pattern
6
+ # rubocop:disable Metrics/ClassLength
7
+ class Lexer
8
+ WHITESPACE = [' ', "\t", "\n"].to_set.freeze
9
+ STRING_PATTERN = /\A[a-zA-Z][_a-zA-Z0-9]*\z/.freeze
10
+
11
+ SINGLE_CHAR =
12
+ {
13
+ '(' => :group_start,
14
+ ')' => :group_end,
15
+ ',' => :delimiter,
16
+ '=' => :eq,
17
+ '{' => :properties_start,
18
+ '}' => :properties_end
19
+ }.freeze
20
+
21
+ def self.call(string)
22
+ new(string).__send__(:run)
23
+ end
24
+
25
+ class Error
26
+ include Anima.new(:token)
27
+
28
+ class InvalidToken < self
29
+ def display_message
30
+ <<~MESSAGE.strip
31
+ Invalid #{token.type} token:
32
+ #{token.display_location}
33
+ MESSAGE
34
+ end
35
+ end # Token
36
+ end # Error
37
+
38
+ private_class_method :new
39
+
40
+ private
41
+
42
+ def initialize(string)
43
+ @line_index = 0
44
+ @line_start = 0
45
+ @next_position = 0
46
+ @source = Source.new(string: string)
47
+ @string = string
48
+ @tokens = []
49
+ end
50
+
51
+ def run
52
+ consume
53
+
54
+ if instance_variable_defined?(:@error)
55
+ Either::Left.new(@error)
56
+ else
57
+ Either::Right.new(@tokens)
58
+ end
59
+ end
60
+
61
+ def consume
62
+ while next? && !instance_variable_defined?(:@error)
63
+ skip_whitespace
64
+
65
+ consume_char || consume_string
66
+
67
+ skip_whitespace
68
+ end
69
+ end
70
+
71
+ def consume_char
72
+ start_position = @next_position
73
+
74
+ char = peek
75
+
76
+ type = SINGLE_CHAR.fetch(char) { return }
77
+
78
+ advance_position
79
+
80
+ @tokens << token(type: type, start_position: start_position)
81
+ end
82
+
83
+ def token(type:, start_position:, value: nil)
84
+ Token.new(
85
+ type: type,
86
+ value: value,
87
+ location: Source::Location.new(
88
+ source: @source,
89
+ line_index: @line_index,
90
+ line_start: @line_start,
91
+ range: range_from(start_position)
92
+ )
93
+ )
94
+ end
95
+
96
+ def consume_string
97
+ start_position = @next_position
98
+
99
+ token = build_string(start_position, read_string_body)
100
+
101
+ if valid_string?(token.value)
102
+ @tokens << token
103
+ else
104
+ @error = Error::InvalidToken.new(token: token)
105
+ end
106
+ end
107
+
108
+ def read_string_body
109
+ string = +''
110
+
111
+ while next?
112
+ char = peek
113
+ break if SINGLE_CHAR.key?(char) || whitespace?(char)
114
+
115
+ string << char
116
+ advance_position
117
+ end
118
+
119
+ string
120
+ end
121
+
122
+ def build_string(start_position, string)
123
+ token(
124
+ type: :string,
125
+ value: string,
126
+ start_position: start_position
127
+ )
128
+ end
129
+
130
+ def range_from(start_position)
131
+ start_position...@next_position
132
+ end
133
+
134
+ def valid_string?(string)
135
+ STRING_PATTERN.match?(string)
136
+ end
137
+
138
+ def advance_position
139
+ @next_position += 1
140
+ end
141
+
142
+ def skip_whitespace
143
+ loop do
144
+ char = peek
145
+
146
+ break unless whitespace?(char)
147
+
148
+ if char.eql?("\n")
149
+ @line_start = @next_position.succ
150
+ @line_index += 1
151
+ end
152
+
153
+ advance_position
154
+ end
155
+ end
156
+
157
+ def peek
158
+ @string[@next_position]
159
+ end
160
+
161
+ def whitespace?(char)
162
+ WHITESPACE.include?(char)
163
+ end
164
+
165
+ def next?
166
+ @next_position < @string.length
167
+ end
168
+ end # Lexer
169
+ end # Pattern
170
+ end # AST
171
+ end # Mutant
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ # rubocop:disable Metrics/ClassLength
6
+ # rubocop:disable Metrics/MethodLength
7
+ class Pattern
8
+ class Parser
9
+ def initialize(tokens)
10
+ @tokens = tokens
11
+ @next_position = 0
12
+ end
13
+
14
+ def self.call(tokens)
15
+ new(tokens).__send__(:run)
16
+ end
17
+
18
+ private
19
+
20
+ def error?
21
+ instance_variable_defined?(:@error)
22
+ end
23
+
24
+ def run
25
+ node = catch(:abort) do
26
+ parse_node.tap do
27
+ if next?
28
+ token = peek
29
+ error(
30
+ message: "Unexpected token: #{token.type}",
31
+ token: token
32
+ )
33
+ end
34
+ end
35
+ end
36
+
37
+ if error?
38
+ Either::Left.new(@error)
39
+ else
40
+ Either::Right.new(node)
41
+ end
42
+ end
43
+
44
+ def parse_node
45
+ structure = parse_node_type
46
+
47
+ attribute, descendant = nil
48
+
49
+ if optional(:properties_start)
50
+ loop do
51
+ break if optional(:properties_end)
52
+
53
+ name = expect(:string)
54
+
55
+ name_sym = name.value.to_sym
56
+
57
+ if structure.maybe_attribute(name_sym)
58
+ expect(:eq)
59
+ attribute = parse_attribute(name_sym)
60
+ next
61
+ end
62
+
63
+ if structure.maybe_descendant(name_sym)
64
+ expect(:eq)
65
+ descendant = parse_descendant(name_sym)
66
+ next
67
+ end
68
+
69
+ error(
70
+ message: "Node: #{structure.type} has no property named: #{name_sym}",
71
+ token: name
72
+ )
73
+ end
74
+ end
75
+
76
+ Node.new(
77
+ attribute: attribute,
78
+ descendant: descendant,
79
+ type: structure.type
80
+ )
81
+ end
82
+
83
+ def parse_attribute(name)
84
+ Node::Attribute.new(
85
+ name: name,
86
+ value: parse_alternative(
87
+ group_start: method(:parse_attribute_group),
88
+ string: method(:parse_attribute_value)
89
+ )
90
+ )
91
+ end
92
+
93
+ def parse_alternative(alternatives)
94
+ token = peek
95
+
96
+ alternatives.fetch(token.type) do
97
+ error(
98
+ message: "Expected one of: #{alternatives.keys.join(',')} but got: #{token.type}",
99
+ token: token
100
+ )
101
+ end.call
102
+ end
103
+
104
+ def parse_descendant(name)
105
+ Node::Descendant.new(
106
+ name: name,
107
+ pattern: parse_node
108
+ )
109
+ end
110
+
111
+ def parse_attribute_group
112
+ expect(:group_start)
113
+
114
+ values = []
115
+
116
+ loop do
117
+ values << parse_attribute_value
118
+ break unless optional(:delimiter)
119
+ end
120
+
121
+ expect(:group_end)
122
+
123
+ Node::Attribute::Value::Group.new(values: values)
124
+ end
125
+
126
+ def parse_attribute_value
127
+ Node::Attribute::Value::Single.new(value: expect(:string).value.to_sym)
128
+ end
129
+
130
+ def error(message:, token: nil)
131
+ @error =
132
+ if token
133
+ "#{message}\n#{token.display_location}"
134
+ else
135
+ message
136
+ end
137
+
138
+ throw(:abort)
139
+ end
140
+
141
+ def optional(type)
142
+ token = peek
143
+
144
+ return unless token&.type.equal?(type)
145
+
146
+ advance_position
147
+ token
148
+ end
149
+
150
+ def parse_node_type
151
+ token = expect(:string)
152
+
153
+ type = token.value.to_sym
154
+
155
+ Structure::ALL.fetch(type) do
156
+ error(token: token, message: "Expected valid node type got: #{type}")
157
+ end
158
+ end
159
+
160
+ def expect(type)
161
+ token = peek
162
+
163
+ unless token
164
+ error(message: "Expected token of type: #{type}, but got no token at all")
165
+ end
166
+
167
+ if token.type.eql?(type)
168
+ advance_position
169
+ token
170
+ else
171
+ error(
172
+ token: token,
173
+ message: "Expected token type: #{type} but got: #{token.type}"
174
+ )
175
+ end
176
+ end
177
+
178
+ def peek
179
+ @tokens.at(@next_position)
180
+ end
181
+
182
+ def advance_position
183
+ @next_position += 1
184
+ end
185
+
186
+ def next?
187
+ @next_position < @tokens.length
188
+ end
189
+ end
190
+ end # Pattern
191
+ # rubocop:enable Metrics/ClassLength
192
+ # rubocop:enable Metrics/MethodLength
193
+ end # AST
194
+ end # Mutant
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ class Pattern
6
+ class Source
7
+ include Anima.new(:string)
8
+
9
+ def initialize(**attributes)
10
+ super
11
+
12
+ @lines = string.split("\n")
13
+ end
14
+
15
+ def line(line_index)
16
+ @lines.fetch(line_index)
17
+ end
18
+
19
+ class Location
20
+ include Anima.new(:source, :range, :line_index, :line_start)
21
+
22
+ def display
23
+ "#{source.line(line_index)}\n#{prefix}#{carets}"
24
+ end
25
+
26
+ private
27
+
28
+ def prefix
29
+ ' ' * (range.begin - line_start)
30
+ end
31
+
32
+ def carets
33
+ '^' * range.size
34
+ end
35
+ end
36
+ end # Source
37
+ end # Pattern
38
+ end # AST
39
+ end # Mutant
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ class Pattern
6
+ class Token
7
+ include Anima.new(:type, :value, :location)
8
+
9
+ def display_location
10
+ location.display
11
+ end
12
+ end # Token
13
+ end # Pattern
14
+ end # AST
15
+ end # Mutant
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ class Pattern
6
+ include Adamantium
7
+
8
+ def self.parse(syntax)
9
+ Lexer.call(syntax)
10
+ .lmap(&:display_message)
11
+ .bind(&Parser.public_method(:call))
12
+ end
13
+
14
+ class Node < self
15
+ include Anima.new(:type, :attribute, :descendant, :variable)
16
+
17
+ DEFAULTS = { attribute: nil, descendant: nil, variable: nil }.freeze
18
+
19
+ def initialize(attributes)
20
+ super(DEFAULTS.merge(attributes))
21
+ end
22
+
23
+ class Attribute
24
+ include Anima.new(:name, :value)
25
+
26
+ class Value
27
+ class Single < self
28
+ include Adamantium, Anima.new(:value)
29
+
30
+ def match?(input)
31
+ input.eql?(value)
32
+ end
33
+
34
+ def syntax
35
+ value
36
+ end
37
+ end
38
+
39
+ class Group < self
40
+ include Adamantium, Anima.new(:values)
41
+
42
+ def match?(value)
43
+ values.any? do |attribute_value|
44
+ attribute_value.match?(value)
45
+ end
46
+ end
47
+
48
+ def syntax
49
+ "(#{values.map(&:syntax).join(',')})"
50
+ end
51
+ end # Group
52
+ end # Value
53
+
54
+ def match?(node)
55
+ attribute = Structure.for(node.type).attribute(name) and value.match?(attribute.value(node))
56
+ end
57
+
58
+ def syntax
59
+ "#{name}=#{value.syntax}"
60
+ end
61
+ end # Attribute
62
+
63
+ class Descendant
64
+ include Anima.new(:name, :pattern)
65
+
66
+ def match?(node)
67
+ descendant = Structure.for(node.type).descendant(name).value(node)
68
+
69
+ !descendant.nil? && pattern.match?(descendant)
70
+ end
71
+
72
+ def syntax
73
+ "#{name}=#{pattern.syntax}"
74
+ end
75
+ end # Descendant
76
+
77
+ def match?(node)
78
+ fail NotImplementedError if variable
79
+
80
+ node.type.eql?(type) \
81
+ && (!attribute || attribute.match?(node)) \
82
+ && (!descendant || descendant.match?(node))
83
+ end
84
+
85
+ def syntax
86
+ "#{type}#{pair_syntax}"
87
+ end
88
+
89
+ private
90
+
91
+ def pair_syntax
92
+ pairs = [*attribute&.syntax, *descendant&.syntax]
93
+
94
+ return if pairs.empty?
95
+
96
+ "{#{pairs.join(' ')}}"
97
+ end
98
+ end
99
+
100
+ class Any < self
101
+ def match?(_node)
102
+ true
103
+ end
104
+ end
105
+
106
+ class None < self
107
+ def match?(_node)
108
+ false
109
+ end
110
+ end
111
+
112
+ class Deep < self
113
+ include Anima.new(:pattern)
114
+
115
+ def match?(node)
116
+ Structure.for(node.type).each_node(node) do |child|
117
+ return true if pattern.match?(child)
118
+ end
119
+
120
+ false
121
+ end
122
+ end
123
+ end # Pattern
124
+ end # AST
125
+ end # Mutant