Skip to content

Commit

Permalink
Allow balanced parens in function arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
siefkenj committed Sep 7, 2023
1 parent 51d2271 commit 7779ded
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ export function gobbleFunctionArguments(
i++;
continue;
}
if (nextNode.value === "(" && nodes[i - 1]?.type === "function") {
if (nextNode.value === "(") {
parenDepth++;
}
if (nextNode.value === ",") {
// Commas separate arguments, but they may appear inside of balanced parenthesis. E.g. `$$f( (a,b) )`
// is a function with exactly one argument of `(a,b)`.
if (nextNode.value === "," && parenDepth <= 1) {
functionNode.input!.push(
trimWhitespace(currentFunctionArg as DastElementContent[]),
);
Expand Down
42 changes: 34 additions & 8 deletions packages/parser/src/macros/macros.peggy
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,31 @@ FunctionArgumentList
return [start, ...rest];
}

FunctionArgument
= arg:(
// A function argument cannot contain commas unless those commas are inside of balanced parens.
// For example `$$f( (0,1) )` should be parsed as a function with a single argument.
FunctionArgument = BalancedParenTextNoComma

BalancedParenTextNoComma
= x:(
Macro
/ FunctionMacro
/ TextWithoutParenOrComma
/ a:OpenParen rest:BalancedParenText b:CloseParen {
return [a, ...rest, b];
}
)* { return x.flat(); }
/ x:EmptyString { return [x]; }

BalancedParenText
= x:(
Macro
/ FunctionMacro
/ TextWithoutClosingParenOrComma
/ EmptyString
) { return [arg]; }
/ TextWithoutParen
/ a:OpenParen rest:BalancedParenText b:CloseParen {
return [a, ...rest, b];
}
)* { return x.flat(); }
/ x:EmptyString { return [x]; }

PropAttrs = "{" _? @(@Attr _?)* "}"

Expand Down Expand Up @@ -154,12 +172,20 @@ EmptyString
return withPosition({
type: "text",
value: "",
position: location(),
});
}

TextWithoutClosingParenOrComma
= value:($[^),$]+ / [^),]) { return withPosition({ type: "text", value }); }
OpenParen = "(" { return withPosition({ type: "text", value: "(" }); }

CloseParen = ")" { return withPosition({ type: "text", value: ")" }); }

TextWithoutParenOrComma
= value:($[^(),$]+ / [^(),]) {
return withPosition({ type: "text", value });
}

TextWithoutParen
= value:($[^()$]+ / [^()]) { return withPosition({ type: "text", value }); }

TextWithoutQuote
= value:$([^'$]+ / [^']) { return withPosition({ type: "text", value }); }
Expand Down
30 changes: 21 additions & 9 deletions packages/parser/src/macros/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,12 @@ export type FunctionMacro =
input: FunctionInput | null;
});
export type FunctionInput = FunctionArgumentList;
export type FunctionArgumentList = [FunctionArgument, ...FunctionArgument[]];
export type FunctionArgument = [
Macro | FunctionMacro | TextWithoutClosingParenOrComma | EmptyString
export type FunctionArgumentList = [
BalancedParenTextNoComma,
...BalancedParenTextNoComma[]
];
export type BalancedParenTextNoComma = (Macro | FunctionMacro | Text)[];
export type BalancedParenText = (Macro | FunctionMacro | Text)[];
export type PropAttrs = Attr[];
export type PropIndex = {
position: {
Expand Down Expand Up @@ -139,16 +141,26 @@ export type EmptyString = {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
};
} & {
type: "text";
value: "";
} & { type: "text"; value: "" };
export type OpenParen = {
position: {
source: string | undefined;
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
};
};
export type TextWithoutClosingParenOrComma = {
} & { type: "text"; value: "(" };
export type CloseParen = {
position: {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
};
} & { type: "text"; value: ")" };
export type TextWithoutParenOrComma = {
position: {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
};
} & { type: "text"; value: string };
export type TextWithoutParen = {
position: {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
Expand Down
262 changes: 262 additions & 0 deletions packages/parser/test/dast-advanced.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,4 +714,266 @@ describe("DAST", async () => {
}
`);
});
it("Function macros can have balanced parens in their arguments", () => {
let source: string;

source = `$$f((x-2), 7)`;
expect(filterPositionInfo(lezerToDast(source))).toMatchInlineSnapshot(`
{
"children": [
{
"input": [
[
{
"type": "text",
"value": "(",
},
{
"type": "text",
"value": "x-2",
},
{
"type": "text",
"value": ")",
},
],
[
{
"type": "text",
"value": "7",
},
],
],
"macro": {
"accessedProp": null,
"attributes": [],
"path": [
{
"index": [],
"name": "f",
"type": "pathPart",
},
],
"type": "macro",
},
"type": "function",
},
],
"type": "root",
}
`);

source = `$$f((3,4), (5,6))`;
expect(filterPositionInfo(lezerToDast(source))).toMatchInlineSnapshot(`
{
"children": [
{
"input": [
[
{
"type": "text",
"value": "(",
},
{
"type": "text",
"value": "3,4",
},
{
"type": "text",
"value": ")",
},
],
[
{
"type": "text",
"value": "(",
},
{
"type": "text",
"value": "5,6",
},
{
"type": "text",
"value": ")",
},
],
],
"macro": {
"accessedProp": null,
"attributes": [],
"path": [
{
"index": [],
"name": "f",
"type": "pathPart",
},
],
"type": "macro",
},
"type": "function",
},
],
"type": "root",
}
`);

source = `$$f(1,(<math>alpha</math>))`;
expect(filterPositionInfo(lezerToDast(source))).toMatchInlineSnapshot(`
{
"children": [
{
"input": [
[
{
"type": "text",
"value": "1",
},
],
[
{
"type": "text",
"value": "(",
},
{
"attributes": [],
"children": [
{
"type": "text",
"value": "alpha",
},
],
"name": "math",
"type": "element",
},
{
"type": "text",
"value": ")",
},
],
],
"macro": {
"accessedProp": null,
"attributes": [],
"path": [
{
"index": [],
"name": "f",
"type": "pathPart",
},
],
"type": "macro",
},
"type": "function",
},
],
"type": "root",
}
`);

source = `$$f(x, <math>alpha</math>)`;
expect(filterPositionInfo(lezerToDast(source))).toMatchInlineSnapshot(`
{
"children": [
{
"input": [
[
{
"type": "text",
"value": "x",
},
],
[
{
"attributes": [],
"children": [
{
"type": "text",
"value": "alpha",
},
],
"name": "math",
"type": "element",
},
],
],
"macro": {
"accessedProp": null,
"attributes": [],
"path": [
{
"index": [],
"name": "f",
"type": "pathPart",
},
],
"type": "macro",
},
"type": "function",
},
],
"type": "root",
}
`);

source = `$$f((x,y), <math>alpha</math>)`;
expect(filterPositionInfo(lezerToDast(source))).toMatchInlineSnapshot(`
{
"children": [
{
"input": [
[
{
"type": "text",
"value": "(",
},
{
"type": "text",
"value": "x",
},
{
"type": "text",
"value": ",",
},
{
"type": "text",
"value": "y",
},
{
"type": "text",
"value": ")",
},
],
[
{
"attributes": [],
"children": [
{
"type": "text",
"value": "alpha",
},
],
"name": "math",
"type": "element",
},
],
],
"macro": {
"accessedProp": null,
"attributes": [],
"path": [
{
"index": [],
"name": "f",
"type": "pathPart",
},
],
"type": "macro",
},
"type": "function",
},
],
"type": "root",
}
`);
});
});
Loading

0 comments on commit 7779ded

Please sign in to comment.