From edd8bcaed4891db77090d8599b20af28eda40a35 Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Fri, 22 Dec 2023 10:34:36 +0900 Subject: [PATCH 1/5] add return keyword --- src/lexer/token/index.test.ts | 1 + src/lexer/token/index.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lexer/token/index.test.ts b/src/lexer/token/index.test.ts index 12f7f83..70e4927 100644 --- a/src/lexer/token/index.test.ts +++ b/src/lexer/token/index.test.ts @@ -127,6 +127,7 @@ describe("keywords", () => { { input: "만약", expected: keyword("만약") }, { input: "아니면", expected: keyword("아니면") }, { input: "함수", expected: keyword("함수") }, + { input: "결과", expected: keyword("결과") }, ]; it.each(cases)("make keyword token for '$input'", ({ input, expected }) => { diff --git a/src/lexer/token/index.ts b/src/lexer/token/index.ts index d7a1b6b..ee587de 100644 --- a/src/lexer/token/index.ts +++ b/src/lexer/token/index.ts @@ -21,9 +21,10 @@ type BooleanLiteralValue = "참" | "거짓"; type GroupDelimiterValue = "(" | ")"; type BlockDelimiterValue = "{" | "}"; type SeparatorValue = ","; -type KeywordValue = BranchKeywordValue | FunctionKeywordValue; +type KeywordValue = BranchKeywordValue | FunctionKeywordValue | ReturnKeywordValue; type BranchKeywordValue = "만약" | "아니면"; type FunctionKeywordValue = "함수"; +type ReturnKeywordValue = "결과"; type EndValue = typeof END_VALUE; export interface Operator { From cf61a04233c26ca9d06efe55600a84a08d4a33f7 Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Fri, 22 Dec 2023 10:34:47 +0900 Subject: [PATCH 2/5] tokenize return keyword --- src/lexer/index.test.ts | 4 +++- src/lexer/index.ts | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lexer/index.test.ts b/src/lexer/index.test.ts index 301fbaf..27f0597 100644 --- a/src/lexer/index.test.ts +++ b/src/lexer/index.test.ts @@ -125,6 +125,7 @@ describe("getToken()", () => { { input: "만약", expected: keyword("만약") }, { input: "아니면", expected: keyword("아니면") }, { input: "함수", expected: keyword("함수") }, + { input: "결과", expected: keyword("결과") }, ]; it.each(cases)("get group delimiter token '$input'", testLexing); @@ -206,7 +207,7 @@ describe("getToken()", () => { ] }, { - input: "함수(사과, 바나나) { 사과 + 바나나 }", + input: "함수(사과, 바나나) { 결과 사과 + 바나나 }", expectedTokens:[ keyword("함수"), groupDelimiter("("), @@ -215,6 +216,7 @@ describe("getToken()", () => { identifier("바나나"), groupDelimiter(")"), blockDelimiter("{"), + keyword("결과"), identifier("사과"), operator("+"), identifier("바나나"), diff --git a/src/lexer/index.ts b/src/lexer/index.ts index ce1e10b..c464bbc 100644 --- a/src/lexer/index.ts +++ b/src/lexer/index.ts @@ -101,7 +101,12 @@ export default class Lexer { return Token.booleanLiteral(read); } - if (read === "만약" || read === "아니면" || read === "함수") { + if ( + read === "만약" || + read === "아니면" || + read === "함수" || + read === "결과" + ) { return Token.keyword(read); } From 22c424bd7a11a05a642749368638bb38819dc0f9 Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Fri, 22 Dec 2023 10:49:53 +0900 Subject: [PATCH 3/5] parse return statement --- src/evaluator/index.ts | 5 ++ src/parser/index.test.ts | 70 +++++++++++++++++++++++ src/parser/index.ts | 14 +++++ src/parser/syntax-tree/statement/index.ts | 12 ++++ 4 files changed, 101 insertions(+) diff --git a/src/evaluator/index.ts b/src/evaluator/index.ts index 490b595..c395d8e 100644 --- a/src/evaluator/index.ts +++ b/src/evaluator/index.ts @@ -218,6 +218,11 @@ export default class Evaluator { return value; } + if (node.type === "return statement") { + // TODO: implement + return {} as Evaluated; + } + const exhaustiveCheck: never = node; return exhaustiveCheck; } diff --git a/src/parser/index.test.ts b/src/parser/index.test.ts index 3dbd0a3..581cec1 100644 --- a/src/parser/index.test.ts +++ b/src/parser/index.test.ts @@ -268,6 +268,76 @@ describe("parseProgram()", () => { it.each(cases)("parse $name", testParsing); }); + describe("return statement", () => { + const cases: { name: string, input: string, expected: Program }[] = [ + { + name: "return number literal", + input: "결과 42", + expected: { + type: "program", + statements: [ + { + type: "return statement", + expression: { type: "number node", value: 42 }, + }, + ], + }, + }, + { + name: "return arithmetic expression", + input: "결과 사과 + 바나나", + expected: { + type: "program", + statements: [ + { + type: "return statement", + expression: { + type: "infix expression", + infix: "+", + left: { type: "identifier", value: "사과" }, + right: { type: "identifier", value: "바나나" }, + }, + }, + ], + }, + }, + { + name: "return function", + input: "결과 함수(사과) { 결과 사과 + 1 }", + expected: { + type: "program", + statements: [ + { + type: "return statement", + expression: { + type: "function expression", + parameter: [ + { type: "identifier", value: "사과" }, + ], + body: { + type: "block", + statements: [ + { + type: "return statement", + expression: { + type: "infix expression", + infix: "+", + left: { type: "identifier", value: "사과" }, + right: { type: "number node", value: 1 }, + }, + }, + ], + }, + }, + }, + ], + }, + }, + ]; + + it.each(cases)("parse $name", testParsing); + }); + describe("simple expression", () => { const cases: { name: string, input: string, expected: Program }[] = [ { diff --git a/src/parser/index.ts b/src/parser/index.ts index c23626e..78ea614 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -7,6 +7,7 @@ import { makeBooleanNode, makeStringNode, makeBranchStatement, + makeReturnStatement, makeExpressionStatement, makePrefixExpression, makeInfixExpression, @@ -21,6 +22,7 @@ import type { BooleanNode, StringNode, BranchStatement, + ReturnStatement, ExpressionStatement, Expression, FunctionExpression, @@ -125,6 +127,12 @@ export default class Parser { return this.parseBranchStatement(); } + if (token.type === "keyword" && token.value === "결과") { + this.buffer.next(); + + return this.parseReturnStatement(); + } + return this.parseExpressionStatement(); } @@ -146,6 +154,12 @@ export default class Parser { return branchStatement; } + private parseReturnStatement(): ReturnStatement { + const expression = this.parseExpression(bindingPower.lowest); + + return makeReturnStatement(expression); + } + private parseExpressionStatement(): ExpressionStatement { const expression = this.parseExpression(bindingPower.lowest); diff --git a/src/parser/syntax-tree/statement/index.ts b/src/parser/syntax-tree/statement/index.ts index 60c1e18..38603cb 100644 --- a/src/parser/syntax-tree/statement/index.ts +++ b/src/parser/syntax-tree/statement/index.ts @@ -3,6 +3,7 @@ import type { Expression } from "../expression"; export type Statement = BranchStatement | + ReturnStatement | ExpressionStatement; export interface BranchStatement { @@ -12,6 +13,11 @@ export interface BranchStatement { alternative?: Block; } +export interface ReturnStatement { + type: "return statement"; + expression: Expression; +} + /** A wrapper type to treat a single expression as a statement. */ export interface ExpressionStatement { type: "expression statement"; @@ -30,6 +36,12 @@ export const makeBranchStatement: MakeBranchStatement = (predicate, consequence, alternative, }); +type MakeReturnStatement = (expression: ReturnStatement["expression"]) => ReturnStatement; +export const makeReturnStatement: MakeReturnStatement = expression => ({ + type: "return statement", + expression, +}); + export const makeExpressionStatement = (expression: ExpressionStatement["expression"]): ExpressionStatement => ({ type: "expression statement", expression, From bf921d519b7aa497bf24f920ccf5ed12b99ca01c Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Fri, 22 Dec 2023 12:23:47 +0900 Subject: [PATCH 4/5] factor out specific evaluating logic out of large one for general node --- src/evaluator/index.ts | 261 +++++++++++++++++++++++------------------ 1 file changed, 150 insertions(+), 111 deletions(-) diff --git a/src/evaluator/index.ts b/src/evaluator/index.ts index c395d8e..ed02560 100644 --- a/src/evaluator/index.ts +++ b/src/evaluator/index.ts @@ -1,4 +1,17 @@ -import type { Program, Block, BranchStatement, Node, Expression } from "../parser"; +import type { + Program, + Block, + Statement, + BranchStatement, + ExpressionStatement, + Expression, + Identifier, + PrefixExpression, + InfixExpression, + FunctionExpression, + Call, + Assignment, +} from "../parser"; import { makeEvaluatedNumber, makeEvaluatedBoolean, @@ -15,12 +28,16 @@ import type { import Environment from "./environment"; export default class Evaluator { + evaluate(node: Program, env: Environment): Evaluated { + return this.evaluateProgram(node, env); + } + private evaluateProgram(node: Program, env: Environment): Evaluated { if (node.statements.length === 0) { return makeEvaluatedEmpty(); } - const evaluatedStatements = node.statements.map(statement => this.evaluate(statement, env)); + const evaluatedStatements = node.statements.map(statement => this.evaluateStatement(statement, env)); const evaluated = evaluatedStatements[evaluatedStatements.length-1]; return evaluated; @@ -31,12 +48,28 @@ export default class Evaluator { throw new Error(`block cannot be empty`); } - const evaluatedStatements = node.statements.map(statement => this.evaluate(statement, env)); + const evaluatedStatements = node.statements.map(statement => this.evaluateStatement(statement, env)); const evaluated = evaluatedStatements[evaluatedStatements.length-1]; return evaluated; } + private evaluatePrefixExpression(node: PrefixExpression, env: Environment): Evaluated { + const subExpression = this.evaluateExpression(node.expression, env); + + if ( + (node.prefix === "+" || node.prefix === "-") && + subExpression.type == "number" + ) { + return this.evaluatePrefixNumberExpression(node.prefix, subExpression); + } + if (node.prefix === "!" && subExpression.type === "boolean") { + return this.evaluatePrefixBooleanExpression(node.prefix, subExpression); + } + + throw new Error(`bad prefix expression: prefix: '${node.prefix}' with type: '${typeof subExpression}'`); + } + private evaluatePrefixNumberExpression(prefix: string, operand: EvaluatedNumber): EvaluatedNumber { if (prefix === "+") { return operand; @@ -56,14 +89,32 @@ export default class Evaluator { throw new Error(`bad prefix ${prefix}`); } + private evaluateStatement(node: Statement, env: Environment): Evaluated { + if (node.type === "branch statement") { + return this.evaluateBranchStatement(node, env); + } + + if (node.type === "expression statement") { + return this.evaluateExpressionStatement(node, env); + } + + if (node.type === "return statement") { + // TODO: implement + return {} as Evaluated; + } + + const nothing: never = node; + return nothing; + } + private evaluateBranchStatement(node: BranchStatement, env: Environment): Evaluated { - const predicate = this.evaluate(node.predicate, env); + const predicate = this.evaluateExpression(node.predicate, env); if (predicate.type !== "boolean") { throw new Error(`expected boolean expression predicate, but received ${predicate.type}`); } if (predicate.value) { - const consequence = this.evaluate(node.consequence, env); + const consequence = this.evaluateBlock(node.consequence, env); return consequence; } @@ -72,76 +123,56 @@ export default class Evaluator { return makeEvaluatedEmpty(); } - const alternative = this.evaluate(node.alternative, env); + const alternative = this.evaluateBlock(node.alternative, env); return alternative; } - private evaluateInfixExpression(infix: string, left: Evaluated, right: Evaluated): Evaluated { - // type matching order is important: more inclusive case first - - if ( - (left.type === "boolean" && right.type === "boolean") || - (left.type === "number" && right.type === "number") || - (left.type === "string" && right.type === "string") - ) { - if (infix === "==") { - return makeEvaluatedBoolean(left.value == right.value); - } - if (infix === "!=") { - return makeEvaluatedBoolean(left.value != right.value); - } - if (infix === ">") { - return makeEvaluatedBoolean(left.value > right.value); - } - if (infix === "<") { - return makeEvaluatedBoolean(left.value < right.value); - } - if (infix === ">=") { - return makeEvaluatedBoolean(left.value >= right.value); - } - if (infix === "<=") { - return makeEvaluatedBoolean(left.value <= right.value); - } - } + private evaluateExpressionStatement(node: ExpressionStatement, env: Environment): Evaluated { + return this.evaluateExpression(node.expression, env); + } - if (left.type === "number" && right.type === "number") { - if (infix === "+") { - return makeEvaluatedNumber(left.value + right.value); - } - if (infix === "-") { - return makeEvaluatedNumber(left.value - right.value); - } - if (infix === "*") { - return makeEvaluatedNumber(left.value * right.value); - } - if (infix === "/") { - // TODO: guard division by zero - return makeEvaluatedNumber(left.value / right.value); - } + private evaluateFunctionExpression(node: FunctionExpression, env: Environment): Evaluated { + const parameters = node.parameter; + const body = node.body; + return makeEvaluatedFunction(parameters, body, env); + } - throw new Error(`bad infix ${infix} for number operands`); + private evaluateCall(node: Call, env: Environment): Evaluated { + const functionToCall = this.evaluateExpression(node.functionToCall, env); + if (functionToCall.type !== "function") { + throw new Error(`expected function but received ${functionToCall.type}`); } - throw new Error(`bad infix ${infix}, with left '${left}' and right '${right}'`); + const callArguments = this.parseCallArguments(node.callArguments, env); + + const value = this.evaluateFunctionCall(functionToCall, callArguments); + return value; } - evaluate(node: Node, env: Environment): Evaluated { - if (node.type === "program") { - return this.evaluateProgram(node, env); + private evaluateAssignment(node: Assignment, env: Environment): Evaluated { + if (node.left.type !== "identifier") { + throw new Error(`expected identifier on left value, but received ${typeof node.left.type}`); } + const varName = node.left.value; + const varValue = this.evaluateExpression(node.right, env); - if (node.type === "block") { - return this.evaluateBlock(node, env); - } + env.set(varName, varValue); - if (node.type === "branch statement") { - return this.evaluateBranchStatement(node, env); - } + return varValue; // evaluated value of assignment is the evaluated value of variable + } - if (node.type === "expression statement") { - return this.evaluate(node.expression, env); + private evaluateIdentifier(node: Identifier, env: Environment): Evaluated { + const varName = node.value; + const value = env.get(varName); + + if (value === null) { + throw new Error(`identifier '${varName}' not found`); } + return value; + } + + private evaluateExpression(node: Expression, env: Environment): Evaluated { if (node.type === "number node") { return makeEvaluatedNumber(node.value); } @@ -155,82 +186,90 @@ export default class Evaluator { } if (node.type === "infix expression") { - const left = this.evaluate(node.left, env); - const right = this.evaluate(node.right, env); - - return this.evaluateInfixExpression(node.infix, left, right); + return this.evaluateInfixExpression(node, env); } if (node.type === "prefix expression") { - const subExpression = this.evaluate(node.expression, env); - - if ( - (node.prefix === "+" || node.prefix === "-") && - subExpression.type == "number" - ) { - return this.evaluatePrefixNumberExpression(node.prefix, subExpression); - } - if (node.prefix === "!" && subExpression.type === "boolean") { - return this.evaluatePrefixBooleanExpression(node.prefix, subExpression); - } - - throw new Error(`bad prefix expression: prefix: '${node.prefix}' with type: '${typeof subExpression}'`); + return this.evaluatePrefixExpression(node, env); } if (node.type === "function expression") { - const parameters = node.parameter; - const body = node.body; - return makeEvaluatedFunction(parameters, body, env); + return this.evaluateFunctionExpression(node, env); } if (node.type === "call") { - const functionToCall = this.evaluate(node.functionToCall, env); - if (functionToCall.type !== "function") { - throw new Error(`expected function but received ${functionToCall.type}`); - } - - const callArguments = this.parseCallArguments(node.callArguments, env); - - const value = this.evaluateFunctionCall(functionToCall, callArguments); - return value; + return this.evaluateCall(node, env); } if (node.type === "assignment") { - if (node.left.type !== "identifier") { - throw new Error(`expected identifier on left value, but received ${typeof node.left.type}`); - } - const varName = node.left.value; - const varValue = this.evaluate(node.right, env); - - env.set(varName, varValue); - - return varValue; // evaluated value of assignment is the evaluated value of variable + return this.evaluateAssignment(node, env); } if (node.type === "identifier") { - const varName = node.value; - const value = env.get(varName); + return this.evaluateIdentifier(node, env); + } - if (value === null) { - throw new Error(`identifier '${varName}' not found`); - } + const nothing: never = node; + return nothing; + } + + private evaluateInfixExpression(node: InfixExpression, env: Environment): Evaluated { + const infix = node.infix; + const left = this.evaluateExpression(node.left, env); + const right = this.evaluateExpression(node.right, env); + + // type matching order is important: more inclusive case first - return value; + if ( + (left.type === "boolean" && right.type === "boolean") || + (left.type === "number" && right.type === "number") || + (left.type === "string" && right.type === "string") + ) { + if (infix === "==") { + return makeEvaluatedBoolean(left.value == right.value); + } + if (infix === "!=") { + return makeEvaluatedBoolean(left.value != right.value); + } + if (infix === ">") { + return makeEvaluatedBoolean(left.value > right.value); + } + if (infix === "<") { + return makeEvaluatedBoolean(left.value < right.value); + } + if (infix === ">=") { + return makeEvaluatedBoolean(left.value >= right.value); + } + if (infix === "<=") { + return makeEvaluatedBoolean(left.value <= right.value); + } } - if (node.type === "return statement") { - // TODO: implement - return {} as Evaluated; + if (left.type === "number" && right.type === "number") { + if (infix === "+") { + return makeEvaluatedNumber(left.value + right.value); + } + if (infix === "-") { + return makeEvaluatedNumber(left.value - right.value); + } + if (infix === "*") { + return makeEvaluatedNumber(left.value * right.value); + } + if (infix === "/") { + // TODO: guard division by zero + return makeEvaluatedNumber(left.value / right.value); + } + + throw new Error(`bad infix ${infix} for number operands`); } - const exhaustiveCheck: never = node; - return exhaustiveCheck; + throw new Error(`bad infix ${infix}, with left '${left}' and right '${right}'`); } private parseCallArguments(callArguments: Expression[], env: Environment): Evaluated[] { const values = []; for (const arg of callArguments) { - const value = this.evaluate(arg, env); + const value = this.evaluateExpression(arg, env); values.push(value); } return values; @@ -244,7 +283,7 @@ export default class Evaluator { functionEnv.set(name, value); } - const value = this.evaluate(functionToCall.body, functionEnv); + const value = this.evaluateBlock(functionToCall.body, functionEnv); return value; } } From 2f689ac47e6fd8b21c1859e5605bbc5db3e25745 Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Fri, 22 Dec 2023 12:49:26 +0900 Subject: [PATCH 5/5] support closure and returning value statement --- src/evaluator/evaluated/index.test.ts | 12 ++++++ src/evaluator/evaluated/index.ts | 11 ++++++ src/evaluator/index.test.ts | 24 +++++++++++- src/evaluator/index.ts | 55 ++++++++++++++++++++------- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/evaluator/evaluated/index.test.ts b/src/evaluator/evaluated/index.test.ts index 45f024f..c2ee547 100644 --- a/src/evaluator/evaluated/index.test.ts +++ b/src/evaluator/evaluated/index.test.ts @@ -4,7 +4,9 @@ import { makeEvaluatedBoolean, makeEvaluatedFunction, makeEvaluatedEmpty, + wrapReturnValue, } from "./"; +import type { Evaluated } from "./"; import type { FunctionExpression } from "../../parser/syntax-tree"; @@ -80,3 +82,13 @@ describe("makeEvaluatedEmpty()", () => { expect(evaluated.representation).toBe("(비어있음)"); }); }); + +describe("wrapReturnValue()", () => { + it("wrap return value", () => { + const valueMock = {} as Evaluated; + + const wrapped = wrapReturnValue(valueMock); + + expect(wrapped.type).toBe("return value"); + }); +}); diff --git a/src/evaluator/evaluated/index.ts b/src/evaluator/evaluated/index.ts index 38b8e8f..b534735 100644 --- a/src/evaluator/evaluated/index.ts +++ b/src/evaluator/evaluated/index.ts @@ -35,6 +35,11 @@ export interface EvaluatedEmpty extends EvaluatedBase { readonly type: "empty"; } +export interface ReturnValue { + readonly type: "return value"; + readonly value: Evaluated; +} + export type Evaluated = EvaluatedPrimitive | EvaluatedFunction | @@ -93,3 +98,9 @@ export const makeEvaluatedEmpty: MakeEvaluatedEmpty = () => ({ return "(비어있음)"; }, }); + +export type WrapReturnValue = (value: Evaluated) => ReturnValue; +export const wrapReturnValue: WrapReturnValue = value => ({ + type: "return value", + value, +}); diff --git a/src/evaluator/index.test.ts b/src/evaluator/index.test.ts index 2cc6ec0..6118bf8 100644 --- a/src/evaluator/index.test.ts +++ b/src/evaluator/index.test.ts @@ -329,9 +329,31 @@ describe("evaluate()", () => { const cases = [ { name: "function call with function literal", - input: "함수(바나나) { 바나나 + 1 }(42)", + input: "함수(바나나) { 결과 바나나 + 1 }(42)", expected: 43, }, + { + name: "function call with identifier", + input: "더하기 = 함수(숫자1, 숫자2) { 결과 숫자1 + 숫자2 } 더하기(3, 4)", + expected: 7, + }, + ]; + + it.each(cases)("evaluate $name", testEvaluatingPrimitive); + }); + + describe("complex statements with function and calls", () => { + const cases = [ + { + name: "make and call function containing branch statement", + input: "과일 = 함수(색깔) { 만약 (색깔 == '빨강') { 결과 '사과' } 아니면 { '포도' } } 과일('빨강')", + expected: "사과", + }, + { + name: "make and call closure", + input: "더하기 = 함수(숫자1) { 결과 함수(숫자2) { 결과 숫자1+숫자2 } } 하나더하기 = 더하기(1) 하나더하기(4)", + expected: 5, + }, ]; it.each(cases)("evaluate $name", testEvaluatingPrimitive); diff --git a/src/evaluator/index.ts b/src/evaluator/index.ts index ed02560..71fed9d 100644 --- a/src/evaluator/index.ts +++ b/src/evaluator/index.ts @@ -18,12 +18,14 @@ import { makeEvaluatedString, makeEvaluatedFunction, makeEvaluatedEmpty, + wrapReturnValue, } from "./evaluated"; import type { Evaluated, EvaluatedNumber, EvaluatedBoolean, EvaluatedFunction, + ReturnValue, } from "./evaluated"; import Environment from "./environment"; @@ -33,24 +35,46 @@ export default class Evaluator { } private evaluateProgram(node: Program, env: Environment): Evaluated { - if (node.statements.length === 0) { + const { statements } = node; + if (statements.length === 0) { return makeEvaluatedEmpty(); } - const evaluatedStatements = node.statements.map(statement => this.evaluateStatement(statement, env)); - const evaluated = evaluatedStatements[evaluatedStatements.length-1]; + // loop except the last statement + for (let i = 0; i < statements.length; ++i) { + const statement = statements[i]; + const evaluated = this.evaluateStatement(statement, env); + if (evaluated.type === "return value") { + throw new Error(`return value cannot appear in top level scope`); + } + } + // return the last evaluated value + const lastStatement = statements[statements.length-1]; + const evaluated = this.evaluateStatement(lastStatement, env); + if (evaluated.type === "return value") { + throw new Error(`return value cannot appear in top level scope`); + } return evaluated; } - private evaluateBlock(node: Block, env: Environment): Evaluated { - if (node.statements.length === 0) { + private evaluateBlock(node: Block, env: Environment): Evaluated | ReturnValue { + const { statements } = node; + if (statements.length === 0) { throw new Error(`block cannot be empty`); } - const evaluatedStatements = node.statements.map(statement => this.evaluateStatement(statement, env)); - const evaluated = evaluatedStatements[evaluatedStatements.length-1]; + // loop except the last statement + for (let i = 0; i < statements.length; ++i) { + const statement = statements[i]; + const evaluated = this.evaluateStatement(statement, env); + if (evaluated.type === "return value") { // early return if return statement encoutered + return evaluated; + } + } + const lastStatement = statements[statements.length-1]; + const evaluated = this.evaluateStatement(lastStatement, env); return evaluated; } @@ -89,7 +113,7 @@ export default class Evaluator { throw new Error(`bad prefix ${prefix}`); } - private evaluateStatement(node: Statement, env: Environment): Evaluated { + private evaluateStatement(node: Statement, env: Environment): Evaluated | ReturnValue { if (node.type === "branch statement") { return this.evaluateBranchStatement(node, env); } @@ -99,15 +123,15 @@ export default class Evaluator { } if (node.type === "return statement") { - // TODO: implement - return {} as Evaluated; + const value = this.evaluateExpression(node.expression, env); + return wrapReturnValue(value); } const nothing: never = node; return nothing; } - private evaluateBranchStatement(node: BranchStatement, env: Environment): Evaluated { + private evaluateBranchStatement(node: BranchStatement, env: Environment): Evaluated | ReturnValue { const predicate = this.evaluateExpression(node.predicate, env); if (predicate.type !== "boolean") { throw new Error(`expected boolean expression predicate, but received ${predicate.type}`); @@ -283,8 +307,13 @@ export default class Evaluator { functionEnv.set(name, value); } - const value = this.evaluateBlock(functionToCall.body, functionEnv); - return value; + const evaluated = this.evaluateBlock(functionToCall.body, functionEnv); + if (evaluated.type !== "return value") { + throw new Error(`expected return value in function but it didn't`); + } + + const returnValue = evaluated.value; + return returnValue; } }