From 80aae55977c0e4afead94900623fba1ab9895d59 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Mon, 15 Jun 2026 10:27:34 -0400 Subject: [PATCH] Evaluate non-array ISeq forms (cons/concat/lazy-seq) as calls (jolt-2rx) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eval-form treated only a reader LIST (a Janet array) as a call; a runtime-built list — a plist or lazy-seq from cons/concat/list or ~@ (list?/seq? true but array? false) — fell through to self-eval. So (eval (cons '+ '(1 2))) returned the list as data instead of 3, and a macro whose output contained such a subform left it unevaluated. Add a plist?/lazy-seq? branch that coerces to the element array via d-realize and dispatches through eval-list; an empty list self-evals. The analyzer already punts these forms to the interpreter (analyze's :else -> uncompilable -> interpreter fallback), so this one interpreter branch fixes the correctness bug across the eval and macro-expansion paths; compiling them directly (vs punting) would be a separate perf change. Verified: conformance 355/355, syntax-quote ~@ splice, list values unchanged. --- src/jolt/evaluator.janet | 8 ++++++++ test/spec/iseq-call-spec.janet | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 test/spec/iseq-call-spec.janet diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 62763cd..6ebf868 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -98,4 +98,12 @@ (if (= 0 (length form)) @[] (eval-list ctx bindings form)) + # A non-array ISeq used as a form is a CALL too (jolt-2rx): cons/concat/list + # and ~@ build a plist or lazy-seq (list?/seq? true, array? false) — without + # this they fell through to self-eval, so (eval (cons '+ '(1 2))) returned the + # list as data instead of 3, and macro output containing such subforms never + # evaluated. d-realize coerces to the element array; an empty list self-evals. + (or (plist? form) (lazy-seq? form)) + (let [arr (d-realize form)] + (if (= 0 (length arr)) form (eval-list ctx bindings arr))) form))) diff --git a/test/spec/iseq-call-spec.janet b/test/spec/iseq-call-spec.janet new file mode 100644 index 0000000..b1aeaa4 --- /dev/null +++ b/test/spec/iseq-call-spec.janet @@ -0,0 +1,19 @@ +# Specification: a non-array ISeq (plist/lazy-seq, e.g. from cons/concat/list or +# ~@) used as a FORM is evaluated as a call, not returned as self-evaluating data +# (jolt-2rx). The interpreter only treated a reader LIST (Janet array) as a call; +# a runtime-built list (a plist/lazy-seq table) fell through to self-eval, so +# (eval (cons '+ '(1 2))) returned the list instead of 3. The analyzer already +# punts such forms to the interpreter, so the fix lives in eval-form. +(use ../support/harness) + +(defspec "ISeq call forms (jolt-2rx)" + ["eval a cons'd call" "3" "(eval (cons (quote +) (quote (1 2))))"] + ["eval a list-built call" "6" "(eval (list (quote +) 1 2 3))"] + ["eval a concat'd call" "10" "(eval (concat (list (quote +)) (list 1 2 3 4)))"] + ["nested cons'd subform" "7" "(eval (list (quote +) 3 (cons (quote +) (quote (1 3)))))"] + ["empty list self-evals" "()" "(eval (list))"] + ["macro output via cons" "3" "(do (defmacro mc [] (cons (quote +) (quote (1 2)))) (mc))"] + ["macro output via concat" "6" "(do (defmacro mk [] (concat (list (quote +)) (list 1 2 3))) (mk))"] + # regressions: vectors/quoted-data are NOT calls + ["vector value self-evals" "[1 2 3]" "(eval (vec [1 2 3]))"] + ["quoted list of data" "(quote (1 2 3))" "(quote (1 2 3))"])