Spectre 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/CHANGELOG +1 -0
  2. data/LICENSE +23 -0
  3. data/README +20 -0
  4. data/Rakefile +112 -0
  5. data/lib/spectre/base.rb +44 -0
  6. data/lib/spectre/base/closure.rb +96 -0
  7. data/lib/spectre/base/directive.rb +148 -0
  8. data/lib/spectre/base/grammar.rb +269 -0
  9. data/lib/spectre/base/inputiterator.rb +276 -0
  10. data/lib/spectre/base/node.rb +393 -0
  11. data/lib/spectre/base/operators.rb +342 -0
  12. data/lib/spectre/base/parser.rb +110 -0
  13. data/lib/spectre/generic.rb +115 -0
  14. data/lib/spectre/generic/directives.rb +246 -0
  15. data/lib/spectre/generic/negations.rb +68 -0
  16. data/lib/spectre/generic/primitives.rb +172 -0
  17. data/lib/spectre/generic/semanticaction.rb +43 -0
  18. data/lib/spectre/string.rb +57 -0
  19. data/lib/spectre/string/additionals.rb +80 -0
  20. data/lib/spectre/string/directives.rb +51 -0
  21. data/lib/spectre/string/inputiterator.rb +57 -0
  22. data/lib/spectre/string/primitives.rb +400 -0
  23. data/test/base/closure_tests.rb +108 -0
  24. data/test/base/grammar_tests.rb +97 -0
  25. data/test/base/operator_tests.rb +335 -0
  26. data/test/base/semanticaction_tests.rb +53 -0
  27. data/test/generic/directive_tests.rb +224 -0
  28. data/test/generic/negation_tests.rb +146 -0
  29. data/test/generic/primitive_tests.rb +99 -0
  30. data/test/string/POD2Parser_tests.rb +93 -0
  31. data/test/string/additional_tests.rb +43 -0
  32. data/test/string/directive_tests.rb +32 -0
  33. data/test/string/primitive_tests.rb +173 -0
  34. data/test/tests.rb +33 -0
  35. data/test/tutorial/funnymath_tests.rb +57 -0
  36. data/test/tutorial/html_tests.rb +171 -0
  37. data/test/tutorial/skipping_tests.rb +60 -0
  38. 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
+