Skip to content

Instantly share code, notes, and snippets.

@ptn
Created December 20, 2011 17:51
Show Gist options
  • Save ptn/1502488 to your computer and use it in GitHub Desktop.
Save ptn/1502488 to your computer and use it in GitHub Desktop.
Poor Man's Forth
module Stacker
module Construct
class Construct
# Read extra data from the input, parse it and return the new position
# from where to continue parsing or nil if a grammar error is found.
def parse(input, pos)
pos
end
end
class Number < Construct
def recognize?(command)
number = command.to_i
if number == 0
return false unless command =~ /0+$/
end
@number = number
end
def operate(stack)
stack.push @number
end
end
class BinaryOperator < Construct
def initialize()
super
@operators = {
"ADD" => Proc.new { |x, y| x + y },
"SUBSTRACT" => Proc.new { |x, y| x - y },
"MULTIPLY" => Proc.new { |x, y| x * y },
"DIVIDE" => Proc.new { |x, y| x / y },
"MOD" => Proc.new { |x, y| x % y },
"<" => Proc.new { |x, y| x < y },
">" => Proc.new { |x, y| x > y },
"=" => Proc.new { |x, y| x == y },
}
end
def recognize?(command)
@operator = @operators[command]
end
def operate(stack)
param2 = stack.pop
param1 = stack.pop
result = @operator.call(param1, param2)
stack.push result
end
end
class UnaryOperator < Construct
def recognize?(command)
# No unary operators yet.
false
end
end
class ProcedureInterpreter
def initialize(calls)
@calls = calls
@stack = []
end
def execute(param)
@stack.push param
@calls.each do |call|
call.operate(@stack)
end
@stack.first
end
end
# Should this be split in ProcedureCall and ProcedureDefinition ?
# Where to store all ProcedureDefinition objects so that ProcedureCall can
# search them if they are managed by an Interpreter and do not communicate
# directly?
class Procedure < Construct
def initialize()
super
@procedures = {}
@parser = Stacker::Parser::ProcedureParser.new
end
def recognize?(command)
split = command.split
if split.length == 1
@proc = @procedures[command]
elsif split.length == 2 && split[0] == "PROCEDURE"
@name = split[1]
end
end
def parse(input, pos)
if @proc
pos
elsif @name
new_pos = input.index("/PROCEDURE")
return nil if new_pos.nil?
raw_body = input[pos..new_pos-1].strip
body = []
@parser.parse(raw_body) { |construct| body << construct }
@procedures[@name] = ProcedureInterpreter.new(body)
new_pos += "/PROCEDURE\n".length
end
end
def operate(stack)
stack.push @proc.execute(stack.pop) if @proc
@name = @proc = nil
end
end
end
module Parser
class UnknownConstruct < Exception;end
class ParseError < Exception;end
class Parser
def initialize()
@constructs = []
end
def parse(input)
input.concat("\n") unless input[-1] == "\n"
@input = input
@pos = 0
while @pos < @input.length
puts "#{self.class}: #{@pos}" ##############################
command = read_line
parse_error = @constructs.each do |construct|
if construct.recognize? command
@pos = construct.parse(@input, @pos)
raise ParseError if @pos.nil?
yield construct if block_given?
break(nil)
end
end
raise UnknownConstruct, "#{self.class} can't parse construct '#{command}' at position #{@pos} in input '#{input}'" if parse_error
end
end
def add_construct(construct)
@constructs << construct
end
private
def read_line()
index = @input.index("\n", @pos)
command = @input[@pos..index-1]
@pos = index + 1
command
end
end
class ProcedureParser < Parser
def initialize()
super
add_construct Construct::UnaryOperator.new
add_construct Construct::BinaryOperator.new
add_construct Construct::Number.new
end
end
# Should inherit from ProcedureParser
class FortranParser < Parser
def initialize()
super
add_construct Construct::Procedure.new
add_construct Construct::UnaryOperator.new
add_construct Construct::BinaryOperator.new
add_construct Construct::Number.new
end
end
end
class Interpreter
attr_reader :stack
def initialize(parser)
@stack = []
@parser = parser
end
def execute(commands)
@parser.parse(commands) { |construct| construct.operate(@stack) }
end
end
end
module Stacker
module Construct
class Construct
# Read extra data from the input, parse it and return the new position
# from where to continue parsing or nil if a grammar error is found.
def parse(input, pos)
pos
end
end
class Number < Construct
def recognize?(command)
number = command.to_i
if number == 0
return false unless command =~ /0+$/
end
@number = number
end
def operate(stack)
stack.push @number
end
end
class BinaryOperator < Construct
def initialize()
super
@operators = {
"ADD" => Proc.new { |x, y| x + y },
"SUBSTRACT" => Proc.new { |x, y| x - y },
"MULTIPLY" => Proc.new { |x, y| x * y },
"DIVIDE" => Proc.new { |x, y| x / y },
"MOD" => Proc.new { |x, y| x % y },
"<" => Proc.new { |x, y| x < y },
">" => Proc.new { |x, y| x > y },
"=" => Proc.new { |x, y| x == y },
}
end
def recognize?(command)
@operator = @operators[command]
end
def operate(stack)
param2 = stack.pop
param1 = stack.pop
result = @operator.call(param1, param2)
stack.push result
end
end
class UnaryOperator < Construct
def recognize?(command)
# No unary operators yet.
false
end
end
class ProcedureInterpreter
def initialize(calls)
@calls = calls
@stack = []
end
def execute(param)
@stack.push param
@calls.each do |call|
call.operate(@stack)
end
@stack.first
end
end
# Should this be split in ProcedureCall and ProcedureDefinition ?
# Where to store all ProcedureDefinition objects so that ProcedureCall can
# search them if they are managed by an Interpreter and do not communicate
# directly?
class Procedure < Construct
def initialize()
super
@procedures = {}
@parser = Stacker::Parser::ProcedureParser.new
end
def recognize?(command)
split = command.split
if split.length == 1
@proc = @procedures[command]
elsif split.length == 2 && split[0] == "PROCEDURE"
@name = split[1]
end
end
def parse(input, pos)
if @proc
pos
elsif @name
new_pos = input.index("/PROCEDURE")
return nil if new_pos.nil?
raw_body = input[pos..new_pos-1].strip
body = []
@parser.parse(raw_body) { |construct| body << construct }
@procedures[@name] = ProcedureInterpreter.new(body)
new_pos += "/PROCEDURE\n".length
end
end
def operate(stack)
stack.push @proc.execute(stack.pop) if @proc
@name = @proc = nil
end
end
end
module Parser
class UnknownConstruct < Exception;end
class ParseError < Exception;end
class Parser
def initialize()
@constructs = []
end
def parse(input)
input.concat("\n") unless input[-1] == "\n"
@input = input
@pos = 0
while @pos < @input.length
puts "#{self.class}: #{@pos}" ##############################
command = read_line
parse_error = @constructs.each do |construct|
if construct.recognize? command
@pos = construct.parse(@input, @pos)
raise ParseError if @pos.nil?
yield construct if block_given?
break(nil)
end
end
raise UnknownConstruct, "#{self.class} can't parse construct '#{command}' at position #{@pos} in input '#{input}'" if parse_error
end
end
def add_construct(construct)
@constructs << construct
end
private
def read_line()
index = @input.index("\n", @pos)
command = @input[@pos..index-1]
@pos = index + 1
command
end
end
class ProcedureParser < Parser
def initialize()
super
add_construct Construct::UnaryOperator.new
add_construct Construct::BinaryOperator.new
add_construct Construct::Number.new
end
end
# Should inherit from ProcedureParser
class FortranParser < Parser
def initialize()
super
add_construct Construct::Procedure.new
add_construct Construct::UnaryOperator.new
add_construct Construct::BinaryOperator.new
add_construct Construct::Number.new
end
end
end
class Interpreter
attr_reader :stack
def initialize(parser)
@stack = []
@parser = parser
end
def execute(commands)
@parser.parse(commands) { |construct| construct.operate(@stack) }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment