lib/hyperscript/tokenizer.sx — tokenizes real _hyperscript syntax into typed token stream. Handles: Keywords (on, set, add, toggle, if, then, from, etc.) DOM literals (.class, #id, @attr, *style, :local, <sel/>) Strings (single/double quoted, escapes), template literals Numbers (integers, decimals, time units: 100ms, 2s) Operators (==, !=, +, -, 's possessive) Punctuation (parens, brackets, braces, commas, dots) Line comments (// to EOL) Parser will disambiguate .name as class vs property access from context. Possessive 's correctly distinguished from single-quote strings. 2952/2952 tests, zero failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
314 lines
11 KiB
Plaintext
314 lines
11 KiB
Plaintext
;; _hyperscript tokenizer tests
|
|
|
|
;; helper: get token types as a flat list
|
|
(define hs-types (fn (tokens) (map (fn (t) (get t "type")) tokens)))
|
|
(define hs-vals (fn (tokens) (map (fn (t) (get t "value")) tokens)))
|
|
(define hs-tok (fn (tokens n) (nth tokens n)))
|
|
|
|
(defsuite
|
|
"hs-tokenize-basics"
|
|
(deftest
|
|
"empty input"
|
|
(let
|
|
((tokens (hs-tokenize "")))
|
|
(assert= 1 (len tokens))
|
|
(assert= "eof" (get (first tokens) "type"))))
|
|
(deftest
|
|
"single keyword"
|
|
(let
|
|
((tokens (hs-tokenize "on")))
|
|
(assert= 2 (len tokens))
|
|
(assert= "keyword" (get (first tokens) "type"))
|
|
(assert= "on" (get (first tokens) "value"))))
|
|
(deftest
|
|
"identifier"
|
|
(let
|
|
((tokens (hs-tokenize "myVar")))
|
|
(assert= "ident" (get (first tokens) "type"))
|
|
(assert= "myVar" (get (first tokens) "value"))))
|
|
(deftest
|
|
"keywords vs identifiers"
|
|
(let
|
|
((tokens (hs-tokenize "on click add foo")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "ident" (get (hs-tok tokens 1) "type"))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "ident" (get (hs-tok tokens 3) "type"))))
|
|
(deftest
|
|
"whitespace skipped"
|
|
(let
|
|
((tokens (hs-tokenize " on click ")))
|
|
(assert= 3 (len tokens))
|
|
(assert= "on" (get (first tokens) "value")))))
|
|
|
|
(defsuite
|
|
"hs-tokenize-literals"
|
|
(deftest
|
|
"integer"
|
|
(let
|
|
((t (first (hs-tokenize "42"))))
|
|
(assert= "number" (get t "type"))
|
|
(assert= "42" (get t "value"))))
|
|
(deftest
|
|
"decimal"
|
|
(let
|
|
((t (first (hs-tokenize "3.14"))))
|
|
(assert= "number" (get t "type"))
|
|
(assert= "3.14" (get t "value"))))
|
|
(deftest
|
|
"number with ms unit"
|
|
(let
|
|
((t (first (hs-tokenize "100ms"))))
|
|
(assert= "number" (get t "type"))
|
|
(assert= "100ms" (get t "value"))))
|
|
(deftest
|
|
"number with s unit"
|
|
(let
|
|
((t (first (hs-tokenize "2s"))))
|
|
(assert= "number" (get t "type"))
|
|
(assert= "2s" (get t "value"))))
|
|
(deftest
|
|
"double-quoted string"
|
|
(let
|
|
((t (first (hs-tokenize "\"hello world\""))))
|
|
(assert= "string" (get t "type"))
|
|
(assert= "hello world" (get t "value"))))
|
|
(deftest
|
|
"single-quoted string"
|
|
(let
|
|
((t (first (hs-tokenize "'foo'"))))
|
|
(assert= "string" (get t "type"))
|
|
(assert= "foo" (get t "value"))))
|
|
(deftest
|
|
"string with escapes"
|
|
(let
|
|
((t (first (hs-tokenize "\"a\\nb\""))))
|
|
(assert= "string" (get t "type"))
|
|
(assert= "a\nb" (get t "value"))))
|
|
(deftest
|
|
"template literal"
|
|
(let
|
|
((t (first (hs-tokenize "`hello ${name}`"))))
|
|
(assert= "template" (get t "type"))
|
|
(assert= "hello ${name}" (get t "value")))))
|
|
|
|
(defsuite
|
|
"hs-tokenize-dom-refs"
|
|
(deftest
|
|
"class literal"
|
|
(let
|
|
((t (first (hs-tokenize ".foo"))))
|
|
(assert= "class" (get t "type"))
|
|
(assert= "foo" (get t "value"))))
|
|
(deftest
|
|
"class with dashes"
|
|
(let
|
|
((t (first (hs-tokenize ".foo--bar"))))
|
|
(assert= "class" (get t "type"))
|
|
(assert= "foo--bar" (get t "value"))))
|
|
(deftest
|
|
"id literal"
|
|
(let
|
|
((t (first (hs-tokenize "#bar"))))
|
|
(assert= "id" (get t "type"))
|
|
(assert= "bar" (get t "value"))))
|
|
(deftest
|
|
"attribute ref"
|
|
(let
|
|
((t (first (hs-tokenize "@foo"))))
|
|
(assert= "attr" (get t "type"))
|
|
(assert= "foo" (get t "value"))))
|
|
(deftest
|
|
"style ref"
|
|
(let
|
|
((t (first (hs-tokenize "*color"))))
|
|
(assert= "style" (get t "type"))
|
|
(assert= "color" (get t "value"))))
|
|
(deftest
|
|
"element-scoped local"
|
|
(let
|
|
((t (first (hs-tokenize ":myVar"))))
|
|
(assert= "local" (get t "type"))
|
|
(assert= "myVar" (get t "value"))))
|
|
(deftest
|
|
"CSS selector"
|
|
(let
|
|
((t (first (hs-tokenize "<p/>"))))
|
|
(assert= "selector" (get t "type"))
|
|
(assert= "p" (get t "value"))))
|
|
(deftest
|
|
"CSS selector complex"
|
|
(let
|
|
((t (first (hs-tokenize "<div.foo/>"))))
|
|
(assert= "selector" (get t "type"))
|
|
(assert= "div.foo" (get t "value")))))
|
|
|
|
(defsuite
|
|
"hs-tokenize-operators"
|
|
(deftest
|
|
"equals"
|
|
(let
|
|
((t (first (hs-tokenize "=="))))
|
|
(assert= "op" (get t "type"))
|
|
(assert= "==" (get t "value"))))
|
|
(deftest
|
|
"not-equals"
|
|
(let
|
|
((t (first (hs-tokenize "!="))))
|
|
(assert= "op" (get t "type"))
|
|
(assert= "!=" (get t "value"))))
|
|
(deftest
|
|
"plus"
|
|
(let
|
|
((t (first (hs-tokenize "+"))))
|
|
(assert= "op" (get t "type"))
|
|
(assert= "+" (get t "value"))))
|
|
(deftest
|
|
"possessive"
|
|
(let
|
|
((t (first (hs-tokenize "#d1's"))))
|
|
(assert= "id" (get (hs-tok (hs-tokenize "#d1's") 0) "type"))
|
|
(assert= "op" (get (hs-tok (hs-tokenize "#d1's") 1) "type"))
|
|
(assert= "'s" (get (hs-tok (hs-tokenize "#d1's") 1) "value"))))
|
|
(deftest
|
|
"parens"
|
|
(let
|
|
((tokens (hs-tokenize "(foo)")))
|
|
(assert= "paren-open" (get (hs-tok tokens 0) "type"))
|
|
(assert= "ident" (get (hs-tok tokens 1) "type"))
|
|
(assert= "paren-close" (get (hs-tok tokens 2) "type"))))
|
|
(deftest
|
|
"brackets"
|
|
(let
|
|
((tokens (hs-tokenize "[0]")))
|
|
(assert= "bracket-open" (get (hs-tok tokens 0) "type"))
|
|
(assert= "number" (get (hs-tok tokens 1) "type"))
|
|
(assert= "bracket-close" (get (hs-tok tokens 2) "type")))))
|
|
|
|
(defsuite
|
|
"hs-tokenize-comments"
|
|
(deftest
|
|
"line comment skipped"
|
|
(let
|
|
((tokens (hs-tokenize "on // this is a comment\nclick")))
|
|
(assert= 3 (len tokens))
|
|
(assert= "on" (get (hs-tok tokens 0) "value"))
|
|
(assert= "click" (get (hs-tok tokens 1) "value")))))
|
|
|
|
(defsuite
|
|
"hs-tokenize-full-expressions"
|
|
(deftest
|
|
"on click add .called"
|
|
(let
|
|
((tokens (hs-tokenize "on click add .called")))
|
|
(assert= 5 (len tokens))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "on" (get (hs-tok tokens 0) "value"))
|
|
(assert= "ident" (get (hs-tok tokens 1) "type"))
|
|
(assert= "click" (get (hs-tok tokens 1) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "add" (get (hs-tok tokens 2) "value"))
|
|
(assert= "class" (get (hs-tok tokens 3) "type"))
|
|
(assert= "called" (get (hs-tok tokens 3) "value"))
|
|
(assert= "eof" (get (hs-tok tokens 4) "type"))))
|
|
(deftest
|
|
"set #d1.innerHTML to foo"
|
|
(let
|
|
((tokens (hs-tokenize "set #d1.innerHTML to \"foo\"")))
|
|
(assert= 6 (len tokens))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "id" (get (hs-tok tokens 1) "type"))
|
|
(assert= "d1" (get (hs-tok tokens 1) "value"))
|
|
(assert= "class" (get (hs-tok tokens 2) "type"))
|
|
(assert= "innerHTML" (get (hs-tok tokens 2) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 3) "type"))
|
|
(assert= "to" (get (hs-tok tokens 3) "value"))
|
|
(assert= "string" (get (hs-tok tokens 4) "type"))))
|
|
(deftest
|
|
"put \"Clicked\" into my.innerHTML"
|
|
(let
|
|
((tokens (hs-tokenize "put \"Clicked\" into my.innerHTML")))
|
|
(assert= 6 (len tokens))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "put" (get (hs-tok tokens 0) "value"))
|
|
(assert= "string" (get (hs-tok tokens 1) "type"))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "into" (get (hs-tok tokens 2) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 3) "type"))
|
|
(assert= "my" (get (hs-tok tokens 3) "value"))))
|
|
(deftest
|
|
"on click send custom(foo:\"fromBar\") to #d2"
|
|
(let
|
|
((tokens (hs-tokenize "on click send custom(foo:\"fromBar\") to #d2")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "on" (get (hs-tok tokens 0) "value"))
|
|
(assert= "ident" (get (hs-tok tokens 1) "type"))
|
|
(assert= "click" (get (hs-tok tokens 1) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "send" (get (hs-tok tokens 2) "value"))))
|
|
(deftest
|
|
"toggle between .foo and .bar"
|
|
(let
|
|
((tokens (hs-tokenize "toggle between .foo and .bar")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "toggle" (get (hs-tok tokens 0) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 1) "type"))
|
|
(assert= "between" (get (hs-tok tokens 1) "value"))
|
|
(assert= "class" (get (hs-tok tokens 2) "type"))
|
|
(assert= "foo" (get (hs-tok tokens 2) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 3) "type"))
|
|
(assert= "and" (get (hs-tok tokens 3) "value"))
|
|
(assert= "class" (get (hs-tok tokens 4) "type"))
|
|
(assert= "bar" (get (hs-tok tokens 4) "value"))))
|
|
(deftest
|
|
"if true put \"foo\" into me.innerHTML else put \"bar\" into me.innerHTML end"
|
|
(let
|
|
((tokens (hs-tokenize "if true put \"foo\" into me.innerHTML else put \"bar\" into me.innerHTML end")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "if" (get (hs-tok tokens 0) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 1) "type"))
|
|
(assert= "true" (get (hs-tok tokens 1) "value"))))
|
|
(deftest
|
|
"on click[buttons==0] log event"
|
|
(let
|
|
((tokens (hs-tokenize "on click[buttons==0] log event")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "ident" (get (hs-tok tokens 1) "type"))
|
|
(assert= "click" (get (hs-tok tokens 1) "value"))
|
|
(assert= "bracket-open" (get (hs-tok tokens 2) "type"))
|
|
(assert= "ident" (get (hs-tok tokens 3) "type"))
|
|
(assert= "buttons" (get (hs-tok tokens 3) "value"))
|
|
(assert= "op" (get (hs-tok tokens 4) "type"))
|
|
(assert= "==" (get (hs-tok tokens 4) "value"))))
|
|
(deftest
|
|
"wait 100ms then add .done"
|
|
(let
|
|
((tokens (hs-tokenize "wait 100ms then add .done")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "wait" (get (hs-tok tokens 0) "value"))
|
|
(assert= "number" (get (hs-tok tokens 1) "type"))
|
|
(assert= "100ms" (get (hs-tok tokens 1) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "then" (get (hs-tok tokens 2) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 3) "type"))
|
|
(assert= "add" (get (hs-tok tokens 3) "value"))
|
|
(assert= "class" (get (hs-tok tokens 4) "type"))
|
|
(assert= "done" (get (hs-tok tokens 4) "value"))))
|
|
(deftest
|
|
"increment @count then put it into me"
|
|
(let
|
|
((tokens (hs-tokenize "increment @count then put it into me")))
|
|
(assert= "keyword" (get (hs-tok tokens 0) "type"))
|
|
(assert= "increment" (get (hs-tok tokens 0) "value"))
|
|
(assert= "attr" (get (hs-tok tokens 1) "type"))
|
|
(assert= "count" (get (hs-tok tokens 1) "value"))
|
|
(assert= "keyword" (get (hs-tok tokens 4) "type"))
|
|
(assert= "it" (get (hs-tok tokens 4) "value"))))
|
|
(deftest
|
|
"on click from #bar add .clicked"
|
|
(let
|
|
((tokens (hs-tokenize "on click from #bar add .clicked")))
|
|
(assert= "keyword" (get (hs-tok tokens 2) "type"))
|
|
(assert= "from" (get (hs-tok tokens 2) "value"))
|
|
(assert= "id" (get (hs-tok tokens 3) "type"))
|
|
(assert= "bar" (get (hs-tok tokens 3) "value"))))) |