Skip to content

Commit

Permalink
Merge pull request #1 from yazaldefilimone/feat/array
Browse files Browse the repository at this point in the history
Feat/array
  • Loading branch information
yazaldefilimone authored Feb 26, 2024
2 parents 46d333c + c548950 commit 3e5582e
Show file tree
Hide file tree
Showing 24 changed files with 386 additions and 42 deletions.
Binary file added bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test:lexer": "vitest ./lexer",
"test": "vitest ",
"dev": "tsx ./src/index.ts",
"dev:bun": "bun ./src/index.ts",
"lint:fix": "tslint --fix --project ./tsconfig.json"
},
"devDependencies": {
Expand Down
26 changes: 26 additions & 0 deletions src/ast/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Token } from "token/token";
import { Expression, ExpressionKind } from "./base";
import { Maybe } from "utils";

export class ArrayLiteral implements Expression {
token: Token;
kind: ExpressionKind;
public elements: Maybe<Expression[]>;
constructor(token: Token, elements: Maybe<Expression[]> = null) {
this.token = token;
this.kind = ExpressionKind.ARRAY;
this.elements = elements || null;
}
expressionNode(): void {
throw new Error("Method not implemented.");
}
tokenLiteral(): string {
return this.token.literal;
}
toString(): string {
if (this.elements === null) {
return "";
}
return `[${this.elements.map((e) => e.toString()).join(", ")}]`;
}
}
2 changes: 2 additions & 0 deletions src/ast/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ export enum StatementKind {
export enum ExpressionKind {
IDENTIFIER = "IDENTIFIER",
INTEGER = "INTEGER",
ARRAY = "ARRAY",
BOOLEAN = "BOOLEAN",
STRING = "STRING",
PREFIX = "PREFIX",
INFIX = "INFIX",
IF = "IF",
FUNCTION = "FUNCTION",
CALL = "CALL",
INDEX = "INDEX",
}

type nodeKindType = StatementKind | ExpressionKind | ProgramKind;
Expand Down
24 changes: 24 additions & 0 deletions src/ast/index-expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Token } from "token";
import { Expression, ExpressionKind } from "./base";
import { Maybe } from "utils";

export class IndexExpression implements Expression {
token: Token;
left: Maybe<Expression>;
index: Maybe<Expression>;
kind: ExpressionKind;
constructor(token: Token, left: Maybe<Expression> = null, index: Maybe<Expression> = null) {
this.token = token;
this.left = left;
this.index = index;
this.kind = ExpressionKind.INDEX;
}

expressionNode() {}
tokenLiteral() {
return this.token.literal;
}
toString() {
return `(${this.left?.toString()}[${this.index?.toString()}])`;
}
}
2 changes: 2 additions & 0 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export * from "./block";
export * from "./function";
export * from "./call";
export * from "./string";
export * from "./array";
export * from "./index";
78 changes: 60 additions & 18 deletions src/evaluator/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Expression,
CallExpression,
StringLiteral,
ArrayLiteral,
} from "ast";

import {
Expand All @@ -30,6 +31,9 @@ import {
Environment,
EnclosedEnvironment,
BaseString,
BaseFunction,
Builtin,
InternalArray,
} from "evaluator/object";

import { Maybe } from "utils";
Expand All @@ -38,71 +42,109 @@ import {
notFunctionError,
parseTwoObjectToString,
typeMismatchError,
unknownIdentifierError,
unknownOperatorError,
} from "./errors";
import { verifyPermission } from "./permissions";
import { BaseFunction } from "./object/function";
import { Builtin } from "./object/builtin";
import { IndexExpression } from "ast/index-expression";

export function Evaluator(node: Maybe<Node>, env: Environment): Maybe<BaseObject> {
if (node === null) return internal.NULL;
switch (node.kind) {
case ProgramKind.program:
case ProgramKind.program: {
return evalProgram(node as Program, env);
case StatementKind.BLOCK:
}
case StatementKind.BLOCK: {
return evalProgram(node as Program, env);
case StatementKind.EXPRESSION:
}
case StatementKind.EXPRESSION: {
return Evaluator((node as ExpressionStatement).expression, env);
case ExpressionKind.INTEGER:
}
case ExpressionKind.INTEGER: {
return new Integer((node as IntegerLiteral).value);
case ExpressionKind.STRING:
}
case ExpressionKind.STRING: {
return new BaseString((node as StringLiteral).value);
case ExpressionKind.BOOLEAN:
}
case ExpressionKind.BOOLEAN: {
return nativeBooleanObject((node as BooleanLiteral).value);
case ExpressionKind.PREFIX:
}
case ExpressionKind.PREFIX: {
const prefixNode = node as PrefixExpression;
const prefixRight = Evaluator(prefixNode.right, env);
if (prefixRight === null) return internal.NULL;
if (isExpectObject(prefixRight, EBaseObject.ERROR)) return prefixRight;
return evalPrefixExpression(prefixNode.operator, prefixRight);
case ExpressionKind.INFIX:
}
case ExpressionKind.INFIX: {
const infix = node as InfixExpression;
const infixLeft = Evaluator(infix.left, env);
const infixRight = Evaluator(infix.right, env);
return evalInfixExpression(infixLeft, infixRight, infix.operator);
case ExpressionKind.IF:
}
case ExpressionKind.IF: {
const ifNode = node as IfExpression;
return evalIfExpression(ifNode, env);
case StatementKind.RETURN:
}
case StatementKind.RETURN: {
const rNode = node as ReturnStatement;
const returnValue = Evaluator(rNode.returnValue, env);
if (returnValue === null) return internal.NULL;
return new ReturnObject(returnValue);
case StatementKind.LET:
}
case StatementKind.LET: {
const letNode = node as LetStatement;
const exp = Evaluator(letNode.value, env);
if (isError(exp)) return exp;
env.setStore(letNode.name.value, exp as BaseObject);
return null;
case ExpressionKind.IDENTIFIER:
}
case ExpressionKind.IDENTIFIER: {
const identOrError = evalIdentifierExpression(node as Identifier, env);
return identOrError;
case ExpressionKind.FUNCTION:
}
case ExpressionKind.FUNCTION: {
return evalFunctionExpression(node as FunctionLiteral, env);
case ExpressionKind.CALL:
}
case ExpressionKind.CALL: {
const callNode = node as CallExpression;
const func = Evaluator(callNode.function, env);
if (isError(func)) return func;
let args = evalExpressions(callNode.arguments, env);
if (args.length === 1 && isError(args[0])) return args[0];
if (args === null || func == null) return internal.NULL;
return applyFunction(func, args as BaseObject[]);
}
case ExpressionKind.ARRAY: {
const arrayNode = node as ArrayLiteral;
const elements = evalExpressions(arrayNode.elements, env);
if (!elements) return internal.NULL;
if (elements.length === 1 && isError(elements[0])) return elements[0];
return new InternalArray(elements as BaseObject[]);
}
case ExpressionKind.INDEX: {
const indexNode = node as IndexExpression;
const leftExpr = Evaluator(indexNode.left, env);
if (isError(leftExpr) || !leftExpr) return leftExpr;
const indexExpr = Evaluator(indexNode.index, env);
if (isError(indexExpr) || !indexExpr) return indexExpr;
return evalIndexExpression(leftExpr, indexExpr);
}
default:
return identifierNotFoundError(node.tokenLiteral());
}
}

function evalIndexExpression(left: BaseObject, index: BaseObject): Maybe<BaseObject> {
if (!isExpectObject(left, EBaseObject.ARRAY) || !isExpectObject(index, EBaseObject.INTEGER)) {
return unknownOperatorError(`${left.type()}[${index.type()}]`);
}
const array = left as InternalArray;
const i = index as Integer;
const max = array.value.length - 1;
if (i.value < 0 || i.value > max) {
return internal.NULL;
}
return array.value[i.value];
}
function evaluatorStatements(statements: Statement[], env: Environment): Maybe<BaseObject> {
let result: Maybe<BaseObject> = null;
for (const statement of statements) {
Expand Down
17 changes: 17 additions & 0 deletions src/evaluator/object/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BaseObject, EBaseObject } from "evaluator/object";

export class InternalArray implements BaseObject<BaseObject[]> {
private _elements: BaseObject[];
constructor(elements: BaseObject[]) {
this._elements = elements;
}
type(): EBaseObject {
return EBaseObject.ARRAY;
}
inspect(): string {
return this._elements.map((el) => el.inspect()).join(", ");
}
get value(): BaseObject[] {
return this._elements;
}
}
1 change: 1 addition & 0 deletions src/evaluator/object/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum EBaseObject {
ERROR = "ERROR",
FUNCTION = "FUNCTION",
BUILTIN = "BUILTIN",
ARRAY = "ARRAY",
}
export interface BaseObject<T = unknown> {
type(): EBaseObject;
Expand Down
13 changes: 10 additions & 3 deletions src/evaluator/object/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ const len: BuiltinFunction = (...args: BaseObject<string>[]) => {
return new InternalError(`wrong number of arguments. got=${args.length}, want=1`);
}
const arg = args[0];
if (arg.type() !== EBaseObject.STRING) {
return new InternalError(`argument to \`len\` not supported, got ${arg.type()}`);
switch (arg.type()) {
case EBaseObject.STRING: {
return new Integer(arg?.value.length);
}
case EBaseObject.ARRAY: {
return new Integer(arg?.value.length);
}
default: {
return new InternalError(`argument to \`len\` not supported, got ${arg.type()}`);
}
}
return new Integer(arg?.value.length);
};

const keyboardTable = new Map<string, Builtin>();
Expand Down
3 changes: 3 additions & 0 deletions src/evaluator/object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ export * from "./string";
export * from "./error";
export * from "./internal";
export * from "./environment";
export * from "./function";
export * from "./builtin";
export * from "./array";
82 changes: 82 additions & 0 deletions src/evaluator/tests/array-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { it, expect, describe } from "vitest";
import { makeSut } from "./sut";
import { InternalArray, Integer } from "evaluator/object";

describe("Evaluator", () => {
describe("Array", () => {
it("should create array and access by index", () => {
const tests = [
{
input: `
[1, 2, 3][0];
`,
expected: 1,
},
{
input: `
[1, 2, 3][1];
`,
expected: 2,
},
{
input: `
[1, 2, 3][2];
`,
expected: 3,
},
{
input: `
let i = 0;
[1][i];
`,
expected: 1,
},
{
input: `
[1, 2, 3][1 + 1];
`,
expected: 3,
},
{
input: `
let myArray = [1, 2, 3];
myArray[2];
`,
expected: 3,
},
{
input: `
let myArray = [1, 2, 3];
myArray[0] + myArray[1] + myArray[2];
`,
expected: 6,
},
{
input: `
let myArray = [1, 2, 3];
let i = myArray[0];
myArray[i];
`,
expected: 2,
},
{
input: `
[1, 2, 3][3];
`,
expected: null,
},
{
input: `
[1, 2, 3][-1];
`,
expected: null,
},
];
for (const tt of tests) {
const sut = makeSut(tt.input);
expect(sut).not.toBeNull();
expect(sut?.value).toBe(tt.expected);
}
});
});
});
21 changes: 21 additions & 0 deletions src/evaluator/tests/array.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { it, expect, describe } from "vitest";
import { makeSut } from "./sut";
import { InternalArray, Integer } from "evaluator/object";

describe("Evaluator", () => {
describe("Array", () => {
it("should create array", () => {
const arrayTest = [1, 2, 3];
const tt = {
input: `
let myArray = [${arrayTest.join(", ")}];
myArray;
`,
expected: new InternalArray(arrayTest.map((num) => new Integer(num))),
};
const sut = makeSut(tt.input);
expect(sut).not.toBeNull();
expect(sut?.value).toEqual(tt.expected.value);
});
});
});
4 changes: 4 additions & 0 deletions src/evaluator/tests/len.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ describe("Evaluator", () => {
input: `len("hello world")`,
expected: 11,
},
{
input: `len([0,1,2])`,
expected: 3,
},
{
input: `len(1)`,
expected: "argument to `len` not supported, got INTEGER",
Expand Down
Loading

0 comments on commit 3e5582e

Please sign in to comment.