From fee62a20f04d52f78ad3f10382899e2aa5fb9d65 Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 4 May 2026 19:19:54 +0000 Subject: [PATCH] HS: parse-feat paren-open adds string-postfix check (+1 test) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse-feat's paren-open handler stripped the grouping parens and returned the inner feature, leaving any trailing ident (like `em`) as a separate top-level feature. After consuming the closing paren, now checks if the next token is a non-keyword ident or `%` op and wraps as (string-postfix inner unit), making `(0 + 1) em` → "1em". Co-Authored-By: Claude Sonnet 4.6 --- lib/hyperscript/parser.sx | 20 ++++++++++++++++++-- shared/static/wasm/sx/hs-parser.sx | 20 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index d1413bbd..62a6434c 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -987,7 +987,7 @@ (do (when (and - (number? left) + (or (number? left) (list? left)) (= (tp-type) "ident") (not (or @@ -3113,7 +3113,23 @@ (let ((inner (parse-feat))) (if (= (tp-type) "paren-close") (adv!) nil) - inner))) + (if + (and + inner + (or + (and + (= (tp-type) "ident") + (not + (or + (= (tp-val) "then") + (= (tp-val) "end") + (= (tp-val) "else") + (= (tp-val) "otherwise")))) + (and (= (tp-type) "op") (= (tp-val) "%")))) + (let + ((unit (tp-val))) + (do (adv!) (list (quote string-postfix) inner unit))) + inner)))) ((= val "on") (do (adv!) (parse-on-feat))) ((= val "init") (do (adv!) (parse-init-feat))) ((= val "def") (do (adv!) (parse-def-feat))) diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index d1413bbd..62a6434c 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -987,7 +987,7 @@ (do (when (and - (number? left) + (or (number? left) (list? left)) (= (tp-type) "ident") (not (or @@ -3113,7 +3113,23 @@ (let ((inner (parse-feat))) (if (= (tp-type) "paren-close") (adv!) nil) - inner))) + (if + (and + inner + (or + (and + (= (tp-type) "ident") + (not + (or + (= (tp-val) "then") + (= (tp-val) "end") + (= (tp-val) "else") + (= (tp-val) "otherwise")))) + (and (= (tp-type) "op") (= (tp-val) "%")))) + (let + ((unit (tp-val))) + (do (adv!) (list (quote string-postfix) inner unit))) + inner)))) ((= val "on") (do (adv!) (parse-on-feat))) ((= val "init") (do (adv!) (parse-init-feat))) ((= val "def") (do (adv!) (parse-def-feat)))