Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
# ============================================================================== # # RubyFORTH -- Copyright (C) 2007-8, Marc Simpson (GPL). # # Kernel (inner and outer interpreter) # # ============================================================================== require 'vocabulary.rb' # --[ Globals ]----------------------------------------------------------------- # # Important globals. $data_stack = Stack.new(128) ; $retn_stack = Stack.new(128) $data_space = Array.new(4096, 0) $here = 1 ; $ip = 0 ; $header_ip = 0 $rubyforth = Vocabulary.new('rubyforth') push_vocab($rubyforth) $context = "forth" $compiling = false $this_header = nil $parsed = $current_line = nil # --[ Stack operations ]-------------------------------------------------------- def push(n) $data_stack.push(n) end def pop $data_stack.pop end def tos $data_stack.tos end def rot x = pop ; y = pop ; z = pop push(y) ; push(x) ; push(z) end def swap $data_stack.swap end def fetch(addr) # resolve an offset in the dictionary $data_space[addr] end def save_ip $retn_stack.push($ip) end def restore_ip $ip = $retn_stack.pop end # --[ Predicates ]-------------------------------------------------------------- $num_re = /^-?[0-9]+(\.[0-9]+)?$/ # integer or floating point. $word_re = /[ \t]/ # token separator. def is_number(string) $num_re.match(string) end def is_primitive(element) element.class == String end # --[ Wordlists ]--------------------------------------------------------------- def active_wordlist if $context == "compiler" compiler_words else forth_words end end def print_wordlist(w) # Pretty wordlist printing. keys = w.keys.sort col = 4 keys.each do |k| if col > 3 col = 0 print "\n" end printf("%20s", k) col += 1 end puts "\n\n" end def print_words(v) # Print Forth then Compiler words in vocab v. puts "FORTH\n=====\n" print_wordlist(v[0]) puts "COMPILER\n========\n" print_wordlist(v[1]) end # --[ Primitives ]-------------------------------------------------------------- # # Our interpreter will treat strings as primitives; here we define some of the # lengthier operations to keep the code clear, and clean. def comma(n) $data_space[$here] = n ; $here += 1 end def place length = pop ; offset = pop 0.upto(length-1) { |cell| comma($data_space[offset+cell]) } end def null_parse # return '' and skip over the delimiter token $parsed = '' $current_line = $current_line[1..$current_line.length] $parsed end def process_parse(index) # alter the input stream; return $parsed data if index == 0 return null_parse end $parsed = $current_line[0..index-1] $current_line = $current_line[index+1..-1] $current_line = '' if ! $current_line $parsed end def parse # ( c -- ) index = $current_line.index(pop.chr) index = $current_line.length if ! index process_parse(index) end def peek # ( c -- ) index = $current_line.index(pop.chr) index = $current_line.length if ! index return null_parse if index == 0 $current_line[0..index-1] end def next_word # fetch the index of next token separator. m = $word_re.match($current_line) if m i = m.begin(0) else i = $current_line.length end return i end def parse_word # parse using next_word (up to separator) process_parse(next_word) end def peek_word # peek using next_word index = next_word return null_parse if index == 0 $current_line[0..index-1] end def parse_header parse_word # grab the next token $this_header = $parsed # set most recent dictionary header header($parsed) # write the header end def colon_body comma("doCOL") # begin a colon definition $compiling = true # switch to compiling mode end def colon parse_header # write header information colon_body # begin doCOL end def noname push($here) # leave xt on the stack colon_body end def forth_alias # ( "new word" "old word" -- ) new_word = parse_word old_word = parse_word old_xt = xt(old_word) if old_xt active_wordlist[new_word] = old_xt else puts "Error: could not create an alias; '#{old_word}' not found." end end def variable # create a variable parse_header comma("doVAR") comma(0) # Variables default to value 0 end def constant # create a constant parse_header comma("doCONSTANT") comma(pop) end def create_body comma("doCREATE") comma($here+2) $last_does = $here comma(0) end def create # create a 'create' word parse_header create_body end def make # like create, but ( header$ -- ) header(pop) create_body end def dodoes # called at run time from a create/does> word # Points to doCOL... $data_space[$last_does] = $ip + 2 end def does # written by does> comma("dodoes") comma("exit") comma("doCOL") end def forth_exit # Written by semi-colon comma("exit") $compiling = false end def lit # For numerical and string literals $ip += 1 push(fetch($ip)) end def compile # Compile the next word in the input stream. context = $context # --> regardless of context, search FORTH $context = "forth" push(xt(parse_word)) $context = context comma("lit") comma(pop) comma(xt(",")) end def forth_equal return push(-1) if pop == pop push(0) end def forth_flag(f) # convert a ruby bool into a Forth flag return -1 if f return 0 end def forth_true pop != 0 end def branch # Branch to the next cell (take into account the NEXT call) $ip = $data_space[$ip + 1] - 1 end def qbranch # Conditional branching ('?branch'), see 'branch' flag = forth_true if ! flag branch else $ip += 1 end end # --[ Headers and XTs ]--------------------------------------------------------- def header_with(name, address) active_wordlist[name] = address end def header(name) # store $here into the active wordlist header_with(name, $here) end def remove_header(header) w = active_wordlist w.delete(header) end def prim(name, code) # no threading; 'name' points to a ruby string header(name) comma(code) end def forth_prim # one-line primitive, called from Forth name = parse_word push(0) code = parse prim(name, code) end # Return the execution token for header 'name'. If local is 'true', then only # search the current vocabulary. # def lookup_xt(name, local) token = nil if $context == "compiler" if local token = compiler_words[name] else token = compiler_word(name) end return token if token end if local token = forth_words[name] else token = forth_word(name) end return token if token return 0 end def xt(name) lookup_xt(name, false) end def local_xt(name) lookup_xt(name, true) end # --[ Inner Interpreter ]------------------------------------------------------- # # In this section, we define the heart of our interpreter -- forth_execute(), # interpret_token(), compile_token(). Tokenisation is left to the outer # interpreter [see the next section]. $token_re = /^[ \t]*[^ \t]+[ \t]+/ def doCOL save_ip $ip = $header_ip + 1 # perform jump while $data_space[$ip] != "exit" forth_execute($ip) ; $ip += 1 end restore_ip end def doVAR push($header_ip + 1) end def doCONSTANT push(fetch($header_ip + 1)) end def doCREATE address = fetch($header_ip + 1) does_xt = fetch($header_ip + 2) push(address) forth_execute(does_xt) end def forth_execute(xt) resolved = $data_space[xt] # resolve the xt. if resolved.class == String # primitive? $header_ip = xt # can be used if necessary eval resolved else # address... return nil if xt == 0 # NOOP forth_execute(resolved) end end def return_to_toplevel # in case of error, clean up and reset system $current_line = "" $data_stack.reset $compiling = false throw("toplevel") end def interpret_token(token) xt = forth_word(token) return forth_execute(xt) if xt return push(eval(token)) if is_number(token) # Print error message and return to toplevel... puts "'#{token}' not found." return_to_toplevel end def compile_token(token) xt = compiler_word(token) return forth_execute(xt) if xt xt = forth_word(token) return comma(xt) if xt if is_number(token) comma("lit") comma(eval(token)) else remove_header($this_header) puts "'#{token}' not found during compilation of '#{$this_header}'." return_to_toplevel end end # --[ Outer Interpreter ]------------------------------------------------------- # # Here we define operations for reading input from strings, tokenising, and # dispatching these tokens to the inner interpreter. def forth_eval_token(token) if $compiling compile_token(token) else interpret_token(token) end end def forth_process_line # fetch the next token from the input stream $current_line.strip! token = $current_line.split[0] if token $current_line = $current_line[token.length..-1] $current_line.strip! end token end def _forth_eval_line # silently evaluate the line while $current_line and token = forth_process_line forth_eval_token(token) end end def forth_eval_line # as above, but print confirmation of action _forth_eval_line if $compiling puts " compiled" else puts " ok" end end def forth_eval(string) $current_line = string forth_eval_line end def code(string) # for inlining Forth in Ruby scripts $current_line = string _forth_eval_line end # --[ Toplevel ]---------------------------------------------------------------- $alive = true def enter_forth # our REPL loop while $alive and $current_line = gets catch ("toplevel") do begin forth_eval_line rescue Exception puts "Error: Ruby has encountered the following error:\n#{$!}" end end end end def compiler $context = "compiler" end def forth $context = "forth" end