Archive for February, 2008

Abandoning dynamic scope

Wednesday, February 6th, 2008

With eval evaluating lambda to capture an environment, the function and funarg forms are no longer needed. They are replaced by lambda and proc.

Then dynamic scoping is abandoned by evaluating the function position to retrieve a procedure that carries its own environment.

  (#t           (apply
                  (eval (car form) env)
                  (evlis (cdr form) env)
                  env
                )
  )

The lambda recognition can be removed from apply.

The original Lisp interpreter allowed an arbitrary level of indirection. The apply function would follow a chain of atom definitions to find a lambda form. Eliminating lambda and re-evaluation of the function form in apply eliminates all usage of the env argument. At which point, removal of the env argument brings apply closer to the Scheme and Common Lisp versions.

The apply function is now much simpler…

apply =
  (lambda (fn args)
    (cond
      ((pproc? fn)                   (apply-pproc (car(cdr fn)) args))
      ((eq? (car fn) (quote proc))   (eval
                                       (car(cdr(cdr(cdr fn))))
                                       (mkenv
                                         (car(cdr(cdr fn)))
                                         args
                                         (cdr (car(cdr fn)))
                                       )
                                     )
      )
      (#t                            (quote ()))
    )
  )

A simpler software state machine

Sunday, February 3rd, 2008

Until recently, I had been running a software state machine which implemented a next-m-state function that took two arguments: m-state and i-state.

The i-state had not been an input state for quite some time. It is an instruction state, but it is redundant since it is pulled out of m-state.

So next-m-state was redone to accept only one argument, the m-state. The state machine becomes (slightly hacked because the base system does not have set!)…

swlsm =
  (lambda (m-state-binding)
    (loop
      (set-cdr!
        m-state-binding
        (next-m-state (cdr m-state-binding))
      )
    )
  )

The first stage extracts the instruction list and the environment…

next-m-state =
  (lambda (m-state)
    (execute (get-i-list m-state) (get-m-env m-state))
  )

The second stage checks for the existence of instructions. If there is at least one instruction, the first instruction is split from the rest of the instruction list…

execute =
  (lambda (i-list env)
    (cond
      ((null? i-list) (exit))
      (#t             (exec1 (car i-list) (cdr i-list) env)
    )
  )

Finally, the instruction (also called a command, listed here as cmd) is executed…

exec1 =
  (lambda (cmd next-i-list env)
    (cond
      ((eq? (car cmd) (quote define)) (make-m-state
                                        next-i-list
                                        (quote ())
                                        (add-to-env
                                          (make-binding
                                            (car(cdr cmd))
                                            (eval (car(cdr(cdr cmd))) env)
                                          )
                                          env
                                        )
                                      )
      )
      (#t                             (make-m-state
                                        next-i-list
                                        (eval cmd env)
                                        env
                                      )
      )
    )
  )

Byte by byte file I/O

Friday, February 1st, 2008

With sequenced expressions, sequential file I/O can now be added. The basic system interface to Windows for the “little Lisp” actually has a byte-by-byte file I/O interface.

One reason it was hidden: I chose not to guarantee evaluation order of arguments. The other reason: I found it far more tedious to parse using applicative techniques under the evaluation order restriction.

Also, by exposing the low level interface, larger files can be processed. The new file I/O interface is implemented via the xeq-1f machine language interface. The new functions are openin, openout, readbyte, writebyte, and close. Up to four files can be open at once. The console (keyboard/video) I/O used by the REPL is not counted as part of the file system. Windows provides file names CONIN$ and CONOUT$ as one of the means for accessing the console. The functions openin and openout return an I/O port as a byte atom. The readbyte, writebyte, and close functions require an I/O port argument.

Standard names are avoided, at least until some of the metastructure (or infrastructure, if you prefer) is improved. This includes infusing data types into the base system, and subsequently mapping between Lisp/Scheme types and native types for marshalling and compiling.