neg 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+