@@ 4,171 4,238 @@ def is_num?(x)
x !~ /\D/
end
-def feval(str)
- stack = []
- ops = {
- 'T' => -> {stack.push(true)},
- 'F' => -> {stack.push(false)},
- 'dup' => -> {
- x = stack.pop
- stack.push(x)
- stack.push(x)
- },
- 'drop' => -> {
- stack.pop
- },
- 'over' => -> {
- n2, n1 = stack.pop, stack.pop
- stack.push(n1)
- stack.push(n2)
- stack.push(n1)
- },
- 'swap' => -> {
- b, a = stack.pop, stack.pop
- stack.push(b)
- stack.push(a)
- },
- 'rot' => -> {
- c, b, a = stack.pop, stack.pop, stack.pop
- stack.push(b)
- stack.push(c)
- stack.push(a)
- },
- 'and' => -> {
- b, a = stack.pop, stack.pop
- stack.push(b && a)
- },
- 'or' => -> {
- b, a = stack.pop, stack.pop
- stack.push(b || a)
- },
- 'xor' => -> {
- b, a = stack.pop, stack.pop
- stack.push(b ^ a)
- },
- 'invert' => -> {
- stack.push(!stack.pop)
- },
- '+' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a + b)
- },
- '*' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a * b)
- },
- '/' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a / b)
- },
- '<' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a < b)
- },
- '>' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a > b)
- },
- '=' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a == b)
- },
- 'mod' => -> {
- b, a = stack.pop, stack.pop
- stack.push(a % b)
- },
- '2*' => -> {stack.push(stack.pop * 2)},
- '2/' => -> {stack.push(stack.pop / 2)},
- '0=' => -> {stack.push(stack.pop == 0)},
- 'print' => -> {print stack.pop}
- }
- str.split(' ').each do |x|
- if is_num? x
- stack.push x.to_i
+def tokenize(str)
+ str.split(/[ \t]/).map do |w|
+ if is_num? w
+ w.to_i
else
- ops[x].call
+ w
+ end
+ end
+end
+
+def create_ops
+ ops = {}
+ ops['T'] = -> (cs, ds) {ds.unshift(true)}
+ ops['F'] = -> (cs, ds) {ds.unshift(false)}
+ ops['spaces'] = -> (cs, ds) {
+ n = ds.shift
+ print ' ' * n
+ }
+ ops['emit'] = -> (cs, ds) {
+ ascii_code = ds.shift
+ print ascii_code.chr
+ }
+ ops['dup'] = -> (cs, ds) {
+ x = ds.shift
+ ds.unshift(x)
+ ds.unshift(x)
+ }
+ ops['drop'] = -> (cs, ds) {
+ ds.shift
+ }
+ ops['over'] = -> (cs, ds) {
+ n2, n1 = ds.shift, ds.shift
+ ds.unshift(n1)
+ ds.unshift(n2)
+ ds.unshift(n1)
+ }
+ ops['swap'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(b)
+ ds.unshift(a)
+ }
+ ops['rot'] = -> (cs, ds) {
+ a, b, c = ds.shift, ds.shift, ds.shift
+ ds.unshift(a)
+ ds.unshift(c)
+ ds.unshift(b)
+ }
+ ops['and'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(b && a)
+ }
+ ops['or'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(b || a)
+ }
+ ops['xor'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(b ^ a)
+ }
+ ops['invert'] = -> (cs, ds) {
+ ds.unshift(!ds.shift)
+ }
+ ops['+'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a + b)
+ }
+ ops['-'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a - b)
+ }
+ ops['*'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a * b)
+ }
+ ops['$words'] = -> (cs, ds) {
+ puts ops.keys.join ', '
+ }
+ ops[':'] = -> (cs, ds) {
+ # compile mode
+ word = cs.shift
+ insts = []
+ while (t = cs.shift) != ';'
+ insts.push(t)
+ end
+ ops[word] = -> (cs, ds) {
+ feval(insts, ds)
+ }
+ }
+ ops['/'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a / b)
+ }
+ ops['<'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a < b)
+ }
+ ops['>'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a > b)
+ }
+ ops['='] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a == b)
+ }
+ ops['mod'] = -> (cs, ds) {
+ b, a = ds.shift, ds.shift
+ ds.unshift(a % b)
+ }
+ ops['2*'] = -> (cs, ds) {ds.unshift(ds.shift * 2)}
+ ops['2/'] = -> (cs, ds) {ds.unshift(ds.shift / 2)}
+ ops['0='] = -> (cs, ds) {ds.unshift(ds.shift == 0)}
+ ops['print'] = -> (cs, ds) {print ds.shift}
+ ops
+end
+
+# input string, data stack -> data_stack
+def feval(cstack, dstack = [], ops = create_ops)
+ while (word = cstack.shift)
+ if word.is_a? Integer # we get a bit of type safety in ruby
+ dstack.unshift word
+ else
+ raise "no word '#{word}'" if not ops.has_key? word
+ ops[word].call(cstack, dstack)
+ end
+ end
+ dstack
+end
+
+def repl
+ dstack = [] # data stack
+ cstack = [] # control stack
+ ops = create_ops # word table
+ loop do
+ print "> "
+ input = gets
+ break if input.nil?
+ input.chomp!
+ cstack = tokenize(input)
+ begin
+ feval(cstack, dstack, ops)
+ rescue
+ puts $!
+ else
+ puts " ok"
end
end
- stack
end
require "test/unit"
class TestEval < Test::Unit::TestCase
def test_plus
- stack = feval('1 5 +')
- assert_equal(6, stack[0])
+ dstack = feval(tokenize('1 5 +'))
+ assert_equal(6, dstack[0])
end
def test_plus_2
- stack = feval('3 1 5 + +')
- assert_equal(9, stack[0])
+ dstack = feval(tokenize('3 1 5 + +'))
+ assert_equal(9, dstack[0])
end
def test_print
$stdout = StringIO.new
- feval('3 1 5 + + print')
+ feval(tokenize('3 1 5 + + print'))
assert_equal($stdout.string, '9')
end
def test_div
- stack = feval('6 2 /')
- assert_equal(3, stack[0])
+ dstack = feval(tokenize('6 2 /'))
+ assert_equal(3, dstack[0])
end
def test_mod
- stack = feval('6 2 mod')
- assert_equal(0, stack[0])
+ dstack = feval(tokenize('6 2 mod'))
+ assert_equal(0, dstack[0])
end
def test_2times
- stack = feval('6 2*')
- assert_equal(12, stack[0])
+ dstack = feval(tokenize('6 2*'))
+ assert_equal(12, dstack[0])
end
def test_cmp
- stack = feval('1 0=')
- assert_equal(false, stack[0])
- stack = feval('0 0=')
- assert_equal(true, stack[0])
- stack = feval('2 1 >')
- assert_equal(true, stack[0])
- stack = feval('1 2 <')
- assert_equal(true, stack[0])
- stack = feval('1 2 >')
- assert_equal(false, stack[0])
- stack = feval('2 1 <')
- assert_equal(false, stack[0])
- stack = feval('2 1 =')
- assert_equal(false, stack[0])
- stack = feval('1 1 =')
- assert_equal(true, stack[0])
- stack = feval('T T and')
- assert_equal(true, stack[0])
- stack = feval('T F and')
- assert_equal(false, stack[0])
- stack = feval('T F or')
- assert_equal(true, stack[0])
- stack = feval('F F or')
- assert_equal(false, stack[0])
- stack = feval('T F xor')
- assert_equal(true, stack[0])
- stack = feval('T T xor')
- assert_equal(false, stack[0])
- stack = feval('T invert T and')
- assert_equal(false, stack[0])
+ dstack = feval(tokenize('1 0='))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('0 0='))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('2 1 >'))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('1 2 <'))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('1 2 >'))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('2 1 <'))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('2 1 ='))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('1 1 ='))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('T T and'))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('T F and'))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('T F or'))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('F F or'))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('T F xor'))
+ assert_equal(true, dstack[0])
+ dstack = feval(tokenize('T T xor'))
+ assert_equal(false, dstack[0])
+ dstack = feval(tokenize('T invert T and'))
+ assert_equal(false, dstack[0])
end
- def test_juggling # order is right to left
- stack = feval('1 2 dup')
- assert_equal([1, 2, 2], stack)
- stack = feval('1 2 swap')
- assert_equal([2, 1], stack)
- stack = feval('1 2 over')
- assert_equal([1, 2, 1], stack)
- stack = feval('1 2 3 rot')
- assert_equal([2, 3, 1], stack)
+ def test_juggling # top of dstack is to the right,
+ dstack = feval(tokenize('1 2 dup'))
+ assert_equal([2, 2, 1], dstack)
+ dstack = feval(tokenize('1 2 swap'))
+ assert_equal([1, 2], dstack)
+ dstack = feval(tokenize('2 6 swap /'))
+ assert_equal([3], dstack)
+ dstack = feval(tokenize('1 2 over'))
+ assert_equal([1, 2, 1], dstack)
+ dstack = feval(tokenize('3 2 1 rot'))
+ assert_equal([2, 3, 1], dstack)
end
-end
+ def test_compile
+ dstack = feval(tokenize(': add1 1 + ; 5 add1'))
+ assert_equal([6], dstack)
+ end
+end
+repl