Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spike 1... not too sure where I'm going.
- Loading branch information
0 parents
commit c15efc5
Showing
12 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.gem | ||
.bundle | ||
Gemfile.lock | ||
pkg/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
--colour |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source "http://rubygems.org" | ||
|
||
# Specify your gem's dependencies in whittle.gemspec | ||
gemspec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require "bundler/gem_tasks" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
require "whittle/version" | ||
require "whittle/rule" | ||
require "whittle/rule_set" | ||
require "whittle/parser" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
module Whittle | ||
class Parser | ||
class << self | ||
def rules | ||
@rules ||= {} | ||
end | ||
|
||
def rule(name) | ||
raise ArgumentError, "Parser.rule requires a block, but none was given" unless block_given? | ||
|
||
RuleSet.new.tap do |rule_set| | ||
rules[name] = rule_set | ||
yield rule_set | ||
end | ||
end | ||
|
||
def start(name = nil) | ||
@start = name unless name.nil? | ||
@start | ||
end | ||
end | ||
|
||
def rules | ||
self.class.rules | ||
end | ||
|
||
def parse(input) | ||
raise "Undefined start rule #{self.class.start}" unless rules.key?(self.class.start) | ||
|
||
rule = rules[self.class.start] | ||
token = nil | ||
lookahead = nil | ||
root = { | ||
:name => self.class.start, | ||
:rule => nil, | ||
:args => [] | ||
} | ||
stack = [root] | ||
|
||
require 'pp' | ||
|
||
lex(input) do |received| | ||
token = lookahead | ||
lookahead = received | ||
next if token.nil? | ||
|
||
pp rule.table_for_offset(stack.last[:args].length) | ||
|
||
stack.last[:args] << token[:value] | ||
stack.last[:rule] = rules[token[:name]].first | ||
end | ||
|
||
reduce(stack.pop) | ||
end | ||
|
||
def lex(input) | ||
source = input.dup | ||
line = 1 | ||
|
||
until source.length == 0 do | ||
next_token(source, line).tap do |token| | ||
raise "Unmatched input #{source.inspect} on line #{line}" if token.nil? | ||
|
||
line = token[:line] | ||
yield token unless token[:discarded] | ||
end | ||
end | ||
|
||
yield nil | ||
end | ||
|
||
private | ||
|
||
def next_token(source, line) | ||
rules.each do |name, rule| | ||
if token = rule.scan(source, line) | ||
token[:name] = name | ||
return token | ||
end | ||
end | ||
|
||
nil | ||
end | ||
|
||
def reduce(tree) | ||
tree[:rule].action.call(*tree[:args].map { |arg| Hash === arg ? reduce(arg) : arg }) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
module Whittle | ||
class Rule | ||
attr_reader :action | ||
attr_reader :components | ||
|
||
def initialize(*components) | ||
@components = components.map do |c| | ||
case c | ||
when String then Regexp.new("^#{Regexp.escape(c)}") | ||
when Regexp then Regexp.new("^#{c}") | ||
when Symbol then c | ||
else raise ArgumentError, "Unsupported rule component #{c.class}" | ||
end | ||
end | ||
|
||
@pattern = @components.first | ||
@lexable = (@components.count == 1 && Regexp === @pattern) | ||
end | ||
|
||
def as(&block) | ||
raise ArgumentError, "Rule#as requires a block, but none given" unless block_given? | ||
|
||
tap do | ||
@action = block | ||
end | ||
end | ||
|
||
def scan(source, line) | ||
return nil unless @lexable | ||
|
||
copy = source.dup | ||
if match = copy.slice!(@pattern) | ||
source.replace(copy) | ||
{ | ||
:value => match, | ||
:line => line + ("~" + match + "~").lines.count - 1, | ||
:discarded => @action.nil? | ||
} | ||
end | ||
end | ||
|
||
def table_for_offset(offset) | ||
[{ :token => @components[offset], :lookahead => @components[offset + 1], :rule => self }] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
module Whittle | ||
class RuleSet | ||
include Enumerable | ||
|
||
def initialize | ||
@rules = [] | ||
end | ||
|
||
def each(&block) | ||
@rules.each(&block) | ||
end | ||
|
||
def [](*components) | ||
Rule.new(*components).tap do |rule| | ||
@rules << rule | ||
end | ||
end | ||
|
||
def scan(source, line) | ||
each do |rule| | ||
if token = rule.scan(source, line) | ||
return token | ||
end | ||
end | ||
|
||
nil | ||
end | ||
|
||
def table_for_offset(offset) | ||
@rules.inject([]) do |table, rule| | ||
table + rule.table_for_offset(offset) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module Whittle | ||
VERSION = "0.0.1" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
require "whittle" | ||
|
||
RSpec.configure do |config| | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
require "spec_helper" | ||
|
||
describe Whittle::Parser do | ||
context "given no-op program" do | ||
let(:parser) do | ||
Class.new(Whittle::Parser) do | ||
rule(:char) do |r| | ||
r[/./].as { |chr| chr } | ||
end | ||
|
||
rule(:prog) do |r| | ||
r[:prog, :char] | ||
r[:char] | ||
end | ||
|
||
start(:prog) | ||
end | ||
end | ||
|
||
it "returns nil for all inputs" do | ||
pending "I'm undecided on if this should be allowed" | ||
|
||
["a b c", "a (b) > *c"].each do |input| | ||
parser.new.parse(input).should be_nil | ||
end | ||
end | ||
end | ||
|
||
context "given a program returning its input" do | ||
let(:parser) do | ||
Class.new(Whittle::Parser) do | ||
rule(:foo) do |r| | ||
r["FOO"].as { |str| str } | ||
end | ||
|
||
start(:foo) | ||
end | ||
end | ||
|
||
context "for matching input" do | ||
it "returns the input" do | ||
parser.new.parse("FOO").should == "FOO" | ||
end | ||
end | ||
end | ||
|
||
context "given a program returning an integer" do | ||
let(:parser) do | ||
Class.new(Whittle::Parser) do | ||
rule(:int) do |r| | ||
r[/[0-9]+/].as { |int| Integer(int) } | ||
end | ||
|
||
start(:int) | ||
end | ||
end | ||
|
||
context "for matching input" do | ||
it "returns the input as an integer" do | ||
parser.new.parse("123").should == 123 | ||
end | ||
end | ||
end | ||
|
||
context "given a program returning the sum of two integers" do | ||
let(:parser) do | ||
Class.new(Whittle::Parser) do | ||
rule(:int) do |r| | ||
r[/[0-9]+/].as { |int| Integer(int) } | ||
end | ||
|
||
rule(:sum) do |r| | ||
r[:int, "+", :int].as { |a, _, b| a + b } | ||
end | ||
|
||
rule(:default) do |r| | ||
r[/./].as { |c| c } | ||
end | ||
|
||
start(:sum) | ||
end | ||
end | ||
|
||
context "for matching input" do | ||
it "returns the sum of the operands" do | ||
parser.new.parse("10+20").should == 30 | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# -*- encoding: utf-8 -*- | ||
$:.push File.expand_path("../lib", __FILE__) | ||
require "whittle/version" | ||
|
||
Gem::Specification.new do |s| | ||
s.name = "whittle" | ||
s.version = Whittle::VERSION | ||
s.authors = ["d11wtq"] | ||
s.email = ["chris@w3style.co.uk"] | ||
s.homepage = "https://github.com/d11wtq/whittle" | ||
s.summary = %q{An efficient, easy to use, LALR parser for Ruby} | ||
s.description = %q{Write powerful parsers by defining a series of very simple rules | ||
and operations to perform as those rules are matched. Whittle | ||
parsers are written in pure ruby and as such are extremely flexible. | ||
Anybody familiar with parsers like yacc should find Whittle intuitive. | ||
Those unfamiliar with parsers shouldn't find it difficult to | ||
understand.} | ||
|
||
s.rubyforge_project = "whittle" | ||
|
||
s.files = `git ls-files`.split("\n") | ||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") | ||
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } | ||
s.require_paths = ["lib"] | ||
|
||
s.add_development_dependency "rspec", "~> 2.6" | ||
end |