Skip to content

Commit

Permalink
feat: improve continue expressions with equals
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanPiercey committed Aug 6, 2022
1 parent 34cc9e8 commit f886c27
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,39 @@
│ ├─ 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\") }"
│ │ ╰─ attrName
│ ├─ 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
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/fixtures/attr-complex-functions/input.marko
Original file line number Diff line number Diff line change
@@ -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
13 changes: 9 additions & 4 deletions src/states/ATTRIBUTE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Meta,
ErrorCode,
} from "../internal";
import { OPERATOR_TERMINATOR } from "./EXPRESSION";

const enum ATTR_STAGE {
UNKNOWN,
Expand Down Expand Up @@ -109,9 +110,14 @@ export const ATTRIBUTE: StateDefinition<AttrMeta> = {
attr.stage = ATTR_STAGE.VALUE;
const expr = this.enterState(STATE.EXPRESSION);
expr.terminatedByWhitespace = true;
expr.terminator = this.isConcise
? CONCISE_VALUE_TERMINATORS
: HTML_VALUE_TERMINATORS;

if (this.isConcise) {
expr.terminator = CONCISE_VALUE_TERMINATORS;
expr.operatorTerminator = OPERATOR_TERMINATOR.Hyphens;
} else {
expr.terminator = HTML_VALUE_TERMINATORS;
expr.operatorTerminator = OPERATOR_TERMINATOR.CloseAngleBracket;
}
} else if (code === CODE.OPEN_PAREN) {
ensureAttrName(this, attr);
attr.stage = ATTR_STAGE.ARGUMENT;
Expand All @@ -124,7 +130,6 @@ export const ATTRIBUTE: StateDefinition<AttrMeta> = {
this.pos++; // skip {
this.forward = 0;
const expr = this.enterState(STATE.EXPRESSION);
expr.terminatedByWhitespace = false;
expr.terminator = CODE.CLOSE_CURLY_BRACE;
} else if (attr.stage === ATTR_STAGE.UNKNOWN) {
attr.stage = ATTR_STAGE.NAME;
Expand Down
98 changes: 68 additions & 30 deletions src/states/EXPRESSION.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@ import {
ErrorCode,
} from "../internal";

export enum OPERATOR_TERMINATOR {
// All operators are ok.
None = 0,
// Prevents double hyphens (set when in concise mode attrs).
Hyphens = 1,
// Prevent equals continuing an expression (set for tag variable expression)
AttrValue = 1 << 1,
// Prevent close angle bracket continuing an expression (set for html mode attrs)
CloseAngleBracket = 1 << 2,
}

export interface ExpressionMeta extends Meta {
groupStack: number[];
terminator: number | (number | number[])[];
skipOperators: boolean;
terminatedByEOL: boolean;
terminatedByWhitespace: boolean;
operatorTerminator: OPERATOR_TERMINATOR;
}

const unaryKeywords = [
Expand Down Expand Up @@ -43,6 +55,7 @@ export const EXPRESSION: StateDefinition<ExpressionMeta> = {
skipOperators: false,
terminatedByEOL: false,
terminatedByWhitespace: false,
operatorTerminator: OPERATOR_TERMINATOR.None,
};
},

Expand Down Expand Up @@ -227,7 +240,15 @@ function checkForOperators(
}
const terminatedByEOL = expression.terminatedByEOL || parser.isConcise;
if (!(terminatedByEOL && eol)) {
const lookAheadPos = lookAheadForOperator(data, pos, terminatedByEOL);
const lookAheadPos = lookAheadForOperator(
data,
lookAheadWhile(
terminatedByEOL ? isIndentCode : isWhitespaceCode,
data,
pos + 1
),
expression.operatorTerminator
);
if (lookAheadPos !== -1) {
parser.pos = lookAheadPos;
parser.forward = 0;
Expand Down Expand Up @@ -312,13 +333,9 @@ function lookBehindForOperator(data: string, pos: number): number {
function lookAheadForOperator(
data: string,
pos: number,
terminatedByEOL: boolean
terminator: OPERATOR_TERMINATOR
): number {
const isSpace = terminatedByEOL ? isIndentCode : isWhitespaceCode;
const curPos = lookAheadWhile(isSpace, data, pos + 1);
const code = data.charCodeAt(curPos);

switch (code) {
switch (data.charCodeAt(pos)) {
case CODE.AMPERSAND:
case CODE.ASTERISK:
case CODE.CARET:
Expand All @@ -329,65 +346,86 @@ function lookAheadForOperator(
case CODE.QUESTION:
case CODE.TILDE:
case CODE.PLUS:
return curPos + 1;
return pos + 1;

case CODE.HYPHEN: {
// Can't match -- when in concise mode.
return terminatedByEOL && data.charCodeAt(curPos + 1) === CODE.HYPHEN
return (terminator & OPERATOR_TERMINATOR.Hyphens) ===
OPERATOR_TERMINATOR.Hyphens && data.charCodeAt(pos + 1) === CODE.HYPHEN
? -1
: curPos + 1;
: pos + 1;
}

case CODE.OPEN_CURLY_BRACE:
case CODE.OPEN_PAREN:
return curPos; // defers to base expression state to track block groups.
return pos; // defers to base expression state to track block groups.

case CODE.FORWARD_SLASH:
return terminatedByEOL ||
// Only match a / in html mode if it's not `/>`
data.charCodeAt(curPos + 1) !== CODE.CLOSE_ANGLE_BRACKET
? curPos // defers to base expression state to track regexp groups.
: -1;
return (terminator & OPERATOR_TERMINATOR.CloseAngleBracket) ===
OPERATOR_TERMINATOR.CloseAngleBracket &&
data.charCodeAt(pos + 1) === CODE.CLOSE_ANGLE_BRACKET
? -1
: pos; // defers to base expression state to track regexp groups.

case CODE.COLON:
// Only match a colon if its not :=
return data.charCodeAt(curPos + 1) === CODE.EQUAL ? -1 : curPos + 1;
return (terminator & OPERATOR_TERMINATOR.AttrValue) ===
OPERATOR_TERMINATOR.AttrValue && data.charCodeAt(pos + 1) === CODE.EQUAL
? -1
: pos + 1;

case CODE.PERIOD:
// Only match a dot if its not ...
return data.charCodeAt(curPos + 1) === CODE.PERIOD ? -1 : curPos + 1;
return data.charCodeAt(pos + 1) === CODE.PERIOD ? -1 : pos + 1;

case CODE.CLOSE_ANGLE_BRACKET:
// In concise mode a > is a normal operator.
if (terminatedByEOL) return curPos + 1;
if (
(terminator & OPERATOR_TERMINATOR.CloseAngleBracket) !==
OPERATOR_TERMINATOR.CloseAngleBracket
) {
return pos + 1;
}

// Otherwise we match >=, >> and >>>
switch (data.charCodeAt(curPos + 1)) {
switch (data.charCodeAt(pos + 1)) {
case CODE.EQUAL:
return curPos + 2;
return pos + 2;
case CODE.CLOSE_ANGLE_BRACKET:
return (
curPos +
(data.charCodeAt(curPos + 2) === CODE.CLOSE_ANGLE_BRACKET ? 3 : 2)
pos +
(data.charCodeAt(pos + 2) === CODE.CLOSE_ANGLE_BRACKET ? 3 : 2)
);
default:
return -1;
}
case CODE.EQUAL:
if (
(terminator & OPERATOR_TERMINATOR.AttrValue) ===
OPERATOR_TERMINATOR.AttrValue
) {
return -1;
}

if (
(terminator & OPERATOR_TERMINATOR.CloseAngleBracket) !==
OPERATOR_TERMINATOR.CloseAngleBracket
) {
return pos + 1;
}

// We'll match ==, ===, and =>
// This is primarily to support => since otherwise it'd end some expressions.
switch (data.charCodeAt(curPos + 1)) {
// This is primarily to support => since otherwise it'd end a CloseAngleBracket terminator.
switch (data.charCodeAt(pos + 1)) {
case CODE.CLOSE_ANGLE_BRACKET:
return curPos + 2;
return pos + 2;
case CODE.EQUAL:
return curPos + (data.charCodeAt(curPos + 2) === CODE.EQUAL ? 3 : 2);
return pos + (data.charCodeAt(pos + 2) === CODE.EQUAL ? 3 : 2);
default:
return -1;
}

default: {
for (const keyword of binaryKeywords) {
let nextPos = lookAheadFor(data, curPos, keyword);
let nextPos = lookAheadFor(data, pos, keyword);
if (nextPos === -1) continue;

const max = data.length - 1;
Expand Down
16 changes: 12 additions & 4 deletions src/states/OPEN_TAG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
TagType,
ErrorCode,
} from "../internal";
import { OPERATOR_TERMINATOR } from "./EXPRESSION";

export const enum TAG_STAGE {
const enum TAG_STAGE {
UNKNOWN,
VAR,
ARGUMENT,
Expand Down Expand Up @@ -310,9 +311,16 @@ export const OPEN_TAG: StateDefinition<OpenTagMeta> = {

const expr = this.enterState(STATE.EXPRESSION);
expr.terminatedByWhitespace = true;
expr.terminator = this.isConcise
? CONCISE_TAG_VAR_TERMINATORS
: HTML_TAG_VAR_TERMINATORS;

if (this.isConcise) {
expr.terminator = CONCISE_TAG_VAR_TERMINATORS;
expr.operatorTerminator =
OPERATOR_TERMINATOR.AttrValue | OPERATOR_TERMINATOR.Hyphens;
} else {
expr.terminator = HTML_TAG_VAR_TERMINATORS;
expr.operatorTerminator =
OPERATOR_TERMINATOR.AttrValue | OPERATOR_TERMINATOR.CloseAngleBracket;
}
} else if (code === CODE.OPEN_PAREN && !tag.hasAttrs) {
if (tag.hasArgs) {
this.emitError(
Expand Down

0 comments on commit f886c27

Please sign in to comment.