Lispy in Scheme | Assignment and define

Our goal in this part is to implement assignment via the form (define symbol value). There are three challenges to this goal. First we need to come up with a system to store symbol names and their values. Then we need to display those values when the symbol is eval’d. Finally we need to recognize the define form and set the value of its symbol.

 

Starting with a Stub

To start with we need to modify our eval procedure to recognize symbols. We’ll call a procedure lookup-variable-value that will eventually give the value of the symbol. Right now we’ll just have it return a string stating our intentions.

(define (lookup-variable-value expr)
  "Here's where we lookup the value of the symbol.")

(define (self-evaluating? expr)
  (or (number? expr) (string? expr) (char? expr) (boolean? expr)))

(define (lispy-eval expr)
  (cond ((self-evaluating? expr) expr)
        ((symbol? expr) (lookup-variable-value expr))
        (else "Not implemented")))

(define (repl)
  (define input (read))
  (print ";===>" (lispy-eval input))
  (repl))
(repl)
"hello"
;===> hello
hi
;===> Here's where we lookup the value of the symbol.

 

The Global Frame

To set the value of a symbol we need to have somewhere to set it. That somewhere is going to be a hash table. The hash table that stores a set of names and their associated values is referred to as a frame.

For now we’ll define our frame as a global variable and also define a procedure set-variable! to set the value of a symbol. Since we don’t have any method of assigning values to symbols yet, we define a symbol “test” when we make the frame so we can see if it works.

(use srfi-69)

(define frame (make-hash-table))

(define (set-symbol! symbol value)
  (hash-table-set! frame symbol value))

(set-symbol! 'test "Value retrieved successfully")

(define (lookup-variable-value symbol)
  (if (hash-table-exists? frame symbol)
      (hash-table-ref frame symbol)
      "Error: Unbound variable"))

(define (self-evaluating? expr)
  (or (number? expr) (string? expr) (char? expr) (boolean? expr)))

(define (lispy-eval expr)
  (cond ((self-evaluating? expr) expr)
        ((symbol? expr) (lookup-variable-value expr))
        (else "Not implemented")))

(define (repl)
  (define input (read))
  (print ";===>" (lispy-eval input))
  (repl))
(repl)
test
;===> Value retrieved successfully
hello
;===> Error: Unbound variable

 

(define symbol value)

Now that we can store and retrieve the value of a symbol, we need a way to do it from within Lispy. Lispy uses the form (define symbol value) to define a new symbol or set the value of an existing symbol.

The form (define symbol value) is a pair (also a list, but all lists are also pairs) so the first step to detecting its usage is to look for pairs. The next step is to check the symbol in the car of that pair (or the first item of the list), if that symbol is “define” we run the code to define a symbol. For now we’ll just return a string stating our intentions.

(use srfi-69)

(define frame (make-hash-table))

(define (set-symbol! symbol value)
  (hash-table-set! frame symbol value))

(set-symbol! 'test "Value retrieved successfully")

(define (lookup-variable-value symbol)
  (if (hash-table-exists? frame symbol)
      (hash-table-ref frame symbol)
      "Error: Unbound variable"))

(define (self-evaluating? expr)
  (or (number? expr) (string? expr) (char? expr) (boolean? expr)))

(define (lispy-eval expr)
  (cond ((self-evaluating? expr) expr)
        ((symbol? expr) (lookup-variable-value expr))
        ((pair? expr)
          (if (equal? (car expr) 'define)
              "Set the value of the symbol."
              "Not implemented"))
        (else "Not implemented")))

(define (repl)
  (define input (read))
  (print ";===>" (lispy-eval input))
  (repl))
(repl)
test
;===> Value retrieved successfully
(define a 1)
;===> Set the value of the symbol.
(set a 1)
;===> Not implemented

 

Setting the Value of Symbols

To actually set the value of the symbol we need to get the arguments to define. Getting the symbol argument is straightforward, however we need to eval the value argument as it might not be self-evaluating.

(use srfi-69)

(define frame (make-hash-table))

(define (set-symbol! symbol value)
  (hash-table-set! frame symbol value))

(set-symbol! 'test "Value retrieved successfully")

(define (lookup-variable-value symbol)
  (if (hash-table-exists? frame symbol)
      (hash-table-ref frame symbol)
      "Error: Unbound variable"))

(define (self-evaluating? expr)
  (or (number? expr) (string? expr) (char? expr) (boolean? expr)))

(define (lispy-eval expr)
  (cond ((self-evaluating? expr) expr)
        ((symbol? expr) (lookup-variable-value expr))
        ((pair? expr)
          (if (equal? (car expr) 'define)
              (set-symbol! (cadr expr) (lispy-eval (caddr expr)))
              "Not implemented"))
        (else "Not implemented")))

(define (repl)
  (define input (read))
  (print ";===>" (lispy-eval input))
  (repl))
(repl)
(define a 5)
;===> #<unspecified>
a
;===> 5

In addition to being able to set symbols, we now have a glimpse into how basic forms (like define) are handled. It really is no more complicated than checking the procedure name, evaluating the arguments that need to be, then passing the arguments to a Scheme procedure.

* To any Schemers that may be reading this: I avoided implementing environments here for two reasons. I wanted to show the simplest implementation possible and in an upcoming post we will transform our global frame into a lookup table for primitive syntax.

GOTO Part 3 | scheme-syntax macro

GOTO Lispy in Chicken | Table of Contents

Advertisements