Version 5 of GPS RPN

Updated 2004-02-15 07:27:44

if 0 { George Peter Staplin's version of RPN.

Licence (OLL): Get it, use it, share it, improve it, but don't blame me.

Revision 2 }


  array set ::words {
   + ADD
   emit EMIT
   get GET
   set SET
   stack STACK
   - SUB
   vars VARS
  }
  array set ::vars {}

  proc compile s {
   set word_def [list]
   set work_list [list]
   set word ""
   set cur work_list

   while 1 {
    set tok [get.token s type]
    if {"" == $tok} {
     break
    } elseif {"." == $tok} {
     #We found the end of a word definition.  Store the list of instructions for it.
     set ::words($word) $word_def
     set word_def [list]
     set word ""
     set cur work_list
    } elseif {":" == $tok} {
     #We found the start of a word definition.
     set word [get.token s type]
     set cur word_def
    } elseif {"quote" == $type || [string is integer $tok]} {
      lappend $cur PUSH
      lappend $cur $tok
    } else {
     if {![info exists ::words($tok)]} {
      return -code error "invalid word: $tok"
     }
     set $cur [concat [set $cur] [set ::words($tok)]]
    }
   }
   return $work_list
  }

  proc get.token {s_ptr type_ptr} {
   upvar $s_ptr s
   upvar $type_ptr type
   set s_len [string length $s]
   set tok ""
   set type ""
   set is_quote 0

   for {set i 0} {$i < $s_len} {incr i} {
    set c [string index $s $i]
    if {$is_quote} {
     if {"'" == $c} {
      #We have reached the end of our ' quoted string.
      incr i
      set s [string range $s $i end]
      set type quote
      return $tok
     }
     append tok $c
    } elseif {"'" == $c} {
     set is_quote 1
     continue
    } elseif {[string is space $c]} {
     if {[string length $tok] > 0} {
      set s [string range $s $i end]
      return $tok  
     }
    } else {
     append tok $c
    } 
   }
   set s ""
   return $tok
  }

  set ::stack [list]
  proc pop {} {
   set i [lindex $::stack end]
   set ::stack [lrange $::stack 0 end-1]
   set i
  } 
  proc push i {
   lappend ::stack $i
  }

  proc run work_list {
   set work_list_len [llength $work_list]
   for {set i 0} {$i < $work_list_len} {incr i} {
    switch -exact [lindex $work_list $i] {
     ADD {
      push [expr [pop] + [pop]]
     } 
     EMIT {
      puts [pop]
     }
     GET {
      push [set ::vars([pop])]
     }
     PUSH {
      incr i
      push [lindex $work_list $i]
     }
     SET {
      set ::vars([pop]) [pop]
     }
     STACK {
      set count 0
      foreach cell $::stack {
       puts "\[[set count]\] = $cell"
       incr count
      }
     }
     SUB {
      set arg [pop]
      push [expr [pop] - $arg]
     }
     VARS {
      parray ::vars
     }
    }
   }
  }

  proc main {} {
   set s "'Hello World' emit  4 5 +  3 - 'num' set 'num' get emit"
   run [compile $s]
   #Now begin our read, compile, run endless loop
   while 1 {
    puts -nonewline "> "
    flush stdout
    if {[catch {run [compile [gets stdin]]} res]} {
     puts error:$res
    } 
   }
  }
  main

Here's an example run:

 $ tclsh85g.exe tcl_fun.tcl 
 Hello World
 6
 > : incr 1 + .
 > 50
 > incr
 > stack
 [0] = 51
 > incr
 > stack
 [0] = 52
 > 60 + stack
 [0] = 112
 > 
 > : incr 1 + 'incr' emit .
 > : decr 1 - 'decr' emit .
 > : foo incr decr .
 > 400
 > foo
 incr
 decr
 > stack
 [0] = 112
 [1] = 400

Exercises:

  1. add a dup word
  2. add a drop word
  3. add more error checking to compile