Skip to content

Commit

Permalink
support builtin length function (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 authored Feb 5, 2024
1 parent 88eb74e commit 57c5837
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 12 deletions.
26 changes: 26 additions & 0 deletions src/evaluator/builtin/index.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 17 additions & 0 deletions src/evaluator/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down
40 changes: 30 additions & 10 deletions src/evaluator/index.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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 {
Expand All @@ -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[] {
Expand All @@ -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<boolean>(left, right, operator);
}
Expand Down Expand Up @@ -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 "+" | "-" | "*" | "/" {
Expand Down
8 changes: 6 additions & 2 deletions src/evaluator/value/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export interface ValueBase<T extends string = string> {

export type Value = PrimitiveValue
| EmptyValue
| BuiltinFunctionValue;

export type PrimitiveValue = NumberValue
| StringValue
| BooleanValue
| FunctionValue
| FunctionValue;

export interface NumberValue extends ValueBase<"number"> {
readonly value: number,
Expand All @@ -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",
Expand All @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

0 comments on commit 57c5837

Please sign in to comment.