ghostwheel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +46 -0
- data/lib/ghost_wheel.rb +28 -0
- data/lib/ghost_wheel/build_parser.rb +11 -0
- data/lib/ghost_wheel/errors.rb +9 -0
- data/lib/ghost_wheel/expression.rb +22 -0
- data/lib/ghost_wheel/expression/alternation.rb +25 -0
- data/lib/ghost_wheel/expression/empty.rb +15 -0
- data/lib/ghost_wheel/expression/end_of_file.rb +19 -0
- data/lib/ghost_wheel/expression/literal.rb +31 -0
- data/lib/ghost_wheel/expression/look_ahead.rb +33 -0
- data/lib/ghost_wheel/expression/optional.rb +26 -0
- data/lib/ghost_wheel/expression/query.rb +32 -0
- data/lib/ghost_wheel/expression/repetition.rb +44 -0
- data/lib/ghost_wheel/expression/rule.rb +24 -0
- data/lib/ghost_wheel/expression/sequence.rb +30 -0
- data/lib/ghost_wheel/expression/transform.rb +30 -0
- data/lib/ghost_wheel/parse_results.rb +9 -0
- data/lib/ghost_wheel/parser.rb +71 -0
- data/lib/ghost_wheel/parser_builder/ghost_wheel.rb +100 -0
- data/lib/ghost_wheel/parser_builder/ruby.rb +175 -0
- data/lib/ghost_wheel/scanner.rb +42 -0
- data/setup.rb +1360 -0
- data/test/dsl/tc_build_parser.rb +29 -0
- data/test/dsl/tc_ghost_wheel_dsl.rb +143 -0
- data/test/dsl/tc_ruby_dsl.rb +227 -0
- data/test/example/tc_json_core.rb +95 -0
- data/test/example/tc_json_ghost_wheel.rb +48 -0
- data/test/example/tc_json_ruby.rb +81 -0
- data/test/helpers/ghost_wheel_namespace.rb +24 -0
- data/test/helpers/json_tests.rb +63 -0
- data/test/helpers/parse_helpers.rb +83 -0
- data/test/parser/tc_alternation_expression.rb +27 -0
- data/test/parser/tc_empty_expression.rb +15 -0
- data/test/parser/tc_end_of_file_expression.rb +27 -0
- data/test/parser/tc_literal_expression.rb +55 -0
- data/test/parser/tc_look_ahead_expression.rb +41 -0
- data/test/parser/tc_memoization.rb +31 -0
- data/test/parser/tc_optional_expression.rb +31 -0
- data/test/parser/tc_parser.rb +78 -0
- data/test/parser/tc_query_expression.rb +40 -0
- data/test/parser/tc_repetition_expression.rb +76 -0
- data/test/parser/tc_rule_expression.rb +32 -0
- data/test/parser/tc_scanning.rb +192 -0
- data/test/parser/tc_sequence_expression.rb +42 -0
- data/test/parser/tc_transform_expression.rb +77 -0
- data/test/ts_all.rb +7 -0
- data/test/ts_dsl.rb +8 -0
- data/test/ts_example.rb +7 -0
- data/test/ts_parser.rb +19 -0
- metadata +94 -0
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
require "rake/gempackagetask"
|
3
|
+
|
4
|
+
DIR = File.dirname(__FILE__)
|
5
|
+
LIB = File.join(DIR, "lib", "ghost_wheel.rb")
|
6
|
+
VERSION = File.read(LIB)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
|
7
|
+
|
8
|
+
task :default => [:test]
|
9
|
+
|
10
|
+
Rake::TestTask.new do |test|
|
11
|
+
test.libs << "test"
|
12
|
+
test.test_files = %w[test/ts_all.rb]
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
GEM_SPEC = Gem::Specification.new do |spec|
|
17
|
+
spec.name = "ghostwheel"
|
18
|
+
spec.version = VERSION
|
19
|
+
spec.platform = Gem::Platform::RUBY
|
20
|
+
spec.summary = "Ghost Wheel is a pure Ruby parser generator."
|
21
|
+
spec.files = Dir.glob("{lib,test}/**/*.rb").
|
22
|
+
delete_if { |item| item.include?(".svn") } +
|
23
|
+
["Rakefile", "setup.rb"]
|
24
|
+
|
25
|
+
spec.test_suite_file = "test/ts_all.rb"
|
26
|
+
# spec.has_rdoc = true
|
27
|
+
# spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
|
28
|
+
# spec.rdoc_options << '--title' << 'Ghost Wheel Documentation' <<
|
29
|
+
# '--main' << 'README'
|
30
|
+
|
31
|
+
spec.require_path = 'lib'
|
32
|
+
|
33
|
+
spec.author = "James Edward Gray II"
|
34
|
+
spec.email = "james@grayproductions.net"
|
35
|
+
spec.rubyforge_project = "ghostwheel"
|
36
|
+
spec.homepage = "http://ghostwheel.rubyforge.org"
|
37
|
+
spec.description = <<END_DESC
|
38
|
+
Ghost Wheel is a packrat parser generator handling scanning and parsing of
|
39
|
+
context-free grammars. Parsers can be built using an EBNF-like syntax or a with
|
40
|
+
a Ruby DSL.
|
41
|
+
END_DESC
|
42
|
+
end
|
43
|
+
Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
|
44
|
+
pkg.need_zip = true
|
45
|
+
pkg.need_tar = true
|
46
|
+
end
|
data/lib/ghost_wheel.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "ghost_wheel/errors"
|
4
|
+
require "ghost_wheel/parse_results"
|
5
|
+
|
6
|
+
require "ghost_wheel/scanner"
|
7
|
+
require "ghost_wheel/parser"
|
8
|
+
|
9
|
+
require "ghost_wheel/expression"
|
10
|
+
require "ghost_wheel/expression/literal"
|
11
|
+
require "ghost_wheel/expression/sequence"
|
12
|
+
require "ghost_wheel/expression/alternation"
|
13
|
+
require "ghost_wheel/expression/empty"
|
14
|
+
require "ghost_wheel/expression/optional"
|
15
|
+
require "ghost_wheel/expression/repetition"
|
16
|
+
require "ghost_wheel/expression/look_ahead"
|
17
|
+
require "ghost_wheel/expression/end_of_file"
|
18
|
+
require "ghost_wheel/expression/query"
|
19
|
+
require "ghost_wheel/expression/transform"
|
20
|
+
require "ghost_wheel/expression/rule"
|
21
|
+
|
22
|
+
require "ghost_wheel/build_parser"
|
23
|
+
require "ghost_wheel/parser_builder/ruby"
|
24
|
+
require "ghost_wheel/parser_builder/ghost_wheel"
|
25
|
+
|
26
|
+
module GhostWheel
|
27
|
+
VERSION = "0.0.1"
|
28
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class StackUnderflowError < RuntimeError; end
|
5
|
+
class EmptyRuleError < RuntimeError; end
|
6
|
+
class ParseError < RuntimeError; end
|
7
|
+
class EmptyParseError < ParseError; end
|
8
|
+
class FailedParseError < ParseError; end
|
9
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
def parse(scanner, cache = Hash.new)
|
6
|
+
cache_key = "#{object_id}:#{scanner.pos}"
|
7
|
+
cache_hit = cache[cache_key]
|
8
|
+
if cache_hit.nil?
|
9
|
+
result = uncached_parse(scanner, cache)
|
10
|
+
cache[cache_key] = [result, scanner.pos]
|
11
|
+
result
|
12
|
+
else
|
13
|
+
scanner.pos = cache_hit.last
|
14
|
+
cache_hit.first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
self.class == other.class
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Alternation < Expression
|
6
|
+
def initialize(*expressions)
|
7
|
+
@expressions = expressions
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :expressions
|
11
|
+
|
12
|
+
def uncached_parse(scanner, cache)
|
13
|
+
@expressions.each do |expression|
|
14
|
+
result = expression.parse(scanner, cache)
|
15
|
+
return result unless result.is_a? FailedParseResult
|
16
|
+
end
|
17
|
+
FailedParseResult.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
super and @expressions == other.expressions
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module GhostWheel
|
6
|
+
class Expression
|
7
|
+
class EndOfFile < Expression
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def uncached_parse(scanner, cache)
|
11
|
+
if scanner.eos?
|
12
|
+
EmptyParseResult.instance
|
13
|
+
else
|
14
|
+
FailedParseResult.instance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Literal < Expression
|
6
|
+
def initialize(literal, skip = false)
|
7
|
+
@literal = case literal
|
8
|
+
when Regexp then literal
|
9
|
+
when Fixnum then /#{Regexp.escape(literal.chr)}/
|
10
|
+
else /#{Regexp.escape(literal.to_s)}/
|
11
|
+
end
|
12
|
+
@skip = skip
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :literal, :skip
|
16
|
+
alias_method :skip?, :skip
|
17
|
+
|
18
|
+
def uncached_parse(scanner, cache)
|
19
|
+
if scanner.scan(@literal)
|
20
|
+
@skip ? EmptyParseResult.instance : ParseResult.new(scanner.matched)
|
21
|
+
else
|
22
|
+
FailedParseResult.instance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
super and @literal.to_s == other.literal.to_s and @skip == other.skip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class LookAhead < Expression
|
6
|
+
def initialize(expression, negate = false)
|
7
|
+
@expression = expression
|
8
|
+
@negate = negate
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :expression, :negate
|
12
|
+
alias_method :negate?, :negate
|
13
|
+
|
14
|
+
def uncached_parse(scanner, cache)
|
15
|
+
matched = nil
|
16
|
+
scanner.transaction do
|
17
|
+
matched = @expression.parse(scanner, cache)
|
18
|
+
scanner.abort
|
19
|
+
end
|
20
|
+
|
21
|
+
if matched.is_a? FailedParseResult
|
22
|
+
@negate ? EmptyParseResult.instance : matched
|
23
|
+
else
|
24
|
+
@negate ? FailedParseResult.instance : EmptyParseResult.instance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
super and @expression == other.expression and @negate == other.negate
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Optional < Expression
|
6
|
+
def initialize(expression)
|
7
|
+
@expression = expression
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :expression
|
11
|
+
|
12
|
+
def uncached_parse(scanner, cache)
|
13
|
+
result = @expression.parse(scanner, cache)
|
14
|
+
if result.is_a? FailedParseResult
|
15
|
+
EmptyParseResult.instance
|
16
|
+
else
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
super and @expression == other.expression
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Query < Expression
|
6
|
+
def initialize(expression, &query)
|
7
|
+
@expression = expression
|
8
|
+
@query = query
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :expression, :query
|
12
|
+
|
13
|
+
def uncached_parse(scanner, cache)
|
14
|
+
result = @expression.parse(scanner, cache)
|
15
|
+
if result.is_a? FailedParseResult
|
16
|
+
result
|
17
|
+
else
|
18
|
+
value = result.is_a?(ParseResult) ? result.value : result
|
19
|
+
if @query[value]
|
20
|
+
result
|
21
|
+
else
|
22
|
+
FailedParseResult.instance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
super and @expression == other.expression and @query == other.query
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Repetition < Expression
|
6
|
+
def initialize(expression, minimum_count = 0, maximum_count = 1.0 / 0.0)
|
7
|
+
@expression = expression
|
8
|
+
@minimum_count = minimum_count
|
9
|
+
@maximum_count = maximum_count
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :expression, :minimum_count, :maximum_count
|
13
|
+
|
14
|
+
def uncached_parse(scanner, cache)
|
15
|
+
scanner.transaction do
|
16
|
+
parsed = ParseResult.new(Array.new)
|
17
|
+
while parsed.value.size < @maximum_count
|
18
|
+
case result = @expression.parse(scanner, cache)
|
19
|
+
when FailedParseResult then break
|
20
|
+
when ParseResult then parsed.value << result.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if parsed.value.size >= @minimum_count
|
25
|
+
if parsed.value.empty?
|
26
|
+
EmptyParseResult.instance
|
27
|
+
else
|
28
|
+
parsed
|
29
|
+
end
|
30
|
+
else
|
31
|
+
scanner.abort(FailedParseResult.instance)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
super and
|
38
|
+
@expression == other.expression and
|
39
|
+
@minimum_count == other.minimum_count and
|
40
|
+
@maximum_count == other.maximum_count
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Rule < Expression
|
6
|
+
def initialize(expression = nil)
|
7
|
+
@expression = expression
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :expression
|
11
|
+
|
12
|
+
def uncached_parse(scanner, cache)
|
13
|
+
if @expression.nil?
|
14
|
+
raise EmptyRuleError, "You failed to set an expression for this rule."
|
15
|
+
end
|
16
|
+
@expression.parse(scanner, cache)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
super and @expression == other.expression
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Sequence < Expression
|
6
|
+
def initialize(*expressions)
|
7
|
+
@expressions = expressions
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :expressions
|
11
|
+
|
12
|
+
def uncached_parse(scanner, cache)
|
13
|
+
scanner.transaction do
|
14
|
+
parsed = ParseResult.new(Array.new)
|
15
|
+
@expressions.each do |expression|
|
16
|
+
case result = expression.parse(scanner, cache)
|
17
|
+
when FailedParseResult then scanner.abort(result)
|
18
|
+
when ParseResult then parsed.value << result.value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
parsed
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
super and @expressions == other.expressions
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module GhostWheel
|
4
|
+
class Expression
|
5
|
+
class Transform < Expression
|
6
|
+
def initialize(expression, &transformer)
|
7
|
+
@expression = expression
|
8
|
+
@transformer = transformer
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :expression, :transformer
|
12
|
+
|
13
|
+
def uncached_parse(scanner, cache)
|
14
|
+
result = @expression.parse(scanner, cache)
|
15
|
+
if result.is_a? FailedParseResult
|
16
|
+
result
|
17
|
+
else
|
18
|
+
value = result.is_a?(ParseResult) ? result.value : result
|
19
|
+
ParseResult.new(@transformer[value])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
super and
|
25
|
+
@expression == other.expression and
|
26
|
+
@transformer == other.transformer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|