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,66 +523,41 @@
: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.
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]
(cond
(= (NUMBERP expr) T) expr
(symbol? expr) (eval-symbolic expr env)
(string? expr) (if (:strict *options*)
(throw
(ex-info
(str "EVAL: strings not allowed in strict mode: \"" expr "\"")
{:phase :eval
:detail :strict
:expr expr}))
(symbol expr))
(=
(ATOM? (CAR expr))
T) (cond
(= (CAR expr) 'QUOTE) (CADR expr)
(= (CAR expr) 'COND) (EVCON (CDR expr) env)
:else (APPLY
(CAR expr)
(EVLIS (CDR expr) env)
env))
:else (APPLY
(CAR expr)
(EVLIS (CDR expr) env)
env)))
(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 (defn EVAL
"Despatcher for EVAL, selects beteen `traced-eval` and `eval-internal` "Evaluate this `expr` and return the result. If `environment` is not passed,
based on the value of `:trace` in `*options*`. Evaluate this `expr` it defaults to the current value of the global object list. The `depth`
and return the result. If `environment` is not passed, argument is part of the tracing system and should not be set by user code.
if defaults to the current value of the global object list.
All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects." All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell`
([expr] objects."
(EVAL expr @oblist)) ([expr]
([expr env] (EVAL expr @oblist 0))
(if ([expr env depth]
(:trace *options*) (cond
(traced-eval expr env) (= (NUMBERP expr) T) expr
(eval-internal expr env)))) (symbol? expr) (eval-symbolic expr env)
(string? expr) (if (:strict *options*)
(throw
(ex-info
(str "EVAL: strings not allowed in strict mode: \"" expr "\"")
{:phase :eval
:detail :strict
:expr expr}))
(symbol expr))
(=
(ATOM? (CAR expr))
T) (cond
(= (CAR expr) 'QUOTE) (CADR expr)
(= (CAR expr) 'COND) (EVCON (CDR expr) env depth)
:else (APPLY
(CAR expr)
(EVLIS (CDR expr) env depth)
env
depth))
:else (APPLY
(CAR expr)
(EVLIS (CDR expr) env depth)
env
depth))))

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)) %)))))