neg 0.2.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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+
2
+ # neg - CHANGELOG.md
3
+
4
+
5
+ ## neg - 1.0.0 not yet released
6
+
7
+ - initial release
8
+
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ Copyright (c) 2012-2012, John Mettraux, jmettraux@gmail.com
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,12 @@
1
+
2
+ # neg
3
+
4
+ A neg parser.
5
+
6
+ A silly little exploration project.
7
+
8
+
9
+ ## license
10
+
11
+ MIT (see LICENSE.txt)
12
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+
2
+ $:.unshift('.') # 1.9.2
3
+
4
+ require 'rubygems'
5
+
6
+ require 'rake'
7
+ require 'rake/clean'
8
+
9
+
10
+ #
11
+ # clean
12
+
13
+ CLEAN.include('pkg')
14
+
15
+
16
+ #
17
+ # test / spec
18
+
19
+ task :spec do
20
+
21
+ exec 'bundle exec rspec'
22
+ end
23
+
24
+ task :default => [ :spec ]
25
+ task :test => [ :spec ]
26
+
27
+
28
+ #
29
+ # gem
30
+
31
+ GEMSPEC_FILE = Dir['*.gemspec'].first
32
+ GEMSPEC = eval(File.read(GEMSPEC_FILE))
33
+ GEMSPEC.validate
34
+
35
+
36
+ desc %{
37
+ builds the gem and places it in pkg/
38
+ }
39
+ task :build do
40
+
41
+ sh "gem build #{GEMSPEC_FILE}"
42
+ sh "mkdir pkg" rescue nil
43
+ sh "mv #{GEMSPEC.name}-#{GEMSPEC.version}.gem pkg/"
44
+ end
45
+
46
+ desc %{
47
+ builds the gem and pushes it to rubygems.org
48
+ }
49
+ task :push => :build do
50
+
51
+ sh "gem push pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.gem"
52
+ end
53
+
data/TODO.txt ADDED
@@ -0,0 +1,17 @@
1
+
2
+ [o] unconsumed input!
3
+ [o] ^ 0, 1, [ min, max ]
4
+ [o] switch from ^ to * (how * is related to +)
5
+ [ ] lookahead present/absent
6
+ ~ x --> present
7
+ ! x --> absent
8
+ [ ] any
9
+ [ ] chars
10
+ [ ] blankslate
11
+
12
+
13
+ `x` + [.]
14
+ `x` + [a-z]
15
+ `x` + c('a-z')
16
+ `x` + _('a-z')
17
+
data/lib/neg.rb ADDED
@@ -0,0 +1,3 @@
1
+
2
+ require 'neg/parser'
3
+
data/lib/neg/input.rb ADDED
@@ -0,0 +1,89 @@
1
+ #--
2
+ # Copyright (c) 2012-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Neg
27
+
28
+ def self.Input(o)
29
+
30
+ o.is_a?(Input) ? o : Input.new(o)
31
+ end
32
+
33
+ class Input
34
+
35
+ def initialize(s)
36
+
37
+ @s = s
38
+ rewind
39
+ end
40
+
41
+ def read(count)
42
+
43
+ s = ''
44
+
45
+ count.times do
46
+
47
+ c = @s[@offset, 1]
48
+
49
+ break if c.nil?
50
+
51
+ s << c
52
+
53
+ @offset = @offset + 1
54
+
55
+ if c == "\n"
56
+ @line = @line + 1
57
+ @column = 0
58
+ else
59
+ @column = @column + 1
60
+ end
61
+ end
62
+
63
+ s
64
+ end
65
+
66
+ def rewind(pos=[ 0, 1, 1 ])
67
+
68
+ @offset, @line, @column = pos
69
+ end
70
+
71
+ def position
72
+
73
+ [ @offset, @line, @column ]
74
+ end
75
+
76
+ def eoi?
77
+
78
+ @offset >= @s.length
79
+ end
80
+
81
+ def remains
82
+
83
+ rem = read(7)
84
+
85
+ rem.length >= 7 ? rem = rem + '...' : rem
86
+ end
87
+ end
88
+ end
89
+
data/lib/neg/parser.rb ADDED
@@ -0,0 +1,318 @@
1
+ #--
2
+ # Copyright (c) 2012-2012, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'neg/version'
26
+ require 'neg/input'
27
+
28
+
29
+ module Neg
30
+
31
+ class UnconsumedInputError < StandardError; end
32
+
33
+ class Parser
34
+
35
+ def self.method_missing(m, *args)
36
+
37
+ return super if args.any?
38
+ return super if m.to_s == 'to_ary'
39
+
40
+ @root ||= m
41
+ pa = NonTerminalParser.new(m)
42
+
43
+ (class << self; self; end).send(:define_method, m) { pa }
44
+
45
+ pa
46
+ end
47
+
48
+ def self.parse(s)
49
+
50
+ i = Neg::Input(s)
51
+
52
+ result = send(@root).parse(i)
53
+
54
+ raise UnconsumedInputError.new(
55
+ "remaining: #{i.remains.inspect}"
56
+ ) if result[1] && ( ! i.eoi?)
57
+
58
+ result
59
+ end
60
+
61
+ def self.`(s)
62
+
63
+ StringParser.new(s)
64
+ end
65
+
66
+ def self._(c=nil)
67
+
68
+ CharacterParser.new(c)
69
+ end
70
+
71
+ def self.to_s
72
+
73
+ s = [ "#{name}:" ]
74
+
75
+ methods.sort.each do |mname|
76
+
77
+ m = method(mname)
78
+
79
+ next if m.owner == Class
80
+ next if %w[ _ to_s ].include?(mname.to_s)
81
+ next unless m.arity == (RUBY_VERSION > '1.9' ? 0 : -1)
82
+ next unless m.owner.ancestors.include?(Class)
83
+ next unless m.receiver.ancestors.include?(Neg::Parser)
84
+
85
+ s << " #{send(mname).to_s}"
86
+ end
87
+
88
+ s << " root: #{@root}"
89
+
90
+ s.join("\n")
91
+ end
92
+
93
+ #--
94
+ # sub parsers
95
+ #++
96
+
97
+ class SubParser
98
+
99
+ def +(pa)
100
+
101
+ SequenceParser.new(self, pa)
102
+ end
103
+
104
+ def |(pa)
105
+
106
+ AlternativeParser.new(self, pa)
107
+ end
108
+
109
+ def [](name)
110
+
111
+ NonTerminalParser.new(name.to_s, self)
112
+ end
113
+
114
+ def *(range)
115
+
116
+ RepetitionParser.new(self, range)
117
+ end
118
+
119
+ def parse(input_or_string)
120
+
121
+ input = Neg::Input(input_or_string)
122
+ start = input.position
123
+
124
+ success, result = do_parse(input)
125
+
126
+ input.rewind(start) unless success
127
+
128
+ [ nil, success, start, result ]
129
+ end
130
+ end
131
+
132
+ class NonTerminalParser < SubParser
133
+
134
+ def initialize(name, child=nil)
135
+
136
+ @name = name
137
+ @child = child
138
+ end
139
+
140
+ def ==(pa)
141
+
142
+ @child = pa
143
+ end
144
+
145
+ def do_parse(i)
146
+
147
+ @child.do_parse(i)
148
+ end
149
+
150
+ def parse(input_or_string)
151
+
152
+ r = super(input_or_string)
153
+ r[0] = @name
154
+
155
+ r
156
+ end
157
+
158
+ def to_s(parent=nil)
159
+
160
+ child = @child ? @child.to_s(self) : '<missing>'
161
+
162
+ if @name.is_a?(String)
163
+ "#{child}[#{@name.inspect}]"
164
+ elsif parent
165
+ @name.to_s
166
+ else #if @name.is_a?(Symbol)
167
+ "#{@name} == #{child}"
168
+ end
169
+ end
170
+ end
171
+
172
+ class RepetitionParser < SubParser
173
+
174
+ def initialize(child, range)
175
+
176
+ @range = range
177
+ @child = child
178
+
179
+ @min, @max = case range
180
+ when -1 then [ 0, 1 ]
181
+ when Array then range
182
+ else [ range, nil ]
183
+ end
184
+ end
185
+
186
+ def do_parse(i)
187
+
188
+ rs = []
189
+
190
+ loop do
191
+ r = @child.parse(i)
192
+ break if ! r[1] && rs.size >= @min && (@max.nil? || rs.size <= @max)
193
+ rs << r
194
+ break if ! r[1]
195
+ break if rs.size == @max
196
+ end
197
+
198
+ success = (rs.empty? || rs.last[1]) && (rs.size >= @min)
199
+
200
+ [ success, rs ]
201
+ end
202
+
203
+ def to_s(parent=nil)
204
+
205
+ "#{@child.to_s(self)} * #{@range.inspect}"
206
+ end
207
+ end
208
+
209
+ class StringParser < SubParser
210
+
211
+ def initialize(s)
212
+
213
+ @s = s
214
+ end
215
+
216
+ def do_parse(i)
217
+
218
+ if (s = i.read(@s.length)) == @s
219
+ [ true, @s ]
220
+ else
221
+ [ false, "expected #{@s.inspect}, got #{s.inspect}" ]
222
+ end
223
+ end
224
+
225
+ def to_s(parent=nil)
226
+
227
+ "`#{@s}`"
228
+ end
229
+ end
230
+
231
+ class CharacterParser < SubParser
232
+
233
+ def initialize(c)
234
+
235
+ @c = c
236
+ @r = Regexp.new(c ? "[#{c}]" : '.')
237
+ end
238
+
239
+ def do_parse(i)
240
+
241
+ if (s = i.read(1)).match(@r)
242
+ [ true, s ]
243
+ else
244
+ [ false, "#{s.inspect} doesn't match #{@c.inspect}" ]
245
+ end
246
+ end
247
+
248
+ def to_s(parent=nil)
249
+
250
+ @c ? "_(#{@c.inspect})" : '_'
251
+ end
252
+ end
253
+
254
+ class CompositeParser < SubParser
255
+
256
+ def initialize(left, right)
257
+
258
+ @children = [ left, right ]
259
+ end
260
+ end
261
+
262
+ class SequenceParser < CompositeParser
263
+
264
+ def +(pa)
265
+
266
+ @children << pa
267
+
268
+ self
269
+ end
270
+
271
+ def do_parse(i)
272
+
273
+ results = []
274
+
275
+ @children.each do |c|
276
+
277
+ results << c.parse(i)
278
+ break unless results.last[1]
279
+ end
280
+
281
+ [ results.last[1], results ]
282
+ end
283
+
284
+ def to_s(parent=nil)
285
+
286
+ "(#{@children.collect { |c| c.to_s(self) }.join(' + ')})"
287
+ end
288
+ end
289
+
290
+ class AlternativeParser < CompositeParser
291
+
292
+ def |(pa)
293
+
294
+ @children << pa
295
+
296
+ self
297
+ end
298
+
299
+ def do_parse(i)
300
+
301
+ result = []
302
+
303
+ @children.each { |c|
304
+ result << c.parse(i)
305
+ break if result.last[1]
306
+ }
307
+
308
+ [ result.last[1], result ]
309
+ end
310
+
311
+ def to_s(parent=nil)
312
+
313
+ "(#{@children.collect { |c| c.to_s(self) }.join(' | ')})"
314
+ end
315
+ end
316
+ end
317
+ end
318
+