hipe-gorillagrammar 0.0.1beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ coverage
2
+ .yardoc
3
+ .DS_Store
4
+ pkg
5
+ doc
6
+ ri
7
+ email.txt
8
+ .svn
9
+ log
10
+ .project
11
+ .loadpath
12
+ *.swp
13
+ results
14
+ test_apps
15
+ *.tmproj
16
+ *.log
17
+ *.pid
18
+ bin
19
+ vendor/gems
20
+ eraseme.*
21
+ tmp.*
data/History.txt ADDED
@@ -0,0 +1,8 @@
1
+ == 0.0.1 / 2009-11-30
2
+ * 100% C0 & C1 test coverage
3
+ * under 500 LOC
4
+ * I think this is an LR parser generator, but i'm not sure yet
5
+
6
+
7
+ == 0.0.0 / 2009-11-24
8
+ * birthday (but see the original in php on my old laptop)
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Mark Meves
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.txt ADDED
@@ -0,0 +1,11 @@
1
+ Experiments in parsing. Previous version was in the "Githelper" script of mine on github.
2
+
3
+ (The original version was a php class i wrote many years ago with the same name, ...@todo)
4
+
5
+ ThankYou's
6
+ Brian Helkamp and the webrat team for giving me an excellent Thorfile and teach examples of rspec
7
+ argv[0] Sun Nov 29 00:05:46 EST 2009 in #ruby-lang for schooling me on module inheritance chains
8
+ hagabaka Mon Nov 30 02:15:51 EST 2009
9
+ raggi for suggesting Marshal.dump/Marshal.load
10
+
11
+ Above all, thank you Yoko for putting up w/ me
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+
2
+ # require 'spec'
3
+ require 'spec/rake/spectask'
4
+ require 'spec/rake/verify_rcov'
5
+
6
+ desc "Run API and Core specs"
7
+ Spec::Rake::SpecTask.new do |t|
8
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
9
+ #t.spec_files = FileList['spec/public/**/*_spec.rb'] + FileList['spec/private/**/*_spec.rb']
10
+ t.spec_files = FileList['spec/**/*_spec.rb'] + FileList['spec/private/**/*_spec.rb']
11
+ end
12
+
13
+ desc "Run all examples with RCov"
14
+ Spec::Rake::SpecTask.new('rcov') do |t|
15
+ t.spec_files = FileList['spec/**/*_spec.rb']
16
+ t.rcov = true
17
+ t.rcov_opts = ['--exclude', 'spec,/Library/Ruby/Gems/1.8/gems']
18
+ end
19
+
20
+ RCov::VerifyTask.new(:rcovv => 'rcov') do |t|
21
+ t.threshold = 95.0
22
+ t.index_html = 'coverage/index.html'
23
+ end
24
+
25
+
data/Thorfile ADDED
@@ -0,0 +1,135 @@
1
+ # this file was originally copy-pasted from webrat's Thorfile. Thank you Bryan Helmkamp!
2
+ require 'ruby-debug'
3
+
4
+ module GemHelpers
5
+
6
+ def generate_gemspec
7
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
8
+ require "hipe-gorillagrammar"
9
+
10
+ Gem::Specification.new do |s|
11
+ s.name = 'hipe-gorillagrammar'
12
+ s.version = Hipe::GorillaGrammar::VERSION
13
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
14
+ s.authors = ["Mark Meves"]
15
+ s.email = "mark.meves@gmail.com"
16
+ s.homepage = "http://github.com/hipe/hipe-gorillagrammar"
17
+ s.date = %q{2009-11-23}
18
+ s.summary = %q{'beta attempt at a simple LR parser generator driven by DSL under 500LOC 100% C1 test coverage'}
19
+ s.description = <<-EOS.strip
20
+ LR Parser Generator (?) under 500 LOC with 100*% C1 test coverage. No useful AST yet. No useful docs yet.
21
+ EOS
22
+
23
+ # s.rubyforge_project = "webrat"
24
+
25
+ require "git"
26
+ repo = Git.open(".")
27
+
28
+ s.files = normalize_files(repo.ls_files.keys - repo.lib.ignored_files)
29
+ s.test_files = normalize_files(Dir['spec/***.rb'] - repo.lib.ignored_files)
30
+ #s.test_files = normalize_files(Dir['spec/*.rb'] - repo.lib.ignored_files)
31
+
32
+ s.has_rdoc = 'yard' # trying out arg[0]/lsegal's doc tool
33
+ #s.extra_rdoc_files = %w[README.rdoc MIT-LICENSE.txt History.txt]
34
+ #s.extra_rdoc_files = %w[MIT-LICENSE.txt History.txt]
35
+
36
+ #s.add_dependency "nokogiri", ">= 1.2.0"
37
+ #s.add_dependency "rack", ">= 1.0"
38
+ end
39
+ end
40
+
41
+ def normalize_files(array)
42
+ # only keep files, no directories, and sort
43
+ array.select do |path|
44
+ File.file?(path)
45
+ end.sort
46
+ end
47
+
48
+ # Adds extra space when outputting an array. This helps create better version
49
+ # control diffs, because otherwise it is all on the same line.
50
+ def prettyify_array(gemspec_ruby, array_name)
51
+ gemspec_ruby.gsub(/s\.#{array_name.to_s} = \[.+?\]/) do |match|
52
+ leadin, files = match[0..-2].split("[")
53
+ leadin + "[\n #{files.split(",").join(",\n ")}\n ]"
54
+ end
55
+ end
56
+
57
+ def read_gemspec
58
+ @read_gemspec ||= eval(File.read("hipe-gorillagrammar.gemspec"))
59
+ end
60
+
61
+ def sh(command)
62
+ puts command
63
+ system command
64
+ end
65
+ end
66
+
67
+ class Default < Thor
68
+ include GemHelpers
69
+
70
+ desc "gemspec", "Regenerate hipe-gorillagrammar.gemspec"
71
+ def gemspec
72
+ File.open("hipe-gorillagrammar.gemspec", "w") do |file|
73
+ gemspec_ruby = generate_gemspec.to_ruby
74
+ gemspec_ruby = prettyify_array(gemspec_ruby, :files)
75
+ gemspec_ruby = prettyify_array(gemspec_ruby, :test_files)
76
+ gemspec_ruby = prettyify_array(gemspec_ruby, :extra_rdoc_files)
77
+
78
+ file.write gemspec_ruby
79
+ end
80
+
81
+ puts "Wrote gemspec to hipe-gorillagrammar.gemspec"
82
+ read_gemspec.validate
83
+ end
84
+
85
+ desc "build", "Build a hipe-gorillagrammar gem"
86
+ def build
87
+ sh "gem build hipe-gorillagrammar.gemspec"
88
+ FileUtils.mkdir_p "pkg"
89
+ FileUtils.mv read_gemspec.file_name, "pkg"
90
+ end
91
+
92
+ desc "install", "Install the latest built gem"
93
+ def install
94
+ sh "gem install --local pkg/#{read_gemspec.file_name}"
95
+ end
96
+
97
+ desc "release", "Release the current branch to GitHub and Gemcutter"
98
+ def release
99
+ gemspec
100
+ build
101
+ Release.new.tag
102
+ Release.new.gem
103
+ end
104
+
105
+ end
106
+
107
+
108
+ class Spec < Thor
109
+ desc "spec", "el speco"
110
+ def run which='all'
111
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
112
+ t.spec_files = FileList['spec/**/*_spec.rb'] + FileList['spec/**/*_spec.rb']
113
+ debugger
114
+ 'x'
115
+
116
+ end
117
+ end
118
+
119
+
120
+
121
+ class Release < Thor
122
+ include GemHelpers
123
+
124
+ desc "tag", "Tag the gem on the origin server"
125
+ def tag
126
+ release_tag = "v#{read_gemspec.version}"
127
+ sh "git tag -a #{release_tag} -m 'Tagging #{release_tag}'"
128
+ sh "git push origin #{release_tag}"
129
+ end
130
+
131
+ desc "gem", "Push the gem to Gemcutter"
132
+ def gem
133
+ sh "gem push pkg/#{read_gemspec.file_name}"
134
+ end
135
+ end
@@ -0,0 +1,66 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{hipe-gorillagrammar}
5
+ s.version = "0.0.1beta"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Mark Meves"]
9
+ s.date = %q{2009-11-23}
10
+ s.description = %q{LR Parser Generator (?) under 500 LOC with 100*% C1 test coverage. No useful AST yet. No useful docs yet.}
11
+ s.email = %q{mark.meves@gmail.com}
12
+ s.files = [
13
+ ".gitignore",
14
+ "History.txt",
15
+ "LICENSE.txt",
16
+ "README.txt",
17
+ "Rakefile",
18
+ "Thorfile",
19
+ "hipe-gorillagrammar.gemspec",
20
+ "lib/hipe-gorillagrammar.rb",
21
+ "lib/hipe-gorillagrammar/extensions/syntax.rb",
22
+ "spec/argv.rb",
23
+ "spec/extensions/syntax_spec.rb",
24
+ "spec/grammar_spec.rb",
25
+ "spec/helpers.rb",
26
+ "spec/parse_tree_spec.rb",
27
+ "spec/parsing_spec.rb",
28
+ "spec/range_spec.rb",
29
+ "spec/regexp_spec.rb",
30
+ "spec/runtime_spec.rb",
31
+ "spec/sequence_spec.rb",
32
+ "spec/shorthand_spec.rb",
33
+ "spec/spec.opts",
34
+ "spec/symbol_reference_spec.rb",
35
+ "spec/symbol_spec.rb"
36
+ ]
37
+ s.has_rdoc = %q{yard}
38
+ s.homepage = %q{http://github.com/hipe/hipe-gorillagrammar}
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.5}
41
+ s.summary = %q{'beta attempt at a simple LR parser generator driven by DSL under 500LOC 100% C1 test coverage'}
42
+ s.test_files = [
43
+ "spec/argv.rb",
44
+ "spec/grammar_spec.rb",
45
+ "spec/helpers.rb",
46
+ "spec/parse_tree_spec.rb",
47
+ "spec/parsing_spec.rb",
48
+ "spec/range_spec.rb",
49
+ "spec/regexp_spec.rb",
50
+ "spec/runtime_spec.rb",
51
+ "spec/sequence_spec.rb",
52
+ "spec/shorthand_spec.rb",
53
+ "spec/symbol_reference_spec.rb",
54
+ "spec/symbol_spec.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
62
+ else
63
+ end
64
+ else
65
+ end
66
+ end
@@ -0,0 +1,50 @@
1
+ module Hipe::GorillaGrammar
2
+ module GorillaSymbol
3
+ def syntax_pretty_name; @name ? @name.to_s.upcase : nil end
4
+ end
5
+
6
+ module RegexpTerminal
7
+ def syntax_tokens; [syntax] end
8
+ def syntax; syntax_pretty_name || self end
9
+ end
10
+
11
+ module StringTerminal
12
+ def syntax_tokens; [syntax] end
13
+ def syntax; self end
14
+ end
15
+
16
+ class SymbolReference
17
+ def syntax_tokens; [syntax_pretty_name] end
18
+ end
19
+
20
+ module NonTerminalSymbol
21
+ def syntax
22
+ return (syntax_tokens * ' ').gsub(%r{(?:[\[\(] +| +[\]\)]| +\| +)}){|x| x.strip}
23
+ end
24
+ end
25
+
26
+ class Sequence
27
+ def syntax_tokens
28
+ @group.map{ |x| x.syntax_tokens }.flatten
29
+ end
30
+ end
31
+
32
+ class RangeOf
33
+ def syntax_tokens;
34
+ is_one_or_more = (@range == (1..Infinity) and @group.size == 1 and @group[0].kind_of? SymbolReference)
35
+ things = case true
36
+ when (@range == (0..1)) then ['[', nil, ']']
37
+ when (@range == (1..1)) then ['(', nil, ')']
38
+ when (@range == (0..Infinity)) then ['[', '[...]', ']']
39
+ when (is_one_or_more) then ['[', '[...]', ']'] # *special handing below
40
+ else [%{(#{@range.to_s}) of (}, nil, ')']
41
+ end
42
+ thing = @group.map{ |x| x.syntax_tokens }.zip(Array.new(@group.size-1,'|')).flatten.compact
43
+ thing.unshift things[0]
44
+ thing.push things[1] if things[1]
45
+ thing.push things[2]
46
+ thing.unshift @group[0].syntax_pretty_name if is_one_or_more
47
+ thing
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,492 @@
1
+ require 'rubygems'
2
+ #require 'ruby-debug'
3
+ require 'singleton'
4
+
5
+ class Symbol
6
+ def satisfied?; @satisfied; end
7
+ def accepting?; @accepting; end
8
+ end
9
+ # these are the different states that a parsing node can have
10
+ # after it's consumed a token. absurd hack so we can use smiley faces as symbols (note 2)
11
+ # mouth open means "still accepting". Smiley means "is satisfied". four permutations
12
+ :D.instance_variable_set :@satisfied, true # open mouth happy
13
+ :D.instance_variable_set :@accepting, true
14
+ :>.instance_variable_set :@satisfied, true # closed mouth happy
15
+ :>.instance_variable_set :@accepting, false
16
+ :O.instance_variable_set :@satisfied, false # open mouth unhappy
17
+ :O.instance_variable_set :@accepting, true
18
+ :C.instance_variable_set :@satisfied, false # closed mouth unhappy
19
+ :C.instance_variable_set :@accepting, false
20
+ module Hipe
21
+ def self.GorillaGrammar opts=nil, &block
22
+ GorillaGrammar.define(opts, &block)
23
+ end
24
+ module GorillaGrammar
25
+ VERSION = '0.0.1beta'
26
+ Infinity = 1.0 / 0
27
+ def self.define(opts=nil, &block)
28
+ runtime = Runtime.instance
29
+ g = runtime.create_grammar! opts
30
+ runtime.push_grammar g
31
+ begin
32
+ g.define(&block)
33
+ ensure
34
+ runtime.pop_grammar
35
+ end
36
+ end
37
+ class Runtime # for global-like stuff
38
+ include Singleton
39
+ def initialize
40
+ @grammar_stack = []
41
+ @grammars = {}
42
+ end
43
+ def push_grammar(grammar)
44
+ raise UsageFailure.new('sorry, no making grammars inside of grammars') if @grammar_stack.size > 0
45
+ @grammar_stack << grammar
46
+ end
47
+ def pop_grammar; @grammar_stack.pop; end
48
+ def current_grammar!
49
+ raise UsageFailure.new("no current grammar") unless current_grammar
50
+ current_grammar
51
+ end
52
+ def current_grammar; @grammar_stack.last; end
53
+ def self.method_missing(a,*b)
54
+ return instance.send(a,*b) if instance.respond_to? a
55
+ raise NoMethodError.new %{undefined method `#{a}' for #{inspect}}
56
+ end
57
+ def create_grammar! opts
58
+ opts ||= {}
59
+ g = Grammar.new opts
60
+ g.name = %{grammar#{@grammars.size+1}} unless g.name
61
+ raise GorillaException.new("for now we can't reopen grammars") if @grammars[g.name]
62
+ @grammars[g.name] = g
63
+ end
64
+ def get_grammar name; @grammars[name]; end
65
+ # enables the use of =~, |, (0..1).of .., .., .., and :some_name[/regexp/] in grammars
66
+ def enable_operator_shorthands
67
+ return if @shorthands_enabled
68
+ Symbol.instance_eval { include SymbolHack, PipeHack }
69
+ String.instance_eval { include PipeHack }
70
+ Fixnum.instance_eval { include FixnumOfHack }
71
+ Range.instance_eval { include RangeOfHack }
72
+ @shorthands_enabled = true
73
+ end
74
+ end
75
+ class Grammar < Hash
76
+ attr_accessor :name, :root
77
+ alias_method :names, :keys
78
+ def initialize opts
79
+ opts ||= {}
80
+ @name = opts[:name] if opts[:name]
81
+ @with_operator_shorthands = opts.has_key?(:enable_operator_shorthands) ?
82
+ opts[:enable_operator_shorthands] : true
83
+ end
84
+ def == other; self.inspect == other.inspect end #hack
85
+ def define(&block) # should it return grammar or last symbol? grammar. (note 4:)
86
+ Runtime.enable_operator_shorthands if @with_operator_shorthands
87
+ @root = GorillaSymbol.factory instance_eval(&block) # allows anonymous ranges & sequences as grammr
88
+ @root = @root.dereference if @root.instance_of? SymbolReference
89
+ self[@root.name = '__main__'] = @root if (@root.name.nil?)
90
+ self
91
+ end
92
+ def parse tox; @root.parse tox; end
93
+ def self.register_shorthand name, klass
94
+ instance_eval {
95
+ define_method(name) { |*args|
96
+ klass.construct_from_shorthand(name, *args)
97
+ }
98
+ }
99
+ end
100
+ def []= name, symbol
101
+ raise GrammarGrammarException.new(%{Can't redefine symbols. Multiple definitions for :#{name}}) if self[name]
102
+ unless symbol.kind_of? GorillaSymbol
103
+ raise GorillaException.new(%{Expecting GorillaSymbol had #{symbol.inspect}})
104
+ end
105
+ symbol.name = name
106
+ super name, symbol
107
+ end
108
+ end
109
+ class GorillaException < Exception
110
+ def initialize(*args)
111
+ super args.shift if args[0].instance_of? String
112
+ @info = args.last.instance_of?(Hash) ? args.pop : {}
113
+ @extra = args.count > 0 ? args : []
114
+ end
115
+ def tree; @info[:tree]; end
116
+ end
117
+ class UsageFailure < GorillaException; end
118
+ class GrammarGrammarException < UsageFailure; end
119
+ class AmbiguousGrammar < GrammarGrammarException; end
120
+ class ParseFailure < GorillaException
121
+ def is_error?; true; end
122
+ def inspect; message; end # just for irb debugo
123
+ end
124
+ class UnexpectedEndOfInput < ParseFailure;
125
+ def message; <<-EOS.gsub(/^ /,'').gsub("\n",' ')
126
+ sorry, unexpected end of input. I was
127
+ expecting you to say #{RangeOf.join(tree.expecting.uniq,', ',' or ')}.
128
+ EOS
129
+ end
130
+ end
131
+ class UnexpectedInput < ParseFailure;
132
+ def message
133
+ %{sorry, i don't know what you mean by "#{@info[:token]}". } +
134
+ ((ex = tree.expecting.uniq).size == 0 ? %{i wasn't expecting any more input.} :
135
+ %{i was expecting you to say #{RangeOf.join(ex,', ',' or ')}.})
136
+ end
137
+ end
138
+ module PipeHack
139
+ def |(other)
140
+ if self.instance_of? RangeOf
141
+ raise UsageFailure("no") unless self.is_pipe_hack
142
+ self << other
143
+ ret = self
144
+ else
145
+ ret = RangeOf.new((1..1),[self,other])
146
+ ret.is_pipe_hack = true
147
+ end
148
+ ret
149
+ end
150
+ end
151
+ module FixnumOfHack # note that IRL, this will rarely be used for other than 1
152
+ def of *args
153
+ return RangeOf.new((1..1), args)
154
+ end
155
+ end
156
+ module RangeOfHack
157
+ def of *args
158
+ return RangeOf.new(self, args)
159
+ end
160
+ end
161
+ module SymbolHack
162
+ def =~ (symbol_data)
163
+ return super unless ( grammar = Runtime.instance.current_grammar )
164
+ grammar[self] = GorillaSymbol.factory symbol_data
165
+ end
166
+ def [] (*symbol_data)
167
+ return super unless Runtime.instance.current_grammar
168
+ symbol_data = symbol_data[0] if symbol_data.size == 1
169
+ new_symbol = self.=~(symbol_data)
170
+ return SymbolReference.new self
171
+ end
172
+ end
173
+ module ParseTree; def is_error?; false end end
174
+ module GorillaSymbol # @abstract base
175
+ def self.factory obj # maps data structures to symbols. returns same object or new
176
+ case obj
177
+ when GorillaSymbol then obj # this one must stay on top!
178
+ when Array then Sequence.new(*obj)
179
+ # note RangeOf is never constructed directly with factory()
180
+ when Symbol then SymbolReference.new obj
181
+ when String then obj.extend StringTerminal
182
+ when Regexp then obj.extend RegexpTerminal
183
+ else
184
+ raise UsageFailure.new %{Can't determine symbol type for "#{obj.inspect}"},:obj=>obj
185
+ end
186
+ end
187
+ def finalize; self; end
188
+ attr_accessor :name
189
+ attr_reader :kleene
190
+ def natural_name; name ? name.to_s.gsub('_',' ') : nil; end
191
+ def fork_for_parse; Marshal.load Marshal.dump(self); end # note 1
192
+ def reinit_for_parse; extend ParseTree end
193
+ def prune! names=[]; a=(names&instance_variables); a.each{ |x| remove_instance_variable x }; a.size end
194
+ def dereference; self; end
195
+ end
196
+ module TerminalSymbol;
197
+ include GorillaSymbol;
198
+ attr_reader :status
199
+ def inspect; [@name ? @name.inspect : nil, super, %{(#{@token ? '.' : '_' })}].compact.join; end
200
+ def pretty_print q
201
+ q.text inspect
202
+ q.group 1,'{','}' do
203
+ instance_variables.sort.each do |n|
204
+ next if instance_variable_get(n).nil?
205
+ q.text %{#{n}=}; q.pp instance_variable_get(n); q.text(';'); q.breakable
206
+ end
207
+ end
208
+ end
209
+ def recurse &block; yield self end
210
+ def tokens; [@token]; end
211
+ end
212
+ module NonTerminalSymbol;
213
+ include GorillaSymbol;
214
+ def [] (i); super(i) ? super(i) : @group[i]; end
215
+ def size; kind_of?(ParseTree) ? super : @group.size; end
216
+ def == other
217
+ kind_of?(ParseTree) ? builtin_equality(other) :
218
+ (other.class == self.class && @name == @name and @group == other.group)
219
+ end
220
+ attr_reader :group
221
+ def _inspect left, right, name = nil
222
+ name = %{:#{@name}} if name.nil? and @name
223
+ [name, left,
224
+ (0..(@group ? @group.size : self.size)-1).map{|i| (self[i] ? self[i] : @group[i]).inspect}.join(', '),
225
+ right ].compact.join
226
+ end
227
+ def pretty_print q
228
+ names = instance_variables.sort{|a,b| (a=='@group') ? -1 : (b=='@group' ? 1 : a<=>b)}
229
+ names.delete_if{|x| instance_variable_get(x).nil?} # it may have been pruned
230
+ q.group 1,'{','}' do
231
+ q.breakable
232
+ # show any non-nil properties of this object
233
+ names.each do |name|
234
+ q.text %{#{name}:}
235
+ q.pp instance_variable_get(name)
236
+ q.breakable
237
+ end
238
+ # show any captured children of this object (if it's parsed something)
239
+ each_with_index do |child, i|
240
+ q.text %{#{i}=>}
241
+ q.pp child
242
+ q.breakable
243
+ end
244
+ end
245
+ end
246
+ def status; raise 'no' unless @status; @status end
247
+ def _status=(smiley); raise 'no' unless smiley.kind_of? Symbol; @status = smiley end
248
+ def recurse &block
249
+ sum_like = yield self
250
+ self.each{ |x| if x.respond_to?(:recurse) then sum_like += x.recurse(&block) end }
251
+ sum_like
252
+ end
253
+ def tokens; [] end # for use in a call to recurse e.g. non_terminal.recurse{|x| x.tokens}
254
+ end
255
+ module StringTerminal # this could also be considered a special case of regexp terminal
256
+ include TerminalSymbol
257
+ def match token
258
+ status = if (self==token)
259
+ @token = token
260
+ (:>)
261
+ else
262
+ (:C)
263
+ end
264
+ @status = status
265
+ end
266
+ def expecting; [%{"#{self}"}]; end
267
+ end
268
+ module RegexpTerminal
269
+ include TerminalSymbol
270
+ Grammar.register_shorthand :regexp, self
271
+ def self.construct_from_shorthand name, *args
272
+ args[0].extend self
273
+ end
274
+ def [](capture_offset); @captures[capture_offset]; end
275
+ def expecting; @name ? [natural_name] : [self.inspect]; end
276
+ def match token # cleaned up for note 5 @ 11/27 11:39 am
277
+ @status = if (md = super(token))
278
+ @captures = md.captures if (md.captures.size>0)
279
+ @token = token
280
+ (:>)
281
+ else
282
+ (:C)
283
+ end
284
+ end
285
+ end
286
+ class SymbolReference
287
+ include GorillaSymbol
288
+ def inspect; %{::#{@name}}; end
289
+ def pp q; q.text(inspect); end
290
+ def initialize symbol
291
+ @name = symbol
292
+ @grammar_name = Runtime.current_grammar!.name
293
+ end
294
+ def dereference;
295
+ @actual ||= Runtime.instance.get_grammar(@grammar_name)[@name].fork_for_parse.reinit_for_parse
296
+ end
297
+ def dereference_light
298
+ Runtime.instance.get_grammar(@grammar_name)[@name]
299
+ end
300
+ [:kleene, :expecting, :reinit_for_parse].each do |name|
301
+ define_method(name){ dereference_light.send(name) }
302
+ end
303
+ end
304
+ module CanParse
305
+ def parse tokens
306
+ tree = self.fork_for_parse.reinit_for_parse
307
+ while token = tokens.shift and tree.match(token).accepting?; end
308
+ if (:C) == tree.status or (tokens.size > 0) # xtra
309
+ UnexpectedInput.new :token=>(:C==tree.status ? token : tokens.first), :tree=>tree
310
+ elsif ! tree.status.satisfied?
311
+ UnexpectedEndOfInput.new :tree=>tree
312
+ else
313
+ tree.finalize; #tree.prune!
314
+ end
315
+ end
316
+ end
317
+ class Sequence < Array
318
+ alias_method :builtin_equality, :==
319
+ include CanParse, NonTerminalSymbol, PipeHack
320
+ Grammar.register_shorthand :sequence, self
321
+ def self.construct_from_shorthand(name, *args); self.new(*args); end
322
+ def initialize *args
323
+ @index = 0 # we use this to report expecting whether or not we are a parse
324
+ raise GrammarGrammarException.new "Arguments must be non-zero length" unless args.size > 0
325
+ @group = args.map{|x| GorillaSymbol.factory x }
326
+ end
327
+ def reinit_for_parse;
328
+ super
329
+ @stop_here = @group.size;
330
+ num = @group.reverse.map{|x|
331
+ x.reinit_for_parse
332
+ x.kleene
333
+ }.find_index{|x| x==false || x.nil? } || @group.size
334
+ @satisfied_at = @group.size - num # trailing children that can be zero length affect when we are satisfied.
335
+ @status = (@satisfied_at==@index) ? :D : :O
336
+ self
337
+ end
338
+ def inspect; _inspect '[',']'; end
339
+ def finalize; _advance if @child; self; end
340
+ def prune!; super %w(@group @index @satisfied_at @status @stop_here @kleene) end
341
+ def expecting # cleanup @fixme @todo. this was some genetic programming
342
+ return [] if self.status == :>
343
+ child = @child || self.last # might be nil if nothing was grabbed
344
+ expecting = []
345
+ if (child) # if we were able to parse at least one token
346
+ if ((index=@index-1) >= 0) # go backwards, reporting expected from any kleene closures
347
+ (index..0).each do
348
+ if (self[index].kleene) # kleeneup to use find_index
349
+ expecting |= self[index].expecting
350
+ end
351
+ end
352
+ end
353
+ expecting += child.expecting unless child.status == :> # report the expecting tokens from the current symbol ()
354
+ end
355
+ index = size
356
+ # index = child ? ( @index + 1 ) : @index # whether or not we got to a token,
357
+ #index = @index
358
+ if ((!child or child.kleene or child.status.satisfied?) and @group and index < @group.size)
359
+ begin # go forward reporting the expecting from any kleene closures
360
+ expecting |= @group[index].expecting
361
+ break unless @group[index].kleene
362
+ end while((index+=1)<@group.size)
363
+ end
364
+ expecting << "an end to the phrase" if (index == @group.size)
365
+ expecting
366
+ end
367
+ def _advance
368
+ # @child.prune! unless @child.kleene #@todo
369
+ self << remove_instance_variable('@child') # child must be :> (if the child was :D we should keep it)
370
+ case (@index += 1)
371
+ when @stop_here then (:>) # iff we just finished the last child (there is no lookahead note 5)
372
+ when @satisfied_at then (:D)
373
+ else (:O)
374
+ end
375
+ end
376
+ def match token
377
+ while true
378
+ @child ||= @group[@index].dereference; # @group[@index] = nil; save it for prune
379
+ child_prev_status = @child.status
380
+ child_status = @child.match token
381
+ self._status = case child_status
382
+ when :> then _advance
383
+ when :O then :O
384
+ when :D then ((@index+1)>=@satisfied_at) ? :D : :O
385
+ when :C
386
+ if (child_prev_status == :D)
387
+ self._status = _advance
388
+ next if @status.accepting?
389
+ :C
390
+ else
391
+ :C
392
+ end
393
+ else; raise GorillaException.new('symbol returned bad status')
394
+ end # case
395
+ break; # break out of infinite loop
396
+ end # infinite loop
397
+ @status
398
+ end # def match
399
+ end # Sequence
400
+ class MoreOneOff
401
+ Grammar.register_shorthand :more, self
402
+ def self.construct_from_shorthand(a,*b); Infinity; end
403
+ end
404
+ class RangeOf < Array
405
+ alias_method :builtin_equality, :==
406
+ include CanParse, NonTerminalSymbol, PipeHack
407
+ [:zero_or_more,:one_or_more,:zero_or_one,:one,:range_of].each do |name|
408
+ Grammar.register_shorthand name, self
409
+ end
410
+ def self.construct_from_shorthand name, *args; self.new name, args; end
411
+ def initialize name, args
412
+ unless args.size > 0
413
+ raise GrammarGrammarException.new "Arguments must be non-zero length"
414
+ end
415
+ @range = name.instance_of?(Range) ? name : case name
416
+ when :zero_or_more then (0..Infinity)
417
+ when :one_or_more then (1..Infinity)
418
+ when :zero_or_one then (0..1)
419
+ when :one then (1..1)
420
+ when :range_of then args.shift
421
+ else raise UsageFailure.new(%{invalid name string "#{name}"})
422
+ end
423
+ raise UsageFailure.new("must be range") unless @range.instance_of? Range
424
+ @group = args.map{|x| GorillaSymbol.factory x }
425
+ end
426
+ attr_reader :range
427
+ attr_accessor :is_pipe_hack
428
+ def reinit_for_parse
429
+ super
430
+ @group.each{ |x| x.reinit_for_parse; @unkleene = true unless x.kleene }
431
+ @kleene = @range.begin == 0 || ! @unkleene
432
+ @status = @kleene ? :D : :O
433
+ @frame_prototype = Marshal.dump @group
434
+ _reframe
435
+ self
436
+ end
437
+ def prune!; super %w(@frame @frame_prototype @range @group @kleene @unkleene) end
438
+ def << jobber # for PipeHack. code smell (:note 1)
439
+ if kind_of?(ParseTree) then super jobber
440
+ else; @group << GorillaSymbol.factory(jobber); end
441
+ end
442
+ def expecting; (@frame || @group || []).map{ |x| x.expecting }.flatten end
443
+ def inspect;
444
+ _inspect '(',')',[@name ? %{:#{@name}} : nil , '(', @range.to_s.gsub('..Infinity',' or more'),'):'].compact.join
445
+ end
446
+ def match token
447
+ @status = nil
448
+ statii = Hash.new(){ |h,k| h[k] = [] }
449
+ @frame.each { |symbol| status = symbol.match(token); statii[status] << symbol }
450
+ if statii[:C].size == @frame.size then @status = :C
451
+ else case statii[:>].size
452
+ when 2..Infinity then raise AmbiguousGrammar.new(:parse => self, :children => statii[:>] )
453
+ when 1 then @status = _advance(statii[:>][0])
454
+ when 0 #fallthru
455
+ end end
456
+ # past this point we know that zero are :> and not all are :C, so some must be :O or :D
457
+ if @status.nil?
458
+ @frame.delete_if{ |x| ! x.status.accepting? }
459
+ @status = @frame.select{|x| x.status == :D }.count == @frame.size ? :D : :O
460
+ end
461
+ @status
462
+ end
463
+ def _advance object
464
+ self << object # (object.kleene ? object : object.prune!)
465
+ case size
466
+ when @range.end then :>
467
+ when @range then _reframe; :D
468
+ else; _reframe; :O
469
+ end
470
+ end
471
+ def _reframe; @frame = (Marshal.load @frame_prototype).map{|x| x.kind_of?(SymbolReference) ? x.dereference : x}; end
472
+ ## @fixme this is waiting for unparse()
473
+ def self.join list, conj1, conj2, &block
474
+ list.map!(&block) if block
475
+ case list.size
476
+ when 0 then ''
477
+ when 1 then list[0]
478
+ else
479
+ joiners = ['',conj2]
480
+ joiners += Array.new(list.size-2,conj1) if list.size >= 3
481
+ list.zip(joiners.reverse).flatten.join
482
+ end
483
+ end
484
+ end # RangeOf
485
+ end
486
+ end
487
+ # note 1 having grammar nodes as parse tree nodes. is it code smell?
488
+ # note 3 (resolved - we use them now) consider getting rid of unused base classes
489
+ # note 5 peeking isn't even used at this point
490
+ # note 6 you might use to_s for unparse
491
+ # note 7 todo: descention from regexp to string or vice versa,
492
+ # note 8 one day we might have set-like RangeOfs that .., note 9 rangeof forks, sequence just inits group