whittle 0.0.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 +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:
|