Skip to content

Commit

Permalink
support function call (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 authored Dec 21, 2023
1 parent b567083 commit 8642df4
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 9 deletions.
21 changes: 21 additions & 0 deletions src/evaluator/environment/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,24 @@ describe("get()", () => {
expect(value).toBe(null);
});
});

describe("linked environment", () => {
it("set super environment and get via sub environment", () => {
const superEnv = new Environment();
superEnv.set("foo", 42);
const subEnv = new Environment(superEnv);

const value = subEnv.get("foo");

expect(value).toBe(42);
});

it("get null if not found even in super environment", () => {
const superEnv = new Environment();
const subEnv = new Environment(superEnv);

const value = subEnv.get("foo");

expect(value).toBe(null);
});
});
16 changes: 14 additions & 2 deletions src/evaluator/environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@ export interface EnvironmentType {
}

export default class Environment implements EnvironmentType {
private readonly superEnvironment: Environment | null;
private readonly table: Map<string, any>;

constructor() {
constructor(superEnvironment: Environment | null = null) {
this.superEnvironment = superEnvironment;
this.table = new Map<string, any>;
}

get(name: string): unknown {
return this.table.get(name) ?? null;
// return if found in current environment
const fetched = this.table.get(name);
if (fetched !== undefined) {
return fetched;
}

// return value in super environment
if (this.superEnvironment === null) {
return null;
}
return this.superEnvironment.get(name);
}

set(name: string, value: any): unknown {
Expand Down
20 changes: 20 additions & 0 deletions src/evaluator/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,24 @@ describe("evaluate()", () => {
expect(evaluated).toHaveProperty("environment");
});
});

describe("call expressions", () => {
const cases = [
{
name: "function call with function literal",
input: "ν•¨μˆ˜(λ°”λ‚˜λ‚˜) { λ°”λ‚˜λ‚˜ + 1 }(42)"
},
];

it.each(cases)("evaluate $name", ({ input }) => {
const lexer = new Lexer(input);
const parser = new Parser(lexer);
const program = parser.parseProgram();
const evaluator = new Evaluator();
const environment = new Environment();
const evaluated = evaluator.evaluate(program, environment);

expect(evaluated).toBe(43);
});
});
});
31 changes: 30 additions & 1 deletion src/evaluator/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Program, Block, BranchStatement, Node } from "../parser";
import type { Program, Block, BranchStatement, Node, Expression } from "../parser";
import Environment from "./environment";

// TODO: fix any return type to specific ones (by implement value system)
Expand Down Expand Up @@ -153,6 +153,14 @@ export default class Evaluator {
const body = node.body;
return { parameters, body, environment: env };
}
if (node.type === "call") {
const functionToCall = this.evaluate(node.functionToCall, env);

const callArguments = this.parseCallArguments(node.callArguments, env);

const value = this.evaluateFunctionCall(functionToCall, callArguments);
return value;
}
if (node.type === "assignment") {
const varValue = this.evaluate(node.right, env);

Expand All @@ -178,6 +186,27 @@ export default class Evaluator {

const exhaustiveCheck: never = node;
}

private parseCallArguments(callArguments: Expression[], env: Environment): any[] {
const values = [];
for (const arg of callArguments) {
const value = this.evaluate(arg, env);
values.push(value);
}
return values;
}

private evaluateFunctionCall(functionToCall: any, callArguments: any[]): any {
const functionEnv = new Environment(functionToCall.environment);
for (let i = 0; i < functionToCall.parameters.length; ++i) {
const name = functionToCall.parameters[i].value;
const value = callArguments[i];
functionEnv.set(name, value);
}

const value = this.evaluate(functionToCall.body, functionEnv);
return value;
}
}

export { default as Environment } from "./environment";
8 changes: 6 additions & 2 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ it("execute λ§Œμ•½ 1 != 1 { 2 } μ•„λ‹ˆλ©΄ { 3 }", () => {
expect(execute("λ§Œμ•½ 1 != 1 { 2 } μ•„λ‹ˆλ©΄ { 3 }")).toBe("3");
});

it("execute assignment", () => {
expect(execute("λ³€μˆ˜1 = 4 λ³€μˆ˜2 = 9 (λ³€μˆ˜2 - λ³€μˆ˜1) * λ³€μˆ˜1")).toBe("20");
it("execute single assignment", () => {
expect(execute("λ³€μˆ˜1 = 4 λ³€μˆ˜1")).toBe("4");
});

it("execute assignment and calculation", () => {
expect(execute("λ³€μˆ˜1 = 4 λ³€μˆ˜2 = 9 ((λ³€μˆ˜1 - λ³€μˆ˜2) * λ³€μˆ˜1)")).toBe("-20");
});
148 changes: 148 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 Expand Up @@ -905,4 +1015,42 @@ describe("parseProgram()", () => {

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

describe("complex expression", () => {
const cases: { name: string, input: string, expected: Program }[] = [
{
name: "assignment and arithmetic expression",
input: "λ³€μˆ˜1 = 1 ((λ³€μˆ˜1 + λ³€μˆ˜1) * λ³€μˆ˜1)",
expected: {
type: "program",
statements: [
{
type: "expression statement",
expression: {
type: "assignment",
left: { type: "identifier", value: "λ³€μˆ˜1" },
right: { type: "number node", value: 1 },
},
},
{
type: "expression statement",
expression: {
type: "infix expression",
infix: "*",
left: {
type: "infix expression",
infix: "+",
left: { type: "identifier", value: "λ³€μˆ˜1" },
right: { type: "identifier", value: "λ³€μˆ˜1" },
},
right: { type: "identifier", value: "λ³€μˆ˜1" },
},
},
],
},
},
];

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

0 comments on commit 8642df4

Please sign in to comment.