Skip to content

Instantly share code, notes, and snippets.

@vapidbabble
Forked from sneakin/.gitignore
Created September 3, 2009 19:15
Show Gist options
  • Save vapidbabble/180476 to your computer and use it in GitHub Desktop.
Save vapidbabble/180476 to your computer and use it in GitHub Desktop.
*~
*.tab.rb
coverage
require File.join(File.dirname(__FILE__), "../racc/calc_parser.tab")
module Calc
class Tokenizer
def initialize(str = nil)
@str = str
end
def <<(str)
@str ||= ""
@str += str
end
def next
return if @str.nil?
eat_whitespace
c = shift_str
case c
when nil then [false, nil]
when '=' then [:EQ, c]
when '(' then [:LPAREN, c]
when ')' then [:RPAREN, c]
when '+' then [:PLUS, c]
when '-' then [:MINUS, c]
when '*' then [:STAR, c]
when '/' then [:FSLASH, c]
when /[0-9]/ then number(c)
when /[A-Za-z_]/ then symbol(c)
else raise StardandError, "unknown character #{c.inspect}"
end
end
private
def shift_str
c = @str[0]
if c
c = c.chr
@str = @str[1..-1]
end
c
end
def eat_whitespace
m = @str.match(/^([ \n\t]+)/)
if m
@str = @str[m[1].length..-1]
end
end
def symbol(c)
m = (c + @str).match(/^([A-Za-z_][A-Za-z0-9_]*)/)
@str = @str[(m[1].length - 1)..-1]
[:SYMBOL, m[1]]
end
def number(c)
m = (c + @str).match(/^([0-9]+)/)
@str = @str[(m[1].length - 1)..-1]
[:NUMBER, m[1].to_f]
end
end
class Interpretter
def initialize(parser = Parser.new(Tokenizer.new), env = Hash.new)
@parser = parser
@env = env
end
def []=(symbol, value)
@env[symbol] = value
end
def [](symbol)
@env[symbol]
end
def evaluate(input)
# @parser.parse(input.split).collect { |e| e.evaluate(self) }
@parser.parse(input).evaluate(self)
end
end
end
# http://gist.github.com/178048
class Calc::Parser
token NUMBER SYMBOL PLUS MINUS FSLASH STAR LPAREN RPAREN EQ
prechigh
right LPAREN RPAREN
left STAR FSLASH
left PLUS MINUS
preclow
rule
expression:
LPAREN expression RPAREN { result = val[1] }
| atom { result = val[0] }
| assignment { result = val[0] }
| expression PLUS expression { result = BinOp.new(val[1], val[0], val[2]) }
| expression MINUS expression { result = BinOp.new(val[1], val[0], val[2]) }
| expression STAR expression { result = BinOp.new(val[1], val[0], val[2]) }
| expression FSLASH expression { result = BinOp.new(val[1], val[0], val[2]) }
atom:
NUMBER { result = Atom.new(val[0]) }
| SYMBOL { result = Var.new(val[0]) }
assignment:
SYMBOL EQ expression { result = Assignment.new(val[0], val[2]) }
---- header
require 'calc/expressions'
---- inner
def initialize(tokenizer)
super()
@tokenizer = tokenizer
end
def parse(str)
@tokenizer << str
do_parse
end
def next_token
@tokenizer.next
end
require File.join(File.dirname(__FILE__), "spec_helper.rb")
describe Calc::Interpretter do
subject { Calc::Interpretter.new }
{ "1 + 1" => 2,
"2 - 2" => 0,
"3 * 3" => 9,
"4 / 4" => 1,
"1 * 2 + 3" => 5,
"1 + 2 * 3" => 7,
"(1 + 2) * 3" => 9,
"1 * (2 + 3)" => 5,
"(1 + 2 * 3) + 4" => 11,
"1 - (2 + 3 * 4)" => -13,
"a = 3" => 3
}.each do |input, output|
context input.inspect do
it "returns #{output}" do
subject.evaluate(input).should == output
end
end
end
context 'with variables' do
before(:each) do
subject.evaluate("a = 1")
subject.evaluate("b = 2")
subject.evaluate("c = 3")
end
{ 'a + b' => 3,
'a - b' => -1,
'a * b' => 2,
'a / b' => 0.5,
"a * b + c" => 5,
"a + b * c" => 7,
"(a + b) * c" => 9,
"a * (b + c)" => 5,
'(a + b) * b' => 6,
"a = b" => 2,
"a = b + c" => 5,
"xyz = a + b" => 3
}.each do |input, output|
context input.inspect do
it "returns #{output}" do
subject.evaluate(input).should == output
end
end
end
end
end
module Calc
class Atom
def initialize(v)
@value = v
end
def evaluate(scope)
@value
end
end
class Assignment
def initialize(var, val)
@variable = var
@value = val
end
def evaluate(scope)
scope[@variable] = @value.evaluate(scope)
end
end
class Var
attr_accessor :name
def initialize(name)
@name = name
end
def evaluate(scope)
scope[self.name]
end
end
class BinOp
attr_accessor :op, :a, :b
def initialize(op, a, b)
@op = op
@a = a
@b = b
end
def evaluate(scope)
a = @a.evaluate(scope)
b = @b.evaluate(scope)
# a.collect { |e| e.send(@op, b) }
a.send(@op, b)
end
end
end
require 'spec/rake/spectask'
file "racc/calc_parser.tab.rb" => [ "racc/calc_parser.y" ] do |t|
sh("racc -t racc/calc_parser.y")
end
spec_prereq = [ "racc/calc_parser.tab.rb" ]
desc "Run all specs in spec directory (excluding plugin specs)"
Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
# t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb']
end
namespace :spec do
desc "Run all specs in spec directory (excluding plugin specs)"
Spec::Rake::SpecTask.new(:rcov => spec_prereq) do |t|
# t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
t.rcov = true
t.rcov_opts = lambda do
IO.readlines("#{File.dirname(__FILE__)}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
end
t.spec_files = FileList['spec/**/*_spec.rb']
end
end
--exclude "spec/*,/Library/*"
$: << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
require 'spec'
require 'calc'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment