Moved prim methods into a module, cleaned up compiling words

This commit is contained in:
russ 2010-09-10 19:12:36 -04:00
parent 834406e9c1
commit df1a324bd6

View file

@ -1,5 +1,74 @@
require 'pp'
module PrimitiveWords
def dup
@stack << @stack.last
end
def q_dup
@stack << @stack.last unless @stack.last == 0
end
def drop
@stack.pop
end
def swap
@stack += [@stack.pop, @stack.pop]
end
def over
a = @stack.pop
b = @stack.pop
@stack << b << a << b
end
def rot
a = @stack.pop
b = @stack.pop
c = @stack.pop
@stack << b << a << c
end
def plus
@stack << (@stack.pop + @stack.pop)
end
def mult
@stack << (@stack.pop * @stack.pop)
end
def subtract
a = @stack.pop
b = @stack.pop
@stack << b - a
end
def divide
a = @stack.pop
b = @stack.pop
@stack << b / a
end
def dot
@s_out.print( @stack.pop )
end
def cr
@s_out.puts
end
def dot_s
@s_out.print( "#{@stack}\n" )
end
def dot_d
pp @dictionary
end
end
class Dictionary
def initialize( &block )
@entries = {}
@ -16,64 +85,85 @@ class Dictionary
self
end
def alias_word( name, old_name )
entry = self[old_name]
raise "No such word #{old_name}" unless entry
new_entry = entry.dup
new_entry[:name] = name
@entries[name] = entry
end
def []( name )
@entries[name]
end
end
class RForth
include PrimitiveWords
def initialize( s_in = $stdin, s_out = $stdout )
@s_in = s_in
@s_out = s_out
@dictionary = initial_dictionary
@dictionary = Dictionary.new
@stack = []
initialize_dictionary
end
def initial_dictionary
Dictionary.new do |d|
d.word('dup') { @stack << @stack.last }
d.word('?dup') { @stack << @stack.last unless @stack.last == 0 }
d.word('drop') { @stack.pop }
d.word('swap') { @stack += [@stack.pop, @stack.pop] }
d.word('over') do
a = @stack.pop
b = @stack.pop
@stack << b << a << b
end
d.word('rot') do
a = @stack.pop
b = @stack.pop
c = @stack.pop
@stack << b << a << c
end
d.word(':') { define_word }
d.word('+') { @stack << (@stack.pop + @stack.pop) }
d.word('*') { @stack << (@stack.pop * @stack.pop) }
d.word('-') do
a = @stack.pop
b = @stack.pop
@stack << b - a
end
d.word('/') do
a = @stack.pop
b = @stack.pop
@stack << b / a
end
d.word('.') { @s_out.print( @stack.pop ) }
d.word('.S') { @s_out.print( "#{@stack}\n" ) }
d.word('.D') { pp @dictionary }
d.word('cr') { @s_out.puts }
d.word('bye') { exit }
end
# Create all of the initial words.
def initialize_dictionary
PrimitiveWords.public_instance_methods(false).each do |m|
method_clojure = method(m.to_sym)
word( m.to_s, &method_clojure )
end
def define_word
name = read_word
alias_word( '?dup', 'q_dup' )
alias_word( '+', 'plus' )
alias_word( '*', 'mult' )
alias_word( '-', 'subtract' )
alias_word( '/', 'divide' )
alias_word( '.', 'dot' )
alias_word( '.S', 'dot_s' )
alias_word( '.D', 'dot_d' )
word(':') { read_and_define_word }
word('bye') { exit }
immediate_word( '\\' ) { @s_in.readline }
end
# Convience method that takes a word and a closure
# and defines the word in the dictionary
def word( name, &block )
@dictionary.word( name, &block )
end
# Convience method that takes a word and a closure
# and defines an immediate word in the dictionary
def immediate_word( name, &block )
@dictionary.immediate_word( name, &block )
end
# Convience method that takes an existing dict.
# word and a new word and aliases the new word to
# the old.
def alias_word( name, old_name )
@dictionary.alias_word( name, old_name )
end
# Given the name of a new words and the words
# that make up its definition, define the
# new word.
def define_word( name, *words )
@dictionary.word( name, &compile_words( *words ) )
end
# Give an array of (string) words, return
# A block which will run all of those words.
# Executes all immedate words, well, immediately.
def compile_words( *words )
blocks = []
while (word = read_word)
break if word == ';'
entry = @dictionary[word]
words.each do |word|
entry = resolve_word( word )
raise "no such word: #{word}" unless entry
if entry[:immediate]
entry[:block].call
@ -81,22 +171,49 @@ class RForth
blocks << entry[:block]
end
end
@dictionary.word(name) do
proc do
blocks.each {|b| b.call}
end
end
# Read a word definition from input and
# define the word
# Definition looks like:
# new-word w1 w2 w3 ;
def read_and_define_word
name = read_word
words = []
while (word = read_word)
break if word == ';'
words << word
end
@dictionary.word(name, &compile_words( *words ))
end
# Given a (string) word, return the dictionary
# entry for that word or nil.
def resolve_word( word )
return @dictionary[word] if @dictionary[word]
x = to_number(word)
if x
block = proc { @stack << x }
return { :name => word, :block => block, :immediate => false }
end
nil
end
# Evaluate the given word.
def forth_eval( word )
if @dictionary[word]
@dictionary[word][:block].call
elsif (x = to_number(word))
@stack << x
entry = resolve_word(word)
if entry
entry[:block].call
else
@s_out.puts "#{word} ??"
end
end
# Try to turn the word into a number, return nil if
# conversion fails
def to_number( word )
begin
return Integer( word )
@ -111,15 +228,11 @@ class RForth
nil
end
def read_char
@s_in.readchar
end
def read_word
result = nil
ch = nil
until @s_in.eof?
ch = read_char
ch = @s_in.readchar
if result and is_space?(ch)
break
elsif result.nil?