walrus 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/walrus +44 -0
- data/ext/jindex/extconf.rb +11 -0
- data/ext/jindex/jindex.c +79 -0
- data/ext/mkdtemp/extconf.rb +11 -0
- data/ext/mkdtemp/mkdtemp.c +41 -0
- data/lib/walrus/additions/module.rb +36 -0
- data/lib/walrus/additions/string.rb +37 -0
- data/lib/walrus/additions/test/unit/error_collector.rb +62 -0
- data/lib/walrus/compile_error.rb +28 -0
- data/lib/walrus/compiler.rb +124 -0
- data/lib/walrus/contrib/spec/walruscloth_spec.rb +32 -0
- data/lib/walrus/contrib/walruscloth.rb +82 -0
- data/lib/walrus/diff.rb +89 -0
- data/lib/walrus/document.rb +98 -0
- data/lib/walrus/grammar/additions/proc.rb +20 -0
- data/lib/walrus/grammar/additions/regexp.rb +21 -0
- data/lib/walrus/grammar/additions/string.rb +52 -0
- data/lib/walrus/grammar/additions/symbol.rb +42 -0
- data/lib/walrus/grammar/and_predicate.rb +40 -0
- data/lib/walrus/grammar/array_result.rb +19 -0
- data/lib/walrus/grammar/continuation_wrapper_exception.rb +28 -0
- data/lib/walrus/grammar/left_recursion_exception.rb +27 -0
- data/lib/walrus/grammar/location_tracking.rb +105 -0
- data/lib/walrus/grammar/match_data_wrapper.rb +65 -0
- data/lib/walrus/grammar/memoizing.rb +41 -0
- data/lib/walrus/grammar/memoizing_cache.rb +94 -0
- data/lib/walrus/grammar/node.rb +60 -0
- data/lib/walrus/grammar/not_predicate.rb +40 -0
- data/lib/walrus/grammar/parse_error.rb +39 -0
- data/lib/walrus/grammar/parser_state.rb +181 -0
- data/lib/walrus/grammar/parslet.rb +28 -0
- data/lib/walrus/grammar/parslet_choice.rb +120 -0
- data/lib/walrus/grammar/parslet_combination.rb +26 -0
- data/lib/walrus/grammar/parslet_combining.rb +154 -0
- data/lib/walrus/grammar/parslet_merge.rb +88 -0
- data/lib/walrus/grammar/parslet_omission.rb +57 -0
- data/lib/walrus/grammar/parslet_repetition.rb +97 -0
- data/lib/walrus/grammar/parslet_repetition_default.rb +58 -0
- data/lib/walrus/grammar/parslet_sequence.rb +202 -0
- data/lib/walrus/grammar/predicate.rb +57 -0
- data/lib/walrus/grammar/proc_parslet.rb +52 -0
- data/lib/walrus/grammar/regexp_parslet.rb +73 -0
- data/lib/walrus/grammar/skipped_substring_exception.rb +36 -0
- data/lib/walrus/grammar/string_enumerator.rb +45 -0
- data/lib/walrus/grammar/string_parslet.rb +75 -0
- data/lib/walrus/grammar/string_result.rb +24 -0
- data/lib/walrus/grammar/symbol_parslet.rb +63 -0
- data/lib/walrus/grammar.rb +170 -0
- data/lib/walrus/no_parameter_marker.rb +19 -0
- data/lib/walrus/parser.rb +420 -0
- data/lib/walrus/runner.rb +356 -0
- data/lib/walrus/template.rb +75 -0
- data/lib/walrus/walrus_grammar/assignment_expression.rb +24 -0
- data/lib/walrus/walrus_grammar/block_directive.rb +28 -0
- data/lib/walrus/walrus_grammar/comment.rb +24 -0
- data/lib/walrus/walrus_grammar/def_directive.rb +64 -0
- data/lib/walrus/walrus_grammar/echo_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/escape_sequence.rb +24 -0
- data/lib/walrus/walrus_grammar/import_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/include_directive.rb +27 -0
- data/lib/walrus/walrus_grammar/instance_variable.rb +24 -0
- data/lib/walrus/walrus_grammar/literal.rb +24 -0
- data/lib/walrus/walrus_grammar/message_expression.rb +25 -0
- data/lib/walrus/walrus_grammar/multiline_comment.rb +54 -0
- data/lib/walrus/walrus_grammar/placeholder.rb +40 -0
- data/lib/walrus/walrus_grammar/raw_directive.rb +42 -0
- data/lib/walrus/walrus_grammar/raw_text.rb +45 -0
- data/lib/walrus/walrus_grammar/ruby_directive.rb +29 -0
- data/lib/walrus/walrus_grammar/ruby_expression.rb +31 -0
- data/lib/walrus/walrus_grammar/set_directive.rb +24 -0
- data/lib/walrus/walrus_grammar/silent_directive.rb +44 -0
- data/lib/walrus/walrus_grammar/slurp_directive.rb +25 -0
- data/lib/walrus/walrus_grammar/super_directive.rb +27 -0
- data/lib/walrus.rb +64 -0
- data/spec/acceptance/acceptance_spec.rb +97 -0
- data/spec/acceptance/block/basic_block.expected +1 -0
- data/spec/acceptance/block/basic_block.tmpl +3 -0
- data/spec/acceptance/block/nested_blocks.expected +5 -0
- data/spec/acceptance/block/nested_blocks.tmpl +11 -0
- data/spec/acceptance/comments/comments_and_text.expected +3 -0
- data/spec/acceptance/comments/comments_and_text.tmpl +6 -0
- data/spec/acceptance/comments/single_comment.expected +0 -0
- data/spec/acceptance/comments/single_comment.tmpl +1 -0
- data/spec/acceptance/def/alternative_def_calling_conventions.expected +3 -0
- data/spec/acceptance/def/alternative_def_calling_conventions.tmpl +18 -0
- data/spec/acceptance/def/basic_def_block_no_output.expected +0 -0
- data/spec/acceptance/def/basic_def_block_no_output.tmpl +17 -0
- data/spec/acceptance/def/defs_can_be_called_multiple_times.expected +3 -0
- data/spec/acceptance/def/defs_can_be_called_multiple_times.tmpl +6 -0
- data/spec/acceptance/def/defs_can_be_dynamic.expected +4 -0
- data/spec/acceptance/def/defs_can_be_dynamic.tmpl +12 -0
- data/spec/acceptance/echo/echo_directive_with_numeric_literal.expected +1 -0
- data/spec/acceptance/echo/echo_directive_with_numeric_literal.tmpl +1 -0
- data/spec/acceptance/echo/echo_expression_list.expected +1 -0
- data/spec/acceptance/echo/echo_expression_list.tmpl +1 -0
- data/spec/acceptance/echo/echo_short_notation.expected +1 -0
- data/spec/acceptance/echo/echo_short_notation.tmpl +1 -0
- data/spec/acceptance/echo/echo_simple_expression.expected +1 -0
- data/spec/acceptance/echo/echo_simple_expression.tmpl +1 -0
- data/spec/acceptance/echo/echo_single_quoted_string_literal.expected +1 -0
- data/spec/acceptance/echo/echo_single_quoted_string_literal.tmpl +1 -0
- data/spec/acceptance/echo/multiple_echo_statements.expected +1 -0
- data/spec/acceptance/echo/multiple_echo_statements.tmpl +2 -0
- data/spec/acceptance/includes/basic_included_file.txt +1 -0
- data/spec/acceptance/includes/basic_includer.complex +3 -0
- data/spec/acceptance/includes/basic_includer.expected +3 -0
- data/spec/acceptance/includes/basic_includer.rb +38 -0
- data/spec/acceptance/includes/complicated_included_file.txt +3 -0
- data/spec/acceptance/includes/complicated_includer.complex +3 -0
- data/spec/acceptance/includes/complicated_includer.expected +3 -0
- data/spec/acceptance/includes/complicated_includer.rb +41 -0
- data/spec/acceptance/includes/nested_include_1.txt +3 -0
- data/spec/acceptance/includes/nested_include_2.txt +1 -0
- data/spec/acceptance/includes/nested_includer.complex +3 -0
- data/spec/acceptance/includes/nested_includer.expected +4 -0
- data/spec/acceptance/includes/nested_includer.rb +41 -0
- data/spec/acceptance/inheritance/basic_child.complex +10 -0
- data/spec/acceptance/inheritance/basic_child.expected +9 -0
- data/spec/acceptance/inheritance/basic_child.rb +54 -0
- data/spec/acceptance/inheritance/basic_parent.complex +5 -0
- data/spec/acceptance/inheritance/basic_parent.expected +3 -0
- data/spec/acceptance/inheritance/basic_parent.rb +41 -0
- data/spec/acceptance/inheritance/importing_child.complex +8 -0
- data/spec/acceptance/inheritance/importing_child.expected +7 -0
- data/spec/acceptance/inheritance/importing_child.rb +46 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.complex +8 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.expected +7 -0
- data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.rb +44 -0
- data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.expected +0 -0
- data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.tmpl +15 -0
- data/spec/acceptance/multiline_comments/simple_multiline_comment.expected +2 -0
- data/spec/acceptance/multiline_comments/simple_multiline_comment.tmpl +4 -0
- data/spec/acceptance/raw/complicated_raw_example.expected +57 -0
- data/spec/acceptance/raw/complicated_raw_example.tmpl +79 -0
- data/spec/acceptance/raw-text/UTF_8.expected +12 -0
- data/spec/acceptance/raw-text/UTF_8.tmpl +12 -0
- data/spec/acceptance/raw-text/empty_file.expected +0 -0
- data/spec/acceptance/raw-text/empty_file.tmpl +0 -0
- data/spec/acceptance/raw-text/multi_line.expected +4 -0
- data/spec/acceptance/raw-text/multi_line.tmpl +4 -0
- data/spec/acceptance/raw-text/single_line.expected +1 -0
- data/spec/acceptance/raw-text/single_line.tmpl +1 -0
- data/spec/acceptance/raw-text/single_line_whitespace.expected +1 -0
- data/spec/acceptance/raw-text/single_line_whitespace.tmpl +1 -0
- data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate.tmpl +4 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.expected +1 -0
- data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.tmpl +6 -0
- data/spec/acceptance/set/unused_set.expected +0 -0
- data/spec/acceptance/set/unused_set.tmpl +1 -0
- data/spec/acceptance/set/used_set.expected +1 -0
- data/spec/acceptance/set/used_set.tmpl +2 -0
- data/spec/acceptance/silent/silent_and_echo_combined.expected +1 -0
- data/spec/acceptance/silent/silent_and_echo_combined.tmpl +2 -0
- data/spec/acceptance/silent/silent_short_notation.expected +1 -0
- data/spec/acceptance/silent/silent_short_notation.tmpl +1 -0
- data/spec/acceptance/silent/simple_silent_directive.expected +0 -0
- data/spec/acceptance/silent/simple_silent_directive.tmpl +1 -0
- data/spec/acceptance/slurp/basic_slurp_demo.expected +1 -0
- data/spec/acceptance/slurp/basic_slurp_demo.tmpl +4 -0
- data/spec/acceptance/super/super_with_no_effect.expected +4 -0
- data/spec/acceptance/super/super_with_no_effect.tmpl +5 -0
- data/spec/additions/module_spec.rb +126 -0
- data/spec/additions/string_spec.rb +99 -0
- data/spec/compiler_spec.rb +55 -0
- data/spec/grammar/additions/proc_spec.rb +25 -0
- data/spec/grammar/additions/regexp_spec.rb +37 -0
- data/spec/grammar/additions/string_spec.rb +106 -0
- data/spec/grammar/and_predicate_spec.rb +29 -0
- data/spec/grammar/continuation_wrapper_exception_spec.rb +23 -0
- data/spec/grammar/match_data_wrapper_spec.rb +41 -0
- data/spec/grammar/memoizing_cache_spec.rb +112 -0
- data/spec/grammar/node_spec.rb +126 -0
- data/spec/grammar/not_predicate_spec.rb +29 -0
- data/spec/grammar/parser_state_spec.rb +172 -0
- data/spec/grammar/parslet_choice_spec.rb +49 -0
- data/spec/grammar/parslet_combining_spec.rb +287 -0
- data/spec/grammar/parslet_merge_spec.rb +33 -0
- data/spec/grammar/parslet_omission_spec.rb +58 -0
- data/spec/grammar/parslet_repetition_spec.rb +77 -0
- data/spec/grammar/parslet_sequence_spec.rb +49 -0
- data/spec/grammar/parslet_spec.rb +23 -0
- data/spec/grammar/predicate_spec.rb +53 -0
- data/spec/grammar/proc_parslet_spec.rb +52 -0
- data/spec/grammar/regexp_parslet_spec.rb +347 -0
- data/spec/grammar/string_enumerator_spec.rb +94 -0
- data/spec/grammar/string_parslet_spec.rb +143 -0
- data/spec/grammar/symbol_parslet_spec.rb +30 -0
- data/spec/grammar_spec.rb +545 -0
- data/spec/parser_spec.rb +1418 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/walrus_grammar/comment_spec.rb +39 -0
- data/spec/walrus_grammar/echo_directive_spec.rb +63 -0
- data/spec/walrus_grammar/escape_sequence_spec.rb +85 -0
- data/spec/walrus_grammar/literal_spec.rb +41 -0
- data/spec/walrus_grammar/message_expression_spec.rb +37 -0
- data/spec/walrus_grammar/multiline_comment_spec.rb +58 -0
- data/spec/walrus_grammar/placeholder_spec.rb +48 -0
- data/spec/walrus_grammar/raw_directive_spec.rb +81 -0
- data/spec/walrus_grammar/raw_text_spec.rb +65 -0
- data/spec/walrus_grammar/silent_directive_spec.rb +34 -0
- 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
|
+
|