hoozuki 0.2.0 → 1.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/README.md +1 -1
- data/Rakefile +12 -2
- data/lib/hoozuki/automaton/dfa/builder.rb +79 -0
- data/lib/hoozuki/automaton/dfa.rb +4 -41
- data/lib/hoozuki/automaton/nfa.rb +29 -108
- data/lib/hoozuki/automaton/state_id.rb +2 -1
- data/lib/hoozuki/instruction/char.rb +1 -1
- data/lib/hoozuki/instruction/jmp.rb +1 -1
- data/lib/hoozuki/instruction/match.rb +1 -1
- data/lib/hoozuki/instruction/split.rb +1 -1
- data/lib/hoozuki/node/choice.rb +13 -1
- data/lib/hoozuki/node/concatenation.rb +16 -1
- data/lib/hoozuki/node/epsilon.rb +8 -1
- data/lib/hoozuki/node/literal.rb +9 -1
- data/lib/hoozuki/node/repetition.rb +55 -1
- data/lib/hoozuki/parser.rb +888 -76
- data/lib/hoozuki/parser.y +128 -0
- data/lib/hoozuki/version.rb +2 -2
- data/lib/hoozuki/vm/compiler.rb +92 -47
- data/lib/hoozuki/vm/evaluator.rb +6 -6
- data/lib/hoozuki.rb +15 -16
- data/spec/hoozuki/automaton/dfa/builder_spec.rb +79 -0
- data/spec/hoozuki/automaton/dfa_spec.rb +149 -0
- data/spec/hoozuki/automaton/nfa_spec.rb +168 -0
- data/spec/hoozuki/instruction_spec.rb +88 -0
- data/spec/hoozuki/node_spec.rb +110 -0
- data/spec/hoozuki/parser_spec.rb +168 -0
- data/spec/hoozuki/vm/compiler_spec.rb +219 -0
- data/spec/hoozuki/vm/evaluator_spec.rb +260 -0
- data/spec/hoozuki_spec.rb +177 -3
- metadata +12 -2
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Hoozuki::VM::Compiler do
|
|
4
|
+
describe '#compile' do
|
|
5
|
+
let(:compiler) { described_class.new }
|
|
6
|
+
|
|
7
|
+
context 'with Literal node' do
|
|
8
|
+
it 'compiles to Char and Match instructions' do
|
|
9
|
+
node = Hoozuki::Node::Literal.new('a')
|
|
10
|
+
compiler.compile(node)
|
|
11
|
+
|
|
12
|
+
expect(compiler.instructions.size).to eq(2)
|
|
13
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Char)
|
|
14
|
+
expect(compiler.instructions[0].char).to eq('a')
|
|
15
|
+
expect(compiler.instructions[1]).to be_a(Hoozuki::Instruction::Match)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'handles multibyte characters' do
|
|
19
|
+
node = Hoozuki::Node::Literal.new('あ')
|
|
20
|
+
compiler.compile(node)
|
|
21
|
+
|
|
22
|
+
expect(compiler.instructions[0].char).to eq('あ')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'with Epsilon node' do
|
|
27
|
+
it 'compiles to only Match instruction' do
|
|
28
|
+
node = Hoozuki::Node::Epsilon.new
|
|
29
|
+
compiler.compile(node)
|
|
30
|
+
|
|
31
|
+
expect(compiler.instructions.size).to eq(1)
|
|
32
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Match)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'with Concatenation node' do
|
|
37
|
+
it 'compiles to sequential Char instructions' do
|
|
38
|
+
node = Hoozuki::Node::Concatenation.new([
|
|
39
|
+
Hoozuki::Node::Literal.new('a'),
|
|
40
|
+
Hoozuki::Node::Literal.new('b')
|
|
41
|
+
])
|
|
42
|
+
compiler.compile(node)
|
|
43
|
+
|
|
44
|
+
expect(compiler.instructions.size).to eq(3)
|
|
45
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Char)
|
|
46
|
+
expect(compiler.instructions[0].char).to eq('a')
|
|
47
|
+
expect(compiler.instructions[1]).to be_a(Hoozuki::Instruction::Char)
|
|
48
|
+
expect(compiler.instructions[1].char).to eq('b')
|
|
49
|
+
expect(compiler.instructions[2]).to be_a(Hoozuki::Instruction::Match)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'handles longer concatenations' do
|
|
53
|
+
node = Hoozuki::Node::Concatenation.new([
|
|
54
|
+
Hoozuki::Node::Literal.new('a'),
|
|
55
|
+
Hoozuki::Node::Literal.new('b'),
|
|
56
|
+
Hoozuki::Node::Literal.new('c')
|
|
57
|
+
])
|
|
58
|
+
compiler.compile(node)
|
|
59
|
+
|
|
60
|
+
expect(compiler.instructions.size).to eq(4)
|
|
61
|
+
expect(compiler.instructions.map { |i| i.is_a?(Hoozuki::Instruction::Char) ? i.char : nil }.compact).to eq(['a', 'b', 'c'])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'with Choice node' do
|
|
66
|
+
it 'compiles with Split and Jmp instructions' do
|
|
67
|
+
node = Hoozuki::Node::Choice.new([
|
|
68
|
+
Hoozuki::Node::Literal.new('a'),
|
|
69
|
+
Hoozuki::Node::Literal.new('b')
|
|
70
|
+
])
|
|
71
|
+
compiler.compile(node)
|
|
72
|
+
|
|
73
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Split)
|
|
74
|
+
expect(compiler.instructions).to include(an_instance_of(Hoozuki::Instruction::Jmp))
|
|
75
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'sets correct Split targets' do
|
|
79
|
+
node = Hoozuki::Node::Choice.new([
|
|
80
|
+
Hoozuki::Node::Literal.new('a'),
|
|
81
|
+
Hoozuki::Node::Literal.new('b')
|
|
82
|
+
])
|
|
83
|
+
compiler.compile(node)
|
|
84
|
+
|
|
85
|
+
split = compiler.instructions[0]
|
|
86
|
+
expect(split.left).to eq(1)
|
|
87
|
+
expect(split.right).to be > split.left
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'with Repetition node' do
|
|
92
|
+
context 'with zero_or_more' do
|
|
93
|
+
it 'compiles with Split and Jmp for looping' do
|
|
94
|
+
node = Hoozuki::Node::Repetition.new(
|
|
95
|
+
Hoozuki::Node::Literal.new('a'),
|
|
96
|
+
:zero_or_more
|
|
97
|
+
)
|
|
98
|
+
compiler.compile(node)
|
|
99
|
+
|
|
100
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Split)
|
|
101
|
+
expect(compiler.instructions).to include(an_instance_of(Hoozuki::Instruction::Jmp))
|
|
102
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'creates correct loop structure' do
|
|
106
|
+
node = Hoozuki::Node::Repetition.new(
|
|
107
|
+
Hoozuki::Node::Literal.new('a'),
|
|
108
|
+
:zero_or_more
|
|
109
|
+
)
|
|
110
|
+
compiler.compile(node)
|
|
111
|
+
|
|
112
|
+
split = compiler.instructions[0]
|
|
113
|
+
expect(split).to be_a(Hoozuki::Instruction::Split)
|
|
114
|
+
jmp = compiler.instructions.find { |i| i.is_a?(Hoozuki::Instruction::Jmp) }
|
|
115
|
+
expect(jmp.target).to eq(0)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context 'with one_or_more' do
|
|
120
|
+
it 'compiles with Char followed by Split' do
|
|
121
|
+
node = Hoozuki::Node::Repetition.new(
|
|
122
|
+
Hoozuki::Node::Literal.new('a'),
|
|
123
|
+
:one_or_more
|
|
124
|
+
)
|
|
125
|
+
compiler.compile(node)
|
|
126
|
+
|
|
127
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Char)
|
|
128
|
+
expect(compiler.instructions[1]).to be_a(Hoozuki::Instruction::Split)
|
|
129
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'creates correct loop structure' do
|
|
133
|
+
node = Hoozuki::Node::Repetition.new(
|
|
134
|
+
Hoozuki::Node::Literal.new('a'),
|
|
135
|
+
:one_or_more
|
|
136
|
+
)
|
|
137
|
+
compiler.compile(node)
|
|
138
|
+
|
|
139
|
+
split = compiler.instructions[1]
|
|
140
|
+
expect(split.left).to eq(0)
|
|
141
|
+
expect(split.right).to eq(2)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'with optional' do
|
|
146
|
+
it 'compiles with Split around the node' do
|
|
147
|
+
node = Hoozuki::Node::Repetition.new(
|
|
148
|
+
Hoozuki::Node::Literal.new('a'),
|
|
149
|
+
:optional
|
|
150
|
+
)
|
|
151
|
+
compiler.compile(node)
|
|
152
|
+
|
|
153
|
+
expect(compiler.instructions[0]).to be_a(Hoozuki::Instruction::Split)
|
|
154
|
+
expect(compiler.instructions[1]).to be_a(Hoozuki::Instruction::Char)
|
|
155
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'sets Split to allow skipping' do
|
|
159
|
+
node = Hoozuki::Node::Repetition.new(
|
|
160
|
+
Hoozuki::Node::Literal.new('a'),
|
|
161
|
+
:optional
|
|
162
|
+
)
|
|
163
|
+
compiler.compile(node)
|
|
164
|
+
|
|
165
|
+
split = compiler.instructions[0]
|
|
166
|
+
expect(split.left).to eq(1)
|
|
167
|
+
expect(split.right).to eq(2)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context 'with complex nested structures' do
|
|
173
|
+
it 'compiles choice within concatenation' do
|
|
174
|
+
node = Hoozuki::Node::Concatenation.new([
|
|
175
|
+
Hoozuki::Node::Literal.new('x'),
|
|
176
|
+
Hoozuki::Node::Choice.new([
|
|
177
|
+
Hoozuki::Node::Literal.new('a'),
|
|
178
|
+
Hoozuki::Node::Literal.new('b')
|
|
179
|
+
]),
|
|
180
|
+
Hoozuki::Node::Literal.new('y')
|
|
181
|
+
])
|
|
182
|
+
compiler.compile(node)
|
|
183
|
+
|
|
184
|
+
expect(compiler.instructions).to include(an_instance_of(Hoozuki::Instruction::Split))
|
|
185
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'compiles repetition within choice' do
|
|
189
|
+
node = Hoozuki::Node::Choice.new([
|
|
190
|
+
Hoozuki::Node::Repetition.new(
|
|
191
|
+
Hoozuki::Node::Literal.new('a'),
|
|
192
|
+
:zero_or_more
|
|
193
|
+
),
|
|
194
|
+
Hoozuki::Node::Literal.new('b')
|
|
195
|
+
])
|
|
196
|
+
compiler.compile(node)
|
|
197
|
+
|
|
198
|
+
expect(compiler.instructions).to include(an_instance_of(Hoozuki::Instruction::Split))
|
|
199
|
+
expect(compiler.instructions).to include(an_instance_of(Hoozuki::Instruction::Jmp))
|
|
200
|
+
expect(compiler.instructions.last).to be_a(Hoozuki::Instruction::Match)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe '#instructions' do
|
|
206
|
+
it 'returns empty array initially' do
|
|
207
|
+
compiler = described_class.new
|
|
208
|
+
expect(compiler.instructions).to eq([])
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'accumulates instructions after compile' do
|
|
212
|
+
compiler = described_class.new
|
|
213
|
+
node = Hoozuki::Node::Literal.new('a')
|
|
214
|
+
compiler.compile(node)
|
|
215
|
+
|
|
216
|
+
expect(compiler.instructions).not_to be_empty
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Hoozuki::VM::Evaluator do
|
|
4
|
+
describe '.evaluate' do
|
|
5
|
+
context 'with single Char instruction' do
|
|
6
|
+
it 'matches exact character' do
|
|
7
|
+
instructions = [
|
|
8
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
9
|
+
Hoozuki::Instruction::Match.new
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'does not match different character' do
|
|
16
|
+
instructions = [
|
|
17
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
18
|
+
Hoozuki::Instruction::Match.new
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
expect(described_class.evaluate(instructions, 'b')).to be false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'does not match empty string' do
|
|
25
|
+
instructions = [
|
|
26
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
27
|
+
Hoozuki::Instruction::Match.new
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
expect(described_class.evaluate(instructions, '')).to be false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'handles multibyte characters' do
|
|
34
|
+
instructions = [
|
|
35
|
+
Hoozuki::Instruction::Char.new('あ'),
|
|
36
|
+
Hoozuki::Instruction::Match.new
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
expect(described_class.evaluate(instructions, 'あ')).to be true
|
|
40
|
+
expect(described_class.evaluate(instructions, 'い')).to be false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'with multiple Char instructions' do
|
|
45
|
+
it 'matches sequential characters' do
|
|
46
|
+
instructions = [
|
|
47
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
48
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
49
|
+
Hoozuki::Instruction::Char.new('c'),
|
|
50
|
+
Hoozuki::Instruction::Match.new
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
expect(described_class.evaluate(instructions, 'abc')).to be true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'does not match partial sequence' do
|
|
57
|
+
instructions = [
|
|
58
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
59
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
60
|
+
Hoozuki::Instruction::Match.new
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
expect(described_class.evaluate(instructions, 'a')).to be false
|
|
64
|
+
expect(described_class.evaluate(instructions, 'abc')).to be false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'with Jmp instruction' do
|
|
69
|
+
it 'jumps to target address' do
|
|
70
|
+
instructions = [
|
|
71
|
+
Hoozuki::Instruction::Jmp.new(2),
|
|
72
|
+
Hoozuki::Instruction::Char.new('x'),
|
|
73
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
74
|
+
Hoozuki::Instruction::Match.new
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
78
|
+
expect(described_class.evaluate(instructions, 'x')).to be false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'handles backward jumps for loops' do
|
|
82
|
+
instructions = [
|
|
83
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
84
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
85
|
+
Hoozuki::Instruction::Jmp.new(0),
|
|
86
|
+
Hoozuki::Instruction::Match.new
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
expect(described_class.evaluate(instructions, '')).to be true
|
|
90
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
91
|
+
expect(described_class.evaluate(instructions, 'aaa')).to be true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context 'with Split instruction' do
|
|
96
|
+
it 'tries left branch first' do
|
|
97
|
+
instructions = [
|
|
98
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
99
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
100
|
+
Hoozuki::Instruction::Match.new,
|
|
101
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
102
|
+
Hoozuki::Instruction::Match.new
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'tries right branch if left fails' do
|
|
109
|
+
instructions = [
|
|
110
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
111
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
112
|
+
Hoozuki::Instruction::Match.new,
|
|
113
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
114
|
+
Hoozuki::Instruction::Match.new
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
expect(described_class.evaluate(instructions, 'b')).to be true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'returns false if both branches fail' do
|
|
121
|
+
instructions = [
|
|
122
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
123
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
124
|
+
Hoozuki::Instruction::Match.new,
|
|
125
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
126
|
+
Hoozuki::Instruction::Match.new
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
expect(described_class.evaluate(instructions, 'c')).to be false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context 'with Match instruction' do
|
|
134
|
+
it 'returns true only if entire input is consumed' do
|
|
135
|
+
instructions = [
|
|
136
|
+
Hoozuki::Instruction::Match.new
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
expect(described_class.evaluate(instructions, '')).to be true
|
|
140
|
+
expect(described_class.evaluate(instructions, 'a')).to be false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'returns false if input remains unconsumed' do
|
|
144
|
+
instructions = [
|
|
145
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
146
|
+
Hoozuki::Instruction::Match.new
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
expect(described_class.evaluate(instructions, 'ab')).to be false
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context 'with complex patterns' do
|
|
154
|
+
it 'handles choice pattern (a|b)' do
|
|
155
|
+
instructions = [
|
|
156
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
157
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
158
|
+
Hoozuki::Instruction::Jmp.new(4),
|
|
159
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
160
|
+
Hoozuki::Instruction::Match.new
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
164
|
+
expect(described_class.evaluate(instructions, 'b')).to be true
|
|
165
|
+
expect(described_class.evaluate(instructions, 'c')).to be false
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'handles zero-or-more pattern (a*)' do
|
|
169
|
+
instructions = [
|
|
170
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
171
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
172
|
+
Hoozuki::Instruction::Jmp.new(0),
|
|
173
|
+
Hoozuki::Instruction::Match.new
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
expect(described_class.evaluate(instructions, '')).to be true
|
|
177
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
178
|
+
expect(described_class.evaluate(instructions, 'aa')).to be true
|
|
179
|
+
expect(described_class.evaluate(instructions, 'aaa')).to be true
|
|
180
|
+
expect(described_class.evaluate(instructions, 'b')).to be false
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'handles one-or-more pattern (a+)' do
|
|
184
|
+
instructions = [
|
|
185
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
186
|
+
Hoozuki::Instruction::Split.new(0, 2),
|
|
187
|
+
Hoozuki::Instruction::Match.new
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
191
|
+
expect(described_class.evaluate(instructions, 'aa')).to be true
|
|
192
|
+
expect(described_class.evaluate(instructions, 'aaa')).to be true
|
|
193
|
+
expect(described_class.evaluate(instructions, '')).to be false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'handles optional pattern (a?)' do
|
|
197
|
+
instructions = [
|
|
198
|
+
Hoozuki::Instruction::Split.new(1, 2),
|
|
199
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
200
|
+
Hoozuki::Instruction::Match.new
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
expect(described_class.evaluate(instructions, '')).to be true
|
|
204
|
+
expect(described_class.evaluate(instructions, 'a')).to be true
|
|
205
|
+
expect(described_class.evaluate(instructions, 'aa')).to be false
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context 'with edge cases' do
|
|
210
|
+
it 'handles empty instruction list' do
|
|
211
|
+
instructions = []
|
|
212
|
+
|
|
213
|
+
expect(described_class.evaluate(instructions, '')).to be false
|
|
214
|
+
expect(described_class.evaluate(instructions, 'a')).to be false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'handles invalid pc (out of bounds)' do
|
|
218
|
+
instructions = [
|
|
219
|
+
Hoozuki::Instruction::Jmp.new(10),
|
|
220
|
+
Hoozuki::Instruction::Match.new
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
expect(described_class.evaluate(instructions, '')).to be false
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'handles long input strings' do
|
|
227
|
+
instructions = [
|
|
228
|
+
Hoozuki::Instruction::Split.new(1, 3),
|
|
229
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
230
|
+
Hoozuki::Instruction::Jmp.new(0),
|
|
231
|
+
Hoozuki::Instruction::Match.new
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
long_input = 'a' * 1000
|
|
235
|
+
expect(described_class.evaluate(instructions, long_input)).to be true
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
context 'with initial positions' do
|
|
240
|
+
it 'can start from non-zero input position' do
|
|
241
|
+
instructions = [
|
|
242
|
+
Hoozuki::Instruction::Char.new('b'),
|
|
243
|
+
Hoozuki::Instruction::Match.new
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
expect(described_class.evaluate(instructions, 'ab', 1, 0)).to be true
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'can start from non-zero pc' do
|
|
250
|
+
instructions = [
|
|
251
|
+
Hoozuki::Instruction::Char.new('x'),
|
|
252
|
+
Hoozuki::Instruction::Char.new('a'),
|
|
253
|
+
Hoozuki::Instruction::Match.new
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
expect(described_class.evaluate(instructions, 'a', 0, 1)).to be true
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|