whittle 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +468 -0
- data/Rakefile +1 -0
- data/lib/whittle/error.rb +9 -0
- data/lib/whittle/errors/grammar_error.rb +9 -0
- data/lib/whittle/errors/parse_error.rb +35 -0
- data/lib/whittle/errors/unconsumed_input_error.rb +9 -0
- data/lib/whittle/parser.rb +343 -0
- data/lib/whittle/rule.rb +239 -0
- data/lib/whittle/rule_set.rb +118 -0
- data/lib/whittle/version.rb +3 -0
- data/lib/whittle.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/unit/parser/empty_rule_spec.rb +21 -0
- data/spec/unit/parser/empty_string_spec.rb +17 -0
- data/spec/unit/parser/error_reporting_spec.rb +55 -0
- data/spec/unit/parser/grouped_expr_spec.rb +27 -0
- data/spec/unit/parser/multiple_precedence_spec.rb +33 -0
- data/spec/unit/parser/noop_spec.rb +23 -0
- data/spec/unit/parser/pass_through_parser_spec.rb +17 -0
- data/spec/unit/parser/precedence_spec.rb +26 -0
- data/spec/unit/parser/self_referential_expr_spec.rb +26 -0
- data/spec/unit/parser/skipped_tokens_spec.rb +28 -0
- data/spec/unit/parser/sum_parser_spec.rb +23 -0
- data/spec/unit/parser/typecast_parser_spec.rb +17 -0
- data/whittle.gemspec +27 -0
- metadata +104 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser with an empty rule" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:expr) do |r|
|
7
|
+
r[].as { "test" }
|
8
|
+
r["(", :expr, ")"].as { |_, expr, _| expr }
|
9
|
+
end
|
10
|
+
|
11
|
+
rule("(")
|
12
|
+
rule(")")
|
13
|
+
|
14
|
+
start(:expr)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "injects the empty rule to allow matching the input" do
|
19
|
+
parser.new.parse("((()))").should == "test"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser matching the empty string" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:empty) do |r|
|
7
|
+
r[].as { "bob" }
|
8
|
+
end
|
9
|
+
|
10
|
+
start(:empty)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "always matches the empty string" do
|
15
|
+
parser.new.parse("").should == "bob"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser encountering unexpected input" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:wsp) do |r|
|
7
|
+
r[/\s+/]
|
8
|
+
end
|
9
|
+
|
10
|
+
rule(:id) do |r|
|
11
|
+
r[/[a-z]+/].as(:value)
|
12
|
+
end
|
13
|
+
|
14
|
+
rule(",")
|
15
|
+
rule("-")
|
16
|
+
|
17
|
+
rule(:list) do |r|
|
18
|
+
r[:list, ",", :id].as { |list, _, id| list << id }
|
19
|
+
r[:id].as { |id| Array(id) }
|
20
|
+
end
|
21
|
+
|
22
|
+
start(:list)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises an exception of type ParseError" do
|
27
|
+
expect {
|
28
|
+
parser.new.parse("a, b - c")
|
29
|
+
}.to raise_error(Whittle::ParseError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "provides access to the line number" do
|
33
|
+
begin
|
34
|
+
parser.new.parse("a, \nb, \nc- \nd")
|
35
|
+
rescue Whittle::ParseError => e
|
36
|
+
e.line.should == 3
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "provides access to the expected tokens" do
|
41
|
+
begin
|
42
|
+
parser.new.parse("a, \nb, \nc- \nd")
|
43
|
+
rescue Whittle::ParseError => e
|
44
|
+
e.expected.should == [","]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "provides access to the received token" do
|
49
|
+
begin
|
50
|
+
parser.new.parse("a, \nb, \nc- \nd")
|
51
|
+
rescue Whittle::ParseError => e
|
52
|
+
e.received.should == "-"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser with logical grouping" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:expr) do |r|
|
7
|
+
r["(", :expr, ")"].as { |_, expr, _| expr }
|
8
|
+
r[:expr, "-", :expr].as { |a, _, b| a - b }
|
9
|
+
r[:int].as(:value)
|
10
|
+
end
|
11
|
+
|
12
|
+
rule(:int) do |r|
|
13
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
14
|
+
end
|
15
|
+
|
16
|
+
rule("(")
|
17
|
+
rule(")")
|
18
|
+
rule("-") % :left ^ 1
|
19
|
+
|
20
|
+
start(:expr)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses the grouping first" do
|
25
|
+
parser.new.parse("2-(3-1)-1").should == -1
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser with multiple precedence levels" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:expr) do |r|
|
7
|
+
r["(", :expr, ")"].as { |_, expr, _| expr }
|
8
|
+
r[:expr, "+", :expr].as { |a, _, b| a + b }
|
9
|
+
r[:expr, "-", :expr].as { |a, _, b| a - b }
|
10
|
+
r[:expr, "*", :expr].as { |a, _, b| a * b }
|
11
|
+
r[:expr, "/", :expr].as { |a, _, b| a / b }
|
12
|
+
r[:int].as(:value)
|
13
|
+
end
|
14
|
+
|
15
|
+
rule(:int) do |r|
|
16
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
17
|
+
end
|
18
|
+
|
19
|
+
rule("(")
|
20
|
+
rule(")")
|
21
|
+
rule("+") % :left ^ 1
|
22
|
+
rule("-") % :left ^ 1
|
23
|
+
rule("*") % :left ^ 2
|
24
|
+
rule("/") % :left ^ 2
|
25
|
+
|
26
|
+
start(:expr)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "evaluates each precedence as it is encountered" do
|
31
|
+
parser.new.parse("4-2*3-(6/3)/2+1").should == -2
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a noop parser" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:char) do |r|
|
7
|
+
r[/./].as(:value)
|
8
|
+
end
|
9
|
+
|
10
|
+
rule(:prog) do |r|
|
11
|
+
r[:char]
|
12
|
+
end
|
13
|
+
|
14
|
+
start(:prog)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns nil for all inputs" do
|
19
|
+
["a", "b"].each do |input|
|
20
|
+
parser.new.parse(input).should be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a pass-through parser" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:foo) do |r|
|
7
|
+
r["FOO"].as(:value)
|
8
|
+
end
|
9
|
+
|
10
|
+
start(:foo)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the input" do
|
15
|
+
parser.new.parse("FOO").should == "FOO"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser depending on operator precedences" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule("+") % :left ^ 1
|
7
|
+
rule("*") % :left ^ 2
|
8
|
+
|
9
|
+
rule(:int) do |r|
|
10
|
+
r[/[0-9]+/].as { |i| Integer(i) }
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:expr) do |r|
|
14
|
+
r[:expr, "+", :expr].as { |a, _, b| a + b }
|
15
|
+
r[:expr, "*", :expr].as { |a, _, b| a * b }
|
16
|
+
r[:int].as(:value)
|
17
|
+
end
|
18
|
+
|
19
|
+
start(:expr)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "resolves shift-reduce conflicts by precedence" do
|
24
|
+
parser.new.parse("1+2*3").should == 7
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser with a self-referential rule" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule("(")
|
7
|
+
rule(")")
|
8
|
+
rule("+")
|
9
|
+
|
10
|
+
rule(:int) do |r|
|
11
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
12
|
+
end
|
13
|
+
|
14
|
+
rule(:expr) do |r|
|
15
|
+
r[:expr, "+", :expr].as { |a, _, b| a + b }
|
16
|
+
r[:int].as(:value)
|
17
|
+
end
|
18
|
+
|
19
|
+
start(:expr)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "handles the recursion gracefully" do
|
24
|
+
parser.new.parse("2+3+1").should == 6
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser that skips tokens" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:wsp) do |r|
|
7
|
+
r[/\s+/]
|
8
|
+
end
|
9
|
+
|
10
|
+
rule("-") % :left
|
11
|
+
|
12
|
+
rule(:int) do |r|
|
13
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
14
|
+
end
|
15
|
+
|
16
|
+
rule(:expr) do |r|
|
17
|
+
r[:expr, "-", :expr].as { |a, _, b| a - b }
|
18
|
+
r[:int].as(:value)
|
19
|
+
end
|
20
|
+
|
21
|
+
start(:expr)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "reads the input excluding the skipped tokens" do
|
26
|
+
parser.new.parse("6 - 3 - 1").should == 2
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a parser returning the sum of two integers" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule("+")
|
7
|
+
|
8
|
+
rule(:int) do |r|
|
9
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
10
|
+
end
|
11
|
+
|
12
|
+
rule(:sum) do |r|
|
13
|
+
r[:int, "+", :int].as { |a, _, b| a + b }
|
14
|
+
end
|
15
|
+
|
16
|
+
start(:sum)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns the sum of the operands" do
|
21
|
+
parser.new.parse("10+20").should == 30
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "a type-casting parser" do
|
4
|
+
let(:parser) do
|
5
|
+
Class.new(Whittle::Parser) do
|
6
|
+
rule(:int) do |r|
|
7
|
+
r[/[0-9]+/].as { |int| Integer(int) }
|
8
|
+
end
|
9
|
+
|
10
|
+
start(:int)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the input passed through the callback" do
|
15
|
+
parser.new.parse("123").should == 123
|
16
|
+
end
|
17
|
+
end
|
data/whittle.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "whittle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "whittle"
|
7
|
+
s.version = Whittle::VERSION
|
8
|
+
s.authors = ["d11wtq"]
|
9
|
+
s.email = ["chris@w3style.co.uk"]
|
10
|
+
s.homepage = "https://github.com/d11wtq/whittle"
|
11
|
+
s.summary = %q{An efficient, easy to use, LALR parser for Ruby}
|
12
|
+
s.description = %q{Write powerful parsers by defining a series of very simple rules
|
13
|
+
and operations to perform as those rules are matched. Whittle
|
14
|
+
parsers are written in pure ruby and as such are extremely flexible.
|
15
|
+
Anybody familiar with parsers like yacc should find Whittle intuitive.
|
16
|
+
Those unfamiliar with parsers shouldn't find it difficult to
|
17
|
+
understand.}
|
18
|
+
|
19
|
+
s.rubyforge_project = "whittle"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
|
26
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: whittle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- d11wtq
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70265816010420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.6'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70265816010420
|
25
|
+
description: ! "Write powerful parsers by defining a series of very simple rules\n
|
26
|
+
\ and operations to perform as those rules are matched. Whittle\n
|
27
|
+
\ parsers are written in pure ruby and as such are extremely
|
28
|
+
flexible.\n Anybody familiar with parsers like yacc should find
|
29
|
+
Whittle intuitive.\n Those unfamiliar with parsers shouldn't
|
30
|
+
find it difficult to\n understand."
|
31
|
+
email:
|
32
|
+
- chris@w3style.co.uk
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- .rspec
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- lib/whittle.rb
|
44
|
+
- lib/whittle/error.rb
|
45
|
+
- lib/whittle/errors/grammar_error.rb
|
46
|
+
- lib/whittle/errors/parse_error.rb
|
47
|
+
- lib/whittle/errors/unconsumed_input_error.rb
|
48
|
+
- lib/whittle/parser.rb
|
49
|
+
- lib/whittle/rule.rb
|
50
|
+
- lib/whittle/rule_set.rb
|
51
|
+
- lib/whittle/version.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
- spec/unit/parser/empty_rule_spec.rb
|
54
|
+
- spec/unit/parser/empty_string_spec.rb
|
55
|
+
- spec/unit/parser/error_reporting_spec.rb
|
56
|
+
- spec/unit/parser/grouped_expr_spec.rb
|
57
|
+
- spec/unit/parser/multiple_precedence_spec.rb
|
58
|
+
- spec/unit/parser/noop_spec.rb
|
59
|
+
- spec/unit/parser/pass_through_parser_spec.rb
|
60
|
+
- spec/unit/parser/precedence_spec.rb
|
61
|
+
- spec/unit/parser/self_referential_expr_spec.rb
|
62
|
+
- spec/unit/parser/skipped_tokens_spec.rb
|
63
|
+
- spec/unit/parser/sum_parser_spec.rb
|
64
|
+
- spec/unit/parser/typecast_parser_spec.rb
|
65
|
+
- whittle.gemspec
|
66
|
+
homepage: https://github.com/d11wtq/whittle
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project: whittle
|
86
|
+
rubygems_version: 1.8.11
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: An efficient, easy to use, LALR parser for Ruby
|
90
|
+
test_files:
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
- spec/unit/parser/empty_rule_spec.rb
|
93
|
+
- spec/unit/parser/empty_string_spec.rb
|
94
|
+
- spec/unit/parser/error_reporting_spec.rb
|
95
|
+
- spec/unit/parser/grouped_expr_spec.rb
|
96
|
+
- spec/unit/parser/multiple_precedence_spec.rb
|
97
|
+
- spec/unit/parser/noop_spec.rb
|
98
|
+
- spec/unit/parser/pass_through_parser_spec.rb
|
99
|
+
- spec/unit/parser/precedence_spec.rb
|
100
|
+
- spec/unit/parser/self_referential_expr_spec.rb
|
101
|
+
- spec/unit/parser/skipped_tokens_spec.rb
|
102
|
+
- spec/unit/parser/sum_parser_spec.rb
|
103
|
+
- spec/unit/parser/typecast_parser_spec.rb
|
104
|
+
has_rdoc:
|