Skip to content

Commit

Permalink
parse call expression
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 committed Dec 21, 2023
1 parent 581627d commit 1e93030
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 3 deletions.
110 changes: 110 additions & 0 deletions src/parser/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,116 @@ describe("parseProgram()", () => {
it.each(cases)("parse $name", testParsing);
});

describe("calls", () => {
const cases: { name: string, input: string, expected: Program }[] = [
{
name: "call function without arguments",
input: "과일()",
expected: {
type: "program",
statements: [
{
type: "expression statement",
expression: {
type: "call",
functionToCall: { type: "identifier", value: "과일" },
callArguments: [],
},
},
],
},
},
{
name: "call function with identifier arguments",
input: "과일(사과, 바나나, 포도)",
expected: {
type: "program",
statements: [
{
type: "expression statement",
expression: {
type: "call",
functionToCall: { type: "identifier", value: "과일" },
callArguments: [
{ type: "identifier", value: "사과" },
{ type: "identifier", value: "바나나" },
{ type: "identifier", value: "포도" },
],
},
},
],
},
},
{
name: "call function with expression arguments",
input: "과일(1, 2+3)",
expected: {
type: "program",
statements: [
{
type: "expression statement",
expression: {
type: "call",
functionToCall: { type: "identifier", value: "과일" },
callArguments: [
{ type: "number node", value: 1 },
{
type: "infix expression",
infix: "+",
left: { type: "number node", value: 2 },
right: { type: "number node", value: 3 },
},
],
},
},
],
},
},
{
name: "call function with function literal",
input: "함수(사과, 바나나){사과 + 바나나}(1, 2)",
expected: {
type: "program",
statements: [
{
type: "expression statement",
expression: {
type: "call",
functionToCall: {
type: "function expression",
parameter: [
{ type: "identifier", value: "사과" },
{ type: "identifier", value: "바나나" },
],
body: {
type: "block",
statements: [
{
type: "expression statement",
expression: {
type: "infix expression",
infix: "+",
left: { type: "identifier", value: "사과" },
right: { type: "identifier", value: "바나나" },
},
},
],
},
},
callArguments: [
{ type: "number node", value: 1 },
{ type: "number node", value: 2 },
],
},
},
],
},
},
];

it.each(cases)("parse $name", testParsing);
});

describe("branch statements", () => {
const cases: { name: string, input: string, expected: Program }[] = [
{
Expand Down
57 changes: 54 additions & 3 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
makePrefixExpression,
makeInfixExpression,
makeFunctionExpression,
makeCall,
} from "./syntax-tree";
import type {
Program,
Expand All @@ -22,6 +23,8 @@ import type {
BranchStatement,
ExpressionStatement,
Expression,
FunctionExpression,
Call,
Identifier,
InfixExpression,
} from "./syntax-tree";
Expand All @@ -36,6 +39,7 @@ const bindingPower = {
summative: 50,
productive: 60,
prefix: 70,
call: 80,
};
const getBindingPower = (infix: string): BindingPower => {
switch (infix) {
Expand All @@ -54,6 +58,8 @@ const getBindingPower = (infix: string): BindingPower => {
case "*":
case "/":
return bindingPower.productive;
case "(": // when '(' is used infix operator, it behaves as call operator
return bindingPower.call;
default:
return bindingPower.lowest;
}
Expand Down Expand Up @@ -272,14 +278,21 @@ export default class Parser {
}

private parseInfixExpression(left: Expression): Expression | null {
let token = this.buffer.read();
const token = this.buffer.read();
this.buffer.next(); // eat infix token

if (token.type === "group delimiter" && token.value === "(") {
if (left.type !== "function expression" && left.type !== "identifier") {
throw new Error(`expected function expression or identifier, but received ${left.type}`);
}
return this.parseCall(left);
}

if (token.type !== "operator") {
return null;
}

const infix = token.value;
this.buffer.next(); // eat infix token

if (infix === "=" && left.type === "identifier") {
return this.parseAssignment(left);
}
Expand All @@ -300,6 +313,44 @@ export default class Parser {
return null;
}

private parseCall(functionToCall: Identifier | FunctionExpression): Call {
const callArguments = this.parseCallArguments();

return makeCall(functionToCall, callArguments);
}

private parseCallArguments(): Expression[] {
const maybeExpressionOrGroupEnd = this.buffer.read();
if (maybeExpressionOrGroupEnd.type === "group delimiter" && maybeExpressionOrGroupEnd.value === ")") {
this.buffer.next();

return [];
}

const firstArgument = this.parseExpression(bindingPower.lowest);

const callArguments = [firstArgument];
while (true) {
const maybeComma = this.buffer.read();
if (maybeComma.type !== "separator") {
break;
}
this.buffer.next();

const argument = this.parseExpression(bindingPower.lowest);
callArguments.push(argument);
}

// expect ')'
const maybeGroupEnd = this.buffer.read();
this.buffer.next();
if (maybeGroupEnd.type !== "group delimiter" || maybeGroupEnd.value !== ")") {
throw new Error(`expect ) but received ${maybeGroupEnd.type}`);
}

return callArguments;
}

private parseAssignment(left: Identifier): Expression {
const infix = "=";
const infixBindingPower = getBindingPower(infix);
Expand Down

0 comments on commit 1e93030

Please sign in to comment.