add function declaration with input params<

This commit is contained in:
Kurt Hutten IrevDev
2022-11-18 08:20:18 +11:00
parent 7ebfba46e2
commit c7be6e592c
2 changed files with 285 additions and 95 deletions

View File

@ -3,21 +3,22 @@ import { lexer } from "./tokeniser";
describe("findClosingBrace", () => { describe("findClosingBrace", () => {
test("finds the closing brace", () => { test("finds the closing brace", () => {
const basic = "( hey )" const basic = "( hey )";
expect(findClosingBrace(lexer(basic), 0)).toBe(4) expect(findClosingBrace(lexer(basic), 0)).toBe(4);
const handlesNonZeroIndex = "(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)" const handlesNonZeroIndex =
expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4) "(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)";
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6) expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4);
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6);
const handlesNested = "{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }" const handlesNested =
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18) "{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }";
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18);
// throws when not started on a brace // throws when not started on a brace
expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow() expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow();
});
}) });
})
describe("testing AST", () => { describe("testing AST", () => {
test("test 5 + 6", () => { test("test 5 + 6", () => {
@ -167,44 +168,44 @@ const newVar = myVar + 1
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens);
expect(body).toEqual([ expect(body).toEqual([
{ {
"type": "ExpressionStatement", type: "ExpressionStatement",
"start": 0, start: 0,
"end": 28, end: 28,
"expression": { expression: {
"type": "CallExpression", type: "CallExpression",
"start": 0, start: 0,
"end": 28, end: 28,
"callee": { callee: {
"type": "Identifier", type: "Identifier",
"start": 0, start: 0,
"end": 3, end: 3,
"name": "log" name: "log",
}, },
"arguments": [ arguments: [
{ {
"type": "Literal", type: "Literal",
"start": 4, start: 4,
"end": 5, end: 5,
"value": 5, value: 5,
"raw": "5" raw: "5",
}, },
{ {
"type": "Literal", type: "Literal",
"start": 7, start: 7,
"end": 14, end: 14,
"value": "hello", value: "hello",
"raw": "\"hello\"" raw: '"hello"',
}, },
{ {
"type": "Identifier", type: "Identifier",
"start": 16, start: 16,
"end": 27, end: 27,
"name": "aIdentifier" name: "aIdentifier",
} },
], ],
"optional": false optional: false,
} },
} },
]); ]);
}); });
}); });
@ -212,42 +213,117 @@ const newVar = myVar + 1
describe("testing function declaration", () => { describe("testing function declaration", () => {
test("fn funcN = () => {}", () => { test("fn funcN = () => {}", () => {
const tokens = lexer("fn funcN = () => {}"); const tokens = lexer("fn funcN = () => {}");
// const tokens = lexer("const fn = () => {}"); const { body } = abstractSyntaxTree(tokens);
const {body} = abstractSyntaxTree(tokens);
// console.log(JSON.stringify(body, null, 2));
expect(body).toEqual([ expect(body).toEqual([
{ {
"type": "VariableDeclaration", type: "VariableDeclaration",
"start": 0, start: 0,
"end": 19, end: 19,
"kind": "fn", kind: "fn",
"declarations": [ declarations: [
{ {
"type": "VariableDeclarator", type: "VariableDeclarator",
"start": 3, start: 3,
"end": 19, end: 19,
"id": { id: {
"type": "Identifier", type: "Identifier",
"start": 3, start: 3,
"end": 8, end: 8,
"name": "funcN" name: "funcN",
},
init: {
type: "FunctionExpression",
start: 11,
end: 19,
id: null,
params: [],
body: {
type: "BlockStatement",
start: 17,
end: 19,
body: [],
},
},
},
],
},
]);
});
test("fn funcN = (a, b) => {return a + b}", () => {
const tokens = lexer(
["fn funcN = (a, b) => {", " return a + b", "}"].join("\n")
);
const { body } = abstractSyntaxTree(tokens);
expect(body).toEqual([
{
type: "VariableDeclaration",
start: 0,
end: 39,
kind: "fn",
declarations: [
{
type: "VariableDeclarator",
start: 3,
end: 39,
id: {
type: "Identifier",
start: 3,
end: 8,
name: "funcN",
},
init: {
type: "FunctionExpression",
start: 11,
end: 39,
id: null,
params: [
{
type: "Identifier",
start: 12,
end: 13,
name: "a",
},
{
type: "Identifier",
start: 15,
end: 16,
name: "b",
},
],
body: {
type: "BlockStatement",
start: 21,
end: 39,
body: [
{
type: "ReturnStatement",
start: 25,
end: 37,
argument: {
type: "BinaryExpression",
start: 32,
end: 37,
left: {
type: "Identifier",
start: 32,
end: 33,
name: "a",
},
operator: "+",
right: {
type: "Identifier",
start: 36,
end: 37,
name: "b",
},
},
},
],
},
},
},
],
}, },
"init": {
"type": "FunctionExpression",
"start": 11,
"end": 19,
"id": null,
"params": [],
"body": {
"type": "BlockStatement",
"start": 17,
"end": 19,
"body": []
}
}
}
]
}
]); ]);
}); });
}); });

View File

@ -88,7 +88,7 @@ interface GeneralStatement {
interface ExpressionStatement extends GeneralStatement { interface ExpressionStatement extends GeneralStatement {
type: "ExpressionStatement"; type: "ExpressionStatement";
expression: BinaryExpression | CallExpression; expression: Value;
} }
function makeExpressionStatement( function makeExpressionStatement(
@ -125,7 +125,7 @@ function makeExpressionStatement(
interface CallExpression extends GeneralStatement { interface CallExpression extends GeneralStatement {
type: "CallExpression"; type: "CallExpression";
callee: Identifier; callee: Identifier;
arguments: VariableDeclarator["init"][]; arguments: Value[];
optional: boolean; optional: boolean;
} }
@ -159,9 +159,9 @@ function makeCallExpression(
function makeArguments( function makeArguments(
tokens: Token[], tokens: Token[],
index: number, index: number,
previousArgs: VariableDeclarator["init"][] = [] previousArgs: Value[] = []
): { ): {
arguments: VariableDeclarator["init"][]; arguments: Value[];
lastIndex: number; lastIndex: number;
} { } {
const braceOrCommaToken = tokens[index]; const braceOrCommaToken = tokens[index];
@ -237,10 +237,54 @@ function makeVariableDeclaration(
}; };
} }
type Value =
| Literal
| Identifier
| BinaryExpression
| FunctionExpression
| CallExpression;
function makeValue(
tokens: Token[],
index: number
): { value: Value; lastIndex: number } {
const currentToken = tokens[index];
const { token: nextToken } = nextMeaningfulToken(tokens, index);
if (nextToken.type === "brace" && nextToken.value === "(") {
const { expression, lastIndex } = makeCallExpression(tokens, index);
return {
value: expression,
lastIndex,
};
}
if (currentToken.type === "word" && nextToken.type === "operator") {
const { expression, lastIndex } = makeBinaryExpression(tokens, index);
return {
value: expression,
lastIndex,
};
}
if (currentToken.type === "word") {
const identifier = makeIdentifier(tokens, index);
return {
value: identifier,
lastIndex: index,
};
}
if (currentToken.type === "number" || currentToken.type === "string") {
const literal = makeLiteral(tokens, index);
return {
value: literal,
lastIndex: index,
};
}
throw new Error("Expected a previous if statement to match");
}
interface VariableDeclarator extends GeneralStatement { interface VariableDeclarator extends GeneralStatement {
type: "VariableDeclarator"; type: "VariableDeclarator";
id: Identifier; id: Identifier;
init: Literal | Identifier | BinaryExpression | FunctionExpression; init: Value;
} }
function makeVariableDeclarators( function makeVariableDeclarators(
@ -255,7 +299,7 @@ function makeVariableDeclarators(
const assignmentToken = nextMeaningfulToken(tokens, index); const assignmentToken = nextMeaningfulToken(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: VariableDeclarator["init"]; let init: Value;
let lastIndex = contentsStartToken.index; let lastIndex = contentsStartToken.index;
if ( if (
contentsStartToken.token.type === "brace" && contentsStartToken.token.type === "brace" &&
@ -348,23 +392,40 @@ interface BinaryExpression extends GeneralStatement {
right: BinaryPart; right: BinaryPart;
} }
function makeBinaryPart(
tokens: Token[],
index: number
): { part: BinaryPart; lastIndex: number } {
const currentToken = tokens[index];
if (currentToken.type === "word") {
const identifier = makeIdentifier(tokens, index);
return {
part: identifier,
lastIndex: index,
};
}
if (currentToken.type === "number" || currentToken.type === "string") {
const literal = makeLiteral(tokens, index);
return {
part: literal,
lastIndex: index,
};
}
throw new Error("Expected a previous if statement to match");
}
function makeBinaryExpression( function makeBinaryExpression(
tokens: Token[], tokens: Token[],
index: number index: number
): { expression: BinaryExpression; lastIndex: number } { ): { expression: BinaryExpression; lastIndex: number } {
const currentToken = tokens[index]; const currentToken = tokens[index];
let left: BinaryPart; const { part: left } = makeBinaryPart(tokens, index);
if (currentToken.type === "word") {
left = makeIdentifier(tokens, index);
} else {
left = makeLiteral(tokens, index);
}
const { token: operatorToken, index: operatorIndex } = nextMeaningfulToken( const { token: operatorToken, index: operatorIndex } = nextMeaningfulToken(
tokens, tokens,
index index
); );
const rightToken = nextMeaningfulToken(tokens, operatorIndex); const rightToken = nextMeaningfulToken(tokens, operatorIndex);
const right = makeLiteral(tokens, rightToken.index); const { part: right } = makeBinaryPart(tokens, rightToken.index);
return { return {
expression: { expression: {
type: "BinaryExpression", type: "BinaryExpression",
@ -393,11 +454,7 @@ function makeFunctionExpression(
const closingBraceIndex = findClosingBrace(tokens, index); const closingBraceIndex = findClosingBrace(tokens, index);
const arrowToken = nextMeaningfulToken(tokens, closingBraceIndex); const arrowToken = nextMeaningfulToken(tokens, closingBraceIndex);
const bodyStartToken = nextMeaningfulToken(tokens, arrowToken.index); const bodyStartToken = nextMeaningfulToken(tokens, arrowToken.index);
// const { params, lastIndex: paramsLastIndex } = makeParams( const { params } = makeParams(tokens, index);
// tokens,
// nextToken.index
// );
const params: Identifier[] = [];
const { block, lastIndex: bodyLastIndex } = makeBlockStatement( const { block, lastIndex: bodyLastIndex } = makeBlockStatement(
tokens, tokens,
bodyStartToken.index bodyStartToken.index
@ -415,6 +472,31 @@ function makeFunctionExpression(
}; };
} }
function makeParams(
tokens: Token[],
index: number,
previousParams: Identifier[] = []
): { params: Identifier[]; lastIndex: number } {
const braceOrCommaToken = tokens[index];
const argumentToken = nextMeaningfulToken(tokens, index);
const shouldFinishRecursion =
(argumentToken.token.type === "brace" &&
argumentToken.token.value === ")") ||
(braceOrCommaToken.type === "brace" && braceOrCommaToken.value === ")");
if (shouldFinishRecursion) {
return { params: previousParams, lastIndex: index };
}
const nextBraceOrCommaToken = nextMeaningfulToken(
tokens,
argumentToken.index
);
const identifier = makeIdentifier(tokens, argumentToken.index);
return makeParams(tokens, nextBraceOrCommaToken.index, [
...previousParams,
identifier,
]);
}
interface BlockStatement extends GeneralStatement { interface BlockStatement extends GeneralStatement {
type: "BlockStatement"; type: "BlockStatement";
body: Body[]; body: Body[];
@ -441,6 +523,29 @@ function makeBlockStatement(
}; };
} }
interface ReturnStatement extends GeneralStatement {
type: "ReturnStatement";
argument: Value;
}
function makeReturnStatement(
tokens: Token[],
index: number
): { statement: ReturnStatement; lastIndex: number } {
const currentToken = tokens[index];
const nextToken = nextMeaningfulToken(tokens, index);
const { value, lastIndex } = makeValue(tokens, nextToken.index);
return {
statement: {
type: "ReturnStatement",
start: currentToken.start,
end: tokens[lastIndex].end,
argument: value,
},
lastIndex,
};
}
export type All = Program | ExpressionStatement[] | BinaryExpression | Literal; export type All = Program | ExpressionStatement[] | BinaryExpression | Literal;
function nextMeaningfulToken( function nextMeaningfulToken(
@ -459,7 +564,7 @@ function nextMeaningfulToken(
return { token, index: newIndex }; return { token, index: newIndex };
} }
type Body = ExpressionStatement | VariableDeclaration; type Body = ExpressionStatement | VariableDeclaration | ReturnStatement;
function makeBody( function makeBody(
tokens: Token[], tokens: Token[],
@ -469,7 +574,11 @@ function makeBody(
if (tokenIndex >= tokens.length) { if (tokenIndex >= tokens.length) {
return { body: previousBody, lastIndex: tokenIndex }; return { body: previousBody, lastIndex: tokenIndex };
} }
const token = tokens[tokenIndex]; const token = tokens[tokenIndex];
if (token.type === "brace" && token.value === "}") {
return { body: previousBody, lastIndex: tokenIndex };
}
if (typeof token === "undefined") { if (typeof token === "undefined") {
console.log("probably should throw"); console.log("probably should throw");
} }
@ -487,6 +596,11 @@ function makeBody(
const nextThing = nextMeaningfulToken(tokens, lastIndex); const nextThing = nextMeaningfulToken(tokens, lastIndex);
return makeBody(tokens, nextThing.index, [...previousBody, declaration]); return makeBody(tokens, nextThing.index, [...previousBody, declaration]);
} }
if (token.type === "word" && token.value === "return") {
const { statement, lastIndex } = makeReturnStatement(tokens, tokenIndex);
const nextThing = nextMeaningfulToken(tokens, lastIndex);
return makeBody(tokens, nextThing.index, [...previousBody, statement]);
}
if (token.type === "word" && token.value === "log") { if (token.type === "word" && token.value === "log") {
const { expression, lastIndex } = makeExpressionStatement( const { expression, lastIndex } = makeExpressionStatement(
tokens, tokens,