Skip to content

Instantly share code, notes, and snippets.

@tenebrousedge
Created October 13, 2019 03:35
Show Gist options
  • Save tenebrousedge/de2919120f7ea68128731f67c7997190 to your computer and use it in GitHub Desktop.
Save tenebrousedge/de2919120f7ea68128731f67c7997190 to your computer and use it in GitHub Desktop.
Forth Interpreter written in Crystal-lang
module Forth
alias Stack = Array(Int32)
alias Operation = Proc(Stack, Stack)
end
module Forth
struct Statement
getter text : String
getter?(new_word : Bool) { text.starts_with?(":") }
forward_missing_to @text
def initialize(string)
@text = string.strip
end
end
end
require "string_scanner"
module Forth
class Lexer
include Iterator(Statement)
def initialize(source)
@source = StringScanner.new(source.upcase)
end
def next
(@source.scan(/(:[^;]+);|\s*[^\s]+/).try &->Statement.new(String)) || stop
end
end
end
module Forth
class Parser
DICTIONARY = {
"DUP" => ->(stack : Stack) { stack.push(stack.last) },
"DROP" => ->(stack : Stack) { [stack.pop] },
"SWAP" => ->(stack : Stack) { stack[-2], stack[-1] = stack[-1], stack[-2]; stack },
"OVER" => ->(stack : Stack) { stack << stack[-2] },
"+" => ->(stack : Stack) { a, b = stack.pop(2); stack << a + b },
"-" => ->(stack : Stack) { a, b = stack.pop(2); stack << a - b },
"/" => ->(stack : Stack) { a, b = stack.pop(2); stack << a // b },
"*" => ->(stack : Stack) { a, b = stack.pop(2); stack << a * b }
}
@dict : Hash(String, Operation)
def initialize
@dict = DICTIONARY.dup
end
def parse(word)
@dict[word]?
end
def add_word(statement)
statement.match(/^:\s+(?<name>[^\d\s]+)\s+(?<def>(?:\S+\s+)+);$/).try do |matches|
@dict[matches["name"]] = Operation.new do |stack|
matches["def"].split.each_with_object(stack) do |word, s|
(parse(word).try &.call(s)) || s << word.to_i
end
end
end || raise "Bad definition: #{statement}"
end
end
end
module Forth
class Interpreter
def initialize(source)
@program = Lexer.new(source)
@dict = Parser.new
end
def run
@program.each_with_object(Stack.new) do |statement, stack|
if statement.new_word?
@dict.add_word(statement)
elsif (op = @dict.parse(statement.text))
op.call(stack)
else
stack << statement.to_i
end
end
end
def execute(word, stack)
return stack << word if word.is_a?(Int32)
word.call(stack)
end
end
end
module Forth
def self.evaluate(source)
Interpreter.new(source).run
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment