diff --git a/.changeset/bright-clouds-yawn.md b/.changeset/bright-clouds-yawn.md
new file mode 100644
index 00000000..d28a9096
--- /dev/null
+++ b/.changeset/bright-clouds-yawn.md
@@ -0,0 +1,5 @@
+---
+"htmljs-parser": patch
+---
+
+Avoid continuing expressions after a period if after the whitespace is something that could not be an identifier.
diff --git a/src/__tests__/fixtures/scriptlet-eof/__snapshots__/scriptlet-eof.expected.txt b/src/__tests__/fixtures/scriptlet-eof/__snapshots__/scriptlet-eof.expected.txt
new file mode 100644
index 00000000..b83cf54f
--- /dev/null
+++ b/src/__tests__/fixtures/scriptlet-eof/__snapshots__/scriptlet-eof.expected.txt
@@ -0,0 +1,27 @@
+1╭─ $ $global.
+ │ │╰─ scriptlet.value "$global."
+ ╰─ ╰─ scriptlet " $global."
+2├─
+3╭─
+ │ ││ ╰─ openTagEnd
+ │ │╰─ tagName "header"
+ ╰─ ╰─ openTagStart
+4╭─
+ │ │ ││ ││ │ ││ │ ││ ╰─ openTagEnd:selfClosed "/>"
+ │ │ ││ ││ │ ││ │ │╰─ attrValue.value "\"Marko\""
+ │ │ ││ ││ │ ││ │ ╰─ attrValue "=\"Marko\""
+ │ │ ││ ││ │ ││ ╰─ attrName "alt"
+ │ │ ││ ││ │ │╰─ attrValue.value "logo"
+ │ │ ││ ││ │ ╰─ attrValue "=logo"
+ │ │ ││ ││ ╰─ attrName "src"
+ │ │ ││ │╰─ tagShorthandClass.quasis[0] "logo"
+ │ │ ││ ╰─ tagShorthandClass ".logo"
+ │ │ │╰─ tagName "img"
+ │ │ ╰─ openTagStart
+ ╰─ ╰─ text "\n "
+5╭─
+ │ │ │ ╰─ closeTagEnd(header)
+ │ │ ╰─ closeTagName "header"
+ │ ├─ text "\n"
+ ╰─ ╰─ closeTagStart ""
+6╰─
\ No newline at end of file
diff --git a/src/__tests__/fixtures/scriptlet-eof/input.marko b/src/__tests__/fixtures/scriptlet-eof/input.marko
new file mode 100644
index 00000000..3101719a
--- /dev/null
+++ b/src/__tests__/fixtures/scriptlet-eof/input.marko
@@ -0,0 +1,5 @@
+$ $global.
+
+
diff --git a/src/states/EXPRESSION.ts b/src/states/EXPRESSION.ts
index c1ca28c1..d1954cbf 100644
--- a/src/states/EXPRESSION.ts
+++ b/src/states/EXPRESSION.ts
@@ -266,12 +266,17 @@ function lookBehindForOperator(data: string, pos: number): number {
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;
+ case CODE.PERIOD: {
+ // Only matches `.` followed by something that could be an identifier.
+ const nextPos = lookAheadWhile(isWhitespaceCode, data, pos);
+ return isWordCode(data.charCodeAt(nextPos)) ? nextPos : -1;
+ }
+
// special case -- and ++
case CODE.PLUS:
case CODE.HYPHEN: {
@@ -324,9 +329,11 @@ function lookAheadForOperator(data: string, pos: number): number {
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;
+ case CODE.PERIOD: {
+ // Only matches `.` followed by something that could be an identifier.
+ const nextPos = lookAheadWhile(isWhitespaceCode, data, pos + 1);
+ return isWordCode(data.charCodeAt(nextPos)) ? nextPos : -1;
+ }
default: {
for (const keyword of binaryKeywords) {
@@ -383,6 +390,7 @@ function isWordCode(code: number) {
(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.DOLLAR ||
code === CODE.UNDERSCORE
);
}