Skip to content

Instantly share code, notes, and snippets.

@krainboltgreene
Forked from perimosocordiae/forth.rb
Created January 15, 2012 18:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save krainboltgreene/1616738 to your computer and use it in GitHub Desktop.
Save krainboltgreene/1616738 to your computer and use it in GitHub Desktop.
A pure Ruby interpreter for Forth
#!/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
# Create a new word from the expressions given
def new_word
# If there was no word, raise with an EmptyWord error
raise EmptyWord if $word.size < 1
# If the definition of the word has a definition, raise NestedDefintion
raise NestedDefinition if $word.include? ':'
# Extract the name and expression from the word array
name, expression = $word.shift, $word.join(' ')
# Create a new word on the dictionary
$dictionary[name] = -> { parse expression }
# Reset the word variable
$word = nil
end
# Parse the expression given
def parse(expression)
# Output the return of the expression
puts "=> #{expression}"
begin
# Attempt to split up the expression and go over each statement
expression.split.each do |statement|
case
# When skip is not nil, and the statement is not 'fi' then go
# to next statement
when !$skip.nil? && statement != 'fi'
next
# When word is not nil and statement is not ';' then add
# the statement to the word array
when !$word.nil? && statement != ';'
$word << statement
# When the statement is a word, call that associated function
when $dictionary.has_key?(statement)
$dictionary[statement].call
# Otherwise push it onto the stack as an integer
else
push statement.to_i
end
end
rescue
# If something goes wrong, print out the error
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 out the prompt token for the user
print "> "
# If nothing is given, break out of the loop
break unless gets
# Parse the given text
parse $_
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment