mirror of
https://github.com/russolsen/rforth
synced 2024-12-25 09:58:16 +01:00
Moved prim methods into a module, cleaned up compiling words
This commit is contained in:
parent
834406e9c1
commit
df1a324bd6
1 changed files with 165 additions and 52 deletions
217
src/rforth.rb
217
src/rforth.rb
|
@ -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 }
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
def define_word
|
||||
name = read_word
|
||||
# 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?
|
||||
|
|
Loading…
Reference in a new issue