js-on-sx: regex literal lex+parse+transpile+runtime stub

Lexer: js-regex-context? disambiguates / based on prior token;
read-regex handles [...] classes and \ escapes. Emits
{:type "regex" :value {:pattern :flags}}.

Parser: new primary branch → (js-regex pat flags).

Transpile: (js-regex-new pat flags).

Runtime: js-regex? predicate, js-regex-new builds tagged dict with
source/flags/global/ignoreCase/multiline/sticky/unicode/dotAll/
hasIndices/lastIndex. js-regex-invoke-method dispatches .test/.exec/
.toString. js-invoke-method detects regex receivers. Stub engine
uses js-string-index-of; __js_regex_platform__ + override! let a
real engine plug in later.

Runner: repeatable --filter flags (OR'd).

308/310 unit (+30 regex tests), 148/148 slice unchanged.
This commit is contained in:
2026-04-23 20:27:19 +00:00
parent 6b0334affe
commit ce46420c2e
7 changed files with 337 additions and 4 deletions

View File

@@ -91,6 +91,7 @@
(cond
((and (js-promise? recv) (js-promise-builtin-method? key))
(js-invoke-promise-method recv key args))
((js-regex? recv) (js-regex-invoke-method recv key args))
(else
(let
((m (js-get-prop recv key)))
@@ -1593,4 +1594,96 @@
(define __drain (fn () (js-drain-microtasks!) :js-undefined))
(define __js_regex_platform__ (dict))
(define
js-regex-platform-override!
(fn (op impl) (dict-set! __js_regex_platform__ op impl)))
(define
js-regex?
(fn (v) (and (dict? v) (contains? (keys v) "__js_regex__"))))
(define
js-regex-has-flag?
(fn (flags ch) (>= (js-string-index-of flags ch 0) 0)))
(define
js-regex-new
(fn
(pattern flags)
(let
((rx (dict))
(fl (if (js-undefined? flags) "" (if (= flags nil) "" flags))))
(dict-set! rx "__js_regex__" true)
(dict-set! rx "source" pattern)
(dict-set! rx "flags" fl)
(dict-set! rx "global" (js-regex-has-flag? fl "g"))
(dict-set! rx "ignoreCase" (js-regex-has-flag? fl "i"))
(dict-set! rx "multiline" (js-regex-has-flag? fl "m"))
(dict-set! rx "sticky" (js-regex-has-flag? fl "y"))
(dict-set! rx "unicode" (js-regex-has-flag? fl "u"))
(dict-set! rx "dotAll" (js-regex-has-flag? fl "s"))
(dict-set! rx "hasIndices" (js-regex-has-flag? fl "d"))
(dict-set! rx "lastIndex" 0)
rx)))
(define
js-regex-stub-test
(fn
(rx s)
(let
((src (get rx "source")) (ci (get rx "ignoreCase")))
(let
((hay (if ci (js-lower-case s) s))
(needle (if ci (js-lower-case src) src)))
(>= (js-string-index-of hay needle 0) 0)))))
(define
js-regex-stub-exec
(fn
(rx s)
(let
((src (get rx "source")) (ci (get rx "ignoreCase")))
(let
((hay (if ci (js-lower-case s) s))
(needle (if ci (js-lower-case src) src)))
(let
((idx (js-string-index-of hay needle 0)))
(if
(= idx -1)
nil
(let
((matched (js-string-slice s idx (+ idx (len src))))
(res (list)))
(append! res matched)
(dict-set! res "index" idx)
(dict-set! res "input" s)
res)))))))
(define
js-regex-invoke-method
(fn
(rx name args)
(cond
((= name "test")
(let
((impl (get __js_regex_platform__ "test"))
(arg (if (= (len args) 0) "" (js-to-string (nth args 0)))))
(if
(js-undefined? impl)
(js-regex-stub-test rx arg)
(impl rx arg))))
((= name "exec")
(let
((impl (get __js_regex_platform__ "exec"))
(arg (if (= (len args) 0) "" (js-to-string (nth args 0)))))
(if
(js-undefined? impl)
(js-regex-stub-exec rx arg)
(impl rx arg))))
((= name "toString")
(str "/" (get rx "source") "/" (get rx "flags")))
(else js-undefined))))
(define js-global {:console console :Math Math :NaN 0 :Infinity (/ 1 0) :undefined js-undefined})