diff --git a/.changeset/tame-geese-drop.md b/.changeset/tame-geese-drop.md
new file mode 100644
index 00000000..3e4967be
--- /dev/null
+++ b/.changeset/tame-geese-drop.md
@@ -0,0 +1,5 @@
+---
+"htmljs-parser": patch
+---
+
+Switch from regexp based parsing for the expression continuations. This slightly improves performance and more importantly fixes usage of the parser in safari.
diff --git a/src/__tests__/fixtures/attr-complex-functions/__snapshots__/attr-complex-functions.expected.txt b/src/__tests__/fixtures/attr-complex-functions/__snapshots__/attr-complex-functions.expected.txt
index 8a4dd549..4452261c 100644
--- a/src/__tests__/fixtures/attr-complex-functions/__snapshots__/attr-complex-functions.expected.txt
+++ b/src/__tests__/fixtures/attr-complex-functions/__snapshots__/attr-complex-functions.expected.txt
@@ -20,7 +20,31 @@
│ ├─ closeTagEnd(tag)
│ ├─ openTagEnd
╰─ ╰─ tagName "tag"
-4╭─ tag a = async x => { console.log("y") } b
+4╭─ tag a = x => y b
+ │ │ │ │ │ ╰─ attrName
+ │ │ │ │ ╰─ attrValue.value "x => y"
+ │ │ │ ╰─ attrValue "= x => y"
+ │ │ ╰─ attrName
+ │ ├─ closeTagEnd(tag)
+ │ ├─ openTagEnd
+ ╰─ ╰─ tagName "tag"
+5╭─ tag a = x => y + 1 b
+ │ │ │ │ │ ╰─ attrName
+ │ │ │ │ ╰─ attrValue.value "x => y + 1"
+ │ │ │ ╰─ attrValue "= x => y + 1"
+ │ │ ╰─ attrName
+ │ ├─ closeTagEnd(tag)
+ │ ├─ openTagEnd
+ ╰─ ╰─ tagName "tag"
+6╭─ tag a = x => y = 1 b
+ │ │ │ │ │ ╰─ attrName
+ │ │ │ │ ╰─ attrValue.value "x => y = 1"
+ │ │ │ ╰─ attrValue "= x => y = 1"
+ │ │ ╰─ attrName
+ │ ├─ closeTagEnd(tag)
+ │ ├─ openTagEnd
+ ╰─ ╰─ tagName "tag"
+7╭─ tag a = async x => { console.log("y") } b
│ │ │ │ │ ╰─ attrName
│ │ │ │ ╰─ attrValue.value "async x => { console.log(\"y\") }"
│ │ │ ╰─ attrValue "= async x => { console.log(\"y\") }"
@@ -28,7 +52,7 @@
│ ├─ closeTagEnd(tag)
│ ├─ openTagEnd
╰─ ╰─ tagName "tag"
-5╭─ tag a = async function (x) { console.log("y") } b
+8╭─ tag a = async function (x) { console.log("y") } b
│ │ │ │ │ │├─ closeTagEnd(tag)
│ │ │ │ │ │╰─ openTagEnd
│ │ │ │ │ ╰─ attrName
diff --git a/src/__tests__/fixtures/attr-complex-functions/input.marko b/src/__tests__/fixtures/attr-complex-functions/input.marko
index 148f6ccd..2d061747 100644
--- a/src/__tests__/fixtures/attr-complex-functions/input.marko
+++ b/src/__tests__/fixtures/attr-complex-functions/input.marko
@@ -1,5 +1,8 @@
tag a = function (x) { console.log("y") } b
tag a = (x) => { console.log("y") } b
tag a = x => { console.log("y") } b
+tag a = x => y b
+tag a = x => y + 1 b
+tag a = x => y = 1 b
tag a = async x => { console.log("y") } b
tag a = async function (x) { console.log("y") } b
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-complex-instanceof/__snapshots__/attr-complex-instanceof.expected.txt b/src/__tests__/fixtures/attr-complex-instanceof/__snapshots__/attr-complex-instanceof.expected.txt
index af8f1d09..dc74e8d0 100644
--- a/src/__tests__/fixtures/attr-complex-instanceof/__snapshots__/attr-complex-instanceof.expected.txt
+++ b/src/__tests__/fixtures/attr-complex-instanceof/__snapshots__/attr-complex-instanceof.expected.txt
@@ -73,4 +73,23 @@
│ ││ │ ╰─ attrValue "= 'foo'"
│ ││ ╰─ attrName
│ │╰─ tagName "tag"
- ╰─ ╰─ openTagStart
\ No newline at end of file
+ ╰─ ╰─ openTagStart
+12├─
+13╭─ tag a = 'foo' instanceofthing String
+ │ │ │ │ │ │ ╰─ attrName "String"
+ │ │ │ │ │ ╰─ attrName "instanceofthing"
+ │ │ │ │ ╰─ attrValue.value "'foo'"
+ │ │ │ ╰─ attrValue "= 'foo'"
+ │ │ ╰─ attrName
+ ╰─ ╰─ tagName "tag"
+14╭─
+ ╰─ ╰─ openTagEnd
+15╭─ tag a = 'foo' instanceof
+ │ │ │ │ │ │ ├─ closeTagEnd(tag)
+ │ │ │ │ │ │ ╰─ openTagEnd
+ │ │ │ │ │ ╰─ attrName "instanceof"
+ │ │ │ │ ╰─ attrValue.value "'foo'"
+ │ │ │ ╰─ attrValue "= 'foo'"
+ │ │ ╰─ attrName
+ │ ├─ closeTagEnd(tag)
+ ╰─ ╰─ tagName "tag"
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-complex-instanceof/input.marko b/src/__tests__/fixtures/attr-complex-instanceof/input.marko
index 5bb5fcf5..12dc9920 100644
--- a/src/__tests__/fixtures/attr-complex-instanceof/input.marko
+++ b/src/__tests__/fixtures/attr-complex-instanceof/input.marko
@@ -8,4 +8,8 @@ tag a = 'foo' instanceof;
tag a = 'foo' instanceof, b
-
\ No newline at end of file
+
+
+tag a = 'foo' instanceofthing String
+
+tag a = 'foo' instanceof
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-concise-hyphens/__snapshots__/attr-concise-hyphens.expected.txt b/src/__tests__/fixtures/attr-concise-hyphens/__snapshots__/attr-concise-hyphens.expected.txt
new file mode 100644
index 00000000..5795dd7a
--- /dev/null
+++ b/src/__tests__/fixtures/attr-concise-hyphens/__snapshots__/attr-concise-hyphens.expected.txt
@@ -0,0 +1,18 @@
+1╭─ a b=c -- y
+ │ │ │││ │ ╰─ text
+ │ │ │││ ╰─ openTagEnd
+ │ │ ││╰─ attrValue.value
+ │ │ │╰─ attrValue "=c"
+ │ │ ╰─ attrName
+ ╰─ ╰─ tagName
+2├─
+3╭─ a [b=c -- y]
+ │ │ │││ ╰─ attrName
+ │ │ ││╰─ attrValue.value "c --"
+ │ │ │╰─ attrValue "=c --"
+ │ │ ╰─ attrName
+ │ ├─ closeTagEnd(a)
+ ╰─ ╰─ tagName
+4╭─
+ │ ├─ openTagEnd
+ ╰─ ╰─ closeTagEnd(a)
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-concise-hyphens/input.marko b/src/__tests__/fixtures/attr-concise-hyphens/input.marko
new file mode 100644
index 00000000..04201acf
--- /dev/null
+++ b/src/__tests__/fixtures/attr-concise-hyphens/input.marko
@@ -0,0 +1,3 @@
+a b=c -- y
+
+a [b=c -- y]
diff --git a/src/__tests__/fixtures/attr-operator-whitespace-eof/__snapshots__/attr-operator-whitespace-eof.expected.txt b/src/__tests__/fixtures/attr-operator-whitespace-eof/__snapshots__/attr-operator-whitespace-eof.expected.txt
new file mode 100644
index 00000000..5f832363
--- /dev/null
+++ b/src/__tests__/fixtures/attr-operator-whitespace-eof/__snapshots__/attr-operator-whitespace-eof.expected.txt
@@ -0,0 +1,9 @@
+1╭─ tag a = 'foo' instanceof
+ │ │ │ │ │ ╰─ attrName "instanceof"
+ │ │ │ │ ╰─ attrValue.value "'foo'"
+ │ │ │ ╰─ attrValue "= 'foo'"
+ │ │ ╰─ attrName
+ ╰─ ╰─ tagName "tag"
+2╭─
+ │ ├─ openTagEnd
+ ╰─ ╰─ closeTagEnd(tag)
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-operator-whitespace-eof/input.marko b/src/__tests__/fixtures/attr-operator-whitespace-eof/input.marko
new file mode 100644
index 00000000..8bb913cc
--- /dev/null
+++ b/src/__tests__/fixtures/attr-operator-whitespace-eof/input.marko
@@ -0,0 +1 @@
+tag a = 'foo' instanceof
diff --git a/src/__tests__/fixtures/attr-operators-newline-after/__snapshots__/attr-operators-newline-after.expected.txt b/src/__tests__/fixtures/attr-operators-newline-after/__snapshots__/attr-operators-newline-after.expected.txt
index 3b7daf30..7558659a 100644
--- a/src/__tests__/fixtures/attr-operators-newline-after/__snapshots__/attr-operators-newline-after.expected.txt
+++ b/src/__tests__/fixtures/attr-operators-newline-after/__snapshots__/attr-operators-newline-after.expected.txt
@@ -683,14 +683,17 @@
│ │╰─ openTagEnd:selfClosed "/>"
╰─ ╰─ attrName
159╭─ =
- │ │││╰─ attrValue.value "x >=\ny"
- │ ││├─ attrValue "=x >=\ny"
+ │ ││││ │╰─ text "=\ny "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-160╭─ y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+160╭─ y
+ │ │ │╰─ closeTagEnd(a)
+ │ │ ╰─ closeTagName
+ ╰─ ╰─ closeTagStart ""
161╭─ "
╰─ ╰─ attrName
173╭─ >=
- │ │││╰─ attrValue.value "x >>=\ny"
- │ ││├─ attrValue "=x >>=\ny"
+ │ ││││ │╰─ text ">=\ny "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-174╭─ y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+174╭─ y
+ │ │ │╰─ closeTagEnd(a)
+ │ │ ╰─ closeTagName
+ ╰─ ╰─ closeTagStart ""
175╭─ >>=
- │ │││╰─ attrValue.value "x >>>=\ny"
- │ ││├─ attrValue "=x >>>=\ny"
+ │ ││││ │╰─ text ">>=\ny "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-176╭─ y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+176╭─ y
+ │ │ │╰─ closeTagEnd(a)
+ │ │ ╰─ closeTagName
+ ╰─ ╰─ closeTagStart ""
177╭─ "
╰─ ╰─ attrName
193╭─ >
- │ │││╰─ attrValue.value "x >>\ny"
- │ ││├─ attrValue "=x >>\ny"
+ │ ││││ │╰─ text ">\ny "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-194╭─ y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+194╭─ y
+ │ │ │╰─ closeTagEnd(a)
+ │ │ ╰─ closeTagName
+ ╰─ ╰─ closeTagStart ""
195╭─ >>
- │ │││╰─ attrValue.value "x >>>\ny"
- │ ││├─ attrValue "=x >>>\ny"
+ │ ││││ │╰─ text ">>\ny "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-196╭─ y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+196╭─ y
+ │ │ │╰─ closeTagEnd(a)
+ │ │ ╰─ closeTagName
+ ╰─ ╰─ closeTagStart ""
197╭─
=
-y a/>
+y
>=
-y a/>
+y
>>=
-y a/>
+y
>
-y a/>
+y
>>
-y a/>
+y
"
╰─ ╰─ attrName
45╭─ = y"
- │ ││├─ attrValue "=x\n>= y"
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-46╭─ >= y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+46╭─ >= y
+ │ ││ │ │╰─ closeTagEnd(a)
+ │ ││ │ ╰─ closeTagName
+ │ ││ ╰─ closeTagStart ""
+ │ │╰─ text "= y "
+ ╰─ ╰─ openTagEnd
47╭─ "
╰─ ╰─ attrName
59╭─ >= y"
- │ ││├─ attrValue "=x\n>>= y"
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-60╭─ >>= y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+60╭─ >>= y
+ │ ││ │ │╰─ closeTagEnd(a)
+ │ ││ │ ╰─ closeTagName
+ │ ││ ╰─ closeTagStart ""
+ │ │╰─ text ">= y "
+ ╰─ ╰─ openTagEnd
61╭─ >>= y"
- │ ││├─ attrValue "=x\n>>>= y"
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-62╭─ >>>= y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+62╭─ >>>= y
+ │ ││ │ │╰─ closeTagEnd(a)
+ │ ││ │ ╰─ closeTagName
+ │ ││ ╰─ closeTagStart ""
+ │ │╰─ text ">>= y "
+ ╰─ ╰─ openTagEnd
63╭─ "
╰─ ╰─ attrName
79╭─ >y"
- │ ││├─ attrValue "=x\n>>y"
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-80╭─ >>y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+80╭─ >>y
+ │ ││ │ │╰─ closeTagEnd(a)
+ │ ││ │ ╰─ closeTagName
+ │ ││ ╰─ closeTagStart ""
+ │ │╰─ text ">y "
+ ╰─ ╰─ openTagEnd
81╭─ >> y"
- │ ││├─ attrValue "=x\n>>> y"
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-82╭─ >>> y a/>
- │ │╰─ openTagEnd:selfClosed "/>"
- ╰─ ╰─ attrName
+82╭─ >>> y
+ │ ││ │ │╰─ closeTagEnd(a)
+ │ ││ │ ╰─ closeTagName
+ │ ││ ╰─ closeTagStart ""
+ │ │╰─ text ">> y "
+ ╰─ ╰─ openTagEnd
83╭─
= y a/>
+>= y
>= y a/>
+>>= y
>>= y a/>
+>>>= y
>y a/>
+>>y
>> y a/>
+>>> y
= y a/>
- │ │││││╰─ text "= y a/>\n"
+66╭─ = y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
+ │ │││││╰─ text "= y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
@@ -534,7 +537,6 @@
│ ││├─ attrValue "=x&&= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
69╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -543,7 +545,6 @@
│ ││├─ attrValue "=x|= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
70╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -552,7 +553,6 @@
│ ││├─ attrValue "=x||= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
71╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -561,7 +561,6 @@
│ ││├─ attrValue "=x^= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
72╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -570,19 +569,23 @@
│ ││├─ attrValue "=x~= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-73╭─ >= y a/>
- │ │││││╰─ text ">= y a/>\n"
+73╭─ >= y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
+ │ │││││╰─ text ">= y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-74╭─ >>= y a/>
- │ │││││╰─ text ">>= y a/>\n"
+74╭─ >>= y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
+ │ │││││╰─ text ">>= y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
@@ -604,7 +607,6 @@
│ ││├─ attrValue "=x/= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
77╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -613,7 +615,6 @@
│ ││├─ attrValue "=x*= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
78╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -622,7 +623,6 @@
│ ││├─ attrValue "=x**= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
79╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -631,7 +631,6 @@
│ ││├─ attrValue "=x%= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
80╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -640,7 +639,6 @@
│ ││├─ attrValue "=x+= y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
81╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -649,7 +647,6 @@
│ ││├─ attrValue "=x++ + y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
82╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -658,31 +655,34 @@
│ ││├─ attrValue "=x+ ++ y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-83╭─ y
- │ ││││││ ││╰─ openTagEnd:selfClosed "/>"
- │ ││││││ │╰─ tagName
- │ ││││││ ╰─ openTagStart
+83╭─ y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
│ │││││╰─ text " y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-84╭─ > y a/>
- │ │││││╰─ text "> y a/>\n"
+84╭─ > y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
+ │ │││││╰─ text "> y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-85╭─ >> y a/>
- │ │││││╰─ text ">> y a/>\n"
+85╭─ >> y
+ │ ││││││ │ │╰─ closeTagEnd(a)
+ │ ││││││ │ ╰─ closeTagName
+ │ ││││││ ╰─ closeTagStart ""
+ │ │││││╰─ text ">> y "
│ ││││╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
@@ -704,33 +704,30 @@
│ ││├─ attrValue "=z {y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
88╭─ y a/>
- │ ││││ │╰─ text " y a/>\n"
- │ ││││ ╰─ openTagEnd
- │ │││╰─ attrValue.value "x="
- │ ││├─ attrValue "=x="
+ │ ││││ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "x=> y"
+ │ ││├─ attrValue "=x=> y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
89╭─ (y ) a/>
- │ ││││ │╰─ text " (y ) a/>\n"
- │ ││││ ╰─ openTagEnd
- │ │││╰─ attrValue.value "x="
- │ ││├─ attrValue "=x="
+ │ ││││ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "x=> (y )"
+ │ ││├─ attrValue "=x=> (y )"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
90╭─ {y } a/>
- │ ││││ │╰─ text " {y } a/>\n"
- │ ││││ ╰─ openTagEnd
- │ │││╰─ attrValue.value "x="
- │ ││├─ attrValue "=x="
+ │ ││││ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "x=> {y }"
+ │ ││├─ attrValue "=x=> {y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ error(MISSING_END_TAG:Missing ending "a" tag) ""
╰─ ╰─ openTagStart
91╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -740,5 +737,4 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-92╭─
- ╰─ ╰─ text "\n"
\ No newline at end of file
+92╰─
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-operators-space-after/input.marko b/src/__tests__/fixtures/attr-operators-space-after/input.marko
index 70a18e02..4accea87 100644
--- a/src/__tests__/fixtures/attr-operators-space-after/input.marko
+++ b/src/__tests__/fixtures/attr-operators-space-after/input.marko
@@ -63,15 +63,15 @@ a=(x ) {y } a
-= y a/>
+= y
->= y a/>
->>= y a/>
+>= y
+>>= y
@@ -80,9 +80,9 @@ a=(x ) {y } a
- y
-> y a/>
->> y a/>
+ y
+> y
+>> y
y a/>
diff --git a/src/__tests__/fixtures/attr-operators-space-before/__snapshots__/attr-operators-space-before.expected.txt b/src/__tests__/fixtures/attr-operators-space-before/__snapshots__/attr-operators-space-before.expected.txt
index 9290fab3..72b9be8c 100644
--- a/src/__tests__/fixtures/attr-operators-space-before/__snapshots__/attr-operators-space-before.expected.txt
+++ b/src/__tests__/fixtures/attr-operators-space-before/__snapshots__/attr-operators-space-before.expected.txt
@@ -511,11 +511,14 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-66╭─ =y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >=y"
- │ ││├─ attrValue "=x >=y"
+66╭─ =y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text "=y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
@@ -567,19 +570,25 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-73╭─ >=y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>=y"
- │ ││├─ attrValue "=x >>=y"
+73╭─ >=y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">=y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-74╭─ >>=y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>>=y"
- │ ││├─ attrValue "=x >>>=y"
+74╭─ >>=y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">>=y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
@@ -647,35 +656,38 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-83╭─ y
- │ ││││ ││ ││╰─ openTagEnd:selfClosed "/>"
- │ ││││ ││ │╰─ tagName
- │ ││││ ││ ╰─ openTagStart
+83╭─ y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
│ ││││ │╰─ text "y "
│ ││││ ╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ error(MISSING_END_TAG:Missing ending "a" tag) ""
╰─ ╰─ openTagStart
-84╭─ >y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>y"
- │ ││├─ attrValue "=x >>y"
+84╭─ >y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-85╭─ >>y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>>y"
- │ ││├─ attrValue "=x >>>y"
+85╭─ >>y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">>y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
86╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -684,7 +696,6 @@
│ ││├─ attrValue "=x (y )"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
87╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -693,7 +704,6 @@
│ ││├─ attrValue "=x {y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
88╭─ y a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -702,7 +712,6 @@
│ ││├─ attrValue "=x =>y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
89╭─ (y ) a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -711,7 +720,6 @@
│ ││├─ attrValue "=x => (y )"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
90╭─ {y } a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -720,7 +728,6 @@
│ ││├─ attrValue "=x => {y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
91╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
@@ -729,7 +736,5 @@
│ ││├─ attrValue "=( x ) {y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-92╭─
- ╰─ ╰─ text "\n"
\ No newline at end of file
+92╰─
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-operators-space-before/input.marko b/src/__tests__/fixtures/attr-operators-space-before/input.marko
index f87ef5aa..7c1d7a8d 100644
--- a/src/__tests__/fixtures/attr-operators-space-before/input.marko
+++ b/src/__tests__/fixtures/attr-operators-space-before/input.marko
@@ -63,15 +63,15 @@ a=( x ) {y } a
-=y a/>
+=y
->=y a/>
->>=y a/>
+>=y
+>>=y
@@ -80,9 +80,9 @@ a=( x ) {y } a
-y
->y a/>
->>y a/>
+y
+>y
+>>y
y a/>
diff --git a/src/__tests__/fixtures/attr-operators-space-between/__snapshots__/attr-operators-space-between.expected.txt b/src/__tests__/fixtures/attr-operators-space-between/__snapshots__/attr-operators-space-between.expected.txt
index 65d484a0..3c041919 100644
--- a/src/__tests__/fixtures/attr-operators-space-between/__snapshots__/attr-operators-space-between.expected.txt
+++ b/src/__tests__/fixtures/attr-operators-space-between/__snapshots__/attr-operators-space-between.expected.txt
@@ -384,11 +384,10 @@
│ ├─ openTagEnd
╰─ ╰─ tagName
49╭─ a=x ++ y a
- │ │││ │ │ ╰─ attrName
- │ │││ │ ╰─ attrName
- │ │││ ╰─ attrName "++"
- │ ││╰─ attrValue.value
- │ │├─ attrValue "=x"
+ │ │││ │ ╰─ attrName
+ │ │││ ╰─ attrName
+ │ ││╰─ attrValue.value "x ++"
+ │ │├─ attrValue "=x ++"
│ │╰─ attrName
│ ├─ closeTagEnd(a)
│ ├─ openTagEnd
@@ -700,11 +699,14 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-89╭─ = y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >= y"
- │ ││├─ attrValue "=x >= y"
+89╭─ = y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text "= y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
@@ -756,19 +758,25 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-96╭─ >= y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>= y"
- │ ││├─ attrValue "=x >>= y"
+96╭─ >= y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">= y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-97╭─ >>= y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>>= y"
- │ ││├─ attrValue "=x >>>= y"
+97╭─ >>= y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">>= y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
@@ -879,142 +887,149 @@
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-111╭─
- │ ││││ │ │ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ │ │ ╰─ attrName
- │ ││││ │ ╰─ attrName
- │ ││││ ╰─ attrName "++"
- │ │││╰─ attrValue.value
- │ ││├─ attrValue "=x"
+111╭─
+ │ ││││ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "typeof ++ y"
+ │ ││├─ attrValue "=typeof ++ y"
+ │ ││╰─ attrName
+ │ │╰─ tagName
+ ╰─ ╰─ openTagStart
+112╭─
+ │ ││││ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "typeof y ++"
+ │ ││├─ attrValue "=typeof y ++"
+ │ ││╰─ attrName
+ │ │╰─ tagName
+ ╰─ ╰─ openTagStart
+113╭─
+ │ ││││ │ │╰─ openTagEnd:selfClosed "/>"
+ │ ││││ │ ╰─ attrName
+ │ ││││ ╰─ attrName
+ │ │││╰─ attrValue.value "x ++"
+ │ ││├─ attrValue "=x ++"
│ ││╰─ attrName
│ │╰─ tagName
╰─ ╰─ openTagStart
-112╭─ y
- │ ││││ ││ ││╰─ openTagEnd:selfClosed "/>"
- │ ││││ ││ │╰─ tagName
- │ ││││ ││ ╰─ openTagStart
+114╭─ y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
│ ││││ │╰─ text " y "
│ ││││ ╰─ openTagEnd
│ │││╰─ attrValue.value
│ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ error(MISSING_END_TAG:Missing ending "a" tag) ""
╰─ ╰─ openTagStart
-113╭─ > y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >> y"
- │ ││├─ attrValue "=x >> y"
+115╭─ > y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text "> y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-114╭─ >> y a/>
- │ ││││ │╰─ openTagEnd:selfClosed "/>"
- │ ││││ ╰─ attrName
- │ │││╰─ attrValue.value "x >>> y"
- │ ││├─ attrValue "=x >>> y"
+116╭─ >> y
+ │ ││││ ││ │ │╰─ closeTagEnd(a)
+ │ ││││ ││ │ ╰─ closeTagName
+ │ ││││ ││ ╰─ closeTagStart ""
+ │ ││││ │╰─ text ">> y "
+ │ ││││ ╰─ openTagEnd
+ │ │││╰─ attrValue.value
+ │ ││├─ attrValue "=x"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-115╭─
+117╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "x ( y )"
│ ││├─ attrValue "=x ( y )"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-116╭─
+118╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "x { y }"
│ ││├─ attrValue "=x { y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-117╭─ y a/>
+119╭─ y a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "x => y"
│ ││├─ attrValue "=x => y"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-118╭─ ( y ) a/>
+120╭─ ( y ) a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "x => ( y )"
│ ││├─ attrValue "=x => ( y )"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-119╭─ { y } a/>
+121╭─ { y } a/>
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "x => { y }"
│ ││├─ attrValue "=x => { y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-120╭─
+122╭─
│ ││││ │╰─ openTagEnd:selfClosed "/>"
│ ││││ ╰─ attrName
│ │││╰─ attrValue.value "( x ) { y }"
│ ││├─ attrValue "=( x ) { y }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-121╭─ { console.log("y") } a/>
+123╭─ { console.log("y") } a/>
│ │││ │ │╰─ openTagEnd:selfClosed "/>"
│ │││ │ ╰─ attrName
│ │││ ╰─ attrValue.value "(x) => { console.log(\"y\") }"
│ ││├─ attrValue "= (x) => { console.log(\"y\") }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-122╭─ { console.log("y") } a/>
+124╭─ { console.log("y") } a/>
│ │││ │ │╰─ openTagEnd:selfClosed "/>"
│ │││ │ ╰─ attrName
│ │││ ╰─ attrValue.value "async x => { console.log(\"y\") }"
│ ││├─ attrValue "= async x => { console.log(\"y\") }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-123╭─
+125╭─
│ │││ │ │╰─ openTagEnd:selfClosed "/>"
│ │││ │ ╰─ attrName
│ │││ ╰─ attrValue.value "function (x) { console.log(\"y\") }"
│ ││├─ attrValue "= function (x) { console.log(\"y\") }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-124╭─ { console.log("y") } a/>
+126╭─ { console.log("y") } a/>
│ │││ │ │╰─ openTagEnd:selfClosed "/>"
│ │││ │ ╰─ attrName
│ │││ ╰─ attrValue.value "x => { console.log(\"y\") }"
│ ││├─ attrValue "= x => { console.log(\"y\") }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
-125╭─
+127╭─
│ │││ │ │╰─ openTagEnd:selfClosed "/>"
│ │││ │ ╰─ attrName
│ │││ ╰─ attrValue.value "async function (x) { console.log(\"y\") }"
│ ││├─ attrValue "= async function (x) { console.log(\"y\") }"
│ ││╰─ attrName
│ │╰─ tagName
- │ ├─ text "\n"
╰─ ╰─ openTagStart
\ No newline at end of file
diff --git a/src/__tests__/fixtures/attr-operators-space-between/input.marko b/src/__tests__/fixtures/attr-operators-space-between/input.marko
index f960d5ca..af2fb653 100644
--- a/src/__tests__/fixtures/attr-operators-space-between/input.marko
+++ b/src/__tests__/fixtures/attr-operators-space-between/input.marko
@@ -86,15 +86,15 @@ a = async function (x) { console.log("y") } a
-= y a/>
+= y
->= y a/>
->>= y a/>
+>= y
+>>= y
@@ -108,10 +108,12 @@ a = async function (x) { console.log("y") } a
+
+
- y
-> y a/>
->> y a/>
+ y
+> y
+>> y
y a/>
diff --git a/src/states/ATTRIBUTE.ts b/src/states/ATTRIBUTE.ts
index 869f6ee5..a5adfab6 100644
--- a/src/states/ATTRIBUTE.ts
+++ b/src/states/ATTRIBUTE.ts
@@ -8,7 +8,10 @@ import {
Ranges,
Meta,
ErrorCode,
+ matchesCloseCurlyBrace,
+ matchesCloseParen,
} from "../internal";
+import { TAG_STAGE } from "./OPEN_TAG";
const enum ATTR_STAGE {
UNKNOWN,
@@ -27,36 +30,6 @@ export interface AttrMeta extends Meta {
bound: boolean;
}
-const HTML_VALUE_TERMINATORS = [
- CODE.CLOSE_ANGLE_BRACKET,
- CODE.COMMA,
- [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET],
-];
-
-const CONCISE_VALUE_TERMINATORS = [
- CODE.CLOSE_SQUARE_BRACKET,
- CODE.SEMICOLON,
- CODE.COMMA,
-];
-
-const HTML_NAME_TERMINATORS = [
- CODE.CLOSE_ANGLE_BRACKET,
- CODE.COMMA,
- CODE.OPEN_PAREN,
- CODE.EQUAL,
- [CODE.COLON, CODE.EQUAL],
- [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET],
-];
-
-const CONCISE_NAME_TERMINATORS = [
- CODE.CLOSE_SQUARE_BRACKET,
- CODE.SEMICOLON,
- CODE.EQUAL,
- CODE.COMMA,
- CODE.OPEN_PAREN,
- [CODE.COLON, CODE.EQUAL],
-];
-
// We enter STATE.ATTRIBUTE when we see a non-whitespace
// character after reading the tag name
export const ATTRIBUTE: StateDefinition = {
@@ -108,33 +81,36 @@ export const ATTRIBUTE: StateDefinition = {
attr.stage = ATTR_STAGE.VALUE;
const expr = this.enterState(STATE.EXPRESSION);
+ expr.operators = true;
expr.terminatedByWhitespace = true;
- expr.terminator = this.isConcise
- ? CONCISE_VALUE_TERMINATORS
- : HTML_VALUE_TERMINATORS;
+ expr.shouldTerminate = this.isConcise
+ ? this.activeTag!.stage === TAG_STAGE.ATTR_GROUP
+ ? shouldTerminateConciseGroupedAttrValue
+ : shouldTerminateConciseAttrValue
+ : shouldTerminateHtmlAttrValue;
} else if (code === CODE.OPEN_PAREN) {
ensureAttrName(this, attr);
attr.stage = ATTR_STAGE.ARGUMENT;
this.pos++; // skip (
this.forward = 0;
- this.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_PAREN;
+ this.enterState(STATE.EXPRESSION).shouldTerminate = matchesCloseParen;
} else if (code === CODE.OPEN_CURLY_BRACE && attr.args) {
ensureAttrName(this, attr);
attr.stage = ATTR_STAGE.BLOCK;
this.pos++; // skip {
this.forward = 0;
- const expr = this.enterState(STATE.EXPRESSION);
- expr.terminatedByWhitespace = false;
- expr.terminator = CODE.CLOSE_CURLY_BRACE;
+ this.enterState(STATE.EXPRESSION).shouldTerminate =
+ matchesCloseCurlyBrace;
} else if (attr.stage === ATTR_STAGE.UNKNOWN) {
attr.stage = ATTR_STAGE.NAME;
this.forward = 0;
const expr = this.enterState(STATE.EXPRESSION);
expr.terminatedByWhitespace = true;
- expr.skipOperators = true;
- expr.terminator = this.isConcise
- ? CONCISE_NAME_TERMINATORS
- : HTML_NAME_TERMINATORS;
+ expr.shouldTerminate = this.isConcise
+ ? this.activeTag!.stage === TAG_STAGE.ATTR_GROUP
+ ? shouldTerminateConciseGroupedAttrName
+ : shouldTerminateConciseAttrName
+ : shouldTerminateHtmlAttrName;
} else {
this.exitState();
}
@@ -273,3 +249,103 @@ function ensureAttrName(parser: Parser, attr: AttrMeta) {
});
}
}
+
+function shouldTerminateHtmlAttrName(code: number, data: string, pos: number) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.OPEN_PAREN:
+ case CODE.CLOSE_ANGLE_BRACKET:
+ return true;
+ case CODE.COLON:
+ return data.charCodeAt(pos + 1) === CODE.EQUAL;
+ case CODE.FORWARD_SLASH:
+ return data.charCodeAt(pos + 1) === CODE.CLOSE_ANGLE_BRACKET;
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateHtmlAttrValue(code: number, data: string, pos: number) {
+ switch (code) {
+ case CODE.COMMA:
+ return true;
+ case CODE.FORWARD_SLASH:
+ return data.charCodeAt(pos + 1) === CODE.CLOSE_ANGLE_BRACKET;
+ // Add special case for =>
+ case CODE.CLOSE_ANGLE_BRACKET:
+ return data.charCodeAt(pos - 1) !== CODE.EQUAL;
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateConciseAttrName(
+ code: number,
+ data: string,
+ pos: number
+) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.OPEN_PAREN:
+ case CODE.SEMICOLON:
+ return true;
+ case CODE.COLON:
+ return data.charCodeAt(pos + 1) === CODE.EQUAL;
+ case CODE.HYPHEN:
+ return (
+ data.charCodeAt(pos + 1) === CODE.HYPHEN &&
+ isWhitespaceCode(data.charCodeAt(pos - 1))
+ );
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateConciseAttrValue(
+ code: number,
+ data: string,
+ pos: number
+) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.SEMICOLON:
+ return true;
+ case CODE.HYPHEN:
+ return (
+ data.charCodeAt(pos + 1) === CODE.HYPHEN &&
+ isWhitespaceCode(data.charCodeAt(pos - 1))
+ );
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateConciseGroupedAttrName(
+ code: number,
+ data: string,
+ pos: number
+) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.OPEN_PAREN:
+ case CODE.CLOSE_SQUARE_BRACKET:
+ return true;
+ case CODE.COLON:
+ return data.charCodeAt(pos + 1) === CODE.EQUAL;
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateConciseGroupedAttrValue(code: number) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.CLOSE_SQUARE_BRACKET:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/src/states/EXPRESSION.ts b/src/states/EXPRESSION.ts
index 5fa20467..3c8a8dce 100644
--- a/src/states/EXPRESSION.ts
+++ b/src/states/EXPRESSION.ts
@@ -8,23 +8,29 @@ import {
ErrorCode,
} from "../internal";
-const enum PatternType {
- HTML_ATTRS,
- CONCISE_ATTRS,
- CONCISE_ATTRS_GROUP,
-}
-
export interface ExpressionMeta extends Meta {
groupStack: number[];
- terminator: number | (number | number[])[];
- skipOperators: boolean;
+ operators: boolean;
terminatedByEOL: boolean;
terminatedByWhitespace: boolean;
+ shouldTerminate(code: number, data: string, pos: number): boolean;
}
-const htmlAttrsPattern = buildPattern(PatternType.HTML_ATTRS);
-const conciseAttrsPattern = buildPattern(PatternType.CONCISE_ATTRS);
-const conciseAttrsGroupPattern = buildPattern(PatternType.CONCISE_ATTRS_GROUP);
+// Never terminate early by default.
+const shouldTerminate = () => false;
+
+const unaryKeywords = [
+ "async",
+ "await",
+ "keyof",
+ "class",
+ "function",
+ "new",
+ "typeof",
+ "void",
+] as const;
+
+const binaryKeywords = ["instanceof", "in", "as", "extends"] as const;
export const EXPRESSION: StateDefinition = {
name: "EXPRESSION",
@@ -36,8 +42,8 @@ export const EXPRESSION: StateDefinition = {
start,
end: start,
groupStack: [],
- terminator: -1,
- skipOperators: false,
+ shouldTerminate,
+ operators: false,
terminatedByEOL: false,
terminatedByWhitespace: false,
};
@@ -48,17 +54,13 @@ export const EXPRESSION: StateDefinition = {
char(code, expression) {
if (!expression.groupStack.length) {
if (expression.terminatedByWhitespace && isWhitespaceCode(code)) {
- if (!checkForOperators(this, expression)) {
+ if (!checkForOperators(this, expression, false)) {
this.exitState();
}
return;
}
- if (
- typeof expression.terminator === "number"
- ? expression.terminator === code
- : checkForTerminators(this, code, expression.terminator)
- ) {
+ if (expression.shouldTerminate(code, this.data, this.pos)) {
this.exitState();
return;
}
@@ -86,11 +88,7 @@ export const EXPRESSION: StateDefinition = {
this.pos++;
break;
default: {
- if (
- canCharCodeBeFollowedByDivision(
- this.getPreviousNonWhitespaceCharCode()
- )
- ) {
+ if (canFollowDivision(this.getPreviousNonWhitespaceCharCode())) {
this.pos++;
this.consumeWhitespace();
} else {
@@ -145,7 +143,7 @@ export const EXPRESSION: StateDefinition = {
if (
!expression.groupStack.length &&
(expression.terminatedByEOL || expression.terminatedByWhitespace) &&
- !checkForOperators(this, expression)
+ !checkForOperators(this, expression, true)
) {
this.exitState();
}
@@ -212,95 +210,164 @@ export const EXPRESSION: StateDefinition = {
return() {},
};
-function buildPattern(type: PatternType) {
- const space = type === PatternType.CONCISE_ATTRS ? "[ \\t]" : "\\s";
- const binary =
- "(?:[!~*%&^|?<]+=*)+" + // Any of these characters can always continue an expression
- "|:+(?!=)" + // Match a colon without matching a bound attribute
- "|[>/+=-]+=|=>" + // Match equality and multi char assignment operators w/o matching equals by itself
- `|(?${type === PatternType.HTML_ATTRS ? "{2,}" : "+"}` + // in html mode only consume closing angle brackets if it is >>
- "|[ \\t]+(?:in(?:stanceof)?|as|extends)(?=[ \\t]+[^=/,;:>])"; // We only continue after word operators (instanceof/in) when they are not followed by a terminator
- const unary =
- "\\b(?])`; // if we have spaces followed by an opening bracket, we'll consume the spaces and let the expression state handle the brackets
- const lookBehindPattern = `(?<=${unary}|${binary})`;
- return new RegExp(`${lookAheadPattern}|${lookBehindPattern}`, "ym");
-}
+function checkForOperators(
+ parser: Parser,
+ expression: ExpressionMeta,
+ eol: boolean
+) {
+ if (!expression.operators) return false;
-function checkForOperators(parser: Parser, expression: ExpressionMeta) {
- if (expression.skipOperators) {
- return false;
+ const { pos, data } = parser;
+ if (lookBehindForOperator(data, pos) !== -1) {
+ parser.consumeWhitespace();
+ parser.forward = 0;
+ return true;
}
- const pattern = parser.isConcise
- ? parser.activeTag?.stage === STATE.TAG_STAGE.ATTR_GROUP
- ? conciseAttrsGroupPattern
- : conciseAttrsPattern
- : expression.terminatedByEOL
- ? conciseAttrsPattern
- : htmlAttrsPattern;
- pattern.lastIndex = parser.pos;
- const matches = pattern.exec(parser.data);
-
- if (matches) {
- const [match] = matches;
- if (match.length === 0) {
- // We matched a look behind.
- parser.consumeWhitespace();
- } else {
- // We matched a look ahead.
- parser.pos += match.length;
- }
+ const terminatedByEOL = expression.terminatedByEOL || parser.isConcise;
+ if (!(terminatedByEOL && eol)) {
+ const nextNonSpace = lookAheadWhile(
+ terminatedByEOL ? isIndentCode : isWhitespaceCode,
+ data,
+ pos + 1
+ );
- // After this point we should be on the character we want to process next
- // so we don't want to move forward and miss that character.
- parser.forward = 0;
- } else {
- return false;
+ if (
+ !expression.shouldTerminate(
+ data.charCodeAt(nextNonSpace),
+ data,
+ nextNonSpace
+ )
+ ) {
+ const lookAheadPos = lookAheadForOperator(data, nextNonSpace);
+ if (lookAheadPos !== -1) {
+ parser.pos = lookAheadPos;
+ parser.forward = 0;
+ return true;
+ }
+ }
}
- return true;
+ return false;
}
-function checkForTerminators(
- parser: Parser,
- code: number,
- terminators: (number | number[])[]
-) {
- outer: for (const terminator of terminators) {
- if (typeof terminator === "number") {
- if (code === terminator) return true;
- } else {
- if (terminator[0] === code) {
- for (let i = terminator.length; i-- > 1; ) {
- if (parser.data.charCodeAt(parser.pos + i) !== terminator[i])
- continue outer;
+function lookBehindForOperator(data: string, pos: number): number {
+ const curPos = pos - 1;
+ const code = data.charCodeAt(curPos);
+
+ switch (code) {
+ case CODE.AMPERSAND:
+ case CODE.ASTERISK:
+ case CODE.CARET:
+ case CODE.COLON:
+ case CODE.EQUAL:
+ case CODE.EXCLAMATION:
+ case CODE.OPEN_ANGLE_BRACKET:
+ case CODE.CLOSE_ANGLE_BRACKET:
+ case CODE.PERCENT:
+ case CODE.PERIOD:
+ case CODE.PIPE:
+ case CODE.QUESTION:
+ case CODE.TILDE:
+ return curPos;
+
+ // special case -- and ++
+ case CODE.PLUS:
+ case CODE.HYPHEN: {
+ if (data.charCodeAt(curPos - 1) === code) {
+ // Check if we should continue for another reason.
+ // eg "typeof++ x"
+ return lookBehindForOperator(
+ data,
+ lookBehindWhile(isWhitespaceCode, data, curPos - 2)
+ );
+ }
+
+ return curPos;
+ }
+
+ default: {
+ for (const keyword of unaryKeywords) {
+ const keywordPos = lookBehindFor(data, curPos, keyword);
+ if (keywordPos !== -1) {
+ return data.charCodeAt(keywordPos - 1) === CODE.PERIOD
+ ? -1
+ : keywordPos;
}
+ }
+ return -1;
+ }
+ }
+}
- return true;
+function lookAheadForOperator(data: string, pos: number): number {
+ switch (data.charCodeAt(pos)) {
+ case CODE.AMPERSAND:
+ case CODE.ASTERISK:
+ case CODE.CARET:
+ case CODE.EXCLAMATION:
+ case CODE.OPEN_ANGLE_BRACKET:
+ case CODE.PERCENT:
+ case CODE.PIPE:
+ case CODE.QUESTION:
+ case CODE.TILDE:
+ case CODE.PLUS:
+ case CODE.HYPHEN:
+ case CODE.COLON:
+ case CODE.CLOSE_ANGLE_BRACKET:
+ case CODE.EQUAL:
+ return pos + 1;
+
+ case CODE.FORWARD_SLASH:
+ case CODE.OPEN_CURLY_BRACE:
+ case CODE.OPEN_PAREN:
+ return pos; // defers to base expression state to track block groups.
+
+ case CODE.PERIOD:
+ // Only match a dot if its not ...
+ return data.charCodeAt(pos + 1) === CODE.PERIOD ? -1 : pos + 1;
+
+ default: {
+ for (const keyword of binaryKeywords) {
+ let nextPos = lookAheadFor(data, pos, keyword);
+ if (nextPos === -1) continue;
+
+ const max = data.length - 1;
+ if (nextPos === max) return -1;
+
+ let nextCode = data.charCodeAt(nextPos + 1);
+ if (isWhitespaceCode(nextCode)) {
+ // skip any whitespace after the operator
+ nextPos = lookAheadWhile(isWhitespaceCode, data, nextPos + 2);
+ if (nextPos === max) return -1;
+ nextCode = data.charCodeAt(nextPos);
+ } else if (isWordCode(nextCode)) {
+ // bail if we are continuing a word, eg "**in**teger"
+ return -1;
+ }
+
+ // finally check that this is not followed by a terminator.
+ switch (nextCode) {
+ case CODE.COLON:
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.FORWARD_SLASH:
+ case CODE.CLOSE_ANGLE_BRACKET:
+ case CODE.SEMICOLON:
+ return -1;
+ default:
+ return nextPos;
+ }
}
+
+ return -1;
}
}
}
-function canCharCodeBeFollowedByDivision(code: number) {
+function canFollowDivision(code: number) {
return (
- (code >= CODE.NUMBER_0 && code <= CODE.NUMBER_9) ||
- (code >= CODE.UPPER_A && code <= CODE.UPPER_Z) ||
- (code >= CODE.LOWER_A && code <= CODE.LOWER_Z) ||
+ isWordCode(code) ||
code === CODE.PERCENT ||
code === CODE.CLOSE_PAREN ||
code === CODE.PERIOD ||
@@ -309,3 +376,73 @@ function canCharCodeBeFollowedByDivision(code: number) {
code === CODE.CLOSE_CURLY_BRACE
);
}
+
+function isWordCode(code: number) {
+ return (
+ (code >= CODE.UPPER_A && code <= CODE.UPPER_Z) ||
+ (code >= CODE.LOWER_A && code <= CODE.LOWER_Z) ||
+ (code >= CODE.NUMBER_0 && code <= CODE.NUMBER_9) ||
+ code === CODE.UNDERSCORE
+ );
+}
+
+function isIndentCode(code: number) {
+ return code === CODE.TAB || code === CODE.SPACE;
+}
+
+function lookAheadWhile(
+ match: (code: number) => boolean,
+ data: string,
+ pos: number
+) {
+ const max = data.length;
+ for (let i = pos; i < max; i++) {
+ if (!match(data.charCodeAt(i))) return i;
+ }
+
+ return max - 1;
+}
+
+function lookBehindWhile(
+ match: (code: number) => boolean,
+ data: string,
+ pos: number
+) {
+ let i = pos;
+
+ do {
+ if (!match(data.charCodeAt(i))) {
+ return i + 1;
+ }
+ } while (i--);
+
+ return 0;
+}
+
+function lookBehindFor(data: string, pos: number, str: string) {
+ let i = str.length;
+ const endPos = pos - i + 1;
+ if (endPos < 0) return -1;
+
+ while (i--) {
+ if (data.charCodeAt(endPos + i) !== str.charCodeAt(i)) {
+ return -1;
+ }
+ }
+
+ return endPos;
+}
+
+function lookAheadFor(data: string, pos: number, str: string) {
+ let i = str.length;
+ const endPos = pos + i;
+ if (endPos > data.length) return -1;
+
+ while (i--) {
+ if (data.charCodeAt(pos + i) !== str.charCodeAt(i)) {
+ return -1;
+ }
+ }
+
+ return endPos - 1;
+}
diff --git a/src/states/INLINE_SCRIPT.ts b/src/states/INLINE_SCRIPT.ts
index 2b8c462d..83e2d3cb 100644
--- a/src/states/INLINE_SCRIPT.ts
+++ b/src/states/INLINE_SCRIPT.ts
@@ -1,4 +1,11 @@
-import { CODE, Range, STATE, StateDefinition, Meta } from "../internal";
+import {
+ CODE,
+ Range,
+ STATE,
+ StateDefinition,
+ Meta,
+ matchesCloseCurlyBrace,
+} from "../internal";
interface ScriptletMeta extends Meta {
block: boolean;
@@ -43,11 +50,11 @@ export const INLINE_SCRIPT: StateDefinition = {
if (code === CODE.OPEN_CURLY_BRACE) {
inlineScript.block = true;
this.pos++; // skip {
- const expr = this.enterState(STATE.EXPRESSION);
- expr.terminator = CODE.CLOSE_CURLY_BRACE;
- expr.skipOperators = true;
+ this.enterState(STATE.EXPRESSION).shouldTerminate =
+ matchesCloseCurlyBrace;
} else {
const expr = this.enterState(STATE.EXPRESSION);
+ expr.operators = true;
expr.terminatedByEOL = true;
}
},
diff --git a/src/states/OPEN_TAG.ts b/src/states/OPEN_TAG.ts
index e8ae3d0d..ca844836 100644
--- a/src/states/OPEN_TAG.ts
+++ b/src/states/OPEN_TAG.ts
@@ -7,9 +7,11 @@ import {
Meta,
TagType,
ErrorCode,
+ matchesPipe,
+ matchesCloseParen,
} from "../internal";
-export const enum TAG_STAGE {
+export enum TAG_STAGE {
UNKNOWN,
VAR,
ARGUMENT,
@@ -32,24 +34,6 @@ export interface OpenTagMeta extends Meta {
nestedIndent: string | undefined;
parentTag: OpenTagMeta | undefined;
}
-const CONCISE_TAG_VAR_TERMINATORS = [
- CODE.SEMICOLON,
- CODE.OPEN_PAREN,
- CODE.PIPE,
- CODE.EQUAL,
- CODE.COMMA,
- [CODE.COLON, CODE.EQUAL],
-];
-
-const HTML_TAG_VAR_TERMINATORS = [
- CODE.CLOSE_ANGLE_BRACKET,
- CODE.OPEN_PAREN,
- CODE.PIPE,
- CODE.EQUAL,
- CODE.COMMA,
- [CODE.COLON, CODE.EQUAL],
- [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET],
-];
export const OPEN_TAG: StateDefinition = {
name: "OPEN_TAG",
@@ -309,10 +293,11 @@ export const OPEN_TAG: StateDefinition = {
}
const expr = this.enterState(STATE.EXPRESSION);
+ expr.operators = true;
expr.terminatedByWhitespace = true;
- expr.terminator = this.isConcise
- ? CONCISE_TAG_VAR_TERMINATORS
- : HTML_TAG_VAR_TERMINATORS;
+ expr.shouldTerminate = this.isConcise
+ ? shouldTerminateConciseTagVar
+ : shouldTerminateHtmlTagVar;
} else if (code === CODE.OPEN_PAREN && !tag.hasAttrs) {
if (tag.hasArgs) {
this.emitError(
@@ -325,16 +310,12 @@ export const OPEN_TAG: StateDefinition = {
tag.stage = TAG_STAGE.ARGUMENT;
this.pos++; // skip (
this.forward = 0;
- const expr = this.enterState(STATE.EXPRESSION);
- expr.skipOperators = true;
- expr.terminator = CODE.CLOSE_PAREN;
+ this.enterState(STATE.EXPRESSION).shouldTerminate = matchesCloseParen;
} else if (code === CODE.PIPE && !tag.hasAttrs) {
tag.stage = TAG_STAGE.PARAMS;
this.pos++; // skip |
this.forward = 0;
- const expr = this.enterState(STATE.EXPRESSION);
- expr.skipOperators = true;
- expr.terminator = CODE.PIPE;
+ this.enterState(STATE.EXPRESSION).shouldTerminate = matchesPipe;
} else {
this.forward = 0;
@@ -416,3 +397,35 @@ export const OPEN_TAG: StateDefinition = {
}
},
};
+
+function shouldTerminateConciseTagVar(code: number, data: string, pos: number) {
+ switch (code) {
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.PIPE:
+ case CODE.OPEN_PAREN:
+ case CODE.SEMICOLON:
+ return true;
+ case CODE.COLON:
+ return data.charCodeAt(pos + 1) === CODE.EQUAL;
+ default:
+ return false;
+ }
+}
+
+function shouldTerminateHtmlTagVar(code: number, data: string, pos: number) {
+ switch (code) {
+ case CODE.PIPE:
+ case CODE.COMMA:
+ case CODE.EQUAL:
+ case CODE.OPEN_PAREN:
+ case CODE.CLOSE_ANGLE_BRACKET:
+ return true;
+ case CODE.COLON:
+ return data.charCodeAt(pos + 1) === CODE.EQUAL;
+ case CODE.FORWARD_SLASH:
+ return data.charCodeAt(pos + 1) === CODE.CLOSE_ANGLE_BRACKET;
+ default:
+ return false;
+ }
+}
diff --git a/src/states/PLACEHOLDER.ts b/src/states/PLACEHOLDER.ts
index c3a887db..3e0929ae 100644
--- a/src/states/PLACEHOLDER.ts
+++ b/src/states/PLACEHOLDER.ts
@@ -5,6 +5,7 @@ import {
StateDefinition,
Meta,
ErrorCode,
+ matchesCloseCurlyBrace,
} from "../internal";
interface PlaceholderMeta extends Meta {
@@ -94,7 +95,8 @@ export function checkForPlaceholder(parser: Parser, code: number) {
parser.enterState(PLACEHOLDER).escape = escape;
parser.pos += escape ? 2 : 3; // skip ${ or $!{
parser.forward = 0;
- parser.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_CURLY_BRACE;
+ parser.enterState(STATE.EXPRESSION).shouldTerminate =
+ matchesCloseCurlyBrace;
return true;
}
}
diff --git a/src/states/TAG_NAME.ts b/src/states/TAG_NAME.ts
index e45ff421..183e1746 100644
--- a/src/states/TAG_NAME.ts
+++ b/src/states/TAG_NAME.ts
@@ -7,6 +7,7 @@ import {
Meta,
TagType,
ErrorCode,
+ matchesCloseCurlyBrace,
} from "../internal";
export interface TagNameMeta extends Meta, Ranges.Template {
@@ -94,7 +95,9 @@ export const TAG_NAME: StateDefinition = {
);
}
- this.enterState(STATE.EXPRESSION).terminatedByEOL = true;
+ const expr = this.enterState(STATE.EXPRESSION);
+ expr.operators = true;
+ expr.terminatedByEOL = true;
}
}
@@ -110,7 +113,8 @@ export const TAG_NAME: StateDefinition = {
) {
this.pos += 2; // skip ${
this.forward = 0;
- this.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_CURLY_BRACE;
+ this.enterState(STATE.EXPRESSION).shouldTerminate =
+ matchesCloseCurlyBrace;
} else if (
isWhitespaceCode(code) ||
code === CODE.EQUAL ||
diff --git a/src/states/TEMPLATE_STRING.ts b/src/states/TEMPLATE_STRING.ts
index 95893a3a..7e4bf7f3 100644
--- a/src/states/TEMPLATE_STRING.ts
+++ b/src/states/TEMPLATE_STRING.ts
@@ -1,4 +1,10 @@
-import { CODE, ErrorCode, STATE, StateDefinition } from "../internal";
+import {
+ CODE,
+ ErrorCode,
+ STATE,
+ StateDefinition,
+ matchesCloseCurlyBrace,
+} from "../internal";
export const TEMPLATE_STRING: StateDefinition = {
name: "TEMPLATE_STRING",
@@ -15,21 +21,21 @@ export const TEMPLATE_STRING: StateDefinition = {
exit() {},
char(code) {
- if (
- code === CODE.DOLLAR &&
- this.lookAtCharCodeAhead(1) === CODE.OPEN_CURLY_BRACE
- ) {
- this.pos++; // skip {
- const expr = this.enterState(STATE.EXPRESSION);
- expr.skipOperators = true;
- expr.terminator = CODE.CLOSE_CURLY_BRACE;
- } else {
- if (code === CODE.BACK_SLASH) {
+ switch (code) {
+ case CODE.DOLLAR:
+ if (this.lookAtCharCodeAhead(1) === CODE.OPEN_CURLY_BRACE) {
+ this.pos++; // skip {
+ this.enterState(STATE.EXPRESSION).shouldTerminate =
+ matchesCloseCurlyBrace;
+ }
+ break;
+ case CODE.BACK_SLASH:
this.pos++; // skip \
- } else if (code === CODE.BACKTICK) {
+ break;
+ case CODE.BACKTICK:
this.pos++; // skip `
this.exitState();
- }
+ break;
}
},
diff --git a/src/util/constants.ts b/src/util/constants.ts
index 3ed47da0..9ac8a85d 100644
--- a/src/util/constants.ts
+++ b/src/util/constants.ts
@@ -27,6 +27,7 @@ export const enum CODE {
DOLLAR = 36,
PERCENT = 37,
PERIOD = 46,
+ PLUS = 43,
COMMA = 44,
COLON = 58,
SEMICOLON = 59,
@@ -36,6 +37,10 @@ export const enum CODE {
CARRIAGE_RETURN = 13,
SPACE = 32,
TAB = 9,
+ AMPERSAND = 38,
+ CARET = 94,
+ TILDE = 126,
+ UNDERSCORE = 95,
}
// Same format as https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
diff --git a/src/util/util.ts b/src/util/util.ts
index 65a96bcf..090d568f 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -68,6 +68,18 @@ export function htmlEOF(this: Parser) {
}
}
+export function matchesCloseParen(code: number) {
+ return code === CODE.CLOSE_PAREN;
+}
+
+export function matchesCloseCurlyBrace(code: number) {
+ return code === CODE.CLOSE_CURLY_BRACE;
+}
+
+export function matchesPipe(code: number) {
+ return code === CODE.PIPE;
+}
+
function getPosAfterLine(
lines: number[],
startLine: number,