~nch/onward

7f0e33b1eee89c6b2bb659d8f7d59e0e2db076b5 — nc 4 years ago 01efad4
added compile mode
1 files changed, 201 insertions(+), 134 deletions(-)

M prototype.rb
M prototype.rb => prototype.rb +201 -134
@@ 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