Complete (and much improved) reimplementation of the trace system.

This commit is contained in:
Simon Brooke 2023-03-30 15:52:52 +01:00
parent 1f16241af7
commit 197ff0a08f
6 changed files with 98 additions and 109 deletions

View file

@ -37,7 +37,6 @@ Command line arguments as follows:
-p PROMPT, --prompt PROMPT Sprecan:: Set the REPL prompt to PROMPT -p PROMPT, --prompt PROMPT Sprecan:: Set the REPL prompt to PROMPT
-r INITFILE, --read INITFILE Read Lisp functions from the file INITFILE -r INITFILE, --read INITFILE Read Lisp functions from the file INITFILE
-s, --strict Strictly interpret the Lisp 1.5 language, without extensions. -s, --strict Strictly interpret the Lisp 1.5 language, without extensions.
-t, --trace Trace Lisp evaluation.
``` ```
### Architectural plan ### Architectural plan
@ -66,8 +65,7 @@ implementations.
This file is essentially Lisp as defined in Chapter 1 (pages 1-14) of the This file is essentially Lisp as defined in Chapter 1 (pages 1-14) of the
Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language,
which should, I believe, be sufficient in conjunction with the functions which should, I believe, be sufficient in conjunction with the functions
provided by `beowulf.host`, be sufficient to bootstrap the full Lisp 1.5 provided by `beowulf.host`, to bootstrap the full Lisp 1.5 interpreter.
interpreter.
In addition it contains the function `INTEROP`, which allows host language In addition it contains the function `INTEROP`, which allows host language
functions to be called from Lisp. functions to be called from Lisp.

View file

@ -74,4 +74,6 @@
(SYSOUT) (SYSOUT)
(TERPRI) (TERPRI)
(TIMES) (TIMES)
(TRACE)
(UNTRACE)
(ZEROP LAMBDA (N) (EQ N 0))) (ZEROP LAMBDA (N) (EQ N 0)))

View file

@ -10,15 +10,15 @@
therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell` therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell`
objects." objects."
(:require [clojure.string :as s] (:require [clojure.string :as s]
[clojure.tools.trace :refer [deftrace]]
[beowulf.cons-cell :refer [CAR CDR CONS LIST make-beowulf-list make-cons-cell [beowulf.cons-cell :refer [CAR CDR CONS LIST make-beowulf-list make-cons-cell
pretty-print T F]] pretty-print T F]]
[beowulf.host :refer [AND ADD1 DIFFERENCE ERROR FIXP GENSYM GREATERP LESSP [beowulf.host :refer [AND ADD1 DIFFERENCE ERROR FIXP GENSYM GREATERP LESSP
NUMBERP PLUS QUOTIENT NUMBERP PLUS QUOTIENT
REMAINDER RPLACA RPLACD SUB1 TIMES]] REMAINDER RPLACA RPLACD TIMES]]
[beowulf.io :refer [SYSIN SYSOUT]] [beowulf.io :refer [SYSIN SYSOUT]]
[beowulf.oblist :refer [*options* oblist NIL]] [beowulf.oblist :refer [*options* oblist NIL]]
[beowulf.read :refer [READ]]) [beowulf.read :refer [READ]]
[beowulf.trace :refer [TRACE traced? UNTRACE]])
(:import [beowulf.cons_cell ConsCell] (:import [beowulf.cons_cell ConsCell]
[clojure.lang Symbol])) [clojure.lang Symbol]))
@ -399,12 +399,19 @@
(defn- apply-symbolic (defn- apply-symbolic
"Apply this `funtion-symbol` to these `args` in this `environment` and "Apply this `funtion-symbol` to these `args` in this `environment` and
return the result." return the result."
[^Symbol function-symbol ^ConsCell args ^ConsCell environment] [^Symbol function-symbol ^ConsCell args ^ConsCell environment depth]
(let [fn (try (EVAL function-symbol environment) (let [fn (try (EVAL function-symbol environment depth)
(catch Throwable any (when (:trace *options*) (catch Throwable any (when (:trace *options*)
(println any))))] (println any))))
indent (apply str (repeat depth "-"))]
(if (and fn (not= fn NIL)) (if (and fn (not= fn NIL))
(APPLY fn args environment) (if (traced? function-symbol)
(do
(println (str indent "> " function-symbol " " args))
(let [r (APPLY fn args environment depth)]
(println (str "<" indent " " r))
r))
(APPLY fn args environment depth))
(case function-symbol ;; there must be a better way of doing this! (case function-symbol ;; there must be a better way of doing this!
ADD1 (apply ADD1 args) ADD1 (apply ADD1 args)
AND (apply AND args) AND (apply AND args)
@ -446,20 +453,22 @@
(apply SYSOUT args))) (apply SYSOUT args)))
TERPRI (println) TERPRI (println)
TIMES (apply TIMES args) TIMES (apply TIMES args)
TRACE (apply TRACE args)
UNTRACE (apply UNTRACE args)
;; else ;; else
(ex-info "No function found" (ex-info "No function found"
{:context "APPLY" {:context "APPLY"
:function function-symbol :function function-symbol
:args args}))))) :args args})))))
(defn apply-internal (defn APPLY
"Internal guts of both `APPLY` and `traced-apply`. Apply this `function` to "Apply this `function` to these `arguments` in this `environment` and return
these `arguments` in this `environment` and return the result. the result.
For bootstrapping, at least, a version of APPLY written in Clojure. For bootstrapping, at least, a version of APPLY written in Clojure.
All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects.
See page 13 of the Lisp 1.5 Programmers Manual." See page 13 of the Lisp 1.5 Programmers Manual."
[function args environment] [function args environment depth]
(cond (cond
(= NIL function) (if (:strict *options*) (= NIL function) (if (:strict *options*)
NIL NIL
@ -467,10 +476,10 @@
{:context "APPLY" {:context "APPLY"
:function "NIL" :function "NIL"
:args args}))) :args args})))
(= (ATOM? function) T) (apply-symbolic function args environment) (= (ATOM? function) T) (apply-symbolic function args environment (inc depth))
(= (first function) 'LAMBDA) (EVAL (= (first function) 'LAMBDA) (EVAL
(CADDR function) (CADDR function)
(PAIRLIS (CADR function) args environment)) (PAIRLIS (CADR function) args environment) depth)
(= (first function) 'LABEL) (APPLY (= (first function) 'LABEL) (APPLY
(CADDR function) (CADDR function)
args args
@ -478,26 +487,8 @@
(make-cons-cell (make-cons-cell
(CADR function) (CADR function)
(CADDR function)) (CADDR function))
environment)))) environment)
depth)))
(deftrace traced-apply
"Traced wrapper for `internal-apply`, q.v. Apply this `function` to
these `arguments` in this `environment` and return the result."
[function args environment]
(apply-internal function args environment))
(defn APPLY
"Despatcher for APPLY, selects beteen `traced-apply` and `apply-internal`
based on the value of `:trace` in `*options*`. Apply this `function` to
these `arguments` and return the result. If `environment` is not passed,
if defaults to the current value of the global object list."
([function args]
(APPLY function args @oblist))
([function args environment]
(if
(:trace *options*)
(traced-apply function args environment)
(apply-internal function args environment))))
(defn- EVCON (defn- EVCON
"Inner guts of primitive COND. All `clauses` are assumed to be "Inner guts of primitive COND. All `clauses` are assumed to be
@ -505,24 +496,24 @@
often return `F`, not `NIL`, on failure. often return `F`, not `NIL`, on failure.
See page 13 of the Lisp 1.5 Programmers Manual." See page 13 of the Lisp 1.5 Programmers Manual."
[clauses env] [clauses env depth]
(let [test (EVAL (CAAR clauses) env)] (let [test (EVAL (CAAR clauses) env depth)]
(if (if
(and (not= test NIL) (not= test 'F)) (and (not= test NIL) (not= test 'F))
(EVAL (CADAR clauses) env) (EVAL (CADAR clauses) env depth)
(EVCON (CDR clauses) env)))) (EVCON (CDR clauses) env depth))))
(defn- EVLIS (defn- EVLIS
"Map `EVAL` across this list of `args` in the context of this "Map `EVAL` across this list of `args` in the context of this
`env`ironment.All args are assumed to be `beowulf.cons-cell/ConsCell` objects. `env`ironment.All args are assumed to be `beowulf.cons-cell/ConsCell` objects.
See page 13 of the Lisp 1.5 Programmers Manual." See page 13 of the Lisp 1.5 Programmers Manual."
[args env] [args env depth]
(cond (cond
(= NIL args) NIL (= NIL args) NIL
:else :else
(make-cons-cell (make-cons-cell
(EVAL (CAR args) env) (EVAL (CAR args) env depth)
(EVLIS (CDR args) env)))) (EVLIS (CDR args) env depth))))
(defn- eval-symbolic [^Symbol s env] (defn- eval-symbolic [^Symbol s env]
(let [binding (ASSOC s env)] (let [binding (ASSOC s env)]
@ -532,14 +523,17 @@
:symbol s})) :symbol s}))
(CDR binding)))) (CDR binding))))
(defn- eval-internal
"Common guts for both EVAL and traced-eval. Evaluate this `expr`
and return the result.
For bootstrapping, at least, this is a version of EVAL written in Clojure. (defn EVAL
All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. "Evaluate this `expr` and return the result. If `environment` is not passed,
See page 13 of the Lisp 1.5 Programmers Manual." it defaults to the current value of the global object list. The `depth`
[expr env] argument is part of the tracing system and should not be set by user code.
All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell`
objects."
([expr]
(EVAL expr @oblist 0))
([expr env depth]
(cond (cond
(= (NUMBERP expr) T) expr (= (NUMBERP expr) T) expr
(symbol? expr) (eval-symbolic expr env) (symbol? expr) (eval-symbolic expr env)
@ -555,43 +549,15 @@
(ATOM? (CAR expr)) (ATOM? (CAR expr))
T) (cond T) (cond
(= (CAR expr) 'QUOTE) (CADR expr) (= (CAR expr) 'QUOTE) (CADR expr)
(= (CAR expr) 'COND) (EVCON (CDR expr) env) (= (CAR expr) 'COND) (EVCON (CDR expr) env depth)
:else (APPLY :else (APPLY
(CAR expr) (CAR expr)
(EVLIS (CDR expr) env) (EVLIS (CDR expr) env depth)
env)) env
depth))
:else (APPLY :else (APPLY
(CAR expr) (CAR expr)
(EVLIS (CDR expr) env) (EVLIS (CDR expr) env depth)
env))) env
depth))))
(deftrace traced-eval
"Essentially, identical to EVAL except traced."
[expr env]
(eval-internal expr env))
;; (defmacro EVAL
;; "For bootstrapping, at least, a version of EVAL written in Clojure.
;; All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects.
;; See page 13 of the Lisp 1.5 Programmers Manual."
;; [expr env]
;; `(if
;; (:trace *options*)
;; (traced-eval ~expr ~env)
;; (eval-internal ~expr ~env)))
(defn EVAL
"Despatcher for EVAL, selects beteen `traced-eval` and `eval-internal`
based on the value of `:trace` in `*options*`. Evaluate this `expr`
and return the result. If `environment` is not passed,
if defaults to the current value of the global object list.
All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects."
([expr]
(EVAL expr @oblist))
([expr env]
(if
(:trace *options*)
(traced-eval expr env)
(eval-internal expr env))))

View file

@ -29,8 +29,7 @@
(.exists (io/file %)) (.exists (io/file %))
(.canRead (io/file %))) (.canRead (io/file %)))
"Could not find initfile"]] "Could not find initfile"]]
["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."] ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."]])
["-t" "--trace" "Trace Lisp evaluation."]])
(defn repl (defn repl
"Read/eval/print loop." "Read/eval/print loop."
@ -42,7 +41,7 @@
(let [input (trim (read-from-console))] (let [input (trim (read-from-console))]
(cond (cond
(= input stop-word) (throw (ex-info "\nFærwell!" {:cause :quit})) (= input stop-word) (throw (ex-info "\nFærwell!" {:cause :quit}))
input (println (str "> " (print-str (EVAL (READ input) @oblist)))) input (println (str "> " (print-str (EVAL (READ input) @oblist 0))))
:else (println))) :else (println)))
(catch (catch
Exception Exception

24
src/beowulf/trace.clj Normal file
View file

@ -0,0 +1,24 @@
(ns beowulf.trace
"Tracing of function execution")
(def traced-symbols
"Symbols currently being traced."
(atom #{}))
(defn traced?
"Return `true` iff `s` is a symbol currently being traced, else `nil`."
[s]
(try (contains? @traced-symbols s)
(catch Throwable _)))
(defn TRACE
"Add this symbol `s` to the set of symbols currently being traced. If `s`
is not a symbol, does nothing."
[s]
(when (symbol? s)
(swap! traced-symbols #(conj % s))))
(defn UNTRACE
[s]
(when (symbol? s)
(swap! traced-symbols #(set (remove (fn [x] (= s x)) %)))))