rdparser 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []