diff --git a/packages/interpreter/src/components/scope.ts b/packages/interpreter/src/components/scope.ts index af224345..0029786c 100644 --- a/packages/interpreter/src/components/scope.ts +++ b/packages/interpreter/src/components/scope.ts @@ -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( diff --git a/packages/interpreter/src/components/visitor/arrayAccessExpression.ts b/packages/interpreter/src/components/visitor/arrayAccessExpression.ts new file mode 100644 index 00000000..5e02d65d --- /dev/null +++ b/packages/interpreter/src/components/visitor/arrayAccessExpression.ts @@ -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]; + } +} diff --git a/packages/interpreter/src/components/visitor/arrayExpression.ts b/packages/interpreter/src/components/visitor/arrayExpression.ts new file mode 100644 index 00000000..1b875f26 --- /dev/null +++ b/packages/interpreter/src/components/visitor/arrayExpression.ts @@ -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; + } +} diff --git a/packages/interpreter/src/components/visitor/arrayLengthExpression.ts b/packages/interpreter/src/components/visitor/arrayLengthExpression.ts new file mode 100644 index 00000000..1a66b0c7 --- /dev/null +++ b/packages/interpreter/src/components/visitor/arrayLengthExpression.ts @@ -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; + } +} + diff --git a/packages/interpreter/src/components/visitor/assignmentExpression.ts b/packages/interpreter/src/components/visitor/assignmentExpression.ts index 8fc09cf7..1103b52a 100644 --- a/packages/interpreter/src/components/visitor/assignmentExpression.ts +++ b/packages/interpreter/src/components/visitor/assignmentExpression.ts @@ -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) { @@ -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 + ); + } } diff --git a/packages/interpreter/src/components/visitor/printStatement.ts b/packages/interpreter/src/components/visitor/printStatement.ts index 2f8a94de..d0c4c1db 100644 --- a/packages/interpreter/src/components/visitor/printStatement.ts +++ b/packages/interpreter/src/components/visitor/printStatement.ts @@ -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(", ") + "]"; + } } diff --git a/packages/interpreter/src/module/interpreterModule.ts b/packages/interpreter/src/module/interpreterModule.ts index adef304b..635087a6 100644 --- a/packages/interpreter/src/module/interpreterModule.ts +++ b/packages/interpreter/src/module/interpreterModule.ts @@ -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"; @@ -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(), diff --git a/packages/interpreter/test/integration/negativeTestsProvider.ts b/packages/interpreter/test/integration/negativeTestsProvider.ts index 2c138ebe..b99a11e1 100644 --- a/packages/interpreter/test/integration/negativeTestsProvider.ts +++ b/packages/interpreter/test/integration/negativeTestsProvider.ts @@ -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 + } ]; diff --git a/packages/interpreter/test/integration/positiveTestsProvider.ts b/packages/interpreter/test/integration/positiveTestsProvider.ts index b70c8dd0..44eb5a91 100644 --- a/packages/interpreter/test/integration/positiveTestsProvider.ts +++ b/packages/interpreter/test/integration/positiveTestsProvider.ts @@ -996,4 +996,56 @@ export const WithOutputPositiveTests = [ `, output: "1", }, -]; \ No newline at end of file +]; + +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]]`] + } +]; + diff --git a/packages/interpreter/test/integration/testRunner.test.ts b/packages/interpreter/test/integration/testRunner.test.ts index 57b86863..dcff2950 100644 --- a/packages/interpreter/test/integration/testRunner.test.ts +++ b/packages/interpreter/test/integration/testRunner.test.ts @@ -5,7 +5,8 @@ import InterpreterModule from "../../src/module/interpreterModule"; import { NegativeTestCases } from "./negativeTestsProvider"; import { NoOutputPositiveTests, - WithOutputPositiveTests + WithOutputPositiveTests, + WithMultilineOutputPositiveTests } from "./positiveTestsProvider"; @@ -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( diff --git a/packages/parser/src/components/parser/statement/expression/arrayAccessExpression.ts b/packages/parser/src/components/parser/statement/expression/arrayAccessExpression.ts new file mode 100644 index 00000000..ad47e511 --- /dev/null +++ b/packages/parser/src/components/parser/statement/expression/arrayAccessExpression.ts @@ -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; + } +} + diff --git a/packages/parser/src/components/parser/statement/expression/arrayExpression.ts b/packages/parser/src/components/parser/statement/expression/arrayExpression.ts new file mode 100644 index 00000000..1b01c367 --- /dev/null +++ b/packages/parser/src/components/parser/statement/expression/arrayExpression.ts @@ -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, + }; + } +} + diff --git a/packages/parser/src/components/parser/statement/expression/arrayLengthExpression.ts b/packages/parser/src/components/parser/statement/expression/arrayLengthExpression.ts new file mode 100644 index 00000000..be1cdefb --- /dev/null +++ b/packages/parser/src/components/parser/statement/expression/arrayLengthExpression.ts @@ -0,0 +1,25 @@ +import Expression from "."; + +import { TokenTypes } from "../../../../constants/bhaiLangSpec"; +import { NodeType } from "../../../../constants/constants"; +import { ASTNode } from "../../types/nodeTypes"; + + +export default class ArrayLengthExpression extends Expression { + getExpression(): ASTNode { + // name of identifier (could be array access itself) + const identifierExpression = Expression.getExpressionImpl(NodeType.ArrayAccessExpression).getExpression(); + + // this is an actual array length expression, otherwise we default to an array access expression + if (this._tokenExecutor.getLookahead()?.type === TokenTypes.KA_LAMBAI) { + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.KA_LAMBAI); + + return { + type: NodeType.ArrayLengthExpression, + expressions: [identifierExpression] + }; + } + + return identifierExpression; + } +} diff --git a/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts b/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts index d7ab232e..8253b058 100644 --- a/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts +++ b/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts @@ -57,7 +57,8 @@ export default class AssignmentExpression extends Expression { */ private _checkValidAssignmentTarget(node: any) { if (node.type === NodeType.IdentifierExpression) return node; + if (node.type === NodeType.ArrayAccessExpression) return node; - throw new SyntaxError("Invalid left hand side in assignment expression"); + throw new SyntaxError(`bhai kya kar raha hai tu?? Left side mein ${node.type} ho hi nahi sakta`); } } diff --git a/packages/parser/src/components/parser/statement/expression/index.ts b/packages/parser/src/components/parser/statement/expression/index.ts index 28849f74..8bab98b7 100644 --- a/packages/parser/src/components/parser/statement/expression/index.ts +++ b/packages/parser/src/components/parser/statement/expression/index.ts @@ -27,6 +27,15 @@ export default abstract class Expression { case NodeType.ParanthesizedExpression: return BhaiLangModule.getParanthesizedExpression(); + case NodeType.ArrayExpression: + return BhaiLangModule.getArrayExpression(); + + case NodeType.ArrayAccessExpression: + return BhaiLangModule.getArrayAccessExpression(); + + case NodeType.ArrayLengthExpression: + return BhaiLangModule.getArrayLengthExpression(); + case NodeType.AssignmentExpression: return BhaiLangModule.getAssignmentExpression(); diff --git a/packages/parser/src/components/parser/statement/expression/primaryExpression.ts b/packages/parser/src/components/parser/statement/expression/primaryExpression.ts index 2e40c91e..6a8e4ac5 100644 --- a/packages/parser/src/components/parser/statement/expression/primaryExpression.ts +++ b/packages/parser/src/components/parser/statement/expression/primaryExpression.ts @@ -15,6 +15,10 @@ export default class PrimaryExpression extends Expression { return Expression.getExpressionImpl( NodeType.ParanthesizedExpression ).getExpression(); + case TokenTypes.OPEN_BRACKET_TYPE: + return Expression.getExpressionImpl( + NodeType.ArrayExpression + ).getExpression(); case TokenTypes.STRING_TYPE: case TokenTypes.NUMBER_TYPE: case TokenTypes.BOOLEAN_TYPE: @@ -33,7 +37,7 @@ export default class PrimaryExpression extends Expression { private _getLeftHandSideExpression() { return Expression.getExpressionImpl( - NodeType.IdentifierExpression + NodeType.ArrayLengthExpression ).getExpression(); } } diff --git a/packages/parser/src/constants/bhaiLangSpec.ts b/packages/parser/src/constants/bhaiLangSpec.ts index 76537271..a1363947 100644 --- a/packages/parser/src/constants/bhaiLangSpec.ts +++ b/packages/parser/src/constants/bhaiLangSpec.ts @@ -21,6 +21,8 @@ export const TokenTypes = { AGLA_DEKH_BHAI: "agla dekh bhai", + KA_LAMBAI: "ka lambai", + NALLA_TYPE: "NALLA", SEMI_COLON_TYPE: ";", @@ -33,6 +35,10 @@ export const TokenTypes = { CLOSED_PARENTHESIS_TYPE: ")", + OPEN_BRACKET_TYPE: "[", + + CLOSED_BRACKET_TYPE: "]", + COMMA_TYPE: ",", NUMBER_TYPE: "NUMBER", @@ -76,6 +82,8 @@ export const SPEC = [ { regex: /^\}/, tokenType: TokenTypes.CLOSED_CURLY_BRACE_TYPE }, { regex: /^\(/, tokenType: TokenTypes.OPEN_PARENTHESIS_TYPE }, { regex: /^\)/, tokenType: TokenTypes.CLOSED_PARENTHESIS_TYPE }, + { regex: /^\[/, tokenType: TokenTypes.OPEN_BRACKET_TYPE }, + { regex: /^\]/, tokenType: TokenTypes.CLOSED_BRACKET_TYPE }, { regex: /^,/, tokenType: TokenTypes.COMMA_TYPE }, //Keywords @@ -90,6 +98,7 @@ export const SPEC = [ { regex: /^\bjab tak bhai\b/, tokenType: TokenTypes.JAB_TAK_BHAI }, { regex: /^\bbas kar bhai\b/, tokenType: TokenTypes.BAS_KAR_BHAI }, { regex: /^\bagla dekh bhai\b/, tokenType: TokenTypes.AGLA_DEKH_BHAI }, + { regex: /^\bka lambai\b/, tokenType: TokenTypes.KA_LAMBAI }, // Number { regex: /^[+-]?([\d]*[.])?[\d]+/, tokenType: TokenTypes.NUMBER_TYPE }, diff --git a/packages/parser/src/constants/constants.ts b/packages/parser/src/constants/constants.ts index f547d517..e637f5ba 100644 --- a/packages/parser/src/constants/constants.ts +++ b/packages/parser/src/constants/constants.ts @@ -1,5 +1,8 @@ export const NodeType = { AdditiveExpression: "AdditiveExpression", + ArrayExpression: "ArrayExpression", + ArrayAccessExpression: "ArrayAccessExpression", + ArrayLengthExpression: "ArrayLengthExpression", MultiplicativeExpression: "MultiplicativeExpression", PrimaryExpression: "PrimaryExpression", ParanthesizedExpression: "ParanthesizedExpression", diff --git a/packages/parser/src/module/bhaiLangModule.ts b/packages/parser/src/module/bhaiLangModule.ts index 6ddf3199..e805f039 100644 --- a/packages/parser/src/module/bhaiLangModule.ts +++ b/packages/parser/src/module/bhaiLangModule.ts @@ -7,6 +7,12 @@ import ContinueStatement import EmptyStatement from "../components/parser/statement/emptyStatement"; import AdditiveExpression from "../components/parser/statement/expression/addititveExpression"; +import ArrayExpression + from "../components/parser/statement/expression/arrayExpression"; +import ArrayAccessExpression + from "../components/parser/statement/expression/arrayAccessExpression"; +import ArrayLengthExpression + from "../components/parser/statement/expression/arrayLengthExpression"; import AssignmentExpression from "../components/parser/statement/expression/assignmentExpression"; import EqualityExpression @@ -62,6 +68,9 @@ export default class BhaiLangModule { private static _additiveExpression?: AdditiveExpression; private static _multiplicativeExpression?: MultiplicativeExpression; private static _primaryExpression?: PrimaryExpression; + private static _arrayExpression?: ArrayExpression; + private static _arrayAccessExpression?: ArrayAccessExpression; + private static _arrayLengthExpression?: ArrayLengthExpression; private static _paranthesizedExpression?: ParanthesizedExpression; private static _numericLiteral?: NumericLiteral; private static _stringLiteral?: StringLiteral; @@ -226,6 +235,36 @@ export default class BhaiLangModule { return this._paranthesizedExpression; } + static getArrayAccessExpression() { + if (!this._arrayAccessExpression) { + this._arrayAccessExpression = new ArrayAccessExpression( + this.getTokenExecutor() + ); + } + + return this._arrayAccessExpression; + } + + static getArrayLengthExpression() { + if (!this._arrayLengthExpression) { + this._arrayLengthExpression = new ArrayLengthExpression( + this.getTokenExecutor() + ); + } + + return this._arrayLengthExpression; + } + + static getArrayExpression() { + if (!this._arrayExpression) { + this._arrayExpression = new ArrayExpression( + this.getTokenExecutor() + ); + } + + return this._arrayExpression; + } + static getIndentifierExpression() { if (!this._idetifierExpression) this._idetifierExpression = new IdentifierExpression(