walrus 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. data/bin/walrus +44 -0
  2. data/ext/jindex/extconf.rb +11 -0
  3. data/ext/jindex/jindex.c +79 -0
  4. data/ext/mkdtemp/extconf.rb +11 -0
  5. data/ext/mkdtemp/mkdtemp.c +41 -0
  6. data/lib/walrus/additions/module.rb +36 -0
  7. data/lib/walrus/additions/string.rb +37 -0
  8. data/lib/walrus/additions/test/unit/error_collector.rb +62 -0
  9. data/lib/walrus/compile_error.rb +28 -0
  10. data/lib/walrus/compiler.rb +124 -0
  11. data/lib/walrus/contrib/spec/walruscloth_spec.rb +32 -0
  12. data/lib/walrus/contrib/walruscloth.rb +82 -0
  13. data/lib/walrus/diff.rb +89 -0
  14. data/lib/walrus/document.rb +98 -0
  15. data/lib/walrus/grammar/additions/proc.rb +20 -0
  16. data/lib/walrus/grammar/additions/regexp.rb +21 -0
  17. data/lib/walrus/grammar/additions/string.rb +52 -0
  18. data/lib/walrus/grammar/additions/symbol.rb +42 -0
  19. data/lib/walrus/grammar/and_predicate.rb +40 -0
  20. data/lib/walrus/grammar/array_result.rb +19 -0
  21. data/lib/walrus/grammar/continuation_wrapper_exception.rb +28 -0
  22. data/lib/walrus/grammar/left_recursion_exception.rb +27 -0
  23. data/lib/walrus/grammar/location_tracking.rb +105 -0
  24. data/lib/walrus/grammar/match_data_wrapper.rb +65 -0
  25. data/lib/walrus/grammar/memoizing.rb +41 -0
  26. data/lib/walrus/grammar/memoizing_cache.rb +94 -0
  27. data/lib/walrus/grammar/node.rb +60 -0
  28. data/lib/walrus/grammar/not_predicate.rb +40 -0
  29. data/lib/walrus/grammar/parse_error.rb +39 -0
  30. data/lib/walrus/grammar/parser_state.rb +181 -0
  31. data/lib/walrus/grammar/parslet.rb +28 -0
  32. data/lib/walrus/grammar/parslet_choice.rb +120 -0
  33. data/lib/walrus/grammar/parslet_combination.rb +26 -0
  34. data/lib/walrus/grammar/parslet_combining.rb +154 -0
  35. data/lib/walrus/grammar/parslet_merge.rb +88 -0
  36. data/lib/walrus/grammar/parslet_omission.rb +57 -0
  37. data/lib/walrus/grammar/parslet_repetition.rb +97 -0
  38. data/lib/walrus/grammar/parslet_repetition_default.rb +58 -0
  39. data/lib/walrus/grammar/parslet_sequence.rb +202 -0
  40. data/lib/walrus/grammar/predicate.rb +57 -0
  41. data/lib/walrus/grammar/proc_parslet.rb +52 -0
  42. data/lib/walrus/grammar/regexp_parslet.rb +73 -0
  43. data/lib/walrus/grammar/skipped_substring_exception.rb +36 -0
  44. data/lib/walrus/grammar/string_enumerator.rb +45 -0
  45. data/lib/walrus/grammar/string_parslet.rb +75 -0
  46. data/lib/walrus/grammar/string_result.rb +24 -0
  47. data/lib/walrus/grammar/symbol_parslet.rb +63 -0
  48. data/lib/walrus/grammar.rb +170 -0
  49. data/lib/walrus/no_parameter_marker.rb +19 -0
  50. data/lib/walrus/parser.rb +420 -0
  51. data/lib/walrus/runner.rb +356 -0
  52. data/lib/walrus/template.rb +75 -0
  53. data/lib/walrus/walrus_grammar/assignment_expression.rb +24 -0
  54. data/lib/walrus/walrus_grammar/block_directive.rb +28 -0
  55. data/lib/walrus/walrus_grammar/comment.rb +24 -0
  56. data/lib/walrus/walrus_grammar/def_directive.rb +64 -0
  57. data/lib/walrus/walrus_grammar/echo_directive.rb +44 -0
  58. data/lib/walrus/walrus_grammar/escape_sequence.rb +24 -0
  59. data/lib/walrus/walrus_grammar/import_directive.rb +44 -0
  60. data/lib/walrus/walrus_grammar/include_directive.rb +27 -0
  61. data/lib/walrus/walrus_grammar/instance_variable.rb +24 -0
  62. data/lib/walrus/walrus_grammar/literal.rb +24 -0
  63. data/lib/walrus/walrus_grammar/message_expression.rb +25 -0
  64. data/lib/walrus/walrus_grammar/multiline_comment.rb +54 -0
  65. data/lib/walrus/walrus_grammar/placeholder.rb +40 -0
  66. data/lib/walrus/walrus_grammar/raw_directive.rb +42 -0
  67. data/lib/walrus/walrus_grammar/raw_text.rb +45 -0
  68. data/lib/walrus/walrus_grammar/ruby_directive.rb +29 -0
  69. data/lib/walrus/walrus_grammar/ruby_expression.rb +31 -0
  70. data/lib/walrus/walrus_grammar/set_directive.rb +24 -0
  71. data/lib/walrus/walrus_grammar/silent_directive.rb +44 -0
  72. data/lib/walrus/walrus_grammar/slurp_directive.rb +25 -0
  73. data/lib/walrus/walrus_grammar/super_directive.rb +27 -0
  74. data/lib/walrus.rb +64 -0
  75. data/spec/acceptance/acceptance_spec.rb +97 -0
  76. data/spec/acceptance/block/basic_block.expected +1 -0
  77. data/spec/acceptance/block/basic_block.tmpl +3 -0
  78. data/spec/acceptance/block/nested_blocks.expected +5 -0
  79. data/spec/acceptance/block/nested_blocks.tmpl +11 -0
  80. data/spec/acceptance/comments/comments_and_text.expected +3 -0
  81. data/spec/acceptance/comments/comments_and_text.tmpl +6 -0
  82. data/spec/acceptance/comments/single_comment.expected +0 -0
  83. data/spec/acceptance/comments/single_comment.tmpl +1 -0
  84. data/spec/acceptance/def/alternative_def_calling_conventions.expected +3 -0
  85. data/spec/acceptance/def/alternative_def_calling_conventions.tmpl +18 -0
  86. data/spec/acceptance/def/basic_def_block_no_output.expected +0 -0
  87. data/spec/acceptance/def/basic_def_block_no_output.tmpl +17 -0
  88. data/spec/acceptance/def/defs_can_be_called_multiple_times.expected +3 -0
  89. data/spec/acceptance/def/defs_can_be_called_multiple_times.tmpl +6 -0
  90. data/spec/acceptance/def/defs_can_be_dynamic.expected +4 -0
  91. data/spec/acceptance/def/defs_can_be_dynamic.tmpl +12 -0
  92. data/spec/acceptance/echo/echo_directive_with_numeric_literal.expected +1 -0
  93. data/spec/acceptance/echo/echo_directive_with_numeric_literal.tmpl +1 -0
  94. data/spec/acceptance/echo/echo_expression_list.expected +1 -0
  95. data/spec/acceptance/echo/echo_expression_list.tmpl +1 -0
  96. data/spec/acceptance/echo/echo_short_notation.expected +1 -0
  97. data/spec/acceptance/echo/echo_short_notation.tmpl +1 -0
  98. data/spec/acceptance/echo/echo_simple_expression.expected +1 -0
  99. data/spec/acceptance/echo/echo_simple_expression.tmpl +1 -0
  100. data/spec/acceptance/echo/echo_single_quoted_string_literal.expected +1 -0
  101. data/spec/acceptance/echo/echo_single_quoted_string_literal.tmpl +1 -0
  102. data/spec/acceptance/echo/multiple_echo_statements.expected +1 -0
  103. data/spec/acceptance/echo/multiple_echo_statements.tmpl +2 -0
  104. data/spec/acceptance/includes/basic_included_file.txt +1 -0
  105. data/spec/acceptance/includes/basic_includer.complex +3 -0
  106. data/spec/acceptance/includes/basic_includer.expected +3 -0
  107. data/spec/acceptance/includes/basic_includer.rb +38 -0
  108. data/spec/acceptance/includes/complicated_included_file.txt +3 -0
  109. data/spec/acceptance/includes/complicated_includer.complex +3 -0
  110. data/spec/acceptance/includes/complicated_includer.expected +3 -0
  111. data/spec/acceptance/includes/complicated_includer.rb +41 -0
  112. data/spec/acceptance/includes/nested_include_1.txt +3 -0
  113. data/spec/acceptance/includes/nested_include_2.txt +1 -0
  114. data/spec/acceptance/includes/nested_includer.complex +3 -0
  115. data/spec/acceptance/includes/nested_includer.expected +4 -0
  116. data/spec/acceptance/includes/nested_includer.rb +41 -0
  117. data/spec/acceptance/inheritance/basic_child.complex +10 -0
  118. data/spec/acceptance/inheritance/basic_child.expected +9 -0
  119. data/spec/acceptance/inheritance/basic_child.rb +54 -0
  120. data/spec/acceptance/inheritance/basic_parent.complex +5 -0
  121. data/spec/acceptance/inheritance/basic_parent.expected +3 -0
  122. data/spec/acceptance/inheritance/basic_parent.rb +41 -0
  123. data/spec/acceptance/inheritance/importing_child.complex +8 -0
  124. data/spec/acceptance/inheritance/importing_child.expected +7 -0
  125. data/spec/acceptance/inheritance/importing_child.rb +46 -0
  126. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.complex +8 -0
  127. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.expected +7 -0
  128. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.rb +44 -0
  129. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.expected +0 -0
  130. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.tmpl +15 -0
  131. data/spec/acceptance/multiline_comments/simple_multiline_comment.expected +2 -0
  132. data/spec/acceptance/multiline_comments/simple_multiline_comment.tmpl +4 -0
  133. data/spec/acceptance/raw/complicated_raw_example.expected +57 -0
  134. data/spec/acceptance/raw/complicated_raw_example.tmpl +79 -0
  135. data/spec/acceptance/raw-text/UTF_8.expected +12 -0
  136. data/spec/acceptance/raw-text/UTF_8.tmpl +12 -0
  137. data/spec/acceptance/raw-text/empty_file.expected +0 -0
  138. data/spec/acceptance/raw-text/empty_file.tmpl +0 -0
  139. data/spec/acceptance/raw-text/multi_line.expected +4 -0
  140. data/spec/acceptance/raw-text/multi_line.tmpl +4 -0
  141. data/spec/acceptance/raw-text/single_line.expected +1 -0
  142. data/spec/acceptance/raw-text/single_line.tmpl +1 -0
  143. data/spec/acceptance/raw-text/single_line_whitespace.expected +1 -0
  144. data/spec/acceptance/raw-text/single_line_whitespace.tmpl +1 -0
  145. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.expected +1 -0
  146. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.tmpl +4 -0
  147. data/spec/acceptance/ruby/ruby_directive_using_here_doc.expected +1 -0
  148. data/spec/acceptance/ruby/ruby_directive_using_here_doc.tmpl +4 -0
  149. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.expected +1 -0
  150. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.tmpl +4 -0
  151. data/spec/acceptance/ruby/ruby_directive_with_accumulate.expected +1 -0
  152. data/spec/acceptance/ruby/ruby_directive_with_accumulate.tmpl +4 -0
  153. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.expected +1 -0
  154. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.tmpl +6 -0
  155. data/spec/acceptance/set/unused_set.expected +0 -0
  156. data/spec/acceptance/set/unused_set.tmpl +1 -0
  157. data/spec/acceptance/set/used_set.expected +1 -0
  158. data/spec/acceptance/set/used_set.tmpl +2 -0
  159. data/spec/acceptance/silent/silent_and_echo_combined.expected +1 -0
  160. data/spec/acceptance/silent/silent_and_echo_combined.tmpl +2 -0
  161. data/spec/acceptance/silent/silent_short_notation.expected +1 -0
  162. data/spec/acceptance/silent/silent_short_notation.tmpl +1 -0
  163. data/spec/acceptance/silent/simple_silent_directive.expected +0 -0
  164. data/spec/acceptance/silent/simple_silent_directive.tmpl +1 -0
  165. data/spec/acceptance/slurp/basic_slurp_demo.expected +1 -0
  166. data/spec/acceptance/slurp/basic_slurp_demo.tmpl +4 -0
  167. data/spec/acceptance/super/super_with_no_effect.expected +4 -0
  168. data/spec/acceptance/super/super_with_no_effect.tmpl +5 -0
  169. data/spec/additions/module_spec.rb +126 -0
  170. data/spec/additions/string_spec.rb +99 -0
  171. data/spec/compiler_spec.rb +55 -0
  172. data/spec/grammar/additions/proc_spec.rb +25 -0
  173. data/spec/grammar/additions/regexp_spec.rb +37 -0
  174. data/spec/grammar/additions/string_spec.rb +106 -0
  175. data/spec/grammar/and_predicate_spec.rb +29 -0
  176. data/spec/grammar/continuation_wrapper_exception_spec.rb +23 -0
  177. data/spec/grammar/match_data_wrapper_spec.rb +41 -0
  178. data/spec/grammar/memoizing_cache_spec.rb +112 -0
  179. data/spec/grammar/node_spec.rb +126 -0
  180. data/spec/grammar/not_predicate_spec.rb +29 -0
  181. data/spec/grammar/parser_state_spec.rb +172 -0
  182. data/spec/grammar/parslet_choice_spec.rb +49 -0
  183. data/spec/grammar/parslet_combining_spec.rb +287 -0
  184. data/spec/grammar/parslet_merge_spec.rb +33 -0
  185. data/spec/grammar/parslet_omission_spec.rb +58 -0
  186. data/spec/grammar/parslet_repetition_spec.rb +77 -0
  187. data/spec/grammar/parslet_sequence_spec.rb +49 -0
  188. data/spec/grammar/parslet_spec.rb +23 -0
  189. data/spec/grammar/predicate_spec.rb +53 -0
  190. data/spec/grammar/proc_parslet_spec.rb +52 -0
  191. data/spec/grammar/regexp_parslet_spec.rb +347 -0
  192. data/spec/grammar/string_enumerator_spec.rb +94 -0
  193. data/spec/grammar/string_parslet_spec.rb +143 -0
  194. data/spec/grammar/symbol_parslet_spec.rb +30 -0
  195. data/spec/grammar_spec.rb +545 -0
  196. data/spec/parser_spec.rb +1418 -0
  197. data/spec/spec_helper.rb +34 -0
  198. data/spec/walrus_grammar/comment_spec.rb +39 -0
  199. data/spec/walrus_grammar/echo_directive_spec.rb +63 -0
  200. data/spec/walrus_grammar/escape_sequence_spec.rb +85 -0
  201. data/spec/walrus_grammar/literal_spec.rb +41 -0
  202. data/spec/walrus_grammar/message_expression_spec.rb +37 -0
  203. data/spec/walrus_grammar/multiline_comment_spec.rb +58 -0
  204. data/spec/walrus_grammar/placeholder_spec.rb +48 -0
  205. data/spec/walrus_grammar/raw_directive_spec.rb +81 -0
  206. data/spec/walrus_grammar/raw_text_spec.rb +65 -0
  207. data/spec/walrus_grammar/silent_directive_spec.rb +34 -0
  208. metadata +291 -0
@@ -0,0 +1,154 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ # The ParsletCombining module, together with the ParsletCombination class and its subclasses, provides simple container classes for encapsulating relationships among Parslets. By storing this information outside of the Parslet objects themselves their design is kept clean and they can become immutable objects which are much more easily copied and shared among multiple rules in a Grammar.
15
+ module ParsletCombining
16
+
17
+ # Convenience method.
18
+ def memoizing_parse(string, options = {})
19
+ self.to_parseable.memoizing_parse(string, options)
20
+ end
21
+
22
+ # Convenience method.
23
+ def parse(string, options = {})
24
+ self.to_parseable.parse(string, options)
25
+ end
26
+
27
+ # Defines a sequence of Parslets (or ParsletCombinations).
28
+ # Returns a ParsletSequence instance.
29
+ def sequence(first, second, *others)
30
+ Walrus::Grammar::ParsletSequence.new(first.to_parseable, second.to_parseable, *others)
31
+ end
32
+
33
+ # Shorthand for ParsletCombining.sequence(first, second).
34
+ def &(next_parslet)
35
+ self.sequence(self, next_parslet)
36
+ end
37
+
38
+ # Defines a sequence of Parslets similar to the sequence method but with the difference that the contents of array results from the component parslets will be merged into a single array rather than being added as arrays. To illustrate:
39
+ #
40
+ # 'foo' & 'bar'.one_or_more # returns results like ['foo', ['bar', 'bar', 'bar']]
41
+ # 'foo' >> 'bar'.one_or_more # returns results like ['foo', 'bar', 'bar', 'bar']
42
+ #
43
+ def merge(first, second, *others)
44
+ Walrus::Grammar::ParsletMerge.new(first.to_parseable, second.to_parseable, *others)
45
+ end
46
+
47
+ # Shorthand for ParsletCombining.sequence(first, second)
48
+ def >>(next_parslet)
49
+ self.merge(self, next_parslet)
50
+ end
51
+
52
+ # Defines a choice of Parslets (or ParsletCombinations).
53
+ # Returns a ParsletChoice instance.
54
+ def choice(left, right, *others)
55
+ Walrus::Grammar::ParsletChoice.new(left.to_parseable, right.to_parseable, *others)
56
+ end
57
+
58
+ # Shorthand for ParsletCombining.choice(left, right)
59
+ def |(alternative_parslet)
60
+ self.choice(self, alternative_parslet)
61
+ end
62
+
63
+ # Defines a repetition of the supplied Parslet (or ParsletCombination).
64
+ # Returns a ParsletRepetition instance.
65
+ def repetition(parslet, min, max)
66
+ Walrus::Grammar::ParsletRepetition.new(parslet.to_parseable, min, max)
67
+ end
68
+
69
+ # Shorthand for ParsletCombining.repetition.
70
+ def repeat(min = nil, max = nil)
71
+ self.repetition(self, min, max)
72
+ end
73
+
74
+ def repetition_with_default(parslet, min, max, default)
75
+ Walrus::Grammar::ParsletRepetitionDefault.new(parslet.to_parseable, min, max, default)
76
+ end
77
+
78
+ def repeat_with_default(min = nil, max = nil, default = nil)
79
+ self.repetition_with_default(self, min, max, default)
80
+ end
81
+
82
+ # Shorthand for ParsletCombining.repetition(0, 1).
83
+ # This method optionally takes a single parameter specifying what object should be returned as a placeholder when there are no matches; this is useful for packing into ASTs where it may be better to parse an empty Array rather than nil. The specified object is cloned and returned in the event that there are no matches. As a convenience, the specified object is automatically extended using the LocationTracking module (this is a convenience so that you can specify empty Arrays, "[]", rather than explicitly passing an "ArrayResult.new")
84
+ def optional(default_return_value = NoParameterMarker.instance)
85
+ if default_return_value == NoParameterMarker.instance
86
+ self.repeat(0, 1) # default behaviour
87
+ else
88
+ self.repeat_with_default(0, 1, default_return_value)
89
+ end
90
+ end
91
+
92
+ # Alternative to optional.
93
+ def zero_or_one
94
+ self.optional
95
+ end
96
+
97
+ # possible synonym "star"
98
+ def zero_or_more(default_return_value = NoParameterMarker.instance)
99
+ if default_return_value == NoParameterMarker.instance
100
+ self.repeat(0) # default behaviour
101
+ else
102
+ self.repeat_with_default(0, nil, default_return_value)
103
+ end
104
+ end
105
+
106
+ # possible synonym "plus"
107
+ def one_or_more
108
+ self.repeat(1)
109
+ end
110
+
111
+ # Parsing Expression Grammar support.
112
+ # Succeeds if parslet succeeds but consumes no input (throws an :AndPredicateSuccess symbol).
113
+ def and_predicate(parslet)
114
+ Walrus::Grammar::AndPredicate.new(parslet.to_parseable)
115
+ end
116
+
117
+ # Shorthand for and_predicate
118
+ # Strictly speaking, this shorthand breaks with established Ruby practice that "?" at the end of a method name should indicate a method that returns true or false.
119
+ def and?
120
+ self.and_predicate(self)
121
+ end
122
+
123
+ # Parsing Expression Grammar support.
124
+ # Succeeds if parslet fails (throws a :NotPredicateSuccess symbol).
125
+ # Fails if parslet succeeds (raise a ParseError).
126
+ # Consumes no output.
127
+ # This method will almost invariably be used in conjuntion with the & operator, like this:
128
+ # rule :foo, :p1 & :p2.not_predicate
129
+ # rule :foo, :p1 & :p2.not!
130
+ def not_predicate(parslet)
131
+ Walrus::Grammar::NotPredicate.new(parslet.to_parseable)
132
+ end
133
+
134
+ # Shorthand for not_predicate.
135
+ # Strictly speaking, this shorthand breaks with established Ruby practice that "!" at the end of a method name should indicate a destructive behaviour on (mutation of) the receiver.
136
+ def not!
137
+ self.not_predicate(self)
138
+ end
139
+
140
+ # Succeeds if parsing succeeds, consuming the output, but doesn't actually return anything.
141
+ # This is for elements which are required but which shouldn't appear in the final AST.
142
+ def omission(parslet)
143
+ Walrus::Grammar::ParsletOmission.new(parslet.to_parseable)
144
+ end
145
+
146
+ # Shorthand for ParsletCombining.omission
147
+ def skip
148
+ self.omission(self)
149
+ end
150
+
151
+ end # module ParsletCombining
152
+
153
+ end # class Grammar
154
+ end # module Walrus
@@ -0,0 +1,88 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class ParsletMerge < ParsletSequence
15
+
16
+ def parse(string, options = {})
17
+ raise ArgumentError if string.nil?
18
+ state = ParserState.new(string, options)
19
+ last_caught = nil # keep track of the last kind of throw to be caught
20
+ @components.each do |parseable|
21
+ catch :ProcessNextComponent do
22
+ catch :NotPredicateSuccess do
23
+ catch :AndPredicateSuccess do
24
+ catch :ZeroWidthParseSuccess do
25
+ begin
26
+ parsed = parseable.memoizing_parse(state.remainder, state.options)
27
+ if parsed.respond_to? :each
28
+ parsed.each { |element| state.parsed(element) }
29
+ else
30
+ state.parsed(parsed)
31
+ end
32
+ rescue SkippedSubstringException => e
33
+ state.skipped(e)
34
+ # rescue ParseError => e # failed, will try to skip; save original error in case skipping fails
35
+ # if options.has_key?(:skipping_override) : skipping_parslet = options[:skipping_override]
36
+ # elsif options.has_key?(:skipping) : skipping_parslet = options[:skipping]
37
+ # else skipping_parslet = nil
38
+ # end
39
+ # raise e if skipping_parslet.nil? # no skipper defined, raise original error
40
+ # begin
41
+ # parsed = skipping_parslet.memoizing_parse(state.remainder, state.options) # guard against self references (possible infinite recursion) here?
42
+ # state.skipped(parsed)
43
+ # redo # skipping succeeded, try to redo
44
+ # rescue ParseError
45
+ # raise e # skipping didn't help either, raise original error
46
+ # end
47
+ end
48
+ last_caught = nil
49
+ throw :ProcessNextComponent # can't use "next" here because it will only break out of innermost "do"
50
+ end
51
+ last_caught = :ZeroWidthParseSuccess
52
+ throw :ProcessNextComponent
53
+ end
54
+ last_caught = :AndPredicateSuccess
55
+ throw :ProcessNextComponent
56
+ end
57
+ last_caught = :NotPredicateSuccess
58
+ end
59
+ end
60
+
61
+ if state.results.respond_to? :empty? and state.results.empty? and
62
+ throw last_caught
63
+ else
64
+ state.results
65
+ end
66
+
67
+ end
68
+
69
+ def eql?(other)
70
+ return false if not other.instance_of? ParsletMerge
71
+ other_components = other.components
72
+ return false if @components.length != other_components.length
73
+ for i in 0..(@components.length - 1)
74
+ return false unless @components[i].eql? other_components[i]
75
+ end
76
+ true
77
+ end
78
+
79
+ private
80
+
81
+ def hash_offset
82
+ 53
83
+ end
84
+
85
+ end # class ParsletMerge
86
+ end # class Grammar
87
+ end # module Walrus
88
+
@@ -0,0 +1,57 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class ParsletOmission < ParsletCombination
15
+
16
+ attr_reader :hash
17
+
18
+ # Raises an ArgumentError if parseable is nil.
19
+ def initialize(parseable)
20
+ raise ArgumentError if parseable.nil?
21
+ @parseable = parseable
22
+ @hash = @parseable.hash + 46 # fixed offset to avoid unwanted collisions with similar classes
23
+ end
24
+
25
+ def parse(string, options = {})
26
+ raise ArgumentError if string.nil?
27
+ substring = StringResult.new
28
+ substring.start = [options[:line_start], options[:column_start]]
29
+ substring.end = [options[:line_start], options[:column_start]]
30
+
31
+ # possibly should catch these here as well
32
+ #catch :NotPredicateSuccess do
33
+ #catch :AndPredicateSuccess do
34
+ # one of the fundamental problems is that if a parslet throws such a symbol any info about already skipped material is lost (because the symbols contain nothing)
35
+ # this may be one reason to change these to exceptions...
36
+ catch :ZeroWidthParseSuccess do
37
+ substring = @parseable.memoizing_parse(string, options)
38
+ end
39
+
40
+ # not enough to just return a ZeroWidthParseSuccess here; that could cause higher levels to stop parsing and in any case there'd be no clean way to embed the scanned substring in the symbol
41
+ raise SkippedSubstringException.new(substring,
42
+ :line_start => options[:line_start], :column_start => options[:column_start],
43
+ :line_end => substring.line_end, :column_end => substring.column_end)
44
+ end
45
+
46
+ def eql?(other)
47
+ other.instance_of? ParsletOmission and other.parseable.eql? @parseable
48
+ end
49
+
50
+ protected
51
+
52
+ # For determining equality.
53
+ attr_reader :parseable
54
+
55
+ end # class ParsletOmission
56
+ end # class Grammar
57
+ end # module Walrus
@@ -0,0 +1,97 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class ParsletRepetition < ParsletCombination
15
+
16
+ attr_reader :hash
17
+
18
+ # Raises an ArgumentError if parseable or min is nil.
19
+ def initialize(parseable, min, max = nil)
20
+ raise ArgumentError if parseable.nil?
21
+ raise ArgumentError if min.nil?
22
+ @parseable = parseable
23
+ self.min = min
24
+ self.max = max
25
+ end
26
+
27
+ def parse(string, options = {})
28
+ raise ArgumentError if string.nil?
29
+ state = ParserState.new(string, options)
30
+ catch :ZeroWidthParseSuccess do # a zero-width match is grounds for immediate abort
31
+ while @max.nil? or state.length < @max # try forever if max is nil; otherwise keep trying while match count < max
32
+ begin
33
+ parsed = @parseable.memoizing_parse(state.remainder, state.options)
34
+ state.parsed(parsed)
35
+ rescue SkippedSubstringException => e
36
+ state.skipped(e)
37
+ rescue ParseError => e # failed, will try to skip; save original error in case skipping fails
38
+ if options.has_key?(:skipping_override) : skipping_parslet = options[:skipping_override]
39
+ elsif options.has_key?(:skipping) : skipping_parslet = options[:skipping]
40
+ else skipping_parslet = nil
41
+ end
42
+ break if skipping_parslet.nil?
43
+ begin
44
+ parsed = skipping_parslet.memoizing_parse(state.remainder, state.options) # guard against self references (possible infinite recursion) here?
45
+ state.skipped(parsed)
46
+ redo # skipping succeeded, try to redo
47
+ rescue ParseError
48
+ break # skipping didn't help either, give up
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # now assess whether our tries met the requirements
55
+ if state.length == 0 and @min == 0 # success (special case)
56
+ throw :ZeroWidthParseSuccess
57
+ elsif state.length < @min # matches < min (failure)
58
+ raise ParseError.new('required %d matches but obtained %d while parsing "%s"' % [@min, state.length, string],
59
+ :line_end => state.options[:line_end], :column_end => state.options[:column_end])
60
+ else # success (general case)
61
+ state.results # returns multiple matches as an array, single matches as a single object
62
+ end
63
+
64
+ end
65
+
66
+ def eql?(other)
67
+ other.instance_of? ParsletRepetition and @min == other.min and @max == other.max and @parseable.eql? other.parseable
68
+ end
69
+
70
+ protected
71
+
72
+ # For determining equality.
73
+ attr_reader :parseable, :min, :max
74
+
75
+ private
76
+
77
+ def hash_offset
78
+ 87
79
+ end
80
+
81
+ def update_hash
82
+ @hash = @min.hash + @max.hash + @parseable.hash + hash_offset # fixed offset to minimize risk of collisions
83
+ end
84
+
85
+ def min=(min)
86
+ @min = (min.clone rescue min)
87
+ update_hash
88
+ end
89
+
90
+ def max=(max)
91
+ @max = (max.clone rescue max)
92
+ update_hash
93
+ end
94
+
95
+ end # class ParsletRepetition
96
+ end # class Grammar
97
+ end # module Walrus
@@ -0,0 +1,58 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ # ParsletRepetitionDefault is a subclass that modifies the behaviour of its parent, ParsletRepetition, in a very small way. Namely, if the outcome of parsing is a ZeroWidthParse success then it is caught and the default value (defined at initialization time) is returned instead.
15
+ class ParsletRepetitionDefault < ParsletRepetition
16
+
17
+ # Possible re-factoring to consider for the future: roll the functionality of this class in to ParsletRepetition itself.
18
+ # Benefit of keeping it separate is that the ParsletRepetition itself is kept simple.
19
+ def initialize(parseable, min, max = nil, default = nil)
20
+ super(parseable, min, max)
21
+ self.default = default
22
+ end
23
+
24
+ def parse(string, options = {})
25
+ catch :ZeroWidthParseSuccess do
26
+ return super(string, options)
27
+ end
28
+ @default.clone rescue @default
29
+ end
30
+
31
+ def eql?(other)
32
+ other.instance_of? ParsletRepetitionDefault and @min == other.min and @max == other.max and @parseable.eql? other.parseable and @default == other.default
33
+ end
34
+
35
+ protected
36
+
37
+ # For determining equality.
38
+ attr_reader :default
39
+
40
+ private
41
+
42
+ def hash_offset
43
+ 69
44
+ end
45
+
46
+ def update_hash
47
+ @hash = super + @default.hash # let super calculate its share of the hash first
48
+ end
49
+
50
+ def default=(default)
51
+ @default = (default.clone rescue default)
52
+ @default.extend(LocationTracking)
53
+ update_hash
54
+ end
55
+
56
+ end # class ParsletRepetitionDefault
57
+ end # class Grammar
58
+ end # module Walrus
@@ -0,0 +1,202 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id$
8
+
9
+ require 'walrus'
10
+
11
+ module Walrus
12
+ class Grammar
13
+
14
+ class ParsletSequence < ParsletCombination
15
+
16
+ attr_reader :hash
17
+
18
+ # first and second may not be nil.
19
+ def initialize(first, second, *others)
20
+ raise ArgumentError if first.nil?
21
+ raise ArgumentError if second.nil?
22
+ @components = [first, second] + others
23
+ update_hash
24
+ end
25
+
26
+ # Override so that sequences are appended to an existing sequence:
27
+ # Consider the following example:
28
+ # A & B
29
+ # This constitutes a single sequence:
30
+ # (A & B)
31
+ # If we then make this a three-element sequence:
32
+ # A & B & C
33
+ # We are effectively creating an nested sequence containing the original sequence and an additional element:
34
+ # ((A & B) & C)
35
+ # Although such a nested sequence is correctly parsed it produces unwanted nesting in the results because instead of returning a one-dimensional an array of results:
36
+ # [a, b, c]
37
+ # It returns a nested array:
38
+ # [[a, b], c]
39
+ # The solution to this unwanted nesting is to allowing appending to an existing sequence by using the private "append" method.
40
+ # This ensures that:
41
+ # A & B & C
42
+ # Translates to a single sequence:
43
+ # (A & B & C)
44
+ # And a single, uni-dimensional results array:
45
+ # [a, b, c]
46
+ #
47
+ def &(next_parslet)
48
+ append(next_parslet)
49
+ end
50
+
51
+ SKIP_FIRST = true
52
+ NO_SKIP = false
53
+
54
+ def parse(string, options = {})
55
+ parse_common(NO_SKIP, string, options)
56
+ end
57
+
58
+ def parse_remainder(string, options = {})
59
+ parse_common(SKIP_FIRST, string, options)
60
+ end
61
+
62
+ def parse_common(skip_first, string, options = {})
63
+ raise ArgumentError if string.nil?
64
+ state = ParserState.new(string, options)
65
+ last_caught = nil # keep track of the last kind of throw to be caught
66
+ left_recursion = false # keep track of whether left recursion was detected
67
+
68
+ @components.each_with_index do |parseable, index|
69
+
70
+ if index == 0 # for first component only
71
+ if skip_first
72
+ next
73
+ end
74
+ begin
75
+ check_left_recursion(parseable, options)
76
+ rescue LeftRecursionException => e
77
+ left_recursion = true
78
+ continuation = nil
79
+ value = callcc { |c| continuation = c }
80
+ if value == continuation # first time that we're here
81
+ e.continuation = continuation # pack continuation into exception
82
+ raise e # and propagate
83
+ else
84
+ grammar = state.options[:grammar]
85
+ rule_name = state.options[:rule_name]
86
+ state.parsed(grammar.wrap(value, rule_name))
87
+ next
88
+ end
89
+ end
90
+ end
91
+
92
+ catch :ProcessNextComponent do
93
+ catch :NotPredicateSuccess do
94
+ catch :AndPredicateSuccess do
95
+ catch :ZeroWidthParseSuccess do
96
+ begin
97
+ parsed = parseable.memoizing_parse(state.remainder, state.options)
98
+ state.parsed(parsed)
99
+ rescue SkippedSubstringException => e
100
+ state.skipped(e)
101
+ rescue ParseError => e # failed, will try to skip; save original error in case skipping fails
102
+ if options.has_key?(:skipping_override) : skipping_parslet = options[:skipping_override]
103
+ elsif options.has_key?(:skipping) : skipping_parslet = options[:skipping]
104
+ else skipping_parslet = nil
105
+ end
106
+ raise e if skipping_parslet.nil? # no skipper defined, raise original error
107
+ begin
108
+ parsed = skipping_parslet.memoizing_parse(state.remainder, state.options) # guard against self references (possible infinite recursion) here?
109
+ state.skipped(parsed)
110
+ redo # skipping succeeded, try to redo
111
+ rescue ParseError
112
+ raise e # skipping didn't help either, raise original error
113
+ end
114
+ end
115
+ last_caught = nil
116
+ throw :ProcessNextComponent # can't use "next" here because it would only break out of innermost "do" rather than continuing the iteration
117
+ end
118
+ last_caught = :ZeroWidthParseSuccess
119
+ throw :ProcessNextComponent
120
+ end
121
+ last_caught = :AndPredicateSuccess
122
+ throw :ProcessNextComponent
123
+ end
124
+ last_caught = :NotPredicateSuccess
125
+ end
126
+ end
127
+
128
+ if left_recursion
129
+ results = recurse(state)
130
+ else
131
+ results = state.results
132
+ end
133
+
134
+ if skip_first
135
+ return results
136
+ end
137
+
138
+ if results.respond_to? :empty? and results.empty? and last_caught
139
+ throw last_caught
140
+ else
141
+ results
142
+ end
143
+
144
+ end
145
+
146
+ # Left-recursion helper
147
+ def recurse(state)
148
+ return state.results if state.remainder == '' # further recursion is not possible
149
+ new_state = ParserState.new(state.remainder, state.options)
150
+ last_successful_result = nil
151
+ while state.remainder != ''
152
+ begin
153
+ new_results = parse_remainder(new_state.remainder, new_state.options)
154
+ new_state.parsed(new_results)
155
+ last_successful_result = ArrayResult[last_successful_result || state.results, new_results]
156
+ rescue ParseError
157
+ break
158
+ end
159
+ end
160
+ last_successful_result || state.results
161
+ end
162
+
163
+ def eql?(other)
164
+ return false if not other.instance_of? ParsletSequence
165
+ other_components = other.components
166
+ return false if @components.length != other_components.length
167
+ for i in 0..(@components.length - 1)
168
+ return false unless @components[i].eql? other_components[i]
169
+ end
170
+ true
171
+ end
172
+
173
+ protected
174
+
175
+ # For determining equality.
176
+ attr_reader :components
177
+
178
+ private
179
+
180
+ def hash_offset
181
+ 40
182
+ end
183
+
184
+ def update_hash
185
+ @hash = hash_offset # fixed offset to avoid unwanted collisions with similar classes
186
+ @components.each { |parseable| @hash += parseable.hash }
187
+ end
188
+
189
+ # Appends another Parslet, ParsletCombination or Predicate to the receiver and returns the receiver.
190
+ # Raises if next_parslet is nil.
191
+ # Cannot use << as a method name because Ruby cannot parse it without the self, and self is not allowed as en explicit receiver for private messages.
192
+ def append(next_parslet)
193
+ raise ArgumentError if next_parslet.nil?
194
+ @components << next_parslet.to_parseable
195
+ update_hash
196
+ self
197
+ end
198
+
199
+ end # class ParsletSequence
200
+ end # class Grammar
201
+ end # module Walrus
202
+