diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index 5191e542..9aaa86a8 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -1129,6 +1129,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = { PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; }; PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; }; PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; }; + PRIMITIVES["adt?"] = function(x) { return x !== null && typeof x === "object" && x._adtv === true; }; PRIMITIVES["component-affinity"] = componentAffinity; ''', @@ -1933,12 +1934,30 @@ PLATFORM_JS_PRE = ''' if (x._regexp) return "regexp"; if (x._bytevector) return "bytevector"; if (x._rational) return "rational"; + if (x._adtv) return x._type; if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; if (Array.isArray(x)) return "list"; if (typeof x === "object") return "dict"; return "unknown"; } + // AdtValue — native algebraic data type instance (Step 6 mirror of OCaml Step 5). + // Constructed by define-type. Carries _adt:true plus _adtv:true tag so type-of + // returns the type name rather than "dict". dict? remains true (shim approach) + // so spec-level match-pattern in evaluator.sx works without changes. + function makeAdtValue(typeName, ctorName, fields) { + return { + _adtv: true, + _adt: true, + _type: typeName, + _ctor: ctorName, + _fields: fields + }; + } + function isAdtValue(x) { + return x !== null && typeof x === "object" && x._adtv === true; + } + function symbolName(s) { return s.name; } function keywordName(k) { return k.name; } function makeSymbol(n) { return new Symbol(n); } @@ -2126,7 +2145,16 @@ PLATFORM_JS_PRE = ''' } function error(msg) { throw new Error(msg); } - function inspect(x) { return JSON.stringify(x); } + function inspect(x) { + if (x !== null && typeof x === "object" && x._adtv === true) { + var fs = x._fields || []; + if (fs.length === 0) return "(" + x._ctor + ")"; + var parts = []; + for (var i = 0; i < fs.length; i++) parts.push(inspect(fs[i])); + return "(" + x._ctor + " " + parts.join(" ") + ")"; + } + return JSON.stringify(x); + } function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); } ''' @@ -2450,6 +2478,7 @@ CEK_FIXUPS_JS = ''' // Platform functions — defined in platform_js.py, not in .sx spec files. // Spec defines self-register via js-emit-define; these are the platform interface. PRIMITIVES["type-of"] = typeOf; + PRIMITIVES["inspect"] = inspect; PRIMITIVES["symbol-name"] = symbolName; PRIMITIVES["keyword-name"] = keywordName; PRIMITIVES["callable?"] = isCallable; @@ -4103,7 +4132,56 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_ function clearStores() { _storeRegistry = {}; return NIL; } PRIMITIVES["def-store"] = defStore; PRIMITIVES["use-store"] = useStore; - PRIMITIVES["clear-stores"] = clearStores;'''] + PRIMITIVES["clear-stores"] = clearStores; + + // ----------------------------------------------------------------------- + // define-type override — produces native AdtValue instances (Step 6). + // The transpiled sfDefineType from evaluator.sx creates plain dict + // instances. We override here to construct AdtValue via makeAdtValue so + // type-of returns the type name and adt? can distinguish from dicts. + // dict? still returns true for AdtValue (shim) so spec-level match-pattern + // continues to work without changes. + // ----------------------------------------------------------------------- + var _sfDefineTypeAdt = function(args, env) { + var typeSym = first(args); + var ctorSpecs = rest(args); + var typeName = symbolName(typeSym); + var ctorNames = map(function(spec) { return symbolName(first(spec)); }, ctorSpecs); + if (!isSxTruthy(envHas(env, "*adt-registry*"))) { + envBind(env, "*adt-registry*", {}); + } + envGet(env, "*adt-registry*")[typeName] = ctorNames; + envBind(env, typeName + "?", function(v) { return isAdtValue(v) && v._type === typeName; }); + for (var _i = 0; _i < ctorSpecs.length; _i++) { + (function(spec) { + var cn = symbolName(first(spec)); + var fieldNames = map(function(f) { return symbolName(f); }, rest(spec)); + var arity = fieldNames.length; + envBind(env, cn, function() { + var ctorArgs = Array.prototype.slice.call(arguments, 0); + if (ctorArgs.length !== arity) { + throw new Error(cn + ": expected " + arity + " args, got " + ctorArgs.length); + } + return makeAdtValue(typeName, cn, ctorArgs); + }); + envBind(env, cn + "?", function(v) { return isAdtValue(v) && v._ctor === cn; }); + for (var _j = 0; _j < fieldNames.length; _j++) { + (function(idx, fieldName) { + envBind(env, cn + "-" + fieldName, function(v) { + if (!isAdtValue(v)) throw new Error(cn + "-" + fieldName + ": not an ADT"); + if (idx >= v._fields.length) throw new Error(cn + "-" + fieldName + ": index out of bounds"); + return v._fields[idx]; + }); + })(_j, fieldNames[_j]); + } + })(ctorSpecs[_i]); + } + return NIL; + }; + PRIMITIVES["sf-define-type"] = _sfDefineTypeAdt; + registerSpecialForm("define-type", _sfDefineTypeAdt); + PRIMITIVES["make-adt-value"] = makeAdtValue; + PRIMITIVES["adt-value?"] = isAdtValue;'''] if has_deps: lines.append(''' // Platform deps functions (native JS, not transpiled — need explicit registration) diff --git a/plans/sx-improvements.md b/plans/sx-improvements.md index 3de774aa..d642f84d 100644 --- a/plans/sx-improvements.md +++ b/plans/sx-improvements.md @@ -190,7 +190,7 @@ these when operands are known numbers/lists. | 3 — tokenizer :end/:line | [x] | 023bc2d8 | | 4 — parser spans complete | [x] | b7ad5152 (subsumed by 023bc2d8) | | 5 — OCaml AdtValue + define-type + match | [x] | 1f49242a | -| 6 — JS AdtValue + define-type + match | [ ] | — | +| 6 — JS AdtValue + define-type + match | [x] | (pending) | | 7 — nested patterns | [ ] | — | | 8 — exhaustiveness warnings | [ ] | — | | 9 — parser feature registry | [ ] | — | diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index f82f9b55..1573e2ac 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -41,7 +41,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-05-01T19:10:01Z"; + var SX_VERSION = "2026-05-06T23:01:54Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -188,12 +188,30 @@ if (x._regexp) return "regexp"; if (x._bytevector) return "bytevector"; if (x._rational) return "rational"; + if (x._adtv) return x._type; if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; if (Array.isArray(x)) return "list"; if (typeof x === "object") return "dict"; return "unknown"; } + // AdtValue — native algebraic data type instance (Step 6 mirror of OCaml Step 5). + // Constructed by define-type. Carries _adt:true plus _adtv:true tag so type-of + // returns the type name rather than "dict". dict? remains true (shim approach) + // so spec-level match-pattern in evaluator.sx works without changes. + function makeAdtValue(typeName, ctorName, fields) { + return { + _adtv: true, + _adt: true, + _type: typeName, + _ctor: ctorName, + _fields: fields + }; + } + function isAdtValue(x) { + return x !== null && typeof x === "object" && x._adtv === true; + } + function symbolName(s) { return s.name; } function keywordName(k) { return k.name; } function makeSymbol(n) { return new Symbol(n); } @@ -381,7 +399,16 @@ } function error(msg) { throw new Error(msg); } - function inspect(x) { return JSON.stringify(x); } + function inspect(x) { + if (x !== null && typeof x === "object" && x._adtv === true) { + var fs = x._fields || []; + if (fs.length === 0) return "(" + x._ctor + ")"; + var parts = []; + for (var i = 0; i < fs.length; i++) parts.push(inspect(fs[i])); + return "(" + x._ctor + " " + parts.join(" ") + ")"; + } + return JSON.stringify(x); + } function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); } @@ -532,6 +559,7 @@ PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; }; PRIMITIVES["symbol?"] = function(x) { return x != null && x._sym === true; }; PRIMITIVES["keyword?"] = function(x) { return x != null && x._kw === true; }; + PRIMITIVES["adt?"] = function(x) { return x !== null && typeof x === "object" && x._adtv === true; }; PRIMITIVES["component-affinity"] = componentAffinity; @@ -4970,6 +4998,55 @@ PRIMITIVES["boot-init"] = bootInit; PRIMITIVES["use-store"] = useStore; PRIMITIVES["clear-stores"] = clearStores; + // ----------------------------------------------------------------------- + // define-type override — produces native AdtValue instances (Step 6). + // The transpiled sfDefineType from evaluator.sx creates plain dict + // instances. We override here to construct AdtValue via makeAdtValue so + // type-of returns the type name and adt? can distinguish from dicts. + // dict? still returns true for AdtValue (shim) so spec-level match-pattern + // continues to work without changes. + // ----------------------------------------------------------------------- + var _sfDefineTypeAdt = function(args, env) { + var typeSym = first(args); + var ctorSpecs = rest(args); + var typeName = symbolName(typeSym); + var ctorNames = map(function(spec) { return symbolName(first(spec)); }, ctorSpecs); + if (!isSxTruthy(envHas(env, "*adt-registry*"))) { + envBind(env, "*adt-registry*", {}); + } + envGet(env, "*adt-registry*")[typeName] = ctorNames; + envBind(env, typeName + "?", function(v) { return isAdtValue(v) && v._type === typeName; }); + for (var _i = 0; _i < ctorSpecs.length; _i++) { + (function(spec) { + var cn = symbolName(first(spec)); + var fieldNames = map(function(f) { return symbolName(f); }, rest(spec)); + var arity = fieldNames.length; + envBind(env, cn, function() { + var ctorArgs = Array.prototype.slice.call(arguments, 0); + if (ctorArgs.length !== arity) { + throw new Error(cn + ": expected " + arity + " args, got " + ctorArgs.length); + } + return makeAdtValue(typeName, cn, ctorArgs); + }); + envBind(env, cn + "?", function(v) { return isAdtValue(v) && v._ctor === cn; }); + for (var _j = 0; _j < fieldNames.length; _j++) { + (function(idx, fieldName) { + envBind(env, cn + "-" + fieldName, function(v) { + if (!isAdtValue(v)) throw new Error(cn + "-" + fieldName + ": not an ADT"); + if (idx >= v._fields.length) throw new Error(cn + "-" + fieldName + ": index out of bounds"); + return v._fields[idx]; + }); + })(_j, fieldNames[_j]); + } + })(ctorSpecs[_i]); + } + return NIL; + }; + PRIMITIVES["sf-define-type"] = _sfDefineTypeAdt; + registerSpecialForm("define-type", _sfDefineTypeAdt); + PRIMITIVES["make-adt-value"] = makeAdtValue; + PRIMITIVES["adt-value?"] = isAdtValue; + // Platform deps functions (native JS, not transpiled — need explicit registration) PRIMITIVES["component-deps"] = componentDeps; PRIMITIVES["component-set-deps!"] = componentSetDeps; @@ -5001,6 +5078,7 @@ PRIMITIVES["boot-init"] = bootInit; // Platform functions — defined in platform_js.py, not in .sx spec files. // Spec defines self-register via js-emit-define; these are the platform interface. PRIMITIVES["type-of"] = typeOf; + PRIMITIVES["inspect"] = inspect; PRIMITIVES["symbol-name"] = symbolName; PRIMITIVES["keyword-name"] = keywordName; PRIMITIVES["callable?"] = isCallable; @@ -5023,7 +5101,7 @@ PRIMITIVES["boot-init"] = bootInit; // === stdlib.sx (eval'd at runtime, not transpiled) === (function() { - var src = ";; ==========================================================================\n;; stdlib.sx — Standard library functions\n;;\n;; Every function here is expressed in SX using the irreducible primitive\n;; set. They are library functions — in band, auditable, portable.\n;;\n;; Depends on: evaluator.sx (special forms)\n;; Must load before: render.sx, freeze.sx, types.sx, user code\n;; ==========================================================================\n\n\n;; Logic + comparison: not, !=, <=, >= stay as primitives.\n;; Replacing them with SX lambdas changes behavior inside shift/reset\n;; because the transpiled evaluator code uses them directly.\n\n\n(define-library (sx stdlib)\n (export\n eq?\n eqv?\n equal?\n boolean?\n number?\n string?\n list?\n dict?\n continuation?\n zero?\n odd?\n even?\n empty?\n abs\n ceil\n round\n min\n max\n clamp\n first\n last\n rest\n nth\n cons\n append\n reverse\n flatten\n range\n chunk-every\n zip-pairs\n vals\n has-key?\n assoc\n dissoc\n into\n upcase\n downcase\n string-length\n substring\n string-contains?\n starts-with?\n ends-with?\n contains?\n pluralize\n escape\n parse-datetime\n assert)\n (begin\n\n(define eq? (fn (a b) (= a b)))\n(define eqv? (fn (a b) (= a b)))\n(define equal? (fn (a b) (= a b)))\n\n\n;; --------------------------------------------------------------------------\n;; Type predicates\n;; --------------------------------------------------------------------------\n\n;; nil? stays as primitive — host's type-of uses it internally.\n\n(define boolean?\n (fn (x) (= (type-of x) \"boolean\")))\n\n(define number?\n (fn (x) (= (type-of x) \"number\")))\n\n(define string?\n (fn (x) (= (type-of x) \"string\")))\n\n(define list?\n (fn (x) (= (type-of x) \"list\")))\n\n(define dict?\n (fn (x) (= (type-of x) \"dict\")))\n\n(define continuation?\n (fn (x) (= (type-of x) \"continuation\")))\n\n(define zero?\n (fn (n) (= n 0)))\n\n(define odd?\n (fn (n) (= (mod n 2) 1)))\n\n(define even?\n (fn (n) (= (mod n 2) 0)))\n\n(define empty?\n (fn (coll) (or (nil? coll) (= (len coll) 0))))\n\n\n;; --------------------------------------------------------------------------\n;; Arithmetic\n;; --------------------------------------------------------------------------\n\n;; inc and dec stay as primitives — used inside continuation contexts.\n\n(define abs\n (fn (x) (if (< x 0) (- x) x)))\n\n(define ceil\n (fn (x)\n (let ((f (floor x)))\n (if (= x f) f (+ f 1)))))\n\n(define round\n (fn (x ndigits)\n (if (nil? ndigits)\n (floor (+ x 0.5))\n (let ((f (pow 10 ndigits)))\n (/ (floor (+ (* x f) 0.5)) f)))))\n\n(define min\n (fn (a b) (if (< a b) a b)))\n\n(define max\n (fn (a b) (if (> a b) a b)))\n\n(define clamp\n (fn (x lo hi) (max lo (min hi x))))\n\n\n;; --------------------------------------------------------------------------\n;; Collection accessors\n;; --------------------------------------------------------------------------\n\n(define first\n (fn (coll)\n (if (and coll (> (len coll) 0)) (get coll 0) nil)))\n\n(define last\n (fn (coll)\n (if (and coll (> (len coll) 0))\n (get coll (- (len coll) 1))\n nil)))\n\n(define rest\n (fn (coll) (if coll (slice coll 1) (list))))\n\n(define nth\n (fn (coll n)\n (if (and coll (>= n 0) (< n (len coll)))\n (get coll n)\n nil)))\n\n(define cons\n (fn (x coll) (concat (list x) (or coll (list)))))\n\n(define append\n (fn (coll x)\n (if (list? x) (concat coll x) (concat coll (list x)))))\n\n\n;; --------------------------------------------------------------------------\n;; Collection transforms\n;; --------------------------------------------------------------------------\n\n(define reverse\n (fn (coll)\n (reduce (fn (acc x) (cons x acc)) (list) coll)))\n\n(define flatten\n (fn (coll)\n (reduce\n (fn (acc x)\n (if (list? x) (concat acc x) (concat acc (list x))))\n (list) coll)))\n\n(define range\n (fn (start end step)\n (let ((s (if (nil? step) 1 step))\n (result (list)))\n (let loop ((i start))\n (when (< i end)\n (append! result i)\n (loop (+ i s))))\n result)))\n\n(define chunk-every\n (fn (coll n)\n (let ((result (list))\n (clen (len coll)))\n (let loop ((i 0))\n (when (< i clen)\n (append! result (slice coll i (min (+ i n) clen)))\n (loop (+ i n))))\n result)))\n\n(define zip-pairs\n (fn (coll)\n (let ((result (list))\n (clen (len coll)))\n (let loop ((i 0))\n (when (< i (- clen 1))\n (append! result (list (get coll i) (get coll (+ i 1))))\n (loop (+ i 1))))\n result)))\n\n\n;; --------------------------------------------------------------------------\n;; Dict operations\n;; --------------------------------------------------------------------------\n\n(define vals\n (fn (d)\n (map (fn (k) (get d k)) (keys d))))\n\n(define has-key?\n (fn (d key)\n (some (fn (k) (= k key)) (keys d))))\n\n(define assoc\n (fn (d key val)\n (let ((result (merge d (dict))))\n (dict-set! result key val)\n result)))\n\n(define dissoc\n (fn (d key)\n (let ((result (dict)))\n (for-each\n (fn (k)\n (when (!= k key)\n (dict-set! result k (get d k))))\n (keys d))\n result)))\n\n(define into\n (fn (target coll)\n (cond\n (list? target)\n (if (list? coll)\n (concat coll (list))\n (let ((result (list)))\n (for-each (fn (k) (append! result (list k (get coll k)))) (keys coll))\n result))\n (dict? target)\n (let ((result (dict)))\n (for-each\n (fn (pair)\n (when (and (list? pair) (>= (len pair) 2))\n (dict-set! result (get pair 0) (get pair 1))))\n coll)\n result)\n :else target)))\n\n\n;; --------------------------------------------------------------------------\n;; String operations\n;; --------------------------------------------------------------------------\n\n(define upcase (fn (s) (upper s)))\n(define downcase (fn (s) (lower s)))\n(define string-length (fn (s) (len s)))\n(define substring (fn (s start end) (slice s start end)))\n\n(define string-contains?\n (fn (s needle) (!= (index-of s needle) -1)))\n\n(define starts-with?\n (fn (s prefix) (= (index-of s prefix) 0)))\n\n(define ends-with?\n (fn (s suffix)\n (let ((slen (len s))\n (plen (len suffix)))\n (if (< slen plen) false\n (= (slice s (- slen plen)) suffix)))))\n\n;; split, join, replace stay as primitives — the stdlib versions cause\n;; stack overflows due to PRIMITIVES entry shadowing in the transpiled output.\n\n(define contains?\n (fn (coll key)\n (cond\n (string? coll) (!= (index-of coll (str key)) -1)\n (dict? coll) (has-key? coll key)\n (list? coll) (some (fn (x) (= x key)) coll)\n :else false)))\n\n\n;; --------------------------------------------------------------------------\n;; Text utilities\n;; --------------------------------------------------------------------------\n\n(define pluralize\n (fn (count singular plural)\n (if (= count 1)\n (or singular \"\")\n (or plural \"s\"))))\n\n(define escape\n (fn (s)\n (let ((r (str s)))\n (set! r (replace r \"&\" \"&\"))\n (set! r (replace r \"<\" \"<\"))\n (set! r (replace r \">\" \">\"))\n (set! r (replace r \"\\\"\" \""\"))\n (set! r (replace r \"'\" \"'\"))\n r)))\n\n(define parse-datetime\n (fn (s) (if s (str s) nil)))\n\n(define assert\n (fn (condition message)\n (when (not condition)\n (error (or message \"Assertion failed\")))\n true))\n\n\n)) ;; end define-library\n\n;; Re-export to global namespace for backward compatibility\n(import (sx stdlib))\n"; + var src = ";; ==========================================================================\n;; stdlib.sx — Pure SX standard library functions\n;;\n;; Loaded by test runners after primitives. These functions are implemented\n;; in SX and require no host-specific code.\n;;\n;; IMPORTANT: SX let/when bodies evaluate only the LAST expression.\n;; Multi-step bodies must be wrapped in (do expr1 expr2 ...).\n;; ==========================================================================\n\n;; --------------------------------------------------------------------------\n;; format — CL-style string formatting\n;;\n;; Directives:\n;; ~a display (no quotes) ~s write (with quotes)\n;; ~d decimal ~x hex ~o octal ~b binary\n;; ~f fixed-point (6dp) ~% newline\n;; ~& fresh line ~~ literal tilde\n;; ~t tab\n;;\n;; Signature: (format template arg...) -> string\n;; --------------------------------------------------------------------------\n\n(define\n (format template &rest args)\n (let\n ((buf (make-string-buffer)) (n (string-length template)))\n (define\n (consume-arg args)\n (if\n (nil? args)\n (list \"\" nil)\n (list (display-to-string (first args)) (rest args))))\n (define\n (consume-num args radix)\n (if\n (nil? args)\n (list \"\" nil)\n (list (number->string (first args) radix) (rest args))))\n (define\n (loop i args)\n (cond\n ((>= i n) (string-buffer->string buf))\n ((and (= (substring template i (+ i 1)) \"~\") (< (+ i 1) n))\n (let\n ((dir (substring template (+ i 1) (+ i 2))))\n (cond\n ((= dir \"a\")\n (let\n ((p (consume-arg args)))\n (do\n (string-buffer-append! buf (first p))\n (loop (+ i 2) (first (rest p))))))\n ((= dir \"s\")\n (if\n (nil? args)\n (loop (+ i 2) args)\n (do\n (string-buffer-append!\n buf\n (write-to-string (first args)))\n (loop (+ i 2) (rest args)))))\n ((= dir \"d\")\n (let\n ((p (consume-num args 10)))\n (do\n (string-buffer-append! buf (first p))\n (loop (+ i 2) (first (rest p))))))\n ((= dir \"x\")\n (let\n ((p (consume-num args 16)))\n (do\n (string-buffer-append! buf (first p))\n (loop (+ i 2) (first (rest p))))))\n ((= dir \"o\")\n (let\n ((p (consume-num args 8)))\n (do\n (string-buffer-append! buf (first p))\n (loop (+ i 2) (first (rest p))))))\n ((= dir \"b\")\n (let\n ((p (consume-num args 2)))\n (do\n (string-buffer-append! buf (first p))\n (loop (+ i 2) (first (rest p))))))\n ((= dir \"f\")\n (if\n (nil? args)\n (loop (+ i 2) args)\n (do\n (string-buffer-append!\n buf\n (format-decimal (first args) 6))\n (loop (+ i 2) (rest args)))))\n ((= dir \"%\")\n (do\n (string-buffer-append! buf \"\\n\")\n (loop (+ i 2) args)))\n ((= dir \"&\")\n (do\n (let\n ((so-far (string-buffer->string buf)))\n (when\n (or\n (= (string-length so-far) 0)\n (not\n (=\n (substring\n so-far\n (- (string-length so-far) 1)\n (string-length so-far))\n \"\\n\")))\n (string-buffer-append! buf \"\\n\")))\n (loop (+ i 2) args)))\n ((= dir \"~\")\n (do\n (string-buffer-append! buf \"~\")\n (loop (+ i 2) args)))\n ((= dir \"t\")\n (do\n (string-buffer-append! buf \"\\t\")\n (loop (+ i 2) args)))\n (else\n (do\n (string-buffer-append! buf \"~\")\n (loop (+ i 1) args))))))\n (else\n (do\n (string-buffer-append!\n buf\n (substring template i (+ i 1)))\n (loop (+ i 1) args)))))\n (loop 0 args)))\n"; var forms = sxParse(src); var tmpEnv = merge({}, PRIMITIVES); for (var i = 0; i < forms.length; i++) { @@ -5216,8 +5294,8 @@ PRIMITIVES["boot-init"] = bootInit; // If lambda takes 0 params, call without event arg (convenience for on-click handlers) var wrapped = isLambda(handler) ? (lambdaParams(handler).length === 0 - ? function(e) { try { cekCall(handler, NIL); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } - : function(e) { try { cekCall(handler, [e]); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) + ? function(e) { try { var r = cekCall(handler, NIL); if (globalThis._driveAsync) globalThis._driveAsync(r); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } } + : function(e) { try { var r = cekCall(handler, [e]); if (globalThis._driveAsync) globalThis._driveAsync(r); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }) : handler; if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler)); var passiveEvents = { touchstart: 1, touchmove: 1, wheel: 1, scroll: 1 };