Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support builtin length function #57

Merged
merged 1 commit into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
});
Loading