gullah 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require 'gullah'
6
+ require 'byebug'
7
+ require 'date'
8
+
9
+ # :stopdoc:
10
+
11
+ # a test to make sure boundary rules work
12
+ class BoundaryTest < Minitest::Test
13
+ class Bounded
14
+ extend Gullah
15
+
16
+ rule :S, 'word+'
17
+
18
+ leaf :word, /\w+/
19
+ boundary :term, /[.!?](?=\s*\z|\s+"?\p{Lu})|[:;]/
20
+ end
21
+
22
+ def test_example
23
+ parses = Bounded.parse 'One sentence. Another sentence.'
24
+ assert_equal 1, parses.length, 'Got one parse.'
25
+ parse = parses.first
26
+ assert_equal 5, parse.length, 'One node per sentence plus one per boundary plus one space.'
27
+ assert_equal 2, parse.nodes.count(&:boundary?), 'There are two boundary nodes.'
28
+ end
29
+ end
data/test/date_test.rb ADDED
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require 'gullah'
6
+ require 'byebug'
7
+ require 'date'
8
+
9
+ # :stopdoc:
10
+
11
+ class DateTest < Minitest::Test
12
+ class DateGrammar
13
+ extend Gullah
14
+
15
+ rule :iso, 'year "/" month "/" day', tests: %i[sane]
16
+ rule :american, 'month "/" day "/" year', tests: %i[sane]
17
+ rule :euro, 'day "/" month "/" year', tests: %i[sane]
18
+
19
+ leaf :day, /\b\d{1,2}\b/, tests: %i[day], process: :to_i
20
+ leaf :month, /\b\d{1,2}\b/, tests: %i[month], process: :to_i
21
+ # to confirm that we can pass a proc as a processor
22
+ leaf :year, /\b\d+\b/, process: ->(n) { n.atts[:value] = n.text.to_i }
23
+
24
+ def to_i(n)
25
+ n.atts[:value] = n.text.to_i
26
+ end
27
+
28
+ def month(root, n)
29
+ if root == n.parent
30
+ month = n.atts[:value]
31
+ if month < 1
32
+ [:fail, 'month must be greater than 0']
33
+ elsif month > 12
34
+ [:fail, 'month cannot be greater than 12']
35
+ else
36
+ :pass
37
+ end
38
+ end
39
+ end
40
+
41
+ def day(root, n)
42
+ if root == n.parent
43
+ day = n.atts[:value]
44
+ if day < 1
45
+ [:fail, 'day must be greater than 0']
46
+ elsif day > 31
47
+ [:fail, 'day cannot be greater than 31']
48
+ else
49
+ :pass
50
+ end
51
+ end
52
+ end
53
+
54
+ def sane(n)
55
+ day = n.descendants.find { |o| o.name == :day }
56
+ month = n.descendants.find { |o| o.name == :month }
57
+ year = n.descendants.find { |o| o.name == :year }
58
+ if day && month && year
59
+ begin
60
+ Date.new year.atts[:value], month.atts[:value], day.atts[:value]
61
+ rescue ArgumentError
62
+ return [
63
+ :fail,
64
+ "month #{month.text} does not have a day #{day.text} in #{year.text}"
65
+ ]
66
+ end
67
+ :pass
68
+ else
69
+ [:fail, "we don't have all parts of a date"]
70
+ end
71
+ end
72
+ end
73
+
74
+ def test_iso
75
+ parses = DateGrammar.parse '2010/5/6'
76
+ assert_equal 1, parses.length, 'one parse'
77
+ parse = parses.first
78
+ assert_equal 1, parse.roots.length, 'one root node'
79
+ root = parse.roots.first
80
+ assert_equal :iso, root.name, 'got an iso date'
81
+ end
82
+
83
+ def test_american
84
+ parses = DateGrammar.parse '10/31/2021'
85
+ assert_equal 1, parses.length, 'one parse'
86
+ parse = parses.first
87
+ assert_equal 1, parse.roots.length, 'one root node'
88
+ root = parse.roots.first
89
+ assert_equal :american, root.name, 'got an American date'
90
+ end
91
+
92
+ def test_euro
93
+ parses = DateGrammar.parse '31/10/2021'
94
+ assert_equal 1, parses.length, 'one parse'
95
+ parse = parses.first
96
+ assert_equal 1, parse.roots.length, 'one root node'
97
+ root = parse.roots.first
98
+ assert_equal :euro, root.name, 'got a euro date'
99
+ end
100
+
101
+ def test_ambiguous
102
+ parses = DateGrammar.parse '5/6/1969'
103
+ assert_equal 2, parses.length, 'two parses'
104
+ options = %i[euro american]
105
+ parses.each do |p|
106
+ assert_equal 1, p.roots.length
107
+ options -= [p.roots.first.name]
108
+ end
109
+ assert_equal [], options, 'one is american and one euro'
110
+ end
111
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require 'gullah'
6
+ require 'byebug'
7
+
8
+ # :stopdoc:
9
+
10
+ # tests that all the errors that should be raised are raised
11
+ class ErrorTest < Minitest::Test
12
+ class NoLeaf
13
+ extend Gullah
14
+ end
15
+
16
+ def test_no_leaves
17
+ e = assert_raises Gullah::Error, 'leaves required' do
18
+ NoLeaf.parse 'foo'
19
+ end
20
+ assert_match(/no leaves/, e.message, 'expected no-leaf message')
21
+ end
22
+
23
+ class UndefinedRules1
24
+ extend Gullah
25
+
26
+ rule :foo, 'bar'
27
+ end
28
+
29
+ def test_undefined_rules_1
30
+ e = assert_raises Gullah::Error, 'some rules undefined' do
31
+ UndefinedRules1.parse 'bar'
32
+ end
33
+ assert_match(/no leaves/, e.message, 'remain undefined')
34
+ end
35
+
36
+ class UndefinedRules2
37
+ extend Gullah
38
+
39
+ rule :foo, 'bar baz'
40
+ leaf :bar, /bar/
41
+ end
42
+
43
+ def test_undefined_rules_2
44
+ e = assert_raises Gullah::Error, 'some rules undefined' do
45
+ UndefinedRules2.parse 'bar'
46
+ end
47
+ assert_match(/remain undefined/, e.message, 'remain undefined')
48
+ end
49
+
50
+ class AddAfterParse1
51
+ extend Gullah
52
+
53
+ rule :foo, 'bar baz'
54
+ leaf :bar, /bar/
55
+ leaf :baz, /baz/
56
+ end
57
+
58
+ def test_add_after_parse_1
59
+ e = assert_raises Gullah::Error, 'definition after parse' do
60
+ AddAfterParse1.parse 'bar baz'
61
+ AddAfterParse1.rule :plugh, 'plugh'
62
+ end
63
+ assert_match(/must be defined before parsing/, e.message, 'cannot define rule after parsing')
64
+ end
65
+
66
+ class AddAfterParse2
67
+ extend Gullah
68
+
69
+ rule :foo, 'bar baz'
70
+ leaf :bar, /bar/
71
+ leaf :baz, /baz/
72
+ end
73
+
74
+ def test_add_after_parse_2
75
+ e = assert_raises Gullah::Error, 'definition after parse' do
76
+ AddAfterParse2.parse 'bar baz'
77
+ AddAfterParse2.leaf :plugh, /plugh/
78
+ end
79
+ assert_match(/must be defined before parsing/, e.message, 'cannot define leaf after parsing')
80
+ end
81
+
82
+ class UndefinedTest
83
+ extend Gullah
84
+
85
+ rule :foo, 'bar baz', tests: %i[undefined]
86
+ leaf :bar, /bar/
87
+ leaf :baz, /baz/
88
+ end
89
+
90
+ def test_undefined_test
91
+ e = assert_raises Gullah::Error, 'undefined test' do
92
+ UndefinedTest.parse 'bar baz'
93
+ end
94
+ assert_match(/is not defined/, e.message, 'must define tests')
95
+ end
96
+
97
+ class UndefinedProcessor
98
+ extend Gullah
99
+
100
+ rule :foo, 'bar baz', process: :undefined
101
+ leaf :bar, /bar/
102
+ leaf :baz, /baz/
103
+ end
104
+
105
+ def test_undefined_processor
106
+ e = assert_raises Gullah::Error, 'undefined processor' do
107
+ UndefinedProcessor.parse 'bar baz'
108
+ end
109
+ assert_match(/is not defined/, e.message, 'must define processors')
110
+ end
111
+
112
+ class UndefinedPrecondition
113
+ extend Gullah
114
+
115
+ rule :foo, 'bar baz', preconditions: [:undefined]
116
+ leaf :bar, /bar/
117
+ leaf :baz, /baz/
118
+ end
119
+
120
+ def test_undefined_precondition
121
+ e = assert_raises Gullah::Error, 'undefined precondition' do
122
+ UndefinedPrecondition.parse 'bar baz'
123
+ end
124
+ assert_match(/is not defined/, e.message, 'must define preconditions')
125
+ end
126
+
127
+ class BadTest
128
+ extend Gullah
129
+
130
+ rule :foo, 'bar baz', tests: %i[bad]
131
+ leaf :bar, /bar/
132
+ leaf :baz, /baz/
133
+
134
+ def bad
135
+ puts 'I have no arguments at all!'
136
+ end
137
+ end
138
+
139
+ def test_zero_arity
140
+ e = assert_raises Gullah::Error, 'arity 0' do
141
+ BadTest.parse 'bar baz'
142
+ end
143
+ assert_match(/must take either one or two arguments/, e.message, 'needs one arg')
144
+ end
145
+
146
+ class AlsoBadTest
147
+ extend Gullah
148
+
149
+ rule :foo, 'bar baz', tests: %i[bad]
150
+ leaf :bar, /bar/
151
+ leaf :baz, /baz/
152
+
153
+ def bad(_root, _node, _other, _things)
154
+ puts 'I have too many arguments!'
155
+ end
156
+ end
157
+
158
+ def test_excessive_arity
159
+ e = assert_raises Gullah::Error, 'arity many' do
160
+ AlsoBadTest.parse 'bar baz'
161
+ end
162
+ assert_match(/must take either one or two arguments/, e.message, 'no more than 2 args')
163
+ end
164
+
165
+ class MisnamedRule
166
+ extend Gullah
167
+
168
+ leaf :'bar@', /bar/
169
+ leaf :baz, /baz/
170
+ end
171
+
172
+ def test_misnamed_rule
173
+ e = assert_raises Gullah::Error, 'rule name' do
174
+ MisnamedRule.rule :foo, 'bar@ baz'
175
+ end
176
+ assert_match(/cannot parse/, e.message, 'bad rule name')
177
+ end
178
+
179
+ class BadSuffix
180
+ extend Gullah
181
+
182
+ leaf :bar, /bar/
183
+ leaf :baz, /baz/
184
+ end
185
+
186
+ def test_bad_suffix_rule
187
+ e = assert_raises Gullah::Error, 'rule suffix' do
188
+ MisnamedRule.rule :foo, 'bar{2,1} baz'
189
+ end
190
+ assert_match(/is greater than/, e.message, 'bad suffix')
191
+ end
192
+
193
+ class Decent
194
+ extend Gullah
195
+
196
+ rule :foo, 'bar baz'
197
+ leaf :bar, /bar/
198
+ leaf :baz, /baz/
199
+ end
200
+
201
+ def test_filters
202
+ e = assert_raises Gullah::Error, 'unknown filter' do
203
+ Decent.parse 'bar baz', filters: %i[foo]
204
+ end
205
+ assert_match(/unknown filter/, e.message)
206
+ end
207
+
208
+ class BadTestReturnValue
209
+ extend Gullah
210
+
211
+ rule :foo, 'bar baz', tests: %i[foo]
212
+ leaf :bar, /bar/
213
+ leaf :baz, /baz/
214
+
215
+ def foo(_n)
216
+ :foo
217
+ end
218
+ end
219
+
220
+ def test_test_return_value
221
+ e = assert_raises Gullah::Error, 'bad test return value' do
222
+ BadTestReturnValue.parse 'bar baz'
223
+ end
224
+ assert_match(/unexpected value/, e.message)
225
+ end
226
+
227
+ class BadAncestorTestReturnValue
228
+ extend Gullah
229
+
230
+ rule :foo, 'bar baz'
231
+ leaf :bar, /bar/, tests: %i[foo]
232
+ leaf :baz, /baz/
233
+
234
+ def foo(_root, _n)
235
+ :foo
236
+ end
237
+ end
238
+
239
+ def test_ancestor_test_return_value
240
+ e = assert_raises Gullah::Error, 'bad ancestor test return value' do
241
+ BadAncestorTestReturnValue.parse 'bar baz'
242
+ end
243
+ assert_match(/unexpected value/, e.message)
244
+ end
245
+ end
data/test/json_test.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require 'gullah'
6
+ require 'byebug'
7
+ require 'json'
8
+
9
+ # :stopdoc:
10
+
11
+ # a proof of concept JSON parser
12
+ class JsonTest < Minitest::Test
13
+ class Gson
14
+ extend Gullah
15
+
16
+ # NOTE: these rules have processors to simplify testing
17
+ # this is *not* the most efficient way to deserialize the JSON string
18
+ # better would be to convert the AST after parsing
19
+
20
+ rule :object, '"{" key_value_pair* last_pair | empty_object', process: :objectify
21
+ rule :last_pair, 'key ":" json following_brace', process: :inherit_json_value
22
+ rule :key_value_pair, 'key ":" json ","', process: :inherit_json_value
23
+ rule :array, '"[" array_item* json? "]"', process: :arrayify
24
+ rule :json, 'complex | simple', process: :inherit_value
25
+ rule :complex, 'array | object', process: :inherit_value
26
+ rule :array_item, 'json ","', process: :inherit_value
27
+ rule :simple, 'string | null | integer | si | float | boolean', process: :inherit_value
28
+
29
+ leaf :boolean, /\b(true|false)\b/, process: ->(n) { n.atts[:value] = n.text == 'true' }
30
+ leaf :string, /'(?:[^'\\]|\\.)*'(?!\s*:)/, process: :clean_string
31
+ leaf :string, /"(?:[^"\\]|\\.)*"(?!\s*:)/, process: :clean_string
32
+ leaf :null, /\bnull\b/, process: ->(n) { n.atts[:value] = nil }
33
+ leaf :si, /\b\d\.\d+e[1-9]\d*\b/, process: ->(n) { n.atts[:value] = n.text.to_f }
34
+ leaf :float, /\b\d+\.\d+\b/, process: ->(n) { n.atts[:value] = n.text.to_f }
35
+ leaf :integer, /\b[1-9]\d*\b(?!\.\d)/, process: ->(n) { n.atts[:value] = n.text.to_i }
36
+
37
+ # terrible, horrible, no good, very bad hacks to reduce backtracking
38
+ leaf :following_brace, /}/
39
+ leaf :empty_object, /\{\s*\}/
40
+ leaf :key, /'(?:[^'\\]|\\.)*'(?=\s*:)/, process: :clean_string
41
+ leaf :key, /"(?:[^"\\]|\\.)*"(?=\s*:)/, process: :clean_string
42
+
43
+ def inherit_json_value(node)
44
+ node.atts[:value] = node.children.find { |n| n.name == :json }.atts[:value]
45
+ end
46
+
47
+ def inherit_value(node)
48
+ node.atts[:value] = node.children.first.atts[:value]
49
+ end
50
+
51
+ def clean_string(node)
52
+ text = node.text
53
+ node.atts[:value] = text[1...(text.length - 1)].gsub(/\\(.)/, '\1')
54
+ end
55
+
56
+ def arrayify(node)
57
+ node.atts[:value] = node.children.reject(&:leaf?).map do |n|
58
+ n.subtree.find { |c| c.name == :json }.atts[:value]
59
+ end
60
+ end
61
+
62
+ def objectify(node)
63
+ node.atts[:value] = if node.children.first.name == :empty_object
64
+ {}
65
+ else
66
+ node.children.reject(&:leaf?).map do |pair|
67
+ key, _, value = pair.children
68
+ [key.atts[:value], value.atts[:value]]
69
+ end.to_h
70
+ end
71
+ end
72
+ end
73
+
74
+ def test_various
75
+ [
76
+ [],
77
+ {},
78
+ 1,
79
+ 1.1,
80
+ 1.2345678901e10,
81
+ 'string',
82
+ '"string"',
83
+ [1],
84
+ { 'a' => 1 },
85
+ { 'a' => 1, 'b' => 2 },
86
+ ['2', { 'a' => false }],
87
+ [1, nil, '2', { 'a' => false }]
88
+ ].each do |val|
89
+ json = JSON.unparse(val)
90
+ parses = clock(json) { Gson.parse json }
91
+ assert_equal 1, parses.length, "unambiguous: #{json}"
92
+ parse = parses.first
93
+ root = parse.roots.first
94
+ assert_equal val, root.atts[:value], "parsed value correctly: #{json}"
95
+ end
96
+ end
97
+
98
+ # more complex patterns
99
+ def test_monsters
100
+ [
101
+ [[[[1, 2, 3]]]],
102
+ { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => { 'foo' => 1 } } } } } } } } },
103
+ { 'foo' => [1, 2, true], 'bar' => ['baz'], 'baz' => { 'v1' => nil, 'v2' => [], 'v3' => 'corge' } },
104
+ [{ 'foo bar' => 1, 'baz' => ['a string with a lot of spaces in it'] }]
105
+ ].each do |val|
106
+ json = JSON.unparse(val)
107
+ parse = clock(json) { Gson.first json }
108
+ root = parse.roots.first
109
+ assert_equal val, root.atts[:value], "parsed value correctly: #{json}"
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # for catching really slow stuff
116
+ def clock(id)
117
+ t1 = Time.now
118
+ value = yield
119
+ t2 = Time.now
120
+ delta = t2.to_f - t1.to_f
121
+ puts "\n#{id}: #{delta} seconds" if delta > 1
122
+ value
123
+ end
124
+ end