-
-
Save krainboltgreene/1616738 to your computer and use it in GitHub Desktop.
A pure Ruby interpreter for Forth
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
#!/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