rdparser 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .bundle/
2
+ coverage/
3
+ files/
4
+ test_files/
5
+ Gemfile.lock
6
+ .yardoc/
7
+ pkg/
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@rdparser --create
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :gemcutter
2
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rake'
5
+ require 'rdoc/task'
6
+ require 'rspec/core/rake_task'
7
+
8
+ require 'rake/packagetask'
9
+ require 'rubygems/package_task'
10
+
11
+ RDPARSER_GEMSPEC = eval(File.read(File.expand_path("../rdparser.gemspec", __FILE__)))
12
+
13
+ desc 'Default: run specs'
14
+ task :default => 'spec'
15
+
16
+ namespace :spec do
17
+ desc 'Run all specs in spec directory (format=progress)'
18
+ RSpec::Core::RakeTask.new(:progress) do |t|
19
+ t.pattern = './spec/**/*_spec.rb'
20
+ t.rspec_opts = ['--color', '--format=progress']
21
+ end
22
+
23
+ desc 'Run all specs in spec directory (format=documentation)'
24
+ RSpec::Core::RakeTask.new(:documentation) do |t|
25
+ t.pattern = './spec/**/*_spec.rb'
26
+ t.rspec_opts = ['--color', '--format=documentation']
27
+ end
28
+ end
29
+
30
+ task :spec => 'spec:progress'
31
+
32
+ desc 'Generate documentation for the a4-core plugin.'
33
+ Rake::RDocTask.new(:rdoc) do |rdoc|
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = 'RDParser'
36
+ rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
37
+ rdoc.rdoc_files.include('README')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
40
+
41
+ Gem::PackageTask.new(RDPARSER_GEMSPEC) do |p|
42
+ p.gem_spec = RDPARSER_GEMSPEC
43
+ end
@@ -0,0 +1,10 @@
1
+ # Provide Array with method that appends hashes, but concatenates arrays
2
+ module RDParser
3
+ module ArrayExtensions
4
+ def append_or_blend(i)
5
+ i.class == Hash ? self << i : self.concat(i)
6
+ end
7
+ end
8
+ end
9
+
10
+ Array.send :include, RDParser::ArrayExtensions
data/lib/rdparser.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'strscan'
2
+ require File.expand_path('../patches/array.rb', __FILE__)
3
+
4
+ # A Simple Recursive Descending (LL) Parser for Ruby
5
+ module RDParser
6
+ require File.expand_path('../rdparser/exceptions.rb', __FILE__)
7
+ autoload :RDParser, File.expand_path('../rdparser/rdparser.rb', __FILE__)
8
+ autoload :Scanner, File.expand_path('../rdparser/scanner.rb', __FILE__)
9
+
10
+ # change to true to get flooded with debug messages during parsing
11
+ DEBUG = false
12
+ end
@@ -0,0 +1,4 @@
1
+ module RDParser
2
+ # raised when Parser cannot get through the entire string.
3
+ class ParserError < StandardError; end
4
+ end
@@ -0,0 +1,195 @@
1
+ class RDParser::RDParser
2
+ # Class used to accept Ruby metafoo when specifying grammars with a block and virtual methods
3
+ class GenericBlockToHashCreator; def method_missing(m, *args); @h ||= {}; @h[m] = args.first; @h; end; end
4
+
5
+ # Creates a parser based on a certain grammar provided either as a hash or as
6
+ # methods within a block (that then uses GrammarCreator)
7
+ def initialize(grammar = '')
8
+ @grammar = grammar == '' ? yield(GenericBlockToHashCreator.new) : grammar.dup
9
+ end
10
+
11
+ # Parse content using the parser object's grammar set
12
+ def parse(rule, content, options = {})
13
+ # @depth is used to make debugging output look nice and nested
14
+ @depth = -1
15
+
16
+ # Create a string scanner object based on the content
17
+ @content = RDParser::Scanner.new(content, :slurp_whitespace => !options[:manual_whitespace])
18
+
19
+ # Kick off the show by parsing from the rule specified
20
+ result = parse_section(rule.to_sym).flatten
21
+
22
+ # Raises ParserError when cannot get parse the entire content and :partial is not set.
23
+ raise ParserError, %{Cannot parse the entire content! (error was on position ##{@content.position}: "#{content[0...@content.position]}|#{content[@content.position..@content.position]}|#{content[@content.position+1..-1]}")} unless options[:partial] == true or @content.eos?
24
+
25
+ result
26
+ end
27
+
28
+ # Parse the content based on the rulesets for a particular grammar context
29
+ def parse_section(rule)
30
+ # Increase the depth of parsing - used for debug messages currently
31
+ @depth += 1
32
+
33
+ # For each distinct set of rules within a ruleset, get parsing..
34
+ sub_rulesets(rule).each do |ruleset|
35
+ RDParser::DEBUG && debug_message("RULE SET : #{ruleset}")
36
+
37
+ # By default, we assume failure!
38
+ success = false
39
+ output = []
40
+
41
+ # Keep a local copy of the current position to roll back to if things get hairy..
42
+ was_position = @content.position
43
+
44
+ # Go through each rule in this ruleset
45
+ sub_rules(ruleset).each do |r|
46
+ RDParser::DEBUG && debug_message("Running rule '#{r}' against '#{@content.lookahead}'")
47
+ suboutput = []
48
+
49
+ # Match a rule with 1 or more occurrences (e.g. "rule(s)")
50
+ if r =~ /(\w+)(\(s\))$/
51
+ r = $1
52
+
53
+ # Force the first occurrence to succeed or break this sub-ruleset
54
+ unless result = match_for_rule(r.to_sym)
55
+ success = false
56
+ break
57
+ end
58
+
59
+ suboutput.append_or_blend result
60
+
61
+ # Now pick up any of the "or more" occurrences for free
62
+ while result = match_for_rule(r.to_sym)
63
+ suboutput.append_or_blend result
64
+ end
65
+
66
+ # Match a rule with 0 or more occurrences (e.g. "rule(s?)")
67
+ elsif r =~ /(\w+)(\(s\?\))$/
68
+ r = $1
69
+ while result = match_for_rule(r.to_sym)
70
+ suboutput.append_or_blend result
71
+ end
72
+
73
+ # Match a rule with 0 or 1 occurrences (e.g. "rules(?)")
74
+ elsif r =~ /(\w+)(\(\?\))$/
75
+ r = $1
76
+ if result = match_for_rule(r.to_sym)
77
+ suboutput.append_or_blend result
78
+ end
79
+
80
+ # Match a rule that has one single occurrence
81
+ else
82
+ unless result = match_for_rule(r.to_sym)
83
+ success = false
84
+ break
85
+ end
86
+
87
+ suboutput.append_or_blend result
88
+ end
89
+
90
+ success = true
91
+
92
+ # Append the output from this rule to the output we'll use later..
93
+ output += suboutput
94
+ end
95
+
96
+ # We've either processed all the rules for this ruleset, or.. it failed
97
+ if success
98
+ RDParser::DEBUG && debug_message("Success of all rules in #{ruleset}")
99
+
100
+ # No need to check any more rulesets! We've just passed one,
101
+ # so drop the depth a notch, we're headed back up the tree of recursion!
102
+ @depth -= 1
103
+ RDParser::DEBUG && debug_message("SUCCEED: #{ruleset}", :passback)
104
+ return output
105
+ else
106
+ RDParser::DEBUG && debug_message("failed #{ruleset}.. moving on")
107
+
108
+ # If the rule set failed, revert the position back to that we stored earlier
109
+ @content.rollback_to(was_position)
110
+
111
+ # And clean the output.. because any output we got from a broken rule is as useful
112
+ # as an ashtray on a motorbike, a chocolate teapot, or ice cutlery.
113
+ RDParser::DEBUG && debug_message("FAIL: #{ruleset}", :passback)
114
+ end
115
+ end
116
+
117
+ # Well nothing passed, so this rule was totally bogus. Great. We've totally wasted our time.
118
+ @depth -= 1
119
+ return false
120
+ end
121
+
122
+ # Parse the content based on a single subrule
123
+ def match_for_rule(rule)
124
+ RDParser::DEBUG && debug_message("TRYING #{rule}")
125
+ output = []
126
+ rule_data = @grammar[rule]
127
+
128
+ # If the rule is a string literal "likethis" then match it
129
+ if rule.to_s =~ /^\"(.+)\"$/
130
+ m = $1
131
+ if @content.scan(m)
132
+ RDParser::DEBUG && debug_message("GOT #{m}")
133
+ output << {rule => m}
134
+ else
135
+ return false
136
+ end
137
+
138
+ # Is the rule a regular expression?
139
+ elsif rule_data.class == Regexp
140
+ # If we get a match.. do stuff!
141
+ if c = @content.scan(rule_data)
142
+ RDParser::DEBUG && debug_message("GOT IT --> #{c}")
143
+ output << {rule => c}
144
+ else
145
+ # If we get no match, go and cry to mommy^H^H^H^H^Hhead up the recursion ladder
146
+ return false
147
+ end
148
+
149
+ # Is the rule a string of other rules?
150
+ elsif rule_data.class == String
151
+ # A RULE THAT HAS RULES?? RECURSION IN THE HOUSE!
152
+ response = parse_section(rule)
153
+
154
+ # But did it really work out as planned?
155
+ if response
156
+ # Yes.. so celebrate and process the response.
157
+ RDParser::DEBUG && debug_message("GOT #{rule}")
158
+ return {rule => response}
159
+ else
160
+ # No.. so throw a hissyfit
161
+ RDParser::DEBUG && debug_message("NOT GOT #{rule}")
162
+ return false
163
+ end
164
+ end
165
+ output
166
+ end
167
+
168
+ # Splits a ruleset into its constituent rules or matches by whitespace
169
+ def sub_rules(ruleset); ruleset.split(/\s+/); end
170
+
171
+ # Extracts all rulesets from a single rule
172
+ # e.g. 'rule1 rule2 | rule3 | rule4 rule 5' is three rule sets separated by bars
173
+ def sub_rulesets(rule); @grammar[rule].split(/\s+\|\s+/); end
174
+
175
+ # A 'pretty printer' for RDParser's syntax trees (pp just doesn't cut it for these beasties)
176
+ # There's probably a nice iterative way of doing this but I'm tired.
177
+ def RDParser.text_syntax_tree(v, depth = 0, output = '')
178
+ if v.class == Array
179
+ v.each { |a| output = text_syntax_tree(a, depth, output) }
180
+ elsif v.class == Hash
181
+ v.each do |a, b|
182
+ output += (" " * depth) + a.to_s
183
+ b.class == String ? output += " => #{b}\n" : output = text_syntax_tree(b, depth + 1, output + "\n")
184
+ end
185
+ end
186
+ output
187
+ end
188
+
189
+ # Prints a debugging message if debug mode is enabled
190
+ def debug_message(message, priority = true)
191
+ # Let's different types of message through
192
+ return if priority != RDParser::DEBUG
193
+ puts "#{(" " * @depth.to_i) if @depth && @depth > -1 } #{message}"
194
+ end
195
+ end
@@ -0,0 +1,56 @@
1
+ # Attached class below for simplicity of transfer at the moment.. but this should really be
2
+ # separated.
3
+
4
+ # Scanner is basically a proxy class for StringScanner that adds some convenience features.
5
+ # Could probably be a subclass actually, but this was a quick, scrappy job so I could get
6
+ # onto the fun stuff in RDParser!
7
+ class RDParser::Scanner
8
+ def initialize(string, options = {})
9
+ @options = options
10
+ @scanner = StringScanner.new(string.dup)
11
+ @rollback_pointers = []
12
+ end
13
+
14
+ def position
15
+ @scanner.pos
16
+ end
17
+
18
+ def eos?
19
+ @scanner.eos?
20
+ end
21
+
22
+ def rollback_position
23
+ @rollback_pointers.last
24
+ end
25
+
26
+ def lookahead
27
+ @scanner.peek(10)
28
+ end
29
+
30
+ def scan(regexp)
31
+ space = @options[:slurp_whitespace] && @options[:slurp_whitespace] == true ? /\s*/ : //
32
+ if regexp.class == String
33
+ regexp = Regexp.new(regexp.gsub(/\(|\)|\+|\|/) { |a| '\\' + a })
34
+ end
35
+ if match = @scanner.scan(/(#{space}(#{regexp}))/)
36
+ @rollback_pointers << (@scanner.pos - match.length)
37
+ @options[:slurp_whitespace] && @options[:slurp_whitespace] == true ? match.sub(/^\s*/, '') : match
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ def rollback
44
+ begin
45
+ (@scanner.pos = @rollback_pointers.pop) && true
46
+ rescue
47
+ nil
48
+ end
49
+ end
50
+
51
+ def rollback_to(posi)
52
+ @scanner.pos = posi
53
+ @rollback_pointers.delete_if { |p| p >= posi }
54
+ end
55
+ end
56
+
@@ -0,0 +1,3 @@
1
+ module RDParser
2
+ VERSION = "0.1.1"
3
+ end
data/rdparser.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require File.expand_path("../lib/rdparser/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "rdparser"
5
+ s.version = RDParser::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Peter Cooper", "Bence Golda"]
8
+ s.email = ["golda@bitandpixel.hu"]
9
+ s.homepage = "http://www.rubyinside.com/recursive-descent-parser-for-ruby-300.html"
10
+ s.summary = "rdparser-#{RDParser::VERSION}"
11
+ s.description = "A Simple Recursive Descending (LL) Parser for Ruby"
12
+ s.rubyforge_project = "rdparser"
13
+
14
+ s.add_development_dependency "rake"
15
+ s.add_development_dependency "bundler"
16
+ s.add_development_dependency "rspec"
17
+ s.add_development_dependency "simplecov"
18
+ s.add_development_dependency "spork"
19
+ s.add_development_dependency "watchr"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
23
+ #s.extra_rdoc_files = [ "README.markdown" ]
24
+ s.rdoc_options = ["--charset=UTF-8"]
25
+ s.require_path = 'lib'
26
+ end
27
+
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../../support/spec_helper', __FILE__)
2
+
3
+ describe 'A4::Authorization::Parser' do
4
+ include ParserHelper
5
+
6
+ parser do |p|
7
+ p.expression 'expression_u binary expression | expression_u'
8
+ p.expression_u 'unary expression_u | "(" expression ")" | role'
9
+ p.binary /and|or/
10
+ p.unary /not/
11
+ p.role 'role_name preposition instance_name | role_name preposition class_name | role_name'
12
+ p.preposition /of|on|in|at|for|to/
13
+ p.instance_name /:[a-z][a-zA-Z0-9]*/
14
+ p.class_name /[A-Z][a-zA-Z0-9:]*/
15
+ p.role_name /[a-z][-_a-zA-Z0-9]*/
16
+ end
17
+
18
+ it { should parse("role") }
19
+ it { should parse("role1") }
20
+ it { should parse("role1 and role2") }
21
+ it { should parse("role1 or role2") }
22
+ it { should parse("(role)") }
23
+ it { should parse("(role1) and (role2) or (role3)") }
24
+ it { should parse("(role1 and role2) or (role3)") }
25
+ it { should parse("reader of Klass") }
26
+ it { should parse("reader of :instance") }
27
+ it { should parse("admin or reader of Post or (writer of :instance and guest)") }
28
+ it { should parse("role-with-dashes") }
29
+ it { should parse("role_with_underscores") }
30
+
31
+ end
32
+
@@ -0,0 +1,28 @@
1
+ module ParserHelper
2
+ def self.included base
3
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
4
+ base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
5
+ end
6
+
7
+ module ClassMethods
8
+ def parser &block
9
+ subject { RDParser::RDParser.new(&block) }
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ def parse expression, root=:expression
15
+ class << (o=Object.new)
16
+ def description
17
+ %{should parse "#{@_expression}" starting with :#{@_root}}
18
+ end
19
+ def matches? parser
20
+ parser.parse(@_root, @_expression).kind_of?(Array)
21
+ end
22
+ end
23
+ o.instance_variable_set('@_expression', expression)
24
+ o.instance_variable_set('@_root', root)
25
+ o
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'simplecov'
5
+ SimpleCov.start
6
+
7
+ require 'rspec'
8
+ require File.expand_path('../../../lib/rdparser.rb', __FILE__)
9
+
10
+ Dir[File.expand_path('../**/*.rb', __FILE__)].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+ config.alias_it_should_behave_like_to(:it_should_behave_like, '')
14
+ config.filter_run :focused => true
15
+ config.run_all_when_everything_filtered = true
16
+ end
17
+
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdparser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Peter Cooper
9
+ - Bence Golda
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-12-17 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: &15362520 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *15362520
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: &15362100 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *15362100
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &15361680 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *15361680
48
+ - !ruby/object:Gem::Dependency
49
+ name: simplecov
50
+ requirement: &15361260 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *15361260
59
+ - !ruby/object:Gem::Dependency
60
+ name: spork
61
+ requirement: &15360840 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *15360840
70
+ - !ruby/object:Gem::Dependency
71
+ name: watchr
72
+ requirement: &15360420 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *15360420
81
+ description: A Simple Recursive Descending (LL) Parser for Ruby
82
+ email:
83
+ - golda@bitandpixel.hu
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - .gitignore
89
+ - .rvmrc
90
+ - Gemfile
91
+ - Rakefile
92
+ - lib/patches/array.rb
93
+ - lib/rdparser.rb
94
+ - lib/rdparser/exceptions.rb
95
+ - lib/rdparser/rdparser.rb
96
+ - lib/rdparser/scanner.rb
97
+ - lib/rdparser/version.rb
98
+ - rdparser.gemspec
99
+ - spec/examples/a4_spec.rb
100
+ - spec/support/parser_helper.rb
101
+ - spec/support/spec_helper.rb
102
+ homepage: http://www.rubyinside.com/recursive-descent-parser-for-ruby-300.html
103
+ licenses: []
104
+ post_install_message:
105
+ rdoc_options:
106
+ - --charset=UTF-8
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
116
+ - 0
117
+ hash: 2323939059109042522
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ segments:
125
+ - 0
126
+ hash: 2323939059109042522
127
+ requirements: []
128
+ rubyforge_project: rdparser
129
+ rubygems_version: 1.8.10
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: rdparser-0.1.1
133
+ test_files: []