Spectre 0.0.1
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 +1 -0
- data/LICENSE +23 -0
- data/README +20 -0
- data/Rakefile +112 -0
- data/lib/spectre/base.rb +44 -0
- data/lib/spectre/base/closure.rb +96 -0
- data/lib/spectre/base/directive.rb +148 -0
- data/lib/spectre/base/grammar.rb +269 -0
- data/lib/spectre/base/inputiterator.rb +276 -0
- data/lib/spectre/base/node.rb +393 -0
- data/lib/spectre/base/operators.rb +342 -0
- data/lib/spectre/base/parser.rb +110 -0
- data/lib/spectre/generic.rb +115 -0
- data/lib/spectre/generic/directives.rb +246 -0
- data/lib/spectre/generic/negations.rb +68 -0
- data/lib/spectre/generic/primitives.rb +172 -0
- data/lib/spectre/generic/semanticaction.rb +43 -0
- data/lib/spectre/string.rb +57 -0
- data/lib/spectre/string/additionals.rb +80 -0
- data/lib/spectre/string/directives.rb +51 -0
- data/lib/spectre/string/inputiterator.rb +57 -0
- data/lib/spectre/string/primitives.rb +400 -0
- data/test/base/closure_tests.rb +108 -0
- data/test/base/grammar_tests.rb +97 -0
- data/test/base/operator_tests.rb +335 -0
- data/test/base/semanticaction_tests.rb +53 -0
- data/test/generic/directive_tests.rb +224 -0
- data/test/generic/negation_tests.rb +146 -0
- data/test/generic/primitive_tests.rb +99 -0
- data/test/string/POD2Parser_tests.rb +93 -0
- data/test/string/additional_tests.rb +43 -0
- data/test/string/directive_tests.rb +32 -0
- data/test/string/primitive_tests.rb +173 -0
- data/test/tests.rb +33 -0
- data/test/tutorial/funnymath_tests.rb +57 -0
- data/test/tutorial/html_tests.rb +171 -0
- data/test/tutorial/skipping_tests.rb +60 -0
- metadata +109 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
# This is Spectre, a parser framework inspired by Boost.Spirit,
|
2
|
+
# which can be found at http://spirit.sourceforge.net/.
|
3
|
+
#
|
4
|
+
# If you want to find out more or need a tutorial, go to
|
5
|
+
# http://spectre.rubyforge.org/
|
6
|
+
# You'll find a nice wiki there!
|
7
|
+
#
|
8
|
+
# Author:: Fabian Streitel (karottenreibe)
|
9
|
+
# Copyright:: Copyright (c) 2009 Fabian Streitel
|
10
|
+
# License:: Boost Software License 1.0
|
11
|
+
# For further information regarding this license, you can go to
|
12
|
+
# http://www.boost.org/LICENSE_1_0.txt
|
13
|
+
# or read the file LICENSE distributed with this software.
|
14
|
+
# Homepage:: http://spectre.rubyforge.org/
|
15
|
+
# Git repo:: http://rubyforge.org/scm/?group_id=7618
|
16
|
+
#
|
17
|
+
# Keeps the Parser class.
|
18
|
+
#
|
19
|
+
|
20
|
+
module Spectre
|
21
|
+
|
22
|
+
##
|
23
|
+
# Keeps the match a Parser made.
|
24
|
+
#
|
25
|
+
class Match
|
26
|
+
##
|
27
|
+
# The size of the consumed input - Integer.
|
28
|
+
attr_accessor :length
|
29
|
+
|
30
|
+
##
|
31
|
+
# The value extracted by the parsing process.
|
32
|
+
attr_accessor :value
|
33
|
+
|
34
|
+
##
|
35
|
+
# Initializes a new Match object with the +length+ of the consumed input and the
|
36
|
+
# +value+ returned by the parsing process.
|
37
|
+
#
|
38
|
+
def initialize length = 0, value = nil
|
39
|
+
@length, @value = length, value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Does the actual work of parsing the input.
|
45
|
+
# Needs to be refined by a class that +include+s it by implementing the +#scan+ method.
|
46
|
+
# If your Parser needs to be presented with arguments on instantiation, you also have
|
47
|
+
# to supply an +#initialize+ method.
|
48
|
+
#
|
49
|
+
module Parser
|
50
|
+
|
51
|
+
##
|
52
|
+
# The Node this Parser belongs to.
|
53
|
+
attr_accessor :node
|
54
|
+
|
55
|
+
##
|
56
|
+
# Whether or not the Node of this Parser should call +InputIterator#skip!+.
|
57
|
+
# Operators should reimplement that method to return +false+.
|
58
|
+
#
|
59
|
+
def pre_skip?; true; end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
'[]'
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Redirect to Node#backtrack.
|
67
|
+
#
|
68
|
+
def backtrack iter
|
69
|
+
@node.backtrack iter
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Creates a Match object from the given InputIterator +iter+ and the matched +value+.
|
74
|
+
# Alternatively, value can be a Match object, which will be modified (i.e. it's length
|
75
|
+
# will be corrected) and returned.
|
76
|
+
# If +value+ is +nil+, +nil+ will be returned.
|
77
|
+
#
|
78
|
+
# NOTE: You should always use this method to create a valid Match!
|
79
|
+
#
|
80
|
+
def create_match iter, value
|
81
|
+
if value.nil?
|
82
|
+
nil
|
83
|
+
elsif value.is_a? Match
|
84
|
+
value.length = iter - @node.backtrace
|
85
|
+
value
|
86
|
+
else
|
87
|
+
Match.new iter - @node.backtrace, value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Converts this Parser into a Node.
|
93
|
+
#
|
94
|
+
def to_p
|
95
|
+
Node.new self
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Does the actual parsing by advancing the InputIterator +iter+.
|
100
|
+
# Required to return a Match object on success, nil otherwise.
|
101
|
+
#
|
102
|
+
# Must be implemented by a subclass.
|
103
|
+
#
|
104
|
+
def scan iter
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# This is Spectre, a parser framework inspired by Boost.Spirit,
|
2
|
+
# which can be found at http://spirit.sourceforge.net/.
|
3
|
+
#
|
4
|
+
# If you want to find out more or need a tutorial, go to
|
5
|
+
# http://spectre.rubyforge.org/
|
6
|
+
# You'll find a nice wiki there!
|
7
|
+
#
|
8
|
+
# Author:: Fabian Streitel (karottenreibe)
|
9
|
+
# Copyright:: Copyright (c) 2009 Fabian Streitel
|
10
|
+
# License:: Boost Software License 1.0
|
11
|
+
# For further information regarding this license, you can go to
|
12
|
+
# http://www.boost.org/LICENSE_1_0.txt
|
13
|
+
# or read the file LICENSE distributed with this software.
|
14
|
+
# Homepage:: http://spectre.rubyforge.org/
|
15
|
+
# Git repo:: http://rubyforge.org/scm/?group_id=7618
|
16
|
+
#
|
17
|
+
# Keeps the generic Parsers, Directives etc.
|
18
|
+
# More specific standard Classes can be found in the various other files located in
|
19
|
+
# _spectre/*/_.
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'spectre/base'
|
23
|
+
|
24
|
+
module Spectre
|
25
|
+
|
26
|
+
##
|
27
|
+
# For use with +::register_POD+.
|
28
|
+
#
|
29
|
+
module Parser
|
30
|
+
class << self
|
31
|
+
|
32
|
+
##
|
33
|
+
# The registered PODs.
|
34
|
+
attr_accessor :PODs
|
35
|
+
|
36
|
+
##
|
37
|
+
# Converts any Object +obj+ into a Parser, if one is registered to handle
|
38
|
+
# the object's class.
|
39
|
+
# This can be done with +Spectre::register_POD+.
|
40
|
+
#
|
41
|
+
def from_POD obj
|
42
|
+
block = @PODs[obj.class]
|
43
|
+
block ? block.call(obj) : nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Init Parser::PODs to Hash.
|
50
|
+
Parser.PODs = {}
|
51
|
+
|
52
|
+
class << self
|
53
|
+
##
|
54
|
+
# Registers a +klass+ as a POD with the Parser.
|
55
|
+
# The class will gain the method +#to_p+ to convert it to a Parser/Node. Also it will react
|
56
|
+
# to the standard Operators if appropriate.
|
57
|
+
# The Parser for this POD is constructed by passing it to the given +block+, which
|
58
|
+
# must return the correctly instantiated Parser object.
|
59
|
+
#
|
60
|
+
# For examples look in _spectre/std/*.rb_.
|
61
|
+
#
|
62
|
+
def register_POD klass, &block
|
63
|
+
klass.class_eval do
|
64
|
+
def to_p
|
65
|
+
Spectre::Parser.from_POD(self).to_p
|
66
|
+
end
|
67
|
+
|
68
|
+
old = {}
|
69
|
+
single_methods = [:+, :*]
|
70
|
+
dual_methods = [:>>, :**, :|, :&, :%, :^, :-]
|
71
|
+
|
72
|
+
(single_methods + dual_methods).each do |sym|
|
73
|
+
old[sym] = self.instance_method sym if self.instance_methods.include? sym.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
single_methods.each do |sym|
|
77
|
+
define_method sym do |*args|
|
78
|
+
if args.empty?
|
79
|
+
self.to_p.send sym
|
80
|
+
else
|
81
|
+
if old[sym] then old[sym].bind(self).call *args
|
82
|
+
else raise NoMethodError.new "undefined method '#{sym.to_s}' for #{self.inspect}:#{self.class}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
dual_methods.each do |sym|
|
89
|
+
define_method sym do |*args|
|
90
|
+
if args[0].is_a? Spectre::Node or args[0].is_a? Spectre::Parser
|
91
|
+
self.to_p.send sym, args[0].to_p
|
92
|
+
else
|
93
|
+
if old[sym] then old[sym].bind(self).call *args
|
94
|
+
else raise NoMethodError.new "undefined method '#{sym.to_s}' for #{self.inspect}:#{self.class}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Parser.PODs[klass] = block
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Register Symbol as a POD with the SymParser.
|
109
|
+
Spectre::register_POD(Symbol) { |sym| Spectre::SymParser.new sym }
|
110
|
+
|
111
|
+
require 'spectre/generic/primitives'
|
112
|
+
require 'spectre/generic/negations'
|
113
|
+
require 'spectre/generic/directives'
|
114
|
+
require 'spectre/generic/semanticaction'
|
115
|
+
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# This is Spectre, a parser framework inspired by Boost.Spirit,
|
2
|
+
# which can be found at http://spirit.sourceforge.net/.
|
3
|
+
#
|
4
|
+
# If you want to find out more or need a tutorial, go to
|
5
|
+
# http://spectre.rubyforge.org/
|
6
|
+
# You'll find a nice wiki there!
|
7
|
+
#
|
8
|
+
# Author:: Fabian Streitel (karottenreibe)
|
9
|
+
# Copyright:: Copyright (c) 2009 Fabian Streitel
|
10
|
+
# License:: Boost Software License 1.0
|
11
|
+
# For further information regarding this license, you can go to
|
12
|
+
# http://www.boost.org/LICENSE_1_0.txt
|
13
|
+
# or read the file LICENSE distributed with this software.
|
14
|
+
# Homepage:: http://spectre.rubyforge.org/
|
15
|
+
# Git repo:: http://rubyforge.org/scm/?group_id=7618
|
16
|
+
#
|
17
|
+
# Keeps the generic Directive classes.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'spectre/base/directive'
|
21
|
+
require 'spectre/base/grammar'
|
22
|
+
|
23
|
+
module Spectre
|
24
|
+
|
25
|
+
##
|
26
|
+
# Stores the generic directives that can be applied regardless of the input type.
|
27
|
+
#
|
28
|
+
module Directives
|
29
|
+
|
30
|
+
##
|
31
|
+
# Causes semantic actions not to be triggered regardless of wheter a match
|
32
|
+
# was successful.
|
33
|
+
#
|
34
|
+
# Shortcut: +noaction_d+
|
35
|
+
#
|
36
|
+
class NoActionDirective < Directive
|
37
|
+
policy! :actions => false
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
"[noaction_d:#{@left.inspect}]"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ShortcutsMixin.register_shortcut :noaction_d => NoActionDirective
|
45
|
+
|
46
|
+
##
|
47
|
+
# Causes every parser wrapped in it to fail unless the specified minimum and
|
48
|
+
# maximum requirements for the matched values are met. Both values may be nil,
|
49
|
+
# indicating a don't-care condition.
|
50
|
+
# Also, +min+ can be set to a range, in which case +max+ will be ignored and the
|
51
|
+
# range will be taken as the indication.
|
52
|
+
#
|
53
|
+
# The comparison is performed via
|
54
|
+
# _min_ > _value_
|
55
|
+
# _max_ < _value_
|
56
|
+
# That allows you to supply your own classes for min and max that implement those
|
57
|
+
# methods, which allows for more complex comparisons.
|
58
|
+
#
|
59
|
+
# NOTE: The values set are Parser specific and may thus not be applicable to
|
60
|
+
# all Parsers.
|
61
|
+
#
|
62
|
+
# Shortcut: +limit_d+.
|
63
|
+
#
|
64
|
+
class MinMaxDirective < Directive
|
65
|
+
def initialize min = nil, max = nil
|
66
|
+
super()
|
67
|
+
|
68
|
+
if min.is_a? Range then hsh = { :min => min.first, :max => min.last }
|
69
|
+
else hsh = { :min => min, :max => max }
|
70
|
+
end
|
71
|
+
|
72
|
+
@policy ? @policy.merge!(hsh) : @policy = hsh
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
"[limit_d:#{@left.inspect}]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
ShortcutsMixin.register_shortcut :limit_d => MinMaxDirective
|
81
|
+
|
82
|
+
##
|
83
|
+
# Resets all previously applied directives.
|
84
|
+
# Resets the skipper and the transformation to their respective default values.
|
85
|
+
#
|
86
|
+
# Shortcut: reset_d
|
87
|
+
#
|
88
|
+
class ResetDirective < Directive
|
89
|
+
transformation! :default
|
90
|
+
skipper! :default
|
91
|
+
policy! :union => :normal,
|
92
|
+
:actions => true,
|
93
|
+
:min => nil,
|
94
|
+
:max => nil
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
"[reset_d:#{@left.inspect}]"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
ShortcutsMixin.register_shortcut :reset_d => ResetDirective
|
102
|
+
|
103
|
+
##
|
104
|
+
# Forces the parser to ignore pre-skipping via +InputIterator#skip!+.
|
105
|
+
#
|
106
|
+
# Shortcut: +nopreskip_d+.
|
107
|
+
#
|
108
|
+
class NoPreSkipDirective < Directive
|
109
|
+
def parse iter, *args
|
110
|
+
super(iter, false)
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"[nopreskip_d:#{@left.inspect}]"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
ShortcutsMixin.register_shortcut :nopreskip_d => NoPreSkipDirective
|
119
|
+
|
120
|
+
##
|
121
|
+
# Sets a skip Parser or block to use.
|
122
|
+
# If a Parser is given, it will be passed the input stream. If it matches,
|
123
|
+
# the input stream will be advanced as much as the Match's length, so all the
|
124
|
+
# matched tokens are skipped.
|
125
|
+
# Any skip Parser will be explicitly wrapped inside a +noaction_d+ and
|
126
|
+
# +InputIterator#ignore_skipper+.
|
127
|
+
# If a block is given instead of a Parser, the block will be given each
|
128
|
+
# input token and must return true if the token should be skipped, and
|
129
|
+
# false otherwise.
|
130
|
+
#
|
131
|
+
# Shortcut: +skip_d+
|
132
|
+
#
|
133
|
+
class SkipDirective < NoPreSkipDirective
|
134
|
+
def initialize skipper
|
135
|
+
if skipper.respond_to? :call
|
136
|
+
@iter_skipper = lambda { |token,iter|
|
137
|
+
skipper.call(token) ? 1 : nil
|
138
|
+
}
|
139
|
+
elsif skipper.respond_to? :parse
|
140
|
+
skipper = NoActionDirective.new[skipper]
|
141
|
+
|
142
|
+
@iter_skipper = lambda { |token,iter|
|
143
|
+
match = nil
|
144
|
+
iter.ignore_skipper { |i| match = skipper.parse i }
|
145
|
+
|
146
|
+
if match then match.length
|
147
|
+
else nil
|
148
|
+
end
|
149
|
+
}
|
150
|
+
else
|
151
|
+
raise "SkipDirective expects either lambda block or parser Node"
|
152
|
+
end
|
153
|
+
|
154
|
+
super()
|
155
|
+
end
|
156
|
+
|
157
|
+
def inspect
|
158
|
+
"[skip_d:#{@left.inspect}]"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
ShortcutsMixin.register_shortcut :skip_d => SkipDirective
|
163
|
+
|
164
|
+
##
|
165
|
+
# Forces Unions to take the alternative that successfully matches the shortest
|
166
|
+
# input. All alternatives will be tried.
|
167
|
+
#
|
168
|
+
# Shortcut: +shortest_d+
|
169
|
+
#
|
170
|
+
class ShortestDirective < Directive
|
171
|
+
policy! :union => :shortest
|
172
|
+
|
173
|
+
def inspect
|
174
|
+
"[shortest_d:#{@left.inspect}]"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
ShortcutsMixin.register_shortcut :shortest_d => ShortestDirective
|
179
|
+
|
180
|
+
##
|
181
|
+
# Forces Unions to take the alternative that successfully matches the longest
|
182
|
+
# input. All alternatives will be tried.
|
183
|
+
#
|
184
|
+
# Shortcut: +longest_d+
|
185
|
+
#
|
186
|
+
class LongestDirective < Directive
|
187
|
+
policy! :union => :longest
|
188
|
+
|
189
|
+
def inspect
|
190
|
+
"[longest_d:#{@left.inspect}]"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
ShortcutsMixin.register_shortcut :longest_d => LongestDirective
|
195
|
+
|
196
|
+
##
|
197
|
+
# Removes any set skipper from the InputIterator, including the default one.
|
198
|
+
#
|
199
|
+
# Shortcut: +noskip_d+ or +lexeme_d+
|
200
|
+
#
|
201
|
+
class NoSkipDirective < Directive
|
202
|
+
skipper! lambda { nil }
|
203
|
+
|
204
|
+
def inspect
|
205
|
+
"[noskip_d:#{@left.inspect}]"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
ShortcutsMixin.register_shortcut :noskip_d => NoSkipDirective, :lexeme_d => NoSkipDirective
|
210
|
+
|
211
|
+
##
|
212
|
+
# Like +nopreskip_d[lexeme_d[...]]+.
|
213
|
+
#
|
214
|
+
# Shortcut: +noskip_d!+ or +lexeme_d!+
|
215
|
+
#
|
216
|
+
class NoSkipNoPreSkipDirective < Directive
|
217
|
+
def [] parser
|
218
|
+
NoPreSkipDirective.new[NoSkipDirective.new[parser]]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
ShortcutsMixin.register_shortcut :noskip_d! => NoSkipNoPreSkipDirective, :lexeme_d! => NoSkipNoPreSkipDirective
|
223
|
+
|
224
|
+
##
|
225
|
+
# Matches, if the Parser it wraps around matches, but reduces that Match to 0 length,
|
226
|
+
# i.e. will never consume any input.
|
227
|
+
# Shortcut: +epsilon+ or +e+.
|
228
|
+
#
|
229
|
+
class EpsilonDirective < Directive
|
230
|
+
def parse iter, *args
|
231
|
+
ret = super(iter, *args)
|
232
|
+
backtrack iter
|
233
|
+
ret.length = 0 if ret
|
234
|
+
ret
|
235
|
+
end
|
236
|
+
|
237
|
+
def inspect
|
238
|
+
"[e:#{@node.left.inspect}]"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
ShortcutsMixin.register_shortcut :e => EpsilonDirective, :epsilon => EpsilonDirective
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|