~nch/onward

30a8418ac705b1e9754c9191ea593b16cd7d875b — nc 4 years ago 7f0e33b
added loop to read next token and hash the value
2 files changed, 140 insertions(+), 121 deletions(-)

M forth.s
M prototype.rb
M forth.s => forth.s +61 -15
@@ 13,35 13,79 @@ section .text
%define STDIN 0
%define STDOUT 1


%define putchar(x) mov

_start:

repl:
        mov rax, SYS_WRITE          ; write prompt
rep_loop:
        ;; write prompt
        mov rax, SYS_WRITE
        mov rdi, STDOUT
        mov rsi, prompt
        mov rdx, prompt_len
        syscall

        mov rax, SYS_READ           ; read
        ;; read input string
        mov rax, SYS_READ
        mov rdi, STDIN
        mov rsi, buf_in
        mov rsi, in_buf
        mov rdx, buf_len
        syscall

        mov rdx, rax ; length in rax
        cmp rdx, 0
        mov rdx, rax                ; length in rax

        cmp rdx, 0                  ; exit if empty (user sent Ctrl-D)
        jz exit

        ;; write
        mov rax, SYS_WRITE          ; echo
        ;; get the first word in the string
        ;; while calculating hash of string

        ; update hash (sdbm hash):
        ;   for c in str:
        ;     new_hash = c + (hash << 6) + (hash << 16) - hash
        ;     hash = new_hash

        mov rbx, word_buf           ; word_ptr:   rbx = (char *) word_buf
        mov rcx, 0                  ; hash:       rcx = 0
                                    ; in_buf_ptr: rsi (set above)

read_next_word_loop:
        mov rax, 0                  ; clear rax
        mov al, [rsi]               ; c = *in_buf_ptr
        cmp al, byte ' '            ; check for whitespace
        je .end
        cmp al, byte `\n`
        je .end
        cmp al, byte `\t`
        je .end
        mov [rbx], al               ; *word_buf = c

        ; rdx = new_hash (copy of hash for iteration)
        ; rax = current character (because of al)

        ; calculate hash
        mov rdx, rcx                ; rdx = hash. copied so we can shift it around
        sal rdx, 6                  ; hash << 6
        add rax, rdx
        sal rdx, 10                 ; hash << 16
        add rax, rdx
        sub rax, rcx                ; - hash
        mov rcx, rax                ; hash = new_hash

        add rsi, 1                  ; in_buf_ptr++
        add rbx, 1                  ; word_ptr++
        jmp read_next_word_loop
.end:
        ; calculate len, store it in rdx
        mov rdx, rbx
        sub rdx, word_buf

        ;; echo word
        mov rax, SYS_WRITE
        mov rdi, STDOUT
        mov rsi, buf_in
        mov rsi, word_buf
        ; rdx set above
        syscall

        jmp repl                    ; loop
        jmp rep_loop

exit:
        mov rax, SYS_EXIT


@@ 49,8 93,10 @@ exit:
        syscall

section .bss
        buf_in: resb 128
        buf_len: equ $ - buf_in
        in_buf: resb 128
        buf_len: equ $ - in_buf

        word_buf: resb 32

section .rodata
        prompt: db "> "

M prototype.rb => prototype.rb +79 -106
@@ 16,41 16,13 @@ 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['drop'] = -> (cs, ds) { ds.shift }
    ops['dup'] = -> (cs, ds) { x = ds.shift; ds.unshift(x); ds.unshift(x) }
    ops['swap'] = -> (cs, ds) { b, a = ds.shift, ds.shift; ds.unshift(b); ds.unshift(a) }
    ops['over'] = -> (cs, ds) { n2, n1 = ds.shift, ds.shift; ds.unshift(n1); ds.unshift(n2); ds.unshift(n1); }
    ops['rot'] = -> (cs, ds) { a, b, c = ds.shift, ds.shift, ds.shift; ds.unshift(a); ds.unshift(c); ds.unshift(b) }
    ops['-rot'] = -> (cs, ds) { a, b, c = ds.shift, ds.shift, ds.shift; ds.unshift(b); ds.unshift(a); ds.unshift(c) }
    ops['?dup'] = -> (cs, ds) { x = ds.shift; if x then ds.unshift(x); ds.unshift(x) end}
    ops['and'] = -> (cs, ds) {
        b, a = ds.shift, ds.shift
        ds.unshift(b && a)


@@ 63,6 35,10 @@ def create_ops
        b, a = ds.shift, ds.shift
        ds.unshift(b ^ a)
    }

    ops['T'] = -> (cs, ds) {ds.unshift(true)}
    ops['F'] = -> (cs, ds) {ds.unshift(false)}
    ops['emit'] = -> (cs, ds) { ascii_code = ds.shift; print ascii_code.chr }
    ops['invert'] = -> (cs, ds) {
        ds.unshift(!ds.shift)
    }


@@ 78,7 54,7 @@ def create_ops
        b, a = ds.shift, ds.shift
        ds.unshift(a * b)
    }
    ops['$words'] = -> (cs, ds) {
    ops['_words'] = -> (cs, ds) {
        puts ops.keys.join ', '
    }
    ops[':'] = -> (cs, ds) {


@@ 88,8 64,9 @@ def create_ops
        while (t = cs.shift) != ';'
            insts.push(t)
        end
        ops[word] = -> (cs, ds) {
            feval(insts, ds)
        ops[word] = -> (_cs, _ds) {
            # dup so feval doesn't delete the original definition when popping
            feval(insts.dup, ops, ds)
        }
    }
    ops['/'] = -> (cs, ds) {


@@ 112,17 89,21 @@ def create_ops
        b, a = ds.shift, ds.shift
        ds.unshift(a % b)
    }
    ops['('] = -> (cs, ds) {
        while cs.shift != ')'
        end
    }
    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['.'] = -> (cs, ds) {print ds.shift}
    ops
end

# input string, data stack -> data_stack
def feval(cstack, dstack = [], ops = create_ops)
# control stack, ops list, data stack -> data_stack
def feval(cstack, ops = create_ops, dstack = [])
    while (word = cstack.shift)
        if word.is_a? Integer # we get a bit of type safety in ruby
        if word.is_a? Integer # we get a bit of extra type safety in ruby
            dstack.unshift word
        else
            raise "no word '#{word}'" if not ops.has_key? word


@@ 143,11 124,11 @@ def repl
        input.chomp!
        cstack = tokenize(input)
        begin
            feval(cstack, dstack, ops)
            feval(cstack, ops, dstack)
        rescue
            puts $!
            $stderr.puts $!
        else
            puts " ok"
            $stderr.puts " ok"
        end
    end
end


@@ 155,87 136,79 @@ end
require "test/unit"

class TestEval < Test::Unit::TestCase
    def test_plus
        dstack = feval(tokenize('1 5 +'))
        assert_equal(6, dstack[0])
    def assert_eval(final_dstack, prog)
        dstack = feval(tokenize(prog))
        assert_equal(final_dstack, dstack)
    end

    def test_plus_2
        dstack = feval(tokenize('3 1 5 + +'))
        assert_equal(9, dstack[0])
    def assert_eval_stdout(stdout, prog, ops=create_ops)
        $stdout = StringIO.new
        feval(tokenize(prog), ops=ops)
        assert_equal(stdout, $stdout.string)
    end

    def test_print
        $stdout = StringIO.new
        feval(tokenize('3 1 5 + + print'))
        assert_equal($stdout.string, '9')
    def test_plus
        assert_eval([6], '1 5 +')
    end

    def test_div
        dstack = feval(tokenize('6 2 /'))
        assert_equal(3, dstack[0])
    def test_plus_2
        assert_eval([9], '3 1 5 + +')
    end

    def test_mod
        dstack = feval(tokenize('6 2 mod'))
        assert_equal(0, dstack[0])
    def test_print
        assert_eval_stdout('9', '3 1 5 + + .')
    end

    def test_2times
        dstack = feval(tokenize('6 2*'))
        assert_equal(12, dstack[0])
    def test_math
        assert_eval([3], '6 2 /')
        assert_eval([0], '6 2 mod')
        assert_eval([12], '6 2*')
    end

    def test_cmp
        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])
        assert_eval([false], '6 0=')
        assert_eval([true], '0 0=')
        assert_eval([true], '2 1 >')
        assert_eval([false], '2 1 <')
        assert_eval([true], '2 2 =')
        assert_eval([true], 'T T and')
        assert_eval([true], 'T F or')
        assert_eval([false], 'F F xor')
        assert_eval([false], 'F F xor')
        assert_eval([false], 'T invert T and')
    end

    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)
        assert_eval([2, 2, 1], '1 2 dup')
        assert_eval([1, 2], '1 2 swap')
        assert_eval([3], '2 6 swap /')
        assert_eval([1, 2, 1], '1 2 over')
        assert_eval([2, 3, 1], '3 2 1 rot')
    end

    def test_compile
        dstack = feval(tokenize(': add1 1 + ; 5 add1'))
        assert_equal([6], dstack)
        assert_eval([7, 6], ': add1 1 + ; 5 add1 6 add1')
    end

    def test_comment
        assert_eval([], '( plz do nothing )')
        assert_eval([], '( + 1 2 )')
        assert_eval([2], '1 ( + 1 2 ) 1 +')
    end

=begin
    def test_starting_forth_1
        ops = create_ops
        feval(tokenize(': star 42 emit ;'), ops=ops)
        assert_true(ops.has_key? 'star')
        assert_eval_stdout("\n*\n*\n*", 'cr star cr star cr star', ops=ops)
        feval(tokenize(': margin cr 30 spaces ;'), ops=ops)
        assert_eval_stdout("\n#{' ' * 30}*"*3, 'margin star margin star margin star', ops=ops)
        feval(tokenize(': blip margin star ;'), ops=ops)
        feval(tokenize(': stars 0 do star loop ;'), ops=ops)
        assert_eval_stdout("*"*5, '5 stars', ops=ops)
    end
=end
end

repl