From 2ce861d3cec28d76f192aa9d8929ab197c3bbb45 Mon Sep 17 00:00:00 2001 From: "W. Cho" Date: Mon, 5 Feb 2024 22:19:15 +0900 Subject: [PATCH] support builting length function --- src/evaluator/builtin/index.ts | 26 ++++++++++++++++++++++ src/evaluator/index.test.ts | 17 +++++++++++++++ src/evaluator/index.ts | 40 +++++++++++++++++++++++++--------- src/evaluator/value/index.ts | 8 +++++-- src/index.test.ts | 4 ++++ 5 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/evaluator/builtin/index.ts diff --git a/src/evaluator/builtin/index.ts b/src/evaluator/builtin/index.ts new file mode 100644 index 0000000..be4eba5 --- /dev/null +++ b/src/evaluator/builtin/index.ts @@ -0,0 +1,26 @@ +import type * as Value from "../value"; +import * as value from "../value"; + +export type BuiltinFunction = (args: any) => Value.Value; + +const len: BuiltinFunction = (args: Value.Value[]) => { + const arg = args[0]; + if (arg.type === "string") { + const length = arg.value.length; + return value.createNumberValue({ value: length }, String(length), arg.range); + } + + throw new Error(); +}; + +const builtins = { + get(identifier: string): BuiltinFunction | null { + switch (identifier) { + case "길이": + return len; + default: + return null; + } + } +}; +export default builtins; diff --git a/src/evaluator/index.test.ts b/src/evaluator/index.test.ts index 09daf46..4e5eb22 100644 --- a/src/evaluator/index.test.ts +++ b/src/evaluator/index.test.ts @@ -357,6 +357,23 @@ describe("evaluate()", () => { it.each(cases)("evaluate $name", testEvaluatingPrimitive); }); + describe("builtin function calls", () => { + const cases = [ + { + name: "length function for empty string", + input: "길이('')", + expected: 0, + }, + { + name: "length function for nonempty string", + input: "길이('사과')", + expected: 2, + }, + ]; + + it.each(cases)("evaluate $name", testEvaluatingPrimitive); + }); + describe("errors", () => { const cases = [ { diff --git a/src/evaluator/index.ts b/src/evaluator/index.ts index 3064b69..d4e5425 100644 --- a/src/evaluator/index.ts +++ b/src/evaluator/index.ts @@ -1,6 +1,7 @@ import type * as Node from "../parser"; import type * as Value from "./value"; import * as value from "./value"; +import builtin, { type BuiltinFunction } from "./builtin"; import Environment from "./environment"; import type { Range } from "../util/position"; @@ -173,14 +174,19 @@ export default class Evaluator { } private evaluateIdentifier(node: Node.IdentifierNode, env: Environment): Value.Value { - const varName = node.value; - const value = env.get(varName); + const name = node.value; + const envValue = env.get(name); - if (value === null) { - throw new BadIdentifierError(node.range, varName); + if (envValue !== null) { + return envValue; } - return value; + const builtinValue = builtin.get(name); + if (builtinValue !== null) { + return this.createBuiltinFunctionValue(builtinValue, node.range); + } + + throw new BadIdentifierError(node.range, name); } private evaluateAssignment(node: Node.AssignmentNode, env: Environment): Value.Value { @@ -202,14 +208,20 @@ export default class Evaluator { private evaluateCall(node: Node.CallNode, env: Environment): Value.Value { const func = this.evaluateExpression(node.func, env); - if (func.type !== "function") { - throw new Error(`expected function but received ${func.type}`); + + if (func.type === "function") { + const callArguments = this.evaluateCallArguments(node.args, env); + + const value = this.evaluateFunctionCall(func, callArguments); + return value; } + if (func.type === "builtin function") { + const callArguments = this.evaluateCallArguments(node.args, env); - const callArguments = this.evaluateCallArguments(node.args, env); + return this.evaluateBuiltinFunctionCall(func, callArguments); + } - const value = this.evaluateFunctionCall(func, callArguments); - return value; + throw new Error(`expected function but received ${func.type}`); } private evaluateCallArguments(args: Node.ExpressionNode[], env: Environment): Value.Value[] { @@ -229,6 +241,10 @@ export default class Evaluator { return returnValue; } + private evaluateBuiltinFunctionCall(func: Value.BuiltinFunctionValue, callArguments: Value.Value[]): Value.Value { + return func.body(callArguments); + } + private getBooleanComparisonInfixOperationValue(left: boolean, right: boolean, operator: ComparisonOperator): boolean { return this.getComparisonInfixOperationValue(left, right, operator); } @@ -338,6 +354,10 @@ export default class Evaluator { return value.createFunctionValue({ parameters, body, environment }, "(함수)", range); } + private createBuiltinFunctionValue(func: BuiltinFunction, range: Range): Value.BuiltinFunctionValue { + return value.createBuiltinFunctionValue({ body: func }, "(내장 함수)", range); + } + // util predicate functions private isArithmeticInfixOperator(operator: string): operator is "+" | "-" | "*" | "/" { diff --git a/src/evaluator/value/index.ts b/src/evaluator/value/index.ts index 1261beb..9b7aa14 100644 --- a/src/evaluator/value/index.ts +++ b/src/evaluator/value/index.ts @@ -10,11 +10,12 @@ export interface ValueBase { export type Value = PrimitiveValue | EmptyValue + | BuiltinFunctionValue; export type PrimitiveValue = NumberValue | StringValue | BooleanValue - | FunctionValue + | FunctionValue; export interface NumberValue extends ValueBase<"number"> { readonly value: number, @@ -33,6 +34,9 @@ export interface FunctionValue extends ValueBase<"function"> { export interface EmptyValue extends ValueBase<"empty"> { readonly value: null, } +export interface BuiltinFunctionValue extends ValueBase<"builtin function"> { + readonly body: (args: any) => Value, +} export interface ReturnValue { readonly type: "return", @@ -54,7 +58,7 @@ export const createBooleanValue: CreateValue<"boolean", BooleanValue> = createVa export const createStringValue: CreateValue<"string", StringValue> = createValueCreator<"string", StringValue>("string"); export const createEmptyValue: CreateValue<"empty", EmptyValue> = createValueCreator<"empty", EmptyValue>("empty"); export const createFunctionValue: CreateValue<"function", FunctionValue> = createValueCreator<"function", FunctionValue>("function"); - +export const createBuiltinFunctionValue: CreateValue<"builtin function", BuiltinFunctionValue> = createValueCreator<"builtin function", BuiltinFunctionValue>("builtin function"); export const createReturnValue = (value: Value): ReturnValue => ({ type: "return", value, diff --git a/src/index.test.ts b/src/index.test.ts index f19e464..3eed534 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -51,3 +51,7 @@ it("execute single assignment", () => { it("execute assignment and calculation", () => { expect(execute("변수1 = 4 변수2 = 9 ((변수1 - 변수2) * 변수1)")).toBe("-20"); }); + +it("execute builtin 길이 function", () => { + expect(execute("길이('사과')")).toBe("2"); +});