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.
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
+