add ast for sketch expression

This commit is contained in:
Kurt Hutten IrevDev
2022-11-20 17:43:21 +11:00
parent e5a527b519
commit dd140d041a
2 changed files with 391 additions and 115 deletions

View File

@ -329,125 +329,318 @@ describe("testing function declaration", () => {
test("call expression assignment", () => { test("call expression assignment", () => {
const tokens = lexer( const tokens = lexer(
`fn funcN = (a, b) => { return a + b } `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`); const myVar = funcN(1, 2)`
);
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens);
expect(body).toEqual([ expect(body).toEqual([
{ {
"type": "VariableDeclaration", type: "VariableDeclaration",
"start": 0, start: 0,
"end": 37, end: 37,
"kind": "fn", kind: "fn",
"declarations": [ declarations: [
{ {
"type": "VariableDeclarator", type: "VariableDeclarator",
"start": 3, start: 3,
"end": 37, end: 37,
"id": { id: {
"type": "Identifier", type: "Identifier",
"start": 3, start: 3,
"end": 8, end: 8,
"name": "funcN" name: "funcN",
}, },
"init": { init: {
"type": "FunctionExpression", type: "FunctionExpression",
"start": 11, start: 11,
"end": 37, end: 37,
"id": null, id: null,
"params": [ params: [
{ {
"type": "Identifier", type: "Identifier",
"start": 12, start: 12,
"end": 13, end: 13,
"name": "a" name: "a",
}, },
{ {
"type": "Identifier", type: "Identifier",
"start": 15, start: 15,
"end": 16, end: 16,
"name": "b" name: "b",
} },
], ],
"body": { body: {
"type": "BlockStatement", type: "BlockStatement",
"start": 21, start: 21,
"end": 37, end: 37,
"body": [ body: [
{ {
"type": "ReturnStatement", type: "ReturnStatement",
"start": 23, start: 23,
"end": 35, end: 35,
"argument": { argument: {
"type": "BinaryExpression", type: "BinaryExpression",
"start": 30, start: 30,
"end": 35, end: 35,
"left": { left: {
"type": "Identifier", type: "Identifier",
"start": 30, start: 30,
"end": 31, end: 31,
"name": "a" name: "a",
}, },
"operator": "+", operator: "+",
"right": { right: {
"type": "Identifier", type: "Identifier",
"start": 34, start: 34,
"end": 35, end: 35,
"name": "b" name: "b",
}
}
}
]
}
}
}
]
}, },
{
"type": "VariableDeclaration",
"start": 38,
"end": 63,
"kind": "const",
"declarations": [
{
"type": "VariableDeclarator",
"start": 44,
"end": 63,
"id": {
"type": "Identifier",
"start": 44,
"end": 49,
"name": "myVar"
}, },
"init": {
"type": "CallExpression",
"start": 52,
"end": 63,
"callee": {
"type": "Identifier",
"start": 52,
"end": 57,
"name": "funcN"
}, },
"arguments": [
{
"type": "Literal",
"start": 58,
"end": 59,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"start": 61,
"end": 62,
"value": 2,
"raw": "2"
}
], ],
"optional": false },
} },
} },
] ],
} },
]) {
type: "VariableDeclaration",
start: 38,
end: 63,
kind: "const",
declarations: [
{
type: "VariableDeclarator",
start: 44,
end: 63,
id: {
type: "Identifier",
start: 44,
end: 49,
name: "myVar",
},
init: {
type: "CallExpression",
start: 52,
end: 63,
callee: {
type: "Identifier",
start: 52,
end: 57,
name: "funcN",
},
arguments: [
{
type: "Literal",
start: 58,
end: 59,
value: 1,
raw: "1",
},
{
type: "Literal",
start: 61,
end: 62,
value: 2,
raw: "2",
},
],
optional: false,
},
},
],
},
]);
});
});
describe("structures specific to this lang", () => {
test("sketch", () => {
let code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,1)
path rightPath = lineTo(1,0)
close()
}
`;
const tokens = lexer(code);
const { body } = abstractSyntaxTree(tokens);
expect(body).toEqual([
{
type: "VariableDeclaration",
start: 0,
end: 102,
kind: "sketch",
declarations: [
{
type: "VariableDeclarator",
start: 7,
end: 102,
id: {
type: "Identifier",
start: 7,
end: 15,
name: "mySketch",
},
init: {
type: "SketchExpression",
start: 16,
end: 102,
body: {
type: "BlockStatement",
start: 16,
end: 102,
body: [
{
type: "VariableDeclaration",
start: 20,
end: 45,
kind: "path",
declarations: [
{
type: "VariableDeclarator",
start: 25,
end: 45,
id: {
type: "Identifier",
start: 25,
end: 31,
name: "myPath",
},
init: {
type: "CallExpression",
start: 34,
end: 45,
callee: {
type: "Identifier",
start: 34,
end: 40,
name: "lineTo",
},
arguments: [
{
type: "Literal",
start: 41,
end: 42,
value: 0,
raw: "0",
},
{
type: "Literal",
start: 43,
end: 44,
value: 1,
raw: "1",
},
],
optional: false,
},
},
],
},
{
type: "ExpressionStatement",
start: 48,
end: 59,
expression: {
type: "CallExpression",
start: 48,
end: 59,
callee: {
type: "Identifier",
start: 48,
end: 54,
name: "lineTo",
},
arguments: [
{
type: "Literal",
start: 55,
end: 56,
value: 1,
raw: "1",
},
{
type: "Literal",
start: 57,
end: 58,
value: 1,
raw: "1",
},
],
optional: false,
},
},
{
type: "VariableDeclaration",
start: 62,
end: 90,
kind: "path",
declarations: [
{
type: "VariableDeclarator",
start: 67,
end: 90,
id: {
type: "Identifier",
start: 67,
end: 76,
name: "rightPath",
},
init: {
type: "CallExpression",
start: 79,
end: 90,
callee: {
type: "Identifier",
start: 79,
end: 85,
name: "lineTo",
},
arguments: [
{
type: "Literal",
start: 86,
end: 87,
value: 1,
raw: "1",
},
{
type: "Literal",
start: 88,
end: 89,
value: 0,
raw: "0",
},
],
optional: false,
},
},
],
},
{
type: "ExpressionStatement",
start: 93,
end: 100,
expression: {
type: "CallExpression",
start: 93,
end: 100,
callee: {
type: "Identifier",
start: 93,
end: 98,
name: "close",
},
arguments: [],
optional: false,
},
},
],
},
},
},
],
},
]);
}); });
}); });

View File

@ -41,6 +41,7 @@ type syntaxType =
| "UpdateExpression" | "UpdateExpression"
// | "ArrowFunctionExpression" // | "ArrowFunctionExpression"
| "FunctionExpression" | "FunctionExpression"
| "SketchExpression"
| "YieldExpression" | "YieldExpression"
| "AwaitExpression" | "AwaitExpression"
| "ImportDeclaration" | "ImportDeclaration"
@ -200,6 +201,8 @@ function makeArguments(
...previousArgs, ...previousArgs,
literal, literal,
]); ]);
} else if (argumentToken.token.type === "brace" && argumentToken.token.value === ")") {
return makeArguments(tokens, argumentToken.index, previousArgs)
} }
throw new Error("Expected a previous if statement to match"); throw new Error("Expected a previous if statement to match");
} }
@ -207,7 +210,7 @@ function makeArguments(
interface VariableDeclaration extends GeneralStatement { interface VariableDeclaration extends GeneralStatement {
type: "VariableDeclaration"; type: "VariableDeclaration";
declarations: VariableDeclarator[]; declarations: VariableDeclarator[];
kind: "const" | "unknown" | "fn"; //| "solid" | "surface" | "face" kind: "const" | "unknown" | "fn" | "sketch" | "path"; //| "solid" | "surface" | "face"
} }
function makeVariableDeclaration( function makeVariableDeclaration(
@ -230,6 +233,10 @@ function makeVariableDeclaration(
? "const" ? "const"
: currentToken.value === "fn" : currentToken.value === "fn"
? "fn" ? "fn"
: currentToken.value === "sketch"
? "sketch"
: currentToken.value === "path"
? "path"
: "unknown", : "unknown",
declarations, declarations,
}, },
@ -242,7 +249,8 @@ type Value =
| Identifier | Identifier
| BinaryExpression | BinaryExpression
| FunctionExpression | FunctionExpression
| CallExpression; | CallExpression
| SketchExpression;
function makeValue( function makeValue(
tokens: Token[], tokens: Token[],
@ -297,6 +305,7 @@ function makeVariableDeclarators(
} { } {
const currentToken = tokens[index]; const currentToken = tokens[index];
const assignmentToken = nextMeaningfulToken(tokens, index); const assignmentToken = nextMeaningfulToken(tokens, index);
const declarationToken = previousMeaningfulToken(tokens, index);
const contentsStartToken = nextMeaningfulToken(tokens, assignmentToken.index); const contentsStartToken = nextMeaningfulToken(tokens, assignmentToken.index);
const nextAfterInit = nextMeaningfulToken(tokens, contentsStartToken.index); const nextAfterInit = nextMeaningfulToken(tokens, contentsStartToken.index);
let init: Value; let init: Value;
@ -321,14 +330,25 @@ function makeVariableDeclarators(
} else { } else {
throw new Error("TODO - handle expression with braces"); throw new Error("TODO - handle expression with braces");
} }
} else if (
declarationToken.token.type === "word" &&
declarationToken.token.value === "sketch"
) {
const sketchExp =
makeSketchExpression(tokens, assignmentToken.index);
init = sketchExp.expression
lastIndex = sketchExp.lastIndex
} else if (nextAfterInit.token?.type === "operator") { } else if (nextAfterInit.token?.type === "operator") {
const binExp = makeBinaryExpression(tokens, contentsStartToken.index); const binExp = makeBinaryExpression(tokens, contentsStartToken.index);
init = binExp.expression; init = binExp.expression;
lastIndex = binExp.lastIndex; lastIndex = binExp.lastIndex;
} else if (nextAfterInit.token?.type === "brace" && nextAfterInit.token.value === "(") { } else if (
nextAfterInit.token?.type === "brace" &&
nextAfterInit.token.value === "("
) {
const callExInfo = makeCallExpression(tokens, contentsStartToken.index); const callExInfo = makeCallExpression(tokens, contentsStartToken.index);
init = callExInfo.expression init = callExInfo.expression;
lastIndex = callExInfo.lastIndex lastIndex = callExInfo.lastIndex;
} else { } else {
init = makeLiteral(tokens, contentsStartToken.index); init = makeLiteral(tokens, contentsStartToken.index);
} }
@ -443,6 +463,30 @@ function makeBinaryExpression(
}; };
} }
interface SketchExpression extends GeneralStatement {
type: "SketchExpression";
body: BlockStatement;
}
function makeSketchExpression(
tokens: Token[],
index: number
): { expression: SketchExpression; lastIndex: number } {
const currentToken = tokens[index];
const { block, lastIndex: bodyLastIndex } = makeBlockStatement(tokens, index);
const endToken = tokens[bodyLastIndex];
return {
expression: {
type: "SketchExpression",
start: currentToken.start,
end: endToken.end,
body: block,
},
lastIndex: bodyLastIndex,
};
}
interface FunctionExpression extends GeneralStatement { interface FunctionExpression extends GeneralStatement {
type: "FunctionExpression"; type: "FunctionExpression";
id: Identifier | null; id: Identifier | null;
@ -515,7 +559,7 @@ function makeBlockStatement(
const { body, lastIndex } = const { body, lastIndex } =
nextToken.token.value === "}" nextToken.token.value === "}"
? { body: [], lastIndex: nextToken.index } ? { body: [], lastIndex: nextToken.index }
: makeBody(tokens, nextToken.index); : makeBody({ tokens, tokenIndex: nextToken.index });
return { return {
block: { block: {
type: "BlockStatement", type: "BlockStatement",
@ -568,11 +612,32 @@ function nextMeaningfulToken(
return { token, index: newIndex }; return { token, index: newIndex };
} }
function previousMeaningfulToken(
tokens: Token[],
index: number,
offset: number = 1
): { token: Token; index: number } {
const newIndex = index - offset;
const token = tokens[newIndex];
if (!token) {
return { token, index: 0 };
}
if (token.type === "whitespace") {
return previousMeaningfulToken(tokens, index, offset + 1);
}
return { token, index: newIndex };
}
type Body = ExpressionStatement | VariableDeclaration | ReturnStatement; type Body = ExpressionStatement | VariableDeclaration | ReturnStatement;
function makeBody( function makeBody(
tokens: Token[], {
tokenIndex: number = 0, tokens,
tokenIndex = 0,
}: {
tokens: Token[];
tokenIndex?: number;
},
previousBody: Body[] = [] previousBody: Body[] = []
): { body: Body[]; lastIndex: number } { ): { body: Body[]; lastIndex: number } {
if (tokenIndex >= tokens.length) { if (tokenIndex >= tokens.length) {
@ -587,31 +652,48 @@ function makeBody(
console.log("probably should throw"); console.log("probably should throw");
} }
if (token.type === "whitespace") { if (token.type === "whitespace") {
return makeBody(tokens, tokenIndex + 1, previousBody); return makeBody({ tokens, tokenIndex: tokenIndex + 1 }, previousBody);
} }
const nextToken = nextMeaningfulToken(tokens, tokenIndex); const nextToken = nextMeaningfulToken(tokens, tokenIndex);
if ( if (
token.type === "word" && token.type === "word" &&
(token.value === "const" || token.value === "fn") (token.value === "const" ||
token.value === "fn" ||
token.value === "sketch" ||
token.value === "path")
) { ) {
const { declaration, lastIndex } = makeVariableDeclaration( const { declaration, lastIndex } = makeVariableDeclaration(
tokens, tokens,
tokenIndex tokenIndex
); );
const nextThing = nextMeaningfulToken(tokens, lastIndex); const nextThing = nextMeaningfulToken(tokens, lastIndex);
return makeBody(tokens, nextThing.index, [...previousBody, declaration]); return makeBody({ tokens, tokenIndex: nextThing.index }, [
...previousBody,
declaration,
]);
} }
if (token.type === "word" && token.value === "return") { if (token.type === "word" && token.value === "return") {
const { statement, lastIndex } = makeReturnStatement(tokens, tokenIndex); const { statement, lastIndex } = makeReturnStatement(tokens, tokenIndex);
const nextThing = nextMeaningfulToken(tokens, lastIndex); const nextThing = nextMeaningfulToken(tokens, lastIndex);
return makeBody(tokens, nextThing.index, [...previousBody, statement]); return makeBody({ tokens, tokenIndex: nextThing.index }, [
...previousBody,
statement,
]);
} }
if (token.type === "word" && nextToken.token.type === "brace" && nextToken.token.value === '(') { if (
token.type === "word" &&
nextToken.token.type === "brace" &&
nextToken.token.value === "("
) {
const { expression, lastIndex } = makeExpressionStatement( const { expression, lastIndex } = makeExpressionStatement(
tokens, tokens,
tokenIndex tokenIndex
); );
return { body: [...previousBody, expression], lastIndex }; const nextThing = nextMeaningfulToken(tokens, lastIndex);
return makeBody({ tokens, tokenIndex: nextThing.index }, [
...previousBody,
expression,
]);
} }
if ( if (
(token.type === "number" || token.type === "word") && (token.type === "number" || token.type === "word") &&
@ -624,10 +706,11 @@ function makeBody(
// return startTree(tokens, tokenIndex, [...previousBody, makeExpressionStatement(tokens, tokenIndex)]); // return startTree(tokens, tokenIndex, [...previousBody, makeExpressionStatement(tokens, tokenIndex)]);
return { body: [...previousBody, expression], lastIndex }; return { body: [...previousBody, expression], lastIndex };
} }
console.log("should throw", tokens.slice(tokenIndex));
throw new Error("Unexpected token"); throw new Error("Unexpected token");
} }
export const abstractSyntaxTree = (tokens: Token[]): Program => { export const abstractSyntaxTree = (tokens: Token[]): Program => {
const { body } = makeBody(tokens); const { body } = makeBody({ tokens });
const program: Program = { const program: Program = {
type: "Program", type: "Program",
start: 0, start: 0,