gullah 0.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.
@@ -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