diff --git a/apps/docs/components/Code/index.tsx b/apps/docs/components/Code/index.tsx index 4b8467ea..80e50f81 100644 --- a/apps/docs/components/Code/index.tsx +++ b/apps/docs/components/Code/index.tsx @@ -15,8 +15,12 @@ hi bhai bhai ye hai a = 3; bhai ye hai b = 0; + apna funda square(x){ + rakh le bhai x*x; + } + jab tak bhai (b < 5) { - bol bhai b; + bol bhai square(b); agar bhai (b == a) { bol bhai "b is equal to a"; diff --git a/apps/docs/components/Documentation/index.tsx b/apps/docs/components/Documentation/index.tsx index c0a51083..12f81c84 100644 --- a/apps/docs/components/Documentation/index.tsx +++ b/apps/docs/components/Documentation/index.tsx @@ -96,6 +96,22 @@ bye bhai } warna bhai { bol bhai "a is greater than or equal to 25"; } +bye bhai + ` + }, + { + name: "Function", + description: ( + <> + Define function using apna funda then the funcrtion name and parameters within ( ), blocks are executed and the return value can be provided using rakh le bhai. + + ), + code: `hi bhai + apna funda test(c){ + bol bhai c; + rakh le bhai "return bhi "+c; + } + bol bhai test("kam kiya bhai"); bye bhai ` }, diff --git a/apps/docs/components/common/syntax.ts b/apps/docs/components/common/syntax.ts index 1d2c7c08..14f19589 100644 --- a/apps/docs/components/common/syntax.ts +++ b/apps/docs/components/common/syntax.ts @@ -18,7 +18,7 @@ export const bhaiLangSyntax = languages.extend("clike", { pattern: /(["'])((?:\\\1|(?:(?!\1)).)*)(\1)/, greedy: true, }, - keyword: /\b(?:hi bhai|bye bhai|bol bhai|bhai ye hai|nalla|agar bhai|nahi to bhai|warna bhai|jab tak bhai|bas kar bhai|agla dekh bhai)\b/, + keyword: /\b(?:hi bhai|bye bhai|bol bhai|bhai ye hai|nalla|agar bhai|nahi to bhai|warna bhai|jab tak bhai|bas kar bhai|agla dekh bhai|apna funda|rakh le bhai)\b/, boolean: /\b(?:sahi|galat)\b/, number: /(?:(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i, operator: diff --git a/packages/interpreter/src/components/dataClass.ts b/packages/interpreter/src/components/dataClass.ts new file mode 100644 index 00000000..df58dd13 --- /dev/null +++ b/packages/interpreter/src/components/dataClass.ts @@ -0,0 +1,86 @@ +export enum DataTypes{ + Null='null', + Boolean='boolean', + Numeric='numeric', + String='string', + Callable='callable', +} +export class DataObject { + protected _value: any; + protected _type:string; + public isDataObject:boolean; + + constructor(value: any,type:string) { + this._value = value; + this._type = type; + this.isDataObject=true; + } + + getValue(): any { + return this._value; + } + getType():string{ + return this._type; + } + getStringValue():string{ + return this._value.toString(); + } +} + +export class BooleanObject extends DataObject{ + constructor(value: boolean) { + super(value,DataTypes.Boolean); + } + getStringValue(): string { + return this._value?"sahi":"galat"; + } +} + +export class NumericObject extends DataObject{ + constructor(value: number) { + super(value,DataTypes.Numeric); + } +} + +export class StringObject extends DataObject{ + constructor(value: string) { + super(value,DataTypes.String); + } +} + +export class NullObject extends DataObject{ + constructor() { + super(null,DataTypes.Null); + } + getStringValue(): string { + return "nalla"; + } +} + +export class CallableObject extends DataObject{ + constructor(value: any) { + super(value,DataTypes.Callable); + } +} + +export function sanatizeData(data:any|unknown):DataObject{ + if((data==null)||(data==undefined)){ + return new NullObject(); + } + if(typeof data=='boolean'){ + return new BooleanObject(data); + } + if(typeof data=='number'){ + return new NumericObject(data); + } + if(typeof data=='string'){ + return new StringObject(data); + } + if(typeof data=='function'){ + return new CallableObject(data); + } + if(data.isDataObject==true){ + return data as DataObject; + } + else throw new Error(`Ye kya kar raha hai: "${data}" sahi nhi hai. ye konsa data type hai bhai`); +} diff --git a/packages/interpreter/src/components/scope.ts b/packages/interpreter/src/components/scope.ts index af224345..3128d421 100644 --- a/packages/interpreter/src/components/scope.ts +++ b/packages/interpreter/src/components/scope.ts @@ -1,17 +1,40 @@ import RuntimeException from "../exceptions/runtimeException"; +import { DataObject, NullObject, sanatizeData } from "./dataClass"; export default class Scope { - _variables: Map = new Map(); + _variables: Map = new Map(); _isLoop = false; + _isFunction=false; _isBreakStatement = false; _isContinueStatement = false; _parentScope: Scope | null; + _isReturnStatement=false; + _returnVal:any=null; constructor(parentScope: Scope | null) { this._parentScope = parentScope; } + isFunction(){ + return this._isFunction; + } + + setFunction(isFunction:boolean){ + this._isFunction=isFunction; + } + setReturnStatement(isReturnStatement: boolean,returnValue:any) { + this._isReturnStatement=isReturnStatement; + this._returnVal=returnValue; + } + isReturnStatement() { + return this._isReturnStatement; + } + getReturnValue(){ + if(!this._returnVal) this._returnVal=new NullObject(); + return this._returnVal; + } + isLoop() { return this._isLoop; } @@ -36,9 +59,10 @@ export default class Scope { return this._isContinueStatement; } - get(identifier: string): unknown { + get(identifier: string): DataObject { if (this._variables.has(identifier)) { - return this._variables.get(identifier); + let value = sanatizeData(this._variables.get(identifier)); + return value; } if (this._parentScope !== null) { @@ -48,7 +72,7 @@ export default class Scope { throw new RuntimeException(`Variable "${identifier}" bana to le pehle.`); } - assign(identifier: string, value: unknown) { + assign(identifier: string, value: DataObject) { if (this._variables.has(identifier)) { this._variables.set(identifier, value); return; @@ -64,7 +88,7 @@ export default class Scope { ); } - declare(identifier: string, value: unknown) { + declare(identifier: string, value: DataObject) { if (this._variables.has(identifier)) { throw new RuntimeException( `Variable "${identifier}" pehle se exist karta hai bhai. Check karle.` diff --git a/packages/interpreter/src/components/visitor/assignmentExpression.ts b/packages/interpreter/src/components/visitor/assignmentExpression.ts index 8fc09cf7..2a8d141c 100644 --- a/packages/interpreter/src/components/visitor/assignmentExpression.ts +++ b/packages/interpreter/src/components/visitor/assignmentExpression.ts @@ -6,6 +6,7 @@ import NallaPointerException from "../../exceptions/nallaPointerException"; import RuntimeException from "../../exceptions/runtimeException"; import { getOperationValue } from "../../helpers"; import InterpreterModule from "../../module/interpreterModule"; +import { DataObject, DataTypes, NullObject } from "../dataClass"; export default class AssignmentExpression implements Visitor { @@ -16,7 +17,7 @@ export default class AssignmentExpression implements Visitor { ); let identifier = node.left.name; - let value: unknown; + let value: DataObject|null|void=null; const currentScope = InterpreterModule.getCurrentScope(); if (node.right) { @@ -24,6 +25,7 @@ export default class AssignmentExpression implements Visitor { node.right ); } + if(value==null) value =new NullObject() if (identifier && node.operator) { const left = currentScope.get(identifier); @@ -33,7 +35,7 @@ export default class AssignmentExpression implements Visitor { `Nalla operand ni jamta "${node.operator}" ke sath` ); - if ((left === true || left === false) && node.operator !== "=") + if (left.getType()==DataTypes.Boolean && node.operator !== "=") throw new RuntimeException( `Boolean operand ni jamta "${node.operator}" ke sath` ); @@ -46,5 +48,6 @@ export default class AssignmentExpression implements Visitor { return currentScope.get(identifier); } + } } diff --git a/packages/interpreter/src/components/visitor/binaryExpression.ts b/packages/interpreter/src/components/visitor/binaryExpression.ts index c9909950..3fd1ac7f 100644 --- a/packages/interpreter/src/components/visitor/binaryExpression.ts +++ b/packages/interpreter/src/components/visitor/binaryExpression.ts @@ -6,6 +6,7 @@ import NallaPointerException from "../../exceptions/nallaPointerException"; import RuntimeException from "../../exceptions/runtimeException"; import { getOperationValue } from "../../helpers"; import InterpreterModule from "../../module/interpreterModule"; +import { sanatizeData } from "../dataClass"; export default class BinaryExpression implements Visitor { @@ -16,28 +17,17 @@ export default class BinaryExpression implements Visitor { ); } - let left, right; - // handling logical & binary both at the same place as both operate on two operands if (node.type == NodeType.BinaryExpression) { if (node.operator !== "==" && node.operator !== "!=") { this._checkNalla(node); this._checkBoolean(node); } - left = this._getNodeValue(node.left); - right = this._getNodeValue(node.right); } else if (node.type == NodeType.LogicalExpression) { this._checkNalla(node); - - left = node.left.type == NodeType.BooleanLiteral ? (node.left.value == "sahi" ? true : false) : InterpreterModule.getVisitor(node.left.type).visitNode( - node.left - ); - - right = node.right.type == NodeType.BooleanLiteral ? (node.right.value == "sahi" ? true : false) : InterpreterModule.getVisitor(node.right.type).visitNode( - node.right - ); - } + const left = sanatizeData(InterpreterModule.getVisitor(node.left.type).visitNode(node.left)); + const right = sanatizeData(InterpreterModule.getVisitor(node.right.type).visitNode(node.right)); return getOperationValue({ left, right }, node.operator); } @@ -60,12 +50,12 @@ export default class BinaryExpression implements Visitor { if (node.left.type === NodeType.IdentifierExpression && node.left.name) { const value = InterpreterModule.getCurrentScope().get(node.left.name); - if (value === null) throw nallaException; + if (value === null||value.getValue()==null) throw nallaException; } if (node.right.type === NodeType.IdentifierExpression && node.right.name) { const value = InterpreterModule.getCurrentScope().get(node.right.name); - if (value === null) throw nallaException; + if (value === null||value.getValue()==null) throw nallaException; } } @@ -89,30 +79,12 @@ export default class BinaryExpression implements Visitor { if (node.left.type === NodeType.IdentifierExpression && node.left.name) { const value = InterpreterModule.getCurrentScope().get(node.left.name); - if (value === true || value === false) throw runtimeException; + if (value.getValue() === true || value.getValue() === false) throw runtimeException; } if (node.right.type === NodeType.IdentifierExpression && node.right.name) { const value = InterpreterModule.getCurrentScope().get(node.right.name); - if (value === true || value === false) throw runtimeException; + if (value.getValue() === true || value.getValue() === false) throw runtimeException; } } - - private _getNodeValue(node: ASTNode) { - - if (node.type === NodeType.NullLiteral) - return null; - - if (node.type === NodeType.BooleanLiteral) { - return node.value === "sahi" ? true : false; - } - - if (node.type === NodeType.IdentifierExpression && node.name) - return InterpreterModule.getCurrentScope().get(node.name); - - return InterpreterModule.getVisitor(node.type).visitNode( - node - ); - } - } diff --git a/packages/interpreter/src/components/visitor/blockStatement.ts b/packages/interpreter/src/components/visitor/blockStatement.ts index cf0fc382..a4cf993d 100644 --- a/packages/interpreter/src/components/visitor/blockStatement.ts +++ b/packages/interpreter/src/components/visitor/blockStatement.ts @@ -11,9 +11,12 @@ export default class BlockStatement implements Visitor { InterpreterModule.setCurrentScope(new Scope(parentScope)); InterpreterModule.getCurrentScope().setLoop(parentScope.isLoop()); + InterpreterModule.getCurrentScope().setFunction(parentScope.isFunction()); if (Array.isArray(node.body)) { node.body.every((statement: ASTNode) => { + InterpreterModule.getVisitor(statement.type).visitNode(statement); + if (InterpreterModule.getCurrentScope().isBreakStatement()) { return false; } @@ -21,7 +24,12 @@ export default class BlockStatement implements Visitor { parentScope.setContinueStatement(true); return false; } - InterpreterModule.getVisitor(statement.type).visitNode(statement); + if (InterpreterModule.getCurrentScope().isReturnStatement() && parentScope.isFunction()) { + let retValue = InterpreterModule.getCurrentScope().getReturnValue(); + parentScope.setReturnStatement(true, retValue); + return false; + } + return true; }); } diff --git a/packages/interpreter/src/components/visitor/booleanLiteral.ts b/packages/interpreter/src/components/visitor/booleanLiteral.ts index a41a5bbd..ad3322a7 100644 --- a/packages/interpreter/src/components/visitor/booleanLiteral.ts +++ b/packages/interpreter/src/components/visitor/booleanLiteral.ts @@ -1,8 +1,12 @@ import Visitor from "."; import { ASTNode } from "bhai-lang-parser"; +import RuntimeException from "../../exceptions/runtimeException"; +import { BooleanObject } from "../dataClass"; export default class BooleanLiteral implements Visitor { visitNode(node: ASTNode) { - return node.value; + if(node.value!=="sahi"&&node.value!=="galat") + throw new RuntimeException(`Ye kya kar raha hai: "${node.value}" sahi nhi hai ${node.type} me. isme sahi/galat dal`); + return new BooleanObject(node.value === "sahi" ? true : false); } } diff --git a/packages/interpreter/src/components/visitor/callableExpression.ts b/packages/interpreter/src/components/visitor/callableExpression.ts new file mode 100644 index 00000000..8fb8a91b --- /dev/null +++ b/packages/interpreter/src/components/visitor/callableExpression.ts @@ -0,0 +1,40 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; +import { NullObject, sanatizeData } from "../dataClass"; +import RuntimeException from "../../exceptions/runtimeException"; + +export default class CallableExpression implements Visitor { + visitNode(node: ASTNode) { + if (!node.name) { + throw new InvalidStateException(`Invalid node name for: ${node.type}`); + } + + let callable = sanatizeData(InterpreterModule.getCurrentScope().get(node.name)); + if (callable.getType() !== "callable") + throw new RuntimeException(`ye kya kar rha tu: ${node.name} to koi funda hai hi nhi, aise nhi chalega`); + + let value=callable.getValue(); + let args=[]; + if (value.args) { + for (let i = 0; i < value.args.length; i++) { + if(node.args&&node.args[i]){ + let argv=sanatizeData(InterpreterModule.getVisitor(node.args[i].type).visitNode(node.args[i])); + args.push({ + identifier:value.args[i], + value:argv + }); + } + else{ + args.push({ + identifier:value.args[i], + value:new NullObject() + }); + } + } + } + return value.code(args); + } +} diff --git a/packages/interpreter/src/components/visitor/functionDeclaration.ts b/packages/interpreter/src/components/visitor/functionDeclaration.ts new file mode 100644 index 00000000..a5b8de01 --- /dev/null +++ b/packages/interpreter/src/components/visitor/functionDeclaration.ts @@ -0,0 +1,44 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; +import Scope from "../scope"; +import { CallableObject, DataObject } from "../dataClass"; + +export default class FunctionDeclaration implements Visitor { + visitNode(node: ASTNode) { + if (!node.signature || !node.body||!node) { + throw new InvalidStateException(`id or body not found for ${node.type}`); + } + + const functionName = node.signature.name + + let value; + const body = node.body; + if (body && !Array.isArray(body)) { + let scope=InterpreterModule.getCurrentScope() + value={ + args:node.signature.args?.map(arg=>arg.id?.name), + code:(args:{identifier:string,value:DataObject}[]):any=>{ + let oldScope=InterpreterModule.getCurrentScope() + let newScope=new Scope(scope) + args.forEach(arg=>{ + newScope.declare(arg.identifier,arg.value) + }) + newScope.setFunction(true); + InterpreterModule.setCurrentScope(newScope) + InterpreterModule.getVisitor(body.type).visitNode(body); + let result=newScope.getReturnValue() + InterpreterModule.setCurrentScope(new Scope(oldScope)) + return result + } + } + } + const currentScope = InterpreterModule.getCurrentScope(); + + if (functionName) { + currentScope.declare(functionName, new CallableObject(value)); + } + } +} diff --git a/packages/interpreter/src/components/visitor/functionStatement.ts b/packages/interpreter/src/components/visitor/functionStatement.ts new file mode 100644 index 00000000..b4d3aa90 --- /dev/null +++ b/packages/interpreter/src/components/visitor/functionStatement.ts @@ -0,0 +1,15 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; + +export default class FunctionStatement implements Visitor { + visitNode(node: ASTNode) { + if (!node.declaration) + throw new InvalidStateException( + `funda declarations in funda statement is not present: ${node.declaration}` + ); + InterpreterModule.getVisitor(node.declaration.type).visitNode(node.declaration); + } +} diff --git a/packages/interpreter/src/components/visitor/identifierExpression.ts b/packages/interpreter/src/components/visitor/identifierExpression.ts index 16e0270d..4777d656 100644 --- a/packages/interpreter/src/components/visitor/identifierExpression.ts +++ b/packages/interpreter/src/components/visitor/identifierExpression.ts @@ -12,10 +12,6 @@ export default class IdentifierExpression implements Visitor { let value = InterpreterModule.getCurrentScope().get(node.name); - if (value === null) value = "nalla"; - else if (value === true) value = "sahi"; - else if (value === false) value = "galat"; - return value; } } diff --git a/packages/interpreter/src/components/visitor/ifStatement.ts b/packages/interpreter/src/components/visitor/ifStatement.ts index 7ee8555a..b7f857f7 100644 --- a/packages/interpreter/src/components/visitor/ifStatement.ts +++ b/packages/interpreter/src/components/visitor/ifStatement.ts @@ -3,6 +3,7 @@ import { ASTNode } from "bhai-lang-parser"; import InterpreterModule from "../../module/interpreterModule"; import Scope from "../scope"; +import { sanatizeData } from "../dataClass"; export default class IfStatement implements Visitor { @@ -19,10 +20,11 @@ export default class IfStatement implements Visitor { const test = node.test; const parentScope = InterpreterModule.getCurrentScope(); if (test) { - const testResult = InterpreterModule.getVisitor(test.type).visitNode(test); - if (testResult === true || testResult === "sahi") { + const testResult = sanatizeData(InterpreterModule.getVisitor(test.type).visitNode(test)); + if (testResult.getValue()) { this.evaluateNode(node.consequent, parentScope); - } else { + } + else { const alternates = node.alternates; if (alternates && alternates.length > 0) { for (var alternate of alternates) { @@ -34,12 +36,16 @@ export default class IfStatement implements Visitor { } else { // Evaluate the "test" condition of the "nahi to bhai" node // If the condition is true, evaluate the node and break - const testResult = InterpreterModule.getVisitor(alternateTest!.type).visitNode(alternateTest); - if (testResult === true || testResult === "sahi") { - this.evaluateNode(alternate.consequent, parentScope); + const testResult = sanatizeData(InterpreterModule.getVisitor(alternateTest!.type).visitNode(alternateTest)); + if (testResult.getValue()) { + this.evaluateNode(alternate, parentScope); break; } } + if (testResult.getValue()) { + this.evaluateNode(alternate.consequent, parentScope); + break; + } } } } diff --git a/packages/interpreter/src/components/visitor/index.ts b/packages/interpreter/src/components/visitor/index.ts index cc6b9e33..6f1f8f63 100644 --- a/packages/interpreter/src/components/visitor/index.ts +++ b/packages/interpreter/src/components/visitor/index.ts @@ -1,5 +1,6 @@ import { ASTNode } from "bhai-lang-parser"; +import { DataObject } from "../dataClass"; export default interface Visitor { - visitNode(node: ASTNode): unknown; + visitNode(node: ASTNode): DataObject|null|void; } diff --git a/packages/interpreter/src/components/visitor/nullLiteral.ts b/packages/interpreter/src/components/visitor/nullLiteral.ts index eda6d55d..7a7dd0ad 100644 --- a/packages/interpreter/src/components/visitor/nullLiteral.ts +++ b/packages/interpreter/src/components/visitor/nullLiteral.ts @@ -1,8 +1,11 @@ import Visitor from "."; import { ASTNode } from "bhai-lang-parser"; +import { NullObject } from "../dataClass"; export default class NullLiteral implements Visitor { visitNode(node: ASTNode) { - return node.value; + if (node.value !== "nalla") + throw new Error(`Ye kya kar raha hai: "${node.value}" sahi nhi hai. isme nalla dal`); + return new NullObject(); } } diff --git a/packages/interpreter/src/components/visitor/numericLiteral.ts b/packages/interpreter/src/components/visitor/numericLiteral.ts index ca022029..86416093 100644 --- a/packages/interpreter/src/components/visitor/numericLiteral.ts +++ b/packages/interpreter/src/components/visitor/numericLiteral.ts @@ -1,8 +1,12 @@ import Visitor from "."; import { ASTNode } from "bhai-lang-parser"; +import { NumericObject } from "../dataClass"; +import RuntimeException from "../../exceptions/runtimeException"; export default class NumericLiteral implements Visitor { visitNode(node: ASTNode) { - return node.value; + if(typeof node.value!=="number") + throw new RuntimeException(`Ye kya kar raha hai: "${node.value}" sahi nhi hai ${node.type} me. isme number dal`); + return new NumericObject(node.value); } } diff --git a/packages/interpreter/src/components/visitor/printStatement.ts b/packages/interpreter/src/components/visitor/printStatement.ts index 2f8a94de..5c049abb 100644 --- a/packages/interpreter/src/components/visitor/printStatement.ts +++ b/packages/interpreter/src/components/visitor/printStatement.ts @@ -3,6 +3,7 @@ import { ASTNode } from "bhai-lang-parser"; import InvalidStateException from "../../exceptions/invalidStateException"; import InterpreterModule from "../../module/interpreterModule"; +import { sanatizeData } from "../dataClass"; export default class PrintStatement implements Visitor { @@ -14,15 +15,10 @@ export default class PrintStatement implements Visitor { const value = node.expressions .map((expression: ASTNode) => { - let currentNodeOutput = InterpreterModule.getVisitor(expression.type).visitNode(expression); - if (currentNodeOutput === true) - currentNodeOutput = "sahi"; - else if (currentNodeOutput === false) - currentNodeOutput = "galat"; - return currentNodeOutput; - } - ) - .join(" "); + let currentNodeOutput = sanatizeData(InterpreterModule.getVisitor(expression.type).visitNode(expression)); + return currentNodeOutput?.getStringValue(); + } + ).join(" "); console.log(value); } } diff --git a/packages/interpreter/src/components/visitor/returnStatement.ts b/packages/interpreter/src/components/visitor/returnStatement.ts new file mode 100644 index 00000000..3aa2c1ee --- /dev/null +++ b/packages/interpreter/src/components/visitor/returnStatement.ts @@ -0,0 +1,25 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; +import { sanatizeData } from "../dataClass"; +import RuntimeException from "../../exceptions/runtimeException"; + + +export default class ReturnStatement implements Visitor { + visitNode(node: ASTNode) { + if (InterpreterModule.getCurrentScope().isFunction()) { + if (!node.expression) + throw new InvalidStateException( + `No expressions to print: ${node.expressions}` + ); + let retVal= sanatizeData(InterpreterModule.getVisitor(node.expression.type).visitNode(node.expression)); + InterpreterModule.getCurrentScope().setReturnStatement(true,retVal); + return retVal; + } + else{ + throw new RuntimeException(`Kya "rakh le bhai"?? Funda kidhar hai?`); + } + } +} diff --git a/packages/interpreter/src/components/visitor/stringLiteral.ts b/packages/interpreter/src/components/visitor/stringLiteral.ts index be354200..ca4a056e 100644 --- a/packages/interpreter/src/components/visitor/stringLiteral.ts +++ b/packages/interpreter/src/components/visitor/stringLiteral.ts @@ -1,8 +1,12 @@ import Visitor from "."; import { ASTNode } from "bhai-lang-parser"; +import RuntimeException from "../../exceptions/runtimeException"; +import { StringObject } from "../dataClass"; export default class StringLiteral implements Visitor { visitNode(node: ASTNode) { - return node.value; + if(typeof node.value!=="string") + throw new RuntimeException(`Ye kya kar raha hai: "${node.value}" sahi nhi hai ${node.name} me. isme sting dal`); + return new StringObject(node.value); } } diff --git a/packages/interpreter/src/components/visitor/variableDeclaration.ts b/packages/interpreter/src/components/visitor/variableDeclaration.ts index c459e5ee..0b2059d4 100644 --- a/packages/interpreter/src/components/visitor/variableDeclaration.ts +++ b/packages/interpreter/src/components/visitor/variableDeclaration.ts @@ -1,8 +1,9 @@ import Visitor from "."; -import { ASTNode, NodeType } from "bhai-lang-parser"; +import { ASTNode } from "bhai-lang-parser"; import InvalidStateException from "../../exceptions/invalidStateException"; import InterpreterModule from "../../module/interpreterModule"; +import { sanatizeData } from "../dataClass"; export default class VariableDeclaration implements Visitor { visitNode(node: ASTNode) { @@ -12,13 +13,7 @@ export default class VariableDeclaration implements Visitor { const identifier = node.id.name; - let value; - - if (node.init.type === NodeType.NullLiteral) value = null; - else if (node.init.type === NodeType.BooleanLiteral) - value = node.init.value === "sahi" ? true : false; - else - value = InterpreterModule.getVisitor(node.init.type).visitNode(node.init); + let value= sanatizeData(InterpreterModule.getVisitor(node.init.type).visitNode(node.init)); const currentScope = InterpreterModule.getCurrentScope(); diff --git a/packages/interpreter/src/components/visitor/whileStatement.ts b/packages/interpreter/src/components/visitor/whileStatement.ts index e7037b52..7347bec7 100644 --- a/packages/interpreter/src/components/visitor/whileStatement.ts +++ b/packages/interpreter/src/components/visitor/whileStatement.ts @@ -4,13 +4,15 @@ import { ASTNode } from "bhai-lang-parser"; import RuntimeException from "../../exceptions/runtimeException"; import InterpreterModule from "../../module/interpreterModule"; import Scope from "../scope"; +import { sanatizeData } from "../dataClass"; export default class WhileStatement implements Visitor { visitNode(node: ASTNode) { const test = node.test; if (test) { - const getConditionValue = ()=> InterpreterModule.getVisitor(test.type).visitNode(test); + const getConditionValue = ()=> sanatizeData(InterpreterModule.getVisitor(test.type).visitNode(test)); + const parentScope = InterpreterModule.getCurrentScope(); @@ -19,7 +21,9 @@ export default class WhileStatement implements Visitor { InterpreterModule.getCurrentScope().setLoop(true); - for (let testResult = getConditionValue(), executions = 0; testResult === true || testResult === "sahi"; testResult = getConditionValue(), executions++) { + for (let testResult = getConditionValue(), executions = 0; + testResult.getValue(); + testResult = getConditionValue(), executions++) { if (InterpreterModule.getCurrentScope().isBreakStatement()) { break; @@ -45,3 +49,4 @@ export default class WhileStatement implements Visitor { } } } + diff --git a/packages/interpreter/src/helpers/index.ts b/packages/interpreter/src/helpers/index.ts index 3b3eb4f7..deedc9c0 100644 --- a/packages/interpreter/src/helpers/index.ts +++ b/packages/interpreter/src/helpers/index.ts @@ -1,40 +1,42 @@ +import { BooleanObject, DataObject, DataTypes, NumericObject, StringObject } from "../components/dataClass"; import InvalidStateException from "../exceptions/invalidStateException"; import RuntimeException from "../exceptions/runtimeException"; export function checkNumberOperands(operands: { - left: unknown; - right: unknown; -}): operands is { left: number; right: number } { + left: DataObject; + right: DataObject; +}):boolean{ return ( - typeof operands.left === "number" && typeof operands.right === "number" + operands.left.getType() === DataTypes.Numeric && operands.right.getType() === DataTypes.Numeric ); } export function checkStringOperands(operands: { - left: unknown; - right: unknown; -}): operands is { left: string; right: string } { + left: DataObject; + right: DataObject; +}):boolean{ return ( - typeof operands.left === "string" && typeof operands.right === "string" + operands.left.getType() === DataTypes.String && operands.right.getType() === DataTypes.String ); } export function checkNumberAndStringOperands(operands: { - left: unknown; - right: unknown; -}): operands is { left: string; right: string } { + left: DataObject; + right: DataObject; +}): operands is { left: StringObject; right: NumericObject }|{ left: NumericObject; right: StringObject } { return ( - (typeof operands.left === "string" && typeof operands.right === "number") || (typeof operands.right === "string" && typeof operands.left === "number") + (operands.left.getType() === DataTypes.String && operands.right.getType() === DataTypes.Numeric) || + (operands.right.getType() === DataTypes.String && operands.left.getType() === DataTypes.Numeric) ); } export function getOperationValue( - operands: { left: unknown; right: unknown }, + operands: { left: DataObject; right: DataObject }, operator: string ) { const exception = new RuntimeException( - `Ye kya kar raha hai: "${operator}" ke sath "${typeof operands.left}" aur "${typeof operands.right}" nahi jamte.` + `Ye kya kar raha hai: "${operator}" ke sath "${typeof operands.left.getStringValue()}" aur "${typeof operands.right.getStringValue()}" nahi jamte.` ); switch (operator) { @@ -44,15 +46,15 @@ export function getOperationValue( case "+=": case "+": if (checkNumberOperands(operands)) { - return operands.left + operands.right; + return new NumericObject(operands.left.getValue() + operands.right.getValue()); } if (checkStringOperands(operands)) { - return operands.left + operands.right; + return new StringObject(operands.left.getValue() + operands.right.getValue()); } if (checkNumberAndStringOperands(operands)) { - return operands.left.toString() + operands.right.toString(); + return new StringObject(operands.left.getStringValue() + operands.right.getStringValue()); } throw exception; @@ -60,7 +62,7 @@ export function getOperationValue( case "-=": case "-": if (checkNumberOperands(operands)) { - return operands.left - operands.right; + return new NumericObject(operands.left.getValue() - operands.right.getValue()); } throw exception; @@ -68,19 +70,19 @@ export function getOperationValue( case "*=": case "*": if (checkNumberOperands(operands)) { - return operands.left * operands.right; + return new NumericObject(operands.left.getValue() * operands.right.getValue()); } throw exception; case "/=": case "/": - if (operands.right === 0) { + if (operands.right.getValue() === 0) { throw new RuntimeException(`Kya kar rha hai tu??...zero se divide ni karte`); } if (checkNumberOperands(operands)) { - return operands.left / operands.right; + return new NumericObject(operands.left.getValue() / operands.right.getValue()); } throw exception; @@ -88,53 +90,60 @@ export function getOperationValue( case "%=": case "%": if (checkNumberOperands(operands)) { - return operands.left % operands.right; + return new NumericObject(operands.left.getValue() % operands.right.getValue()); } throw exception; case "==": - - return operands.left === operands.right; - + return new BooleanObject(operands.left.getValue() === operands.right.getValue()); case "!=": - - return operands.left !== operands.right; - + return new BooleanObject(operands.left.getValue() !== operands.right.getValue()); + case ">": if (checkNumberOperands(operands)) { - return operands.left > operands.right; + return new BooleanObject(operands.left.getValue() > operands.right.getValue()); } throw exception; case "<": if (checkNumberOperands(operands)) { - return operands.left < operands.right; + return new BooleanObject(operands.left.getValue() < operands.right.getValue()); } throw exception; case ">=": if (checkNumberOperands(operands)) { - return operands.left >= operands.right; + return new BooleanObject(operands.left.getValue() >= operands.right.getValue()); } throw exception; case "<=": if (checkNumberOperands(operands)) { - return operands.left <= operands.right; + return new BooleanObject(operands.left.getValue() <= operands.right.getValue()); } throw exception; case "&&": - return operands.left && operands.right; + if(operands.left.getValue()){ + return operands.right; + } + else { + return operands.left; + } case "||": - return operands.left || operands.right; - + if(operands.left.getValue()){ + return operands.left; + } + else{ + return operands.right; + } + default: throw new InvalidStateException(`Unsupported operator: ${operator}`); } diff --git a/packages/interpreter/src/module/interpreterModule.ts b/packages/interpreter/src/module/interpreterModule.ts index adef304b..b8fff628 100644 --- a/packages/interpreter/src/module/interpreterModule.ts +++ b/packages/interpreter/src/module/interpreterModule.ts @@ -8,9 +8,12 @@ import BinaryExpression from "../components/visitor/binaryExpression"; import BlockStatement from "../components/visitor/blockStatement"; import BooleanLiteral from "../components/visitor/booleanLiteral"; import BreakStatement from "../components/visitor/breakStatement"; +import CallableExpression from "../components/visitor/callableExpression"; import ContinueStatement from "../components/visitor/continueStatement"; import EmptyStatement from "../components/visitor/emptyStatement"; import ExpressionStatement from "../components/visitor/expressionStatement"; +import FunctionDeclaration from "../components/visitor/functionDeclaration"; +import FunctionStatement from "../components/visitor/functionStatement"; import IdentifierExpression from "../components/visitor/identifierExpression"; import IfStatement from "../components/visitor/ifStatement"; import InitStatement from "../components/visitor/initStatement"; @@ -18,6 +21,7 @@ import NullLiteral from "../components/visitor/nullLiteral"; import NumericLiteral from "../components/visitor/numericLiteral"; import PrintStatement from "../components/visitor/printStatement"; import Program from "../components/visitor/program"; +import ReturnStatement from "../components/visitor/returnStatement"; import StringLiteral from "../components/visitor/stringLiteral"; import VariableDeclaration from "../components/visitor/variableDeclaration"; import VariableStatement from "../components/visitor/variableStatement"; @@ -47,6 +51,11 @@ export default class InterpreterModule { [NodeType.WhileStatement]: new WhileStatement(), [NodeType.BreakStatement]: new BreakStatement(), [NodeType.ContinueStatement]: new ContinueStatement(), + [NodeType.FunctionStatement]: new FunctionStatement(), + [NodeType.FunctionDeclaration]: new FunctionDeclaration(), + [NodeType.CallableExpression]: new CallableExpression(), + [NodeType.ReturnStatement]: new ReturnStatement(), + } as Record; private static _currentScope: Scope; diff --git a/packages/interpreter/test/visitorUnitTests/helper.test.ts b/packages/interpreter/test/visitorUnitTests/helper.test.ts index 162138c6..68f635bb 100644 --- a/packages/interpreter/test/visitorUnitTests/helper.test.ts +++ b/packages/interpreter/test/visitorUnitTests/helper.test.ts @@ -1,5 +1,6 @@ import { RuntimeException } from "../../src"; import InvalidStateException from "../../src/exceptions/invalidStateException"; +import {BooleanObject, DataObject, NullObject, NumericObject, StringObject} from "../../src/components/dataClass"; import { checkNumberOperands, checkStringOperands, @@ -12,8 +13,8 @@ const testCaseProvider = [ { name: "test checkNumberOperands with number oprands, should return true", input: { - left: 45, - right: 67, + left: new NumericObject(45), + right: new NumericObject(67), }, output: true, function: checkNumberOperands, @@ -21,8 +22,8 @@ const testCaseProvider = [ { name: "test checkNumberOperands without number oprands, should return false", input: { - left: "hello", - right: "67", + left: new StringObject("hello"), + right: new StringObject("67"), }, output: false, function: checkNumberOperands, @@ -30,8 +31,8 @@ const testCaseProvider = [ { name: "test checkNumberOperands with one number oprand and one non-number operand, should return false", input: { - left: 90, - right: "67", + left: new NumericObject(90), + right: new StringObject("67"), }, output: false, function: checkNumberOperands, @@ -39,8 +40,8 @@ const testCaseProvider = [ { name: "test checkNumberOperands with one number oprand and one non-number operand - 2, should return false", input: { - left: "67", - right: 5678, + left: new StringObject("67"), + right: new NumericObject(5678), }, output: false, function: checkNumberOperands, @@ -49,8 +50,8 @@ const testCaseProvider = [ { name: "test checkStringOperands with string oprands, should return true", input: { - left: "45", - right: "asdasdas", + left: new StringObject("45"), + right: new StringObject("asdasdas"), }, output: true, function: checkStringOperands, @@ -58,8 +59,8 @@ const testCaseProvider = [ { name: "test checkStringOperands without string oprands, should return false", input: { - left: 23432, - right: null, + left: new NumericObject(23432), + right: new NullObject(), }, output: false, function: checkStringOperands, @@ -67,8 +68,8 @@ const testCaseProvider = [ { name: "test checkStringOperands with one string oprand and one non-string operand, should return false", input: { - left: 90, - right: "67", + left: new NumericObject(90), + right: new StringObject("67"), }, output: false, function: checkStringOperands, @@ -76,419 +77,437 @@ const testCaseProvider = [ { name: "test checkStringOperands with one number string and one non-string operand - 2, should return false", input: { - left: "67", - right: 5678, + left: new StringObject("67"), + right: new NumericObject(5678), }, output: false, function: checkStringOperands, }, ]; -const getOperationValuePosTestCasesProvider = [ +const getOperationValuePosTestCasesProvider:{ + name: string, + input1: { + left: DataObject, + right: DataObject, + }, + input2: string, + output: DataObject, + function: Function, +}[] = [ // getOperationValue tests { name: `test getOperationValue "=" operator with string oprands, should return value of right operand - number`, input1: { - left: 23432, - right: 890, + left: new NumericObject(23432), + right: new NumericObject(890), }, input2: "=", - output: 890, + output: new NumericObject(890), function: getOperationValue, }, { name: `test getOperationValue "=" operator with string oprands, should return value of right operand - null`, input1: { - left: 23432, - right: null, + left: new NumericObject(23432), + right: new NullObject(), }, input2: "=", - output: null, + output: new NullObject(), function: getOperationValue, }, { name: `test getOperationValue "=" operator with string oprands, should return value of right operand - string`, input1: { - left: 23432, - right: "hello", + left: new NumericObject(23432), + right: new StringObject("hello"), }, input2: "=", - output: "hello", + output: new StringObject("hello"), function: getOperationValue, }, { name: `test getOperationValue "+" operator with string oprands, should success`, input1: { - left: "hello", - right: "crap", + left: new StringObject("hello"), + right: new StringObject("crap"), }, input2: "+", - output: "hellocrap", + output: new StringObject("hellocrap"), function: getOperationValue, }, { name: `test getOperationValue "+" operator with number oprands, should success`, input1: { - left: 2, - right: 3, + left: new NumericObject(2), + right: new NumericObject(3), }, input2: "+", - output: 5, + output: new NumericObject(5), function: getOperationValue, }, { name: `test getOperationValue "+" operator with one number and one string oprands, should success`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "+", - output: "15hello", + output: new StringObject("15hello"), function: getOperationValue, }, { name: `test getOperationValue "+" operator with second operand number and first string, should success`, input1: { - left: "hello", - right: 15, + left: new StringObject("hello"), + right: new NumericObject(15), }, input2: "+", - output: "hello15", + output: new StringObject("hello15"), function: getOperationValue, }, { name: `test getOperationValue "+" operator with one very large number and one string oprands, should success`, input1: { - left: 15378247823432, - right: "hello", + left: new NumericObject(15378247823432), + right: new StringObject("hello"), }, input2: "+", - output: "15378247823432hello", + output: new StringObject("15378247823432hello"), function: getOperationValue, }, { name: `test getOperationValue "+=" operator with number oprands, should success`, input1: { - left: 2, - right: 3, + left: new NumericObject(2), + right: new NumericObject(3), }, input2: "+=", - output: 5, + output: new NumericObject(5), function: getOperationValue, }, { name: `test getOperationValue "+=" operator with string oprands, should success`, input1: { - left: "hello", - right: "crap", + left: new StringObject("hello"), + right: new StringObject("crap"), }, input2: "+=", - output: "hellocrap", + output: new StringObject("hellocrap"), function: getOperationValue, }, { name: `test getOperationValue "+=" operator with one number and one string oprands, should success`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "+=", - output: "15hello", + output: new StringObject("15hello"), function: getOperationValue, }, { name: `test getOperationValue "+=" operator with second operand number and first string, should success`, input1: { - left: "hello", - right: 15, + left: new StringObject("hello"), + right: new NumericObject(15), }, input2: "+=", - output: "hello15", + output: new StringObject("hello15"), function: getOperationValue, }, { name: `test getOperationValue "+=" operator with one very large number and one string oprands, should success`, input1: { - left: 15378247823432, - right: "hello", + left: new NumericObject(15378247823432), + right: new StringObject("hello"), }, input2: "+", - output: "15378247823432hello", + output: new StringObject("15378247823432hello"), function: getOperationValue, }, { name: `test getOperationValue "-" operator with number oprands, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "-", - output: 2, + output: new NumericObject(2), function: getOperationValue, }, { name: `test getOperationValue "-=" operator with number oprands, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "-=", - output: 2, + output: new NumericObject(2), function: getOperationValue, }, { name: `test getOperationValue "*=" operator with number oprands, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "*=", - output: 15, + output: new NumericObject(15), function: getOperationValue, }, { name: `test getOperationValue "*" operator with number oprands, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "*", - output: 15, + output: new NumericObject(15), function: getOperationValue, }, { name: `test getOperationValue "/=" operator with number oprands, should success`, input1: { - left: 15, - right: 3, + left: new NumericObject(15), + right: new NumericObject(3), }, input2: "/=", - output: 5, + output: new NumericObject(5), function: getOperationValue, }, { name: `test getOperationValue "/" operator with number oprands, should success`, input1: { - left: 15, - right: 3, + left: new NumericObject(15), + right: new NumericObject(3), }, input2: "/", - output: 5, + output: new NumericObject(5), function: getOperationValue, }, { name: `test getOperationValue "==" operator with number oprands, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: "==", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue "==" operator with number oprands unequal, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "==", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "==" operator with string oprands, should success`, input1: { - left: "hell", - right: "hell", + left: new StringObject("hell"), + right: new StringObject("hell"), }, input2: "==", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue "==" operator with string oprands unequal, should success`, input1: { - left: "crap", - right: "hell", + left: new StringObject("crap"), + right: new StringObject("hell"), }, input2: "==", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "==" operator with one string & one number, should success`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: "==", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, // != { name: `test getOperationValue "!=" operator with number oprands, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: "!=", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "!=" operator with number oprands unequal, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "!=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue "!=" operator with string oprands, should success`, input1: { - left: "hell", - right: "hell", + left: new StringObject("hell"), + right: new StringObject("hell"), }, input2: "!=", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "!=" operator with string oprands unequal, should success`, input1: { - left: "crap", - right: "hell", + left: new StringObject("crap"), + right: new StringObject("hell"), }, input2: "!=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue "!=" operator with one string & one number, should success`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: "!=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, // > { name: `test getOperationValue ">" operator with number oprands, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: ">", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue ">" operator with number oprands left greater, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: ">", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, // < { name: `test getOperationValue "<" operator with number oprands, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: "<", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "<" operator with number oprands left smaller, should success`, input1: { - left: 1, - right: 3, + left: new NumericObject(1), + right: new NumericObject(3), }, input2: "<", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, // >= { name: `test getOperationValue ">=" operator with number oprands equal, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: ">=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue ">=" operator with number oprands left greater, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: ">=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue ">=" operator with number oprands left smaller, should success`, input1: { - left: 1, - right: 3, + left: new NumericObject(1), + right: new NumericObject(3), }, input2: ">=", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, // <= { name: `test getOperationValue "<=" operator with number oprands equal, should success`, input1: { - left: 3, - right: 3, + left: new NumericObject(3), + right: new NumericObject(3), }, input2: "<=", - output: true, + output: new BooleanObject(true), function: getOperationValue, }, { name: `test getOperationValue "<=" operator with number oprands left greater, should success`, input1: { - left: 5, - right: 3, + left: new NumericObject(5), + right: new NumericObject(3), }, input2: "<=", - output: false, + output: new BooleanObject(false), function: getOperationValue, }, { name: `test getOperationValue "<=" operator with number oprands left smaller, should success`, input1: { - left: 1, - right: 3, + left: new NumericObject(1), + right: new NumericObject(3), }, input2: "<=", - output: true, + output: new BooleanObject(true), function: getOperationValue, } ]; -const getOperationValueNegTestCasesProvider = [ +const getOperationValueNegTestCasesProvider:{ + name: string, + input1: { + left: DataObject, + right: DataObject, + }, + input2: string, + function: Function, + exception: any, +}[] = [ { name: `test getOperationValue "+" operator with one boolean and one string oprands, should throw an exception`, input1: { - left: true, - right: "hello", + left: new BooleanObject(true), + right: new StringObject("hello"), }, input2: "+", exception: RuntimeException, @@ -497,8 +516,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "+" operator with second operand boolean and first string, should throw an exception`, input1: { - left: "true", - right: false, + left: new StringObject("true"), + right: new BooleanObject(false), }, input2: "+", exception: RuntimeException, @@ -507,8 +526,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "+=" operator with one boolean and one string oprands, should throw an exception`, input1: { - left: true, - right: "hello", + left: new BooleanObject(true), + right: new StringObject("hello"), }, input2: "+=", exception: RuntimeException, @@ -517,8 +536,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "+=" operator with second operand boolean and first string, should throw an exception`, input1: { - left: "true", - right: false, + left: new StringObject("true"), + right: new BooleanObject(false), }, input2: "+=", exception: RuntimeException, @@ -527,8 +546,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "-" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "-", exception: RuntimeException, @@ -537,8 +556,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "-=" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "-=", exception: RuntimeException, @@ -547,8 +566,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "-=" operator with both strings oprands, should throw an exception`, input1: { - left: "15", - right: "hello", + left: new StringObject("15"), + right: new StringObject("hello"), }, input2: "-=", exception: RuntimeException, @@ -557,8 +576,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "*" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "*", exception: RuntimeException, @@ -567,8 +586,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "*=" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "*=", exception: RuntimeException, @@ -577,8 +596,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "/" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "/", exception: RuntimeException, @@ -587,8 +606,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "/=" operator with one number and one string oprands, should throw an exception`, input1: { - left: 15, - right: "hello", + left: new NumericObject(15), + right: new StringObject("hello"), }, input2: "/=", exception: RuntimeException, @@ -597,8 +616,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "/" operator with zero divisor, should throw an exception`, input1: { - left: 15, - right: 0, + left: new NumericObject(15), + right: new NumericObject(0), }, input2: "/", exception: RuntimeException, @@ -607,8 +626,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "/=" operator with zero divisor, should throw an exception`, input1: { - left: 15, - right: 0, + left: new NumericObject(15), + right: new NumericObject(0), }, input2: "/", exception: RuntimeException, @@ -617,8 +636,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "#" operator, should throw an exception`, input1: { - left: 15, - right: 5, + left: new NumericObject(15), + right: new NumericObject(5), }, input2: "#", exception: InvalidStateException, @@ -627,8 +646,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue ">" operator with one string & one number, should throw an exception`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: ">", exception: RuntimeException, @@ -637,8 +656,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue ">" operator with both string , should throw an exception`, input1: { - left: "cap", - right: "hell", + left: new StringObject("cap"), + right: new StringObject("hell"), }, input2: ">", exception: RuntimeException, @@ -647,8 +666,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "<" operator with one string & one number, should throw an exception`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: "<", exception: RuntimeException, @@ -657,8 +676,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "<" operator with both string , should throw an exception`, input1: { - left: "cap", - right: "hell", + left: new StringObject("cap"), + right: new StringObject("hell"), }, input2: "<", exception: RuntimeException, @@ -667,8 +686,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue ">=" operator with one string & one number, should throw an exception`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: ">=", exception: RuntimeException, @@ -677,8 +696,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue ">=" operator with both string , should throw an exception`, input1: { - left: "cap", - right: "hell", + left: new StringObject("cap"), + right: new StringObject("hell"), }, input2: ">=", exception: RuntimeException, @@ -687,8 +706,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "<=" operator with one string & one number, should throw an exception`, input1: { - left: 15, - right: "hell", + left: new NumericObject(15), + right: new StringObject("hell"), }, input2: "<=", exception: RuntimeException, @@ -697,8 +716,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "<=" operator with both string , should throw an exception`, input1: { - left: "cap", - right: "hell", + left: new StringObject("cap"), + right: new StringObject("hell"), }, input2: "<=", exception: RuntimeException, @@ -707,8 +726,8 @@ const getOperationValueNegTestCasesProvider = [ { name: `test getOperationValue "**" operator with unsupported operator , should throw an exception`, input1: { - left: "cap", - right: "hell", + left: new StringObject("cap"), + right: new StringObject("hell"), }, input2: "**", exception: InvalidStateException, diff --git a/packages/interpreter/test/visitorUnitTests/unitTestsForNegativeCases.test.ts b/packages/interpreter/test/visitorUnitTests/unitTestsForNegativeCases.test.ts index 38a3f306..63f72aae 100644 --- a/packages/interpreter/test/visitorUnitTests/unitTestsForNegativeCases.test.ts +++ b/packages/interpreter/test/visitorUnitTests/unitTestsForNegativeCases.test.ts @@ -1,6 +1,7 @@ import { NodeType } from "bhai-lang-parser"; import { RuntimeException } from "../../src"; +import { NumericObject } from "../../src/components/dataClass"; import Scope from "../../src/components/scope"; import AssignmentExpression from "../../src/components/visitor/assignmentExpression"; import BinaryExpression from "../../src/components/visitor/binaryExpression"; @@ -160,7 +161,7 @@ test("interpreter test PrintStatement without expressions should throw an except }); test("interpreter test Scope assign with undeclared variable should throw an exception", () => { - expect(() => scope.assign("undeclared_identifier", 45)).toThrow( + expect(() => scope.assign("undeclared_identifier", new NumericObject(45))).toThrow( RuntimeException ); }); diff --git a/packages/parser/src/components/parser/statement/expression/callableExpression.ts b/packages/parser/src/components/parser/statement/expression/callableExpression.ts new file mode 100644 index 00000000..b14fdfa6 --- /dev/null +++ b/packages/parser/src/components/parser/statement/expression/callableExpression.ts @@ -0,0 +1,33 @@ +import Expression from "."; + +import { TokenTypes } from "../../../../constants/bhaiLangSpec"; +import { NodeType } from "../../../../constants/constants"; +import { ASTNode } from "../../types/nodeTypes"; + +export default class CallableExpression extends Expression { + getExpression(): ASTNode { + const name = this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CALLABLE_TYPE).value; + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.OPEN_PARENTHESIS_TYPE); + + // read arguments + let args: any[] = []; + if(this._tokenExecutor.getLookahead()?.type !== TokenTypes.CLOSED_PARENTHESIS_TYPE) { + do { + args.push(this._getArgs()); + } while ( + this._tokenExecutor.getLookahead()?.type === TokenTypes.COMMA_TYPE && + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.COMMA_TYPE) + ); + } + + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CLOSED_PARENTHESIS_TYPE); + return { + type: NodeType.CallableExpression, + name, + args, + }; + } + private _getArgs() { + return Expression.getExpressionImpl(NodeType.AssignmentExpression).getExpression(); + } +} diff --git a/packages/parser/src/components/parser/statement/expression/index.ts b/packages/parser/src/components/parser/statement/expression/index.ts index 28849f74..0ff6135d 100644 --- a/packages/parser/src/components/parser/statement/expression/index.ts +++ b/packages/parser/src/components/parser/statement/expression/index.ts @@ -41,6 +41,10 @@ export default abstract class Expression { case NodeType.RelationalExpression: return BhaiLangModule.getRelationalExpression(); + + case NodeType.CallableExpression: + return BhaiLangModule.getCallableExpression(); + default: return BhaiLangModule.getIndentifierExpression(); diff --git a/packages/parser/src/components/parser/statement/expression/primaryExpression.ts b/packages/parser/src/components/parser/statement/expression/primaryExpression.ts index 2e40c91e..c54cd550 100644 --- a/packages/parser/src/components/parser/statement/expression/primaryExpression.ts +++ b/packages/parser/src/components/parser/statement/expression/primaryExpression.ts @@ -21,6 +21,10 @@ export default class PrimaryExpression extends Expression { return Literal.getLiteralImpl(token.type).getLiteral(); case TokenTypes.NALLA_TYPE: return this._getNallaLiteral(); + case TokenTypes.CALLABLE_TYPE: + return Expression.getExpressionImpl( + NodeType.CallableExpression + ).getExpression(); default: return this._getLeftHandSideExpression(); } diff --git a/packages/parser/src/components/parser/statement/functionStatement.ts b/packages/parser/src/components/parser/statement/functionStatement.ts new file mode 100644 index 00000000..462a4b1a --- /dev/null +++ b/packages/parser/src/components/parser/statement/functionStatement.ts @@ -0,0 +1,112 @@ +import Statement from "."; + +import { TokenTypes } from "../../../constants/bhaiLangSpec"; +import { NodeType } from "../../../constants/constants"; +import TokenExecutor from "../tokenExecutor"; +import { ASTNode } from "../types/nodeTypes"; + +import Expression from "./expression"; +import NullLiteral from "./expression/literals/nullLiteral"; + +export default class FunctionStatement extends Statement { + _nullLiteral: NullLiteral; + + constructor(tokenExecutor: TokenExecutor, nullLiteral: NullLiteral) { + super(tokenExecutor); + this._nullLiteral = nullLiteral; + } + + getStatement(): ASTNode { + this._tokenExecutor.eatTokenAndForwardLookahead( + TokenTypes.FUNDA_TYPE + ); + + const declaration = this._getFunctionDeclaration(); + + return { + type: NodeType.FunctionStatement, + declaration, + }; + } + + + private _getFunctionDeclaration(): ASTNode { + + const functionSigneture = this._getFunctionSignature(); + + let lookahead=this._tokenExecutor.getLookahead() + + if (lookahead == null) { + throw new SyntaxError(`Unexpected end of "apna funda" statement`); + } + + if(lookahead.type!==TokenTypes.OPEN_CURLY_BRACE_TYPE){ + throw new SyntaxError(`Unexpected token after funda signature ${functionSigneture.name}, got "${lookahead.value}" : expected "{"`); + } + + const body=Statement.getStatementImpl(this._tokenExecutor.getLookahead()!).getStatement(); + + return { + type: NodeType.FunctionDeclaration, + signature:functionSigneture, + body + }; + } + + private _getFunctionSignature(): ASTNode { + const functionName = this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CALLABLE_TYPE).value; + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.OPEN_PARENTHESIS_TYPE); + + let args:ASTNode[]=[] + if(this._tokenExecutor.getLookahead()?.type!=TokenTypes.CLOSED_PARENTHESIS_TYPE){ + args=this._getFunctionArguments(); + } + + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.CLOSED_PARENTHESIS_TYPE); + return { + type: NodeType.FunctionSignature, + name:functionName, + args + }; + } + + private _getFunctionArguments(): ASTNode[] { + const declarations: ASTNode[] = []; + do { + declarations.push(this._getArgumentDeclaration()); + } while ( + this._tokenExecutor.getLookahead()?.type === TokenTypes.COMMA_TYPE && + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.COMMA_TYPE) + ); + return declarations; + } + + private _getArgumentDeclaration(): ASTNode { + const id = Expression.getExpressionImpl( + NodeType.IdentifierExpression + ).getExpression(); + + // Optional VariableInitializer + const init = + this._tokenExecutor.getLookahead()?.type !== TokenTypes.CLOSED_PARENTHESIS_TYPE && + this._tokenExecutor.getLookahead()?.type !== TokenTypes.COMMA_TYPE + ? this._getArgumentInitializer() + : this._nullLiteral.getLiteral(); + + return { + type: NodeType.VariableDeclaration, + id, + init, + }; + } + private _getArgumentInitializer() { + this._tokenExecutor.eatTokenAndForwardLookahead( + TokenTypes.SIMPLE_ASSIGN_TYPE + ); + + return Expression.getExpressionImpl( + NodeType.PrimaryExpression + ).getExpression(); + } + +} diff --git a/packages/parser/src/components/parser/statement/index.ts b/packages/parser/src/components/parser/statement/index.ts index e03106e5..0f5970f7 100644 --- a/packages/parser/src/components/parser/statement/index.ts +++ b/packages/parser/src/components/parser/statement/index.ts @@ -39,6 +39,10 @@ export default abstract class Statement { case TokenTypes.AGLA_DEKH_BHAI: return BhaiLangModule.getContinueStatement(); + case TokenTypes.FUNDA_TYPE: + return BhaiLangModule.getFunctionStatement(); + case TokenTypes.RAKH_LE_BHAI: + return BhaiLangModule.getReturnStatement(); default: return BhaiLangModule.getExpressionStatement(); diff --git a/packages/parser/src/components/parser/statement/returnStatement.ts b/packages/parser/src/components/parser/statement/returnStatement.ts new file mode 100644 index 00000000..5192bbc6 --- /dev/null +++ b/packages/parser/src/components/parser/statement/returnStatement.ts @@ -0,0 +1,27 @@ +import Statement from "."; + +import { TokenTypes } from "../../../constants/bhaiLangSpec"; +import { NodeType } from "../../../constants/constants"; +import { ASTNode } from "../types/nodeTypes"; + +import Expression from "./expression"; +import NullLiteral from "./expression/literals/nullLiteral"; + + +export default class ReturnStatement extends Statement { + getStatement(): ASTNode { + + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.RAKH_LE_BHAI); + let value:ASTNode = new NullLiteral(this._tokenExecutor).getLiteral(); + if (this._tokenExecutor.getLookahead()?.type!==TokenTypes.SEMI_COLON_TYPE) + value = Expression.getExpressionImpl( + NodeType.AssignmentExpression + ).getExpression(); + this._tokenExecutor.eatOptionalSemiColonToken(); + return { + type: NodeType.ReturnStatement, + expression: value, + }; + } + +} \ No newline at end of file diff --git a/packages/parser/src/components/parser/types/nodeTypes.ts b/packages/parser/src/components/parser/types/nodeTypes.ts index 022bbf56..a0b3fa2c 100644 --- a/packages/parser/src/components/parser/types/nodeTypes.ts +++ b/packages/parser/src/components/parser/types/nodeTypes.ts @@ -11,7 +11,10 @@ export type ASTNode = { id?: ASTNode; init?: ASTNode | null; declarations?: ASTNode[]; + declaration?: ASTNode; test?: ASTNode; consequent?: ASTNode; alternates?: ASTNode[]; + args?: ASTNode[]; + signature?: ASTNode; }; diff --git a/packages/parser/src/constants/bhaiLangSpec.ts b/packages/parser/src/constants/bhaiLangSpec.ts index 50525df9..428a7576 100644 --- a/packages/parser/src/constants/bhaiLangSpec.ts +++ b/packages/parser/src/constants/bhaiLangSpec.ts @@ -21,6 +21,10 @@ export const TokenTypes = { AGLA_DEKH_BHAI: "agla dekh bhai", + FUNDA_TYPE: "bhai ye apna funda", //functional programming + + RAKH_LE_BHAI:"rakh le bhai",// return statement + NALLA_TYPE: "NALLA", SEMI_COLON_TYPE: ";", @@ -39,6 +43,8 @@ export const TokenTypes = { IDENTIFIER_TYPE: "IDENTIFIER", + CALLABLE_TYPE: "CALLABLE", + SIMPLE_ASSIGN_TYPE: "SIMPLE_ASSIGN", COMPLEX_ASSIGN_TYPE: "COMPLEX_ASSIGN", @@ -91,6 +97,11 @@ export const SPEC = [ { regex: /^\bbas kar bhai\b/, tokenType: TokenTypes.BAS_KAR_BHAI }, { regex: /^\bagla dekh bhai\b/, tokenType: TokenTypes.AGLA_DEKH_BHAI }, + //functional programming + { regex: /^\bapna funda\b/, tokenType: TokenTypes.FUNDA_TYPE }, + { regex: /^\brakh le bhai\b/, tokenType: TokenTypes.RAKH_LE_BHAI }, + { regex: /^\w+(?=[ ]*\(.*\))/, tokenType: TokenTypes.CALLABLE_TYPE }, + // Number { regex: /^-?\d+/, tokenType: TokenTypes.NUMBER_TYPE }, diff --git a/packages/parser/src/constants/constants.ts b/packages/parser/src/constants/constants.ts index f547d517..78650c45 100644 --- a/packages/parser/src/constants/constants.ts +++ b/packages/parser/src/constants/constants.ts @@ -11,6 +11,7 @@ export const NodeType = { LogicalORExpression: "LogicalORExpression", RelationalExpression: "RelationalExpression", EqualityExpression: "EqualityExpression", + CallableExpression: "CallableExpression", BlockStatement: "BlockStatement", EmptyStatement: "EmptyStatement", ExpressionStatement: "ExpressionStatement", @@ -26,5 +27,9 @@ export const NodeType = { StringLiteral: "StringLiteral", NullLiteral: "NullLiteral", VariableDeclaration: "VariableDeclaration", + FunctionStatement: "FunctionStatement", + FunctionDeclaration: "FunctionDeclaration", + FunctionSignature: "FunctionSignature", + ReturnStatement: "ReturnStatement", Program: "Program", } as const; diff --git a/packages/parser/src/module/bhaiLangModule.ts b/packages/parser/src/module/bhaiLangModule.ts index 6ddf3199..5e7f4017 100644 --- a/packages/parser/src/module/bhaiLangModule.ts +++ b/packages/parser/src/module/bhaiLangModule.ts @@ -9,6 +9,8 @@ import AdditiveExpression from "../components/parser/statement/expression/addititveExpression"; import AssignmentExpression from "../components/parser/statement/expression/assignmentExpression"; +import CallableExpression + from "../components/parser/statement/expression/callableExpression"; import EqualityExpression from "../components/parser/statement/expression/equalityExpression"; import IdentifierExpression @@ -35,9 +37,11 @@ import RelationalExpression from "../components/parser/statement/expression/relationalExpression"; import ExpressionStatement from "../components/parser/statement/expressionStatement"; +import FunctionStatement from "../components/parser/statement/functionStatement"; import IfStatement from "../components/parser/statement/ifStatement"; import InitStatement from "../components/parser/statement/initStatement"; import PrintStatement from "../components/parser/statement/printStatement"; +import ReturnStatement from "../components/parser/statement/returnStatement"; import VariableStatement from "../components/parser/statement/variableStatement"; import WhileStatement from "../components/parser/statement/whileStatement"; @@ -69,6 +73,7 @@ export default class BhaiLangModule { private static _variableStatement?: VariableStatement; private static _ifStatement?: IfStatement; private static _assignmentExpression?: AssignmentExpression; + private static _callableExpression?: CallableExpression; private static _booleanLiteral?: BooleanLiteral; private static _nullLiteral?: NullLiteral; private static _equalityExpression?: EqualityExpression; @@ -78,6 +83,8 @@ export default class BhaiLangModule { private static _breakStatement?: BreakStatement; private static _continueStatement?: ContinueStatement; private static _whileStatement?: WhileStatement; + private static _functionStatement: FunctionStatement; + private static _returnStatement: ReturnStatement; static getTokenizer() { if (!this._tokenizer) this._tokenizer = new TokenizerImpl(SPEC); @@ -187,6 +194,24 @@ export default class BhaiLangModule { return this._variableStatement; } + static getFunctionStatement() { + if (!this._functionStatement) + this._functionStatement = new FunctionStatement( + this.getTokenExecutor(), + this.getNullLiteral() + ); + + return this._functionStatement; + } + static getReturnStatement() { + if (!this._returnStatement) + this._returnStatement = new ReturnStatement( + this.getTokenExecutor() + ); + + return this._returnStatement; + } + static getAdditiveExpression() { if (!this._additiveExpression) { @@ -280,6 +305,15 @@ export default class BhaiLangModule { return this._assignmentExpression; } + static getCallableExpression() { + if (!this._callableExpression) + this._callableExpression = new CallableExpression( + this.getTokenExecutor() + ); + + return this._callableExpression; + } + static getNumericLiteral() { if (!this._numericLiteral) { this._numericLiteral = new NumericLiteral(this.getTokenExecutor()); diff --git a/packages/parser/test/integration/negativeTestsHelper.ts b/packages/parser/test/integration/negativeTestsHelper.ts index 831e1621..48e56975 100644 --- a/packages/parser/test/integration/negativeTestsHelper.ts +++ b/packages/parser/test/integration/negativeTestsHelper.ts @@ -178,6 +178,26 @@ export const NegativeStatementTests = [ `, output: SyntaxError, }, + // Function statement negative tests + { + name: "Function statement test - bad augument syntax", + input: ` + hi bhai + apna funda add(a+b){ + rakh le bhai; + } + bye bhai + `, + output: SyntaxError, + },{ + name: "Function statement test - no body", + input: ` + hi bhai + apna funda add(a,b) + bye bhai + `, + output: SyntaxError, + }, ]; export const NegativeExpressionsTests = [ @@ -414,4 +434,4 @@ export const ContinueStatementNegativeTests = [ `, output: SyntaxError, }, -] \ No newline at end of file +] diff --git a/packages/parser/test/integration/positiveTestsHelper.ts b/packages/parser/test/integration/positiveTestsHelper.ts index b502efce..d14b2a1a 100644 --- a/packages/parser/test/integration/positiveTestsHelper.ts +++ b/packages/parser/test/integration/positiveTestsHelper.ts @@ -684,5 +684,52 @@ export const WhileStatementTests = [ bye bhai; `, output: `{"type":"Program","body":{"type":"InitStatement","body":[{"type":"WhileStatement","test":{"type":"BinaryExpression","operator":">","left":{"type":"IdentifierExpression","name":"x"},"right":{"type":"NumericLiteral","value":9}},"body":{"type":"BlockStatement","body":[{"type":"ContinueStatement"},{"type":"EmptyStatement"}]}},{"type":"VariableStatement","declarations":[{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"a"},"init":{"type":"NumericLiteral","value":90}}]}]}}`, - }, + } ]; + +export const FunctionStatementTests = [ + { + name: "function statement success test: function calling with print statement", + input: ` + hi bhai + apna funda janam(nam){ + bol bhai nam; + } + janam("test"); + bye bhai; + `, + output: `{"type":"Program","body":{"type":"InitStatement","body":[{"type":"FunctionStatement","declaration":{"type":"FunctionDeclaration","signature":{"type":"FunctionSignature","name":"janam","args":[{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"nam"},"init":{"type":"NullLiteral","value":"nalla"}}]},"body":{"type":"BlockStatement","body":[{"type":"PrintStatement","expressions":[{"type":"IdentifierExpression","name":"nam"}]}]}}},{"type":"ExpressionStatement","expression":{"type":"CallableExpression","name":"janam","args":[{"type":"StringLiteral","value":"test"}]}}]}}`, + }, + ,{ + name: "function statement success test: function calling with return statement", + input: ` + hi bhai + apna funda add(a,b){ + rakh le bhai a+b; + } + bol bhai add(10,20); + bye bhai; + `, + output: `{"type":"Program","body":{"type":"InitStatement","body":[{"type":"FunctionStatement","declaration":{"type":"FunctionDeclaration","signature":{"type":"FunctionSignature","name":"add","args":[{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"a"},"init":{"type":"NullLiteral","value":"nalla"}},{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"b"},"init":{"type":"NullLiteral","value":"nalla"}}]},"body":{"type":"BlockStatement","body":[{"type":"ReturnStatement","expression":{"type":"BinaryExpression","operator":"+","left":{"type":"IdentifierExpression","name":"a"},"right":{"type":"IdentifierExpression","name":"b"}}},{"type":"EmptyStatement"}]}}},{"type":"PrintStatement","expressions":[{"type":"CallableExpression","name":"add","args":[{"type":"NumericLiteral","value":10},{"type":"NumericLiteral","value":20}]}]}]}}`, + },{ + name: "function statement success test: function closures", + input: ` + hi bhai + apna funda Counter() { + bhai ye hai count = 1; + + apna funda increment() { + count += 1; + rakh le bhai count; + } + + rakh le bhai increment; + } + + bhai ye hai tick = Counter(); + bol bhai tick(); + bye bhai; + `, + output: `{"type":"Program","body":{"type":"InitStatement","body":[{"type":"FunctionStatement","declaration":{"type":"FunctionDeclaration","signature":{"type":"FunctionSignature","name":"Counter","args":[]},"body":{"type":"BlockStatement","body":[{"type":"VariableStatement","declarations":[{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"count"},"init":{"type":"NumericLiteral","value":1}}]},{"type":"FunctionStatement","declaration":{"type":"FunctionDeclaration","signature":{"type":"FunctionSignature","name":"increment","args":[]},"body":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"+=","left":{"type":"IdentifierExpression","name":"count"},"right":{"type":"NumericLiteral","value":1}}},{"type":"ReturnStatement","expression":{"type":"IdentifierExpression","name":"count"}},{"type":"EmptyStatement"}]}}},{"type":"ReturnStatement","expression":{"type":"IdentifierExpression","name":"increment"}},{"type":"EmptyStatement"}]}}},{"type":"VariableStatement","declarations":[{"type":"VariableDeclaration","id":{"type":"IdentifierExpression","name":"tick"},"init":{"type":"CallableExpression","name":"Counter","args":[]}}]},{"type":"PrintStatement","expressions":[{"type":"CallableExpression","name":"tick","args":[]}]}]}}`, + }, +] diff --git a/packages/parser/test/parser/statement.test.ts b/packages/parser/test/parser/statement.test.ts index 7e9f6d70..5c2a99a6 100644 --- a/packages/parser/test/parser/statement.test.ts +++ b/packages/parser/test/parser/statement.test.ts @@ -1,19 +1,21 @@ import Statement from "../../src/components/parser/statement"; import BlockStatement from "../../src/components/parser/statement/blockStatement"; +import FunctionStatement from "../../src/components/parser/statement/functionStatement"; import { TokenTypes } from "../../src/constants/bhaiLangSpec"; import BhaiLangModule from "../../src/module/bhaiLangModule"; jest.mock("../../src/module/bhaiLangModule"); -const blockStatementMock = new (( - BlockStatement -))() as jest.Mocked; + afterEach(() => { jest.clearAllMocks(); }); test("test getStatementImpl of statement class with should success", () => { + const statementMock = new (( + BlockStatement + ))() as jest.Mocked; const lookahead = { type: TokenTypes.OPEN_CURLY_BRACE_TYPE, value: "{", @@ -21,11 +23,34 @@ test("test getStatementImpl of statement class with should success", () => { BhaiLangModule.getBlockStatement = jest .fn() - .mockReturnValue(blockStatementMock); + .mockReturnValue(statementMock); expect(Statement.getStatementImpl(lookahead)).toStrictEqual( - blockStatementMock + statementMock ); expect(BhaiLangModule.getBlockStatement).toHaveBeenCalledTimes(1); }); + +test("test getStatementImpl of function declaration statement should success", () => { + const statementMock = new (( + FunctionStatement + ))() as jest.Mocked; + const lookahead = { + type: TokenTypes.FUNDA_TYPE, + value: `apna funda testing(a,b){ + bol bhai a; + }`, + }; + + BhaiLangModule.getFunctionStatement = jest + .fn() + .mockReturnValue(statementMock); + + expect(Statement.getStatementImpl(lookahead)).toStrictEqual( + statementMock + ); + + expect(BhaiLangModule.getFunctionStatement).toHaveBeenCalledTimes(1); +}); + diff --git a/packages/parser/test/tokenizer/tokenizerImpl.test.ts b/packages/parser/test/tokenizer/tokenizerImpl.test.ts index 3447f3a4..6c36743c 100644 --- a/packages/parser/test/tokenizer/tokenizerImpl.test.ts +++ b/packages/parser/test/tokenizer/tokenizerImpl.test.ts @@ -76,3 +76,4 @@ test("test Tokenizer.hasMoreTokens without initTokenizer should success", () => expect(tokenizer.hasMoreTokens()).toStrictEqual(false); }); +