diff --git a/foo.clj b/clojure-features.clj similarity index 86% rename from foo.clj rename to clojure-features.clj index 1fbbf87..793cdf5 100644 --- a/foo.clj +++ b/clojure-features.clj @@ -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 diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 3427d07..5e08bc7 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -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 diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index ba8f086..01d1235 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -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)] diff --git a/src/jolt/main.janet b/src/jolt/main.janet index 6e8a91f..4be0dc5 100644 --- a/src/jolt/main.janet +++ b/src/jolt/main.janet @@ -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")