feat: clojure-features demo runs end-to-end (Janet interop in §14, real exception demo in §11); fix syntax-quote (auto-gensym foo#, leave core/special unqualified) so macros work; (. obj sym) is a zero-arg method call; add long/double/float/num coercions; print Janet stack trace on errors (REPL/file/-e)

This commit is contained in:
Yogthos 2026-06-04 17:08:18 -04:00
parent f20df44d6a
commit 7d490843e5
4 changed files with 81 additions and 26 deletions

View file

@ -185,9 +185,9 @@
(defn exception-demo []
(println "\n--- Exception Handling ---")
(try
(/ 1 0)
(catch ArithmeticException e
(println (str "Caught exception: " (.getMessage e))))
(throw (ex-info "something broke" {:code 42}))
(catch :default e
(println (str "Caught: " (ex-message e) " " (pr-str (ex-data e)))))
(finally
(println "Finally block executed."))))
@ -203,14 +203,21 @@
;; 13. Clojure's core.async? Not pure Clojure, skip.
;; 14. Java interop (still pure Clojure)
(defn java-interop-demo []
(println "\n--- Java Interop ---")
(def now (java.util.Date.))
(println (str "Current date: " (.toString now)))
(def sb (StringBuilder. "Hello"))
(.append sb " Clojure!")
(println (str "StringBuilder: " (.toString sb))))
;; 14. Janet host interop — Jolt runs on Janet, so host interop is Janet-style:
;; `.` dispatches on a table/struct (the receiver is passed as the first
;; arg), and Janet's standard library is reachable through jolt.interop.
(defn janet-interop-demo []
(println "\n--- Janet Interop ---")
;; Method-style call on a Janet table: self is passed as the first argument.
(def counter {:value 41
:describe (fn [self] (str "counter = " (:value self)))})
(println (str "Method call: " (. counter describe)))
;; Field access sugar: (.-k m) is (. m :k)
(println (str "Field access: " (.-value counter)))
;; Reach Janet's standard library through jolt.interop.
(require '[jolt.interop :as j])
(println (str "Janet type: " (j/janet-type [1 2 3])))
(println (str "Table keys: " (pr-str (j/janet-table-keys {:a 1 :b 2})))))
;; ---------- Main entry point ----------
(defn -main []
@ -227,7 +234,7 @@
(threading-demo)
(exception-demo)
(for-demo)
(java-interop-demo)
(janet-interop-demo)
(println "\n=== Demo Complete ==="))
;; Run if executed as script

View file

@ -1277,6 +1277,10 @@
# ============================================================
(def core-int (fn [x] (if (core-char? x) (x :ch) (math/trunc x))))
(def core-long (fn [x] (if (core-char? x) (x :ch) (math/trunc x))))
(def core-double (fn [x] (* 1.0 (if (core-char? x) (x :ch) x))))
(def core-float core-double)
(def core-num (fn [x] (if (core-char? x) (x :ch) x)))
(defn core-char [x]
"(char code-or-char) -> a character value."
(cond
@ -2617,6 +2621,10 @@
"unsigned-bit-shift-right" core-unsigned-bit-shift-right
# Integer coercion / unchecked math
"int" core-int
"long" core-long
"double" core-double
"float" core-float
"num" core-num
"char" core-char
"char?" core-char?
"unchecked-inc" core-unchecked-inc

View file

@ -79,8 +79,28 @@
(error (string "Cannot call " (type f) " as a function"))))
(error (string "Cannot call " (type f) " as a function"))))
(defn- sq-symbol
"Resolve a symbol inside syntax-quote. `foo#` becomes a stable auto-gensym
(per-expansion, via gsmap); special forms and clojure.core names are left
unqualified (they resolve via the core fallback); other symbols are qualified
to the current namespace so they resolve when the macro is used elsewhere."
[ctx form gsmap]
(if (nil? (form :ns))
(let [nm (form :name)]
(cond
(string/has-suffix? "#" nm)
(or (get gsmap nm)
(let [g {:jolt/type :symbol :ns nil
:name (string (string/slice nm 0 -2) "__" (string (gensym)) "__auto")}]
(put gsmap nm g) g))
(special-symbol? nm) form
(ns-find (ctx-find-ns ctx "clojure.core") nm) form
{:jolt/type :symbol :ns (ctx-current-ns ctx) :name nm}))
form))
(defn- syntax-quote*
[ctx bindings form]
[ctx bindings form &opt gsmap]
(default gsmap @{})
(cond
(and (array? form) (> (length form) 0) (sym-name? (first form) "unquote"))
(eval-form ctx bindings (in form 1))
@ -89,29 +109,26 @@
(or (number? form) (string? form) (keyword? form) (nil? form) (= true form) (= false form))
form
(and (struct? form) (= :symbol (form :jolt/type)))
(if (nil? (form :ns))
(if (special-symbol? (form :name)) form
{:jolt/type :symbol :ns (ctx-current-ns ctx) :name (form :name)})
form)
(sq-symbol ctx form gsmap)
(tuple? form)
(do (var result @[]) (var i 0) (while (< i (length form))
(let [item (in form i)]
(if (and (array? item) (> (length item) 0) (sym-name? (first item) "unquote-splicing"))
(each v (eval-form ctx bindings (in item 1)) (array/push result v))
(array/push result (syntax-quote* ctx bindings item))))
(array/push result (syntax-quote* ctx bindings item gsmap))))
(++ i)) (tuple ;result))
(array? form)
(do (var result @[]) (var i 0) (while (< i (length form))
(let [item (in form i)]
(if (and (array? item) (> (length item) 0) (sym-name? (first item) "unquote-splicing"))
(each v (eval-form ctx bindings (in item 1)) (array/push result v))
(array/push result (syntax-quote* ctx bindings item))))
(array/push result (syntax-quote* ctx bindings item gsmap))))
(++ i)) result)
(and (struct? form) (get form :jolt/type)) form
(struct? form)
(do (var kvs @[]) (each k (keys form)
(array/push kvs (syntax-quote* ctx bindings k))
(array/push kvs (syntax-quote* ctx bindings (get form k)))) (struct ;kvs))
(array/push kvs (syntax-quote* ctx bindings k gsmap))
(array/push kvs (syntax-quote* ctx bindings (get form k) gsmap))) (struct ;kvs))
form))
(defn resolve-var
@ -1097,8 +1114,19 @@
(error (string "Cannot call non-function " field-name " on " (type target)))))
(error (string "Cannot call non-function " field-name " on " (type target))))))
(error (string "Cannot call method " field-name " on " (type target))))))
# field access: (. obj field) — works on tables, structs, and deftypes
(get target (keyword field-name))))
# (. obj member) with no extra args: a symbol member naming a
# function is a zero-arg method call (receiver passed as self);
# a keyword or `-field` member is plain field access.
(let [v (get target (keyword field-name))]
(if (and (struct? member-raw) (= :symbol (member-raw :jolt/type))
(not (string/has-prefix? "-" member-name)))
(cond
(or (function? v) (cfunction? v)) (v target)
# value stored as an unevaluated fn* form: compile then call
(array? v) (let [f (eval-form ctx bindings v)]
(if (or (function? f) (cfunction? f)) (f target) f))
v)
v))))
# default: function application — check for macros
(if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
(let [sym-name (first-form :name)]

View file

@ -163,6 +163,18 @@
(write-value v buf)
(print (string buf)))
(defn- err-message [err]
(cond
(string? err) err
(and (or (table? err) (struct? err)) (= :jolt/exception (get err :jolt/type)))
(let [v (get err :value)] (if (string? v) v (string/format "%q" v)))
(string/format "%q" err)))
(defn- report-error [err fib]
(eprint "Error: " (err-message err))
# Janet-level stack trace of where evaluation failed
(when fib (debug/stacktrace fib "")))
(defn- run-repl []
(print "Jolt — Clojure on Janet")
(print "Type (exit) to quit.\n")
@ -188,7 +200,7 @@
(set pending "")
(try
(print-value (eval-string ctx input))
([err] (eprint "Error: " err))))))))))))
([err fib] (report-error err fib))))))))))))
(defn- set-command-line-args [argv]
# bind clojure.core/*command-line-args* to a vector of the remaining args
@ -203,14 +215,14 @@
(let [src (slurp path)]
(try
(load-string ctx src)
([err] (eprint "Error: " err) (os/exit 1))))))
([err fib] (report-error err fib) (os/exit 1))))))
(defn- run-eval [expr argv]
(set-command-line-args argv)
(try
(let [v (load-string ctx expr)]
(when (not (nil? v)) (print-value v)))
([err] (eprint "Error: " err) (os/exit 1))))
([err fib] (report-error err fib) (os/exit 1))))
(defn- print-help []
(print "Jolt — a Clojure interpreter on Janet\n")