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

Adding support for arrays #275

Open
wants to merge 5 commits into
base: feat/basic-array
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions packages/interpreter/src/components/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ export default class Scope {
);
}

assignArray(identifier: string, index: number, value: unknown) {
if (this._variables.has(identifier)) {
let arr: any[] = this._variables.get(identifier) as any[];
arr[index] = value;
return;
}

if (this._parentScope !== null) {
this._parentScope.assignArray(identifier, index, value);
return;
}

throw new RuntimeException(
`Variable "${identifier}" bana to le pehle fir assign karna.`
);
}

declare(identifier: string, value: unknown) {
if (this._variables.has(identifier)) {
throw new RuntimeException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Visitor from ".";
import { ASTNode } from "bhai-lang-parser";

import RuntimeException from "../../exceptions/runtimeException";
import InterpreterModule from "../../module/interpreterModule";

export default class ArrayAccessExpression implements Visitor {
visitNode(node: ASTNode) {
const identifier = node.expressions?.[0]!;
const index = node.expressions?.[1]!;

const array = InterpreterModule.getVisitor(identifier.type).visitNode(identifier);
const indexValue = InterpreterModule.getVisitor(index.type).visitNode(index);
if (typeof indexValue !== 'number') {
throw new RuntimeException(
`Bhai, kuch phat gaya: \`${identifier.name}\` array mein dekhne ke liye number mangta hai, "${indexValue}" nahi.`
);
}

if (!Array.isArray(array)) {
throw new RuntimeException(
`Bhai, kuch phat gaya: \`${identifier.name}\` toh array hi nahi hai.`
);
}

return array[indexValue];
}
}
17 changes: 17 additions & 0 deletions packages/interpreter/src/components/visitor/arrayExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Visitor from ".";
import { ASTNode } from "bhai-lang-parser";

import InterpreterModule from "../../module/interpreterModule";

export default class ArrayExpression implements Visitor {
visitNode(node: ASTNode) {
let values: any[] = [];

node.expressions?.forEach(expr => {
const value = InterpreterModule.getVisitor(expr.type).visitNode(expr);
values.push(value);
});

return values;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Visitor from ".";
import { ASTNode } from "bhai-lang-parser";

import RuntimeException from "../../exceptions/runtimeException";
import InterpreterModule from "../../module/interpreterModule";

export default class ArrayLengthExpression implements Visitor {
visitNode(node: ASTNode) {
const identifier = node.expressions?.[0]!;

const array = InterpreterModule.getVisitor(identifier.type).visitNode(identifier);
if (!Array.isArray(array)) {
throw new RuntimeException(
`Bhai, kuch phat gaya: \`${identifier.name}\` toh array hi nahi hai.`
);
}

return array.length;
}
}

64 changes: 50 additions & 14 deletions packages/interpreter/src/components/visitor/assignmentExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,26 @@ export default class AssignmentExpression implements Visitor {
`left node not present while executing: ${node.type}`
);

let identifier = node.left.name;
let identifier: string | undefined;
let index: number | undefined;
let value: unknown;

if (node.left.expressions) {
// must be an array access expression on LHS
identifier = node.left.expressions?.[0]!.name;
const indexNode = node.left.expressions?.[1]!;
const indexVal = InterpreterModule.getVisitor(indexNode.type).visitNode(indexNode);
if (typeof indexVal !== 'number') {
throw new RuntimeException(
`Bhai, kuch phat gaya: \`${identifier}\` array mein dekhne ke liye number mangta hai, "${indexVal}" nahi.`
);
}
index = indexVal as number;
} else {
// regular identifier LHS
identifier = node.left.name;
}

const currentScope = InterpreterModule.getCurrentScope();

if (node.right) {
Expand All @@ -26,25 +44,43 @@ export default class AssignmentExpression implements Visitor {
}

if (identifier && node.operator) {
const left = currentScope.get(identifier);
if (index !== undefined) {
// assign array
const array: any[] = currentScope.get(identifier) as any[];
if (!Array.isArray(array)) {
throw new RuntimeException(
`Bhai, kuch phat gaya: \`${identifier}\` toh array hi nahi hai.`
);
}

if (left === null && node.operator !== "=")
throw new NallaPointerException(
`Nalla operand ni jamta "${node.operator}" ke sath`
);
const newValue = this._getNewValue(array[index], value, node.operator);
currentScope.assignArray(identifier, index, newValue);

if ((left === true || left === false) && node.operator !== "=")
throw new RuntimeException(
`Boolean operand ni jamta "${node.operator}" ke sath`
);
const updatedArray = currentScope.get(identifier) as any[];
return updatedArray[index];
}

const newValue = getOperationValue(
{ left: currentScope.get(identifier), right: value },
node.operator
);
const newValue = this._getNewValue(currentScope.get(identifier), value, node.operator);
currentScope.assign(identifier, newValue);

return currentScope.get(identifier);
}
}

_getNewValue(left: unknown, value: unknown, operator: string): unknown {
if (left === null && operator !== "=")
throw new NallaPointerException(
`Nalla operand ni jamta "${operator}" ke sath`
);

if ((left === true || left === false) && operator !== "=")
throw new RuntimeException(
`Boolean operand ni jamta "${operator}" ke sath`
);

return getOperationValue(
{ left: left, right: value },
operator
);
}
}
15 changes: 15 additions & 0 deletions packages/interpreter/src/components/visitor/printStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,25 @@ export default class PrintStatement implements Visitor {
currentNodeOutput = "sahi";
else if (currentNodeOutput === false)
currentNodeOutput = "galat";
else if (Array.isArray(currentNodeOutput)) {
currentNodeOutput = this._getArrayOutput(currentNodeOutput);
}
return currentNodeOutput;
}
)
.join(" ");
console.log(value);
}

_getArrayOutput(values: any[]): string {
let output = values.map((value) => {
if (Array.isArray(value)) {
return this._getArrayOutput(value);
} else {
return value;
}
});

return "[" + output.join(", ") + "]";
}
}
7 changes: 7 additions & 0 deletions packages/interpreter/src/module/interpreterModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import parser, { NodeType } from "bhai-lang-parser";
import Interpreter from "../components/interpreter";
import Scope from "../components/scope";
import Visitor from "../components/visitor";
import ArrayExpression from "../components/visitor/arrayExpression";
import ArrayAccessExpression from "../components/visitor/arrayAccessExpression";
import ArrayLengthExpression from "../components/visitor/arrayLengthExpression";
import AssignmentExpression from "../components/visitor/assignmentExpression";
import BinaryExpression from "../components/visitor/binaryExpression";
import BlockStatement from "../components/visitor/blockStatement";
Expand Down Expand Up @@ -35,6 +38,10 @@ export default class InterpreterModule {
[NodeType.VariableStatement]: new VariableStatement(),
[NodeType.IdentifierExpression]: new IdentifierExpression(),
[NodeType.VariableDeclaration]: new VariableDeclaration(),
[NodeType.ArrayExpression]: new ArrayExpression(),
[NodeType.ArrayAccessExpression]: new ArrayAccessExpression(),
[NodeType.ArrayLengthExpression]: new ArrayLengthExpression(),
[NodeType.AssignmentExpression]: new AssignmentExpression(),
[NodeType.AssignmentExpression]: new AssignmentExpression(),
[NodeType.ExpressionStatement]: new ExpressionStatement(),
[NodeType.BinaryExpression]: new BinaryExpression(),
Expand Down
13 changes: 13 additions & 0 deletions packages/interpreter/test/integration/negativeTestsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,4 +528,17 @@ export const NegativeTestCases = [
`,
output: RuntimeException,
},
{
name: "array invalid index",
input: `
hi bhai
bhai ye hai a = [-1, 0, 3, 5];
bhai ye hai k = "invalid";
a[k] = -5;

bol bhai a;
bye bhai
`,
output: RuntimeException
}
];
54 changes: 53 additions & 1 deletion packages/interpreter/test/integration/positiveTestsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,4 +996,56 @@ export const WithOutputPositiveTests = [
`,
output: "1",
},
];
];

export const WithMultilineOutputPositiveTests = [
{
name: "basic arrays test",
input: `
hi bhai
bhai ye hai b = 2;
bhai ye hai a = [1, b + 2, 3];
bol bhai a;
bol bhai a[0];
bol bhai a[1];
bol bhai a[2];
bol bhai a[3];
bye bhai
`,
outputs: [`[1, 4, 3]`, `1`, `4`, `3`]
},
{
name: "array access and length loop test",
input: `
hi bhai
bhai ye hai a = [-1, 0, 3, 5];
a[3] = -5;

bol bhai a;
bol bhai a[1];

bhai ye hai i = 0;
jab tak bhai (i < a ka lambai) {
a[i] += 2;
i += 1;
}
bol bhai a;
bye bhai
`,
outputs: [`[-1, 0, 3, -5]`,`0`,`[1, 2, 5, -3]`]
},
{
name: "inner arrays test",
input: `
hi bhai
bhai ye hai a = [-1, 0, 3, [1, 2]];
bhai ye hai k = a[3];
k[0] = -5;

bol bhai a;
bye bhai
`,
outputs: [`[-1, 0, 3, [-5, 2]]`]
}
];

13 changes: 12 additions & 1 deletion packages/interpreter/test/integration/testRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import InterpreterModule from "../../src/module/interpreterModule";
import { NegativeTestCases } from "./negativeTestsProvider";
import {
NoOutputPositiveTests,
WithOutputPositiveTests
WithOutputPositiveTests,
WithMultilineOutputPositiveTests
} from "./positiveTestsProvider";


Expand All @@ -31,6 +32,16 @@ WithOutputPositiveTests.forEach((testCase) => {
});
});

WithMultilineOutputPositiveTests.forEach((testCase) => {
test(testCase.name, () => {
expect(() => interpreter.interpret(testCase.input)).not.toThrowError();

testCase.outputs.forEach(output => {
expect(console.log).toHaveBeenCalledWith(output);
});
});
});

NegativeTestCases.forEach((testCase) => {
test(testCase.name, () => {
expect(() => interpreter.interpret(testCase.input)).toThrowError(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Expression from ".";

import { TokenTypes } from "../../../../constants/bhaiLangSpec";
import { NodeType } from "../../../../constants/constants";
import { ASTNode } from "../../types/nodeTypes";


export default class ArrayAccessExpression extends Expression {
getExpression(): ASTNode {
const expressions = [];

// name of identifier (could be array name)
const identifierExpression = Expression.getExpressionImpl(NodeType.IdentifierExpression).getExpression();

// this is an actual array access expression, otherwise we default to an identifier expression
if (this._tokenExecutor.getLookahead()?.type === TokenTypes.OPEN_BRACKET_TYPE) {
expressions.push(identifierExpression);

this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.OPEN_BRACKET_TYPE);

// index (additive expression)
expressions.push(Expression.getExpressionImpl(NodeType.AdditiveExpression).getExpression());

this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CLOSED_BRACKET_TYPE);

return {
type: NodeType.ArrayAccessExpression,
expressions,
};
}

return identifierExpression;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Expression from ".";

import { TokenTypes } from "../../../../constants/bhaiLangSpec";
import { NodeType } from "../../../../constants/constants";
import { ASTNode } from "../../types/nodeTypes";


export default class ArrayExpression extends Expression {
getExpression(): ASTNode {
this._tokenExecutor.eatTokenAndForwardLookahead(
TokenTypes.OPEN_BRACKET_TYPE
);

const expressions = [];
while (this._tokenExecutor.getLookahead()?.type != TokenTypes.CLOSED_BRACKET_TYPE) {
expressions.push(Expression.getExpressionImpl(NodeType.LogicalORExpression).getExpression());
if (this._tokenExecutor.getLookahead()?.type == TokenTypes.COMMA_TYPE) {
this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.COMMA_TYPE);
}
}

this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CLOSED_BRACKET_TYPE);

return {
type: NodeType.ArrayExpression,
expressions,
};
}
}

Loading