That’s it! The core functionality is complete. Nice. You can step through any program using the functions defined so far, as long as you define all of the builtins it uses.
Okay, so this isn’t really the end. There’s plenty more to do. The rest of the interpreter will focus on turning this core into something that can run a whole program from source code to completion. For that, we’ll need a main entry point that sets everything up, some sort of loop to step through the program, and builtins. Lots of builtins.
The driving loop interp-run can use context-next to figure out what to run next, and either look it up as a function or push it to the stack as a literal:
(: interp-run (Context Stack . -> . (Values Context Stack))) (define (interp-run ctx stack) (let-values ([(ctx v) (context-next ctx)]) (cond ; Program ended [(void? v) (values ctx stack)] ; Call a symbol [(symbol? v) (let-values ([(ctx stack) (interp-call ctx stack v)]) (interp-run ctx stack))] ; Push anything else to the stack [else (interp-run ctx (cons v stack))])))
Now we have enough code to run a program, we can try testing it. Without any builtin functions yet, this might be a bit tough, so let’s take it one step at a time, starting from zero.
(test-case "Empty context does nothing successfully" (let-values ([(ctx stack) (interp-run (make-context) '())]) (check-equal? (context-body ctx) '()) (check-equal? stack '())))
Good. Running an empty context with no code and an empty stack does nothing.
As everything except symbols is treated literally, a program consisting of a sequence of non-symbols should just result in a stack full of those things (and a completed program body).
(test-case "Non-symbol literals go on the stack" (let-values ([(ctx stack) (interp-run (make-context #:body '(1 2 (list #\A) "string" #t)) '())]) (check-equal? ctx (make-context #:body '())) (check-equal? stack '(#t "string" (list #\A) 2 1))))
Since the stack is a list, the top of the stack is visually at the left, so the last thing in the program is the first thing in the stack.
While we still have no definitions to test, let’s make sure it fails if it encounters a symbol.
(test-case "Throws on error" (check-exn exn:fail? (lambda () (let-values ([(ctx stack) (interp-run (make-context #:body '(undefined reference)) '())]) #t))))
TODO: There are always more tests to write.