Skip to content

Instantly share code, notes, and snippets.

@fogus
Forked from deadprogram/forth.rb
Created January 26, 2012 03:32
Show Gist options
  • Select an option

  • Save fogus/1680793 to your computer and use it in GitHub Desktop.

Select an option

Save fogus/1680793 to your computer and use it in GitHub Desktop.
Forth interpreter in 64 lines of Ruby
#!/usr/bin/env ruby
# Return and remove the last value of the stack array
def pop
$stack.pop || raise(StackUnderflow)
end
# Add a expression to the stack
def push(expression)
$stack << expression
end
# Word helpers
def unary; -> { push(yield pop) } end
def binary; -> { push(yield pop, pop) } end
def unary_boolean; -> { push(if yield pop then 1 else 0 end) } end
def binary_boolean; -> { push(if yield pop, pop then 1 else 0 end) } end
def swap
$stack[-2,2] = $stack[-2,2].reverse
end
def new_word
raise EmptyWord if $word.size < 1
raise NestedDefinition if $word.include? ':'
name, expression = $word.shift, $word.join(' ')
$dictionary[name] = -> { parse(expression) }
$word = nil
end
def parse(expression)
puts "=> #{expression}"
begin
expression.split.each do |statement|
case
when !$skip.nil? && statement != 'fi'
next
when !$word.nil? && statement != ';'
$word << statement
when $dictionary.has_key?(statement)
$dictionary[statement].call
else
push statement.to_i
end
end
rescue
puts "Error: #{$!}"
end
end
# Empty the stack
$stack = []
# Setup the initial dictionary
$dictionary = {
'+' => binary { |a, b| a + b },
'-' => binary { |a, b| a - b },
'*' => binary { |a, b| a * b },
'/' => binary { |a, b| a * b },
'%' => binary { |a, b| a * b },
'<' => binary_boolean { |a, b| a < b },
'>' => binary_boolean { |a, b| a > b },
'=' => binary_boolean { |a, b| a == b },
'&' => binary_boolean { |a, b| a && b },
'|' => binary_boolean { |a, b| a || b },
'not' => binary_boolean { |a, b| a == 0 },
'neg' => binary { |a| -a },
'.' => -> { puts(pop) },
'..' => -> { puts($stack) },
':' => -> { $word = [] },
';' => -> { new_word },
'pop' => -> { pop },
'fi' => -> { $skip = nil },
'words' => -> { p $dictionary.keys.sort },
'if' => -> { $skip = true if pop == 0 },
'dup' => -> { push($stack.last || raise(StackUnderflow)) },
'over' => -> { push($stack.last(2).first || raise(StackUnderflow)) },
'swap' => -> { begin swap rescue raise(StackUnderflow) end }
}
# Load all files given in the command line
puts "Ruby Forth interpreter: enter commands at the prompt"
while ARGV.size > 0
open(ARGV.shift).each { |line| parse(line) }
end
# Here's the REPL
while true
print "> "
break unless gets
parse $_
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment