diff --git a/apps/docs/components/Code/index.tsx b/apps/docs/components/Code/index.tsx index 4b8467ea..e56fe58b 100644 --- a/apps/docs/components/Code/index.tsx +++ b/apps/docs/components/Code/index.tsx @@ -7,101 +7,86 @@ import { sendEvents } from "../../helpers"; import CodeEditor from "./CodeEditor"; import Terminal from "./Terminal"; - const initialCode = ` hi bhai - bol bhai "Hello World"; - - bhai ye hai a = 3; - bhai ye hai b = 0; - - jab tak bhai (b < 5) { - bol bhai b; - - agar bhai (b == a) { - bol bhai "b is equal to a"; - } nahi to bhai (b == 0) { - bol bhai "b is equal to zero"; + samajh le bhai kutta{ + bhai ye hai nam; + apna funda janam(nam){ + iska.nam = nam; + bol bhai iska.nam,":kau kau"; + } + apna funda bhouk(){ + bol bhai iska.nam+":bhou bhou"; } - - b += 1; } + bhai ye hai jimmi = kutta("jimmi"); + jimmi.bhouk(); + bol bhai "iska name hai",jimmi.nam; bye bhai `; const Code = (props: Props) => { - const {} = props; - const [code, setCode] = useState(initialCode); - const [output, setOutput] = useState<{ value: string; isError: boolean }[]>( - [] - ); - const [isSuccess, setIsSuccess] = useState(null); - - const handleChange = (newCode: string) => { - setCode(newCode); - }; - - const executeCode = () => { - let orignalConsoleLog = console.log; - const outputList = []; - let isExecusionSuccess = true; - console.log = function (...args) { - outputList.push({ value: args.join("\n"), isError: false }); - }; - - try { - interpreter.interpret(code); - } catch (e) { - if (e instanceof Error) { - isExecusionSuccess = false; - outputList.push({ value: e.message, isError: true }); - } else { - console.error(e); - } - } - - sendEvents("CodeExecuted", {success: isExecusionSuccess}); - - setIsSuccess(isExecusionSuccess); - setOutput(outputList); - console.log = orignalConsoleLog; - }; - - const clearCode = () => { - sendEvents("CodeCleared"); - setCode(""); - setIsSuccess(null); - setOutput([]); - }; - - return ( -
-
-

- Playground -

-
- - - -
-
- - -
- ); + const {} = props; + const [code, setCode] = useState(initialCode); + const [output, setOutput] = useState<{ value: string; isError: boolean }[]>([]); + const [isSuccess, setIsSuccess] = useState(null); + + const handleChange = (newCode: string) => { + setCode(newCode); + }; + + const executeCode = () => { + let orignalConsoleLog = console.log; + const outputList = []; + let isExecusionSuccess = true; + console.log = function (...args) { + outputList.push({ value: args.join("\n"), isError: false }); + }; + + try { + interpreter.interpret(code); + } catch (e) { + if (e instanceof Error) { + isExecusionSuccess = false; + outputList.push({ value: e.message, isError: true }); + } else { + console.error(e); + } + } + + sendEvents("CodeExecuted", { success: isExecusionSuccess }); + + setIsSuccess(isExecusionSuccess); + setOutput(outputList); + console.log = orignalConsoleLog; + }; + + const clearCode = () => { + sendEvents("CodeCleared"); + setCode(""); + setIsSuccess(null); + setOutput([]); + }; + + return ( +
+
+

Playground

+
+ + + +
+
+ + +
+ ); }; type Props = {}; export default React.memo(Code); diff --git a/apps/docs/components/Documentation/index.tsx b/apps/docs/components/Documentation/index.tsx index c0a51083..c8285136 100644 --- a/apps/docs/components/Documentation/index.tsx +++ b/apps/docs/components/Documentation/index.tsx @@ -1,19 +1,15 @@ import Snippet from "./Snippet"; - /* This example requires Tailwind CSS v2.0+ */ const features = [ - { - name: "General", - description: ( - <> - hi bhai is the entrypoint for the - program and all program must end with{" "} - bye bhai. Anything outside of it - will be ignored. - - ), - code: `This will be ignored + { + name: "General", + description: ( + <> + hi bhai is the entrypoint for the program and all program must end with bye bhai. Anything outside of it will be ignored. + + ), + code: `This will be ignored hi bhai // Write code here @@ -21,16 +17,15 @@ bye bhai This too `, - }, - { - name: "Variables", - description: ( - <> - Variables can be declared using{" "} - bhai ye hai. - - ), - code: `hi bhai + }, + { + name: "Variables", + description: ( + <> + Variables can be declared using bhai ye hai. + + ), + code: `hi bhai bhai ye hai a = 10; bhai ye hai b = "two"; bhai ye hai c = 15; @@ -39,18 +34,15 @@ This too c *= 2; bye bhai `, - }, - { - name: "Types", - description: ( - <> - Numbers and strings are like other languages. Null values can be denoted - using nalla.{" "} - sahi and{" "} - galat are the boolean values. - - ), - code: `hi bhai + }, + { + name: "Types", + description: ( + <> + Numbers and strings are like other languages. Null values can be denoted using nalla. sahi and galat are the boolean values. + + ), + code: `hi bhai bhai ye hai a = 10; bhai ye hai b = 10 + (15*20); bhai ye hai c = "two"; @@ -60,16 +52,15 @@ bye bhai bhai ye hai g = galat; bye bhai `, - }, - { - name: "Built-ins", - description: ( - <> - Use bol bhai to print anything to - console. - - ), - code: `hi bhai + }, + { + name: "Built-ins", + description: ( + <> + Use bol bhai to print anything to console. + + ), + code: `hi bhai bol bhai "Hello World"; bhai ye hai a = 10; { @@ -79,15 +70,16 @@ bye bhai bol bhai 5, 'ok', nalla , sahi , galat; bye bhai `, - }, - { - name: "Conditionals", - description: ( - <> - Bhailang supports if-else-if ladder construct , agar bhai block will execute if condition is sahi, otherwise one of the subsequently added nahi to bhai blocks will execute if their respective condition is sahi, and the warna bhai block will eventually execute if all of the above conditions are galat. - - ), - code: `hi bhai + }, + { + name: "Conditionals", + description: ( + <> + Bhailang supports if-else-if ladder construct , agar bhai block will execute if condition is sahi, otherwise one of the subsequently added nahi to bhai blocks will execute if their respective condition is{" "} + sahi, and the warna bhai block will eventually execute if all of the above conditions are galat. + + ), + code: `hi bhai bhai ye hai a = 10; agar bhai (a < 20) { bol bhai "a is less than 20"; @@ -97,17 +89,17 @@ bye bhai bol bhai "a is greater than or equal to 25"; } bye bhai - ` - }, - { - name: "Loops", - description: ( - <> - Statements inside jab tak bhai blocks are executed as long as a specified condition evaluates to sahi. If the condition becomes galat, statement within the loop stops executing and control passes to the statement following the loop. - Use bas kar bhai to break the loop and agla dekh bhai to continue within loop. - - ), - code: `hi bhai + `, + }, + { + name: "Loops", + description: ( + <> + Statements inside jab tak bhai blocks are executed as long as a specified condition evaluates to sahi. If the condition becomes galat, statement within the loop stops executing and control passes to the statement following the + loop. Use bas kar bhai to break the loop and agla dekh bhai to continue within loop. + + ), + code: `hi bhai bhai ye hai a = 0; jab tak bhai (a < 10) { a += 1; @@ -122,37 +114,69 @@ bye bhai } bol bhai "done"; 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 + `, + }, + { + name: "Class", + description: ( + <> + Define Class using samajh le bhai then the class name, inside {} you can provide constructor as janam and access class member functions and variables with iska similar to "this" pointer in most languages. + + ), + code: `hi bhai + samajh le bhai kutta{ + bhai ye hai nam; + apna funda janam(nam){ + iska.nam = nam; + bol bhai iska.nam,":kau kau"; + } + apna funda bhouk(){ + bol bhai iska.nam+":bhou bhou"; + } + } + bhai ye hai jimmi = kutta("jimmi"); + jimmi.bhouk(); + bol bhai "iska name hai",jimmi.nam; +bye bhai + `, + }, ]; export default function Documentation() { - return ( -
-
-
-

- Documentation -

-

- Bhailang is dynamically typed toy programming language, based on an - inside joke, written in Typescript. -

+ return ( +
+
+
+

Documentation

+

Bhailang is dynamically typed toy programming language, based on an inside joke, written in Typescript.

-
- {features.map((feature) => ( -
-
{feature.name}
-
- {feature.description} -
- -
- ))} -
-
-
-
- ); +
+ {features.map((feature) => ( +
+
{feature.name}
+
{feature.description}
+ +
+ ))} +
+
+
+
+ ); } diff --git a/apps/docs/components/common/syntax.ts b/apps/docs/components/common/syntax.ts index 1d2c7c08..019c3a06 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|samajh le bhai|jo ki ek| jo ki ek bhai|hai bhai|janam|iska)\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..d429e314 --- /dev/null +++ b/packages/interpreter/src/components/dataClass.ts @@ -0,0 +1,178 @@ +import { ASTNode } from "bhai-lang-parser"; +import InterpreterModule from "../module/interpreterModule"; +import Scope from "./scope"; + +export enum DataTypes{ + Null='null', + Boolean='boolean', + Numeric='numeric', + String='string', + Callable='callable', + Class='classDefinition', + ClassInsance='classInstance', +} +export class DataObject { + protected _value: any; + protected _type:string; + protected _scopeRef:Scope; + public isDataObject:boolean; + + constructor(value: any,type:string) { + this._value = value; + this._type = type; + this.isDataObject=true; + this._scopeRef=InterpreterModule.getCurrentScope(); + } + + getValue(): any { + return this._value; + } + getType():string{ + return this._type; + } + valueOf(){ + return this._value; + } + toString(){ + return this._value.toString(); + } + setScopeRef(scope:Scope){ + this._scopeRef=scope; + } + getScopeRef(){ + return this._scopeRef; + } + + +} + +export class BooleanObject extends DataObject{ + constructor(value: boolean) { + super(value,DataTypes.Boolean); + } + toString(): 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); + } + toString(): string { + return "nalla"; + } +} + +export class CallableObject extends DataObject{ + constructor(value: { + args:(string|undefined)[], + code:(args:{identifier:string,value:DataObject}[])=>any + }) { + super(value,DataTypes.Callable); + } + getValue():{ + args:(string|undefined)[], + code:(args:{identifier:string,value:DataObject}[])=>any + } { + return this._value + } +} + +export class ClassObject extends DataObject{ + constructor(value: { + name:string, + methods:ASTNode[], + dataMembers:string[] + }) { + super(value,DataTypes.Class); + this.name=value.name; + this.methods=value.methods; + this.dataMembers=value.dataMembers; + } + name:string; + methods:ASTNode[]; + dataMembers:string[]; + getMembers():string[]{ + return this.dataMembers; + } + getMethods():ASTNode[]{ + return this.methods; + } +} +export class ClassInstanceObject extends DataObject{ + + constructor(value:{ + className:string + members:Scope + methods:{[identifier:string]:ASTNode} + superClasses?:ClassInstanceObject + }) { + super(value,DataTypes.ClassInsance); + + this._value.members.declare('iska',this);// defining this pointer + + Object.keys(this._value.methods).forEach(methodName=>{ + let methodAst=this._value.methods[methodName] + let currentScope=InterpreterModule.getCurrentScope() + InterpreterModule.setCurrentScope(this._value.members) + let method=sanatizeData(InterpreterModule.getVisitor(methodAst.type).visitNode(methodAst)); + InterpreterModule.setCurrentScope(currentScope) + this._value.members.declare(methodName,method) + }) + } + getScope():Scope{ + return this._value.members + } + getMember(name:string):DataObject{ + let member=this._value.members.get(name); + if(!member){ + for (let i = 0; i < this._value.superClasses.length; i++) { + const superClass = this._value.superClasses[i]; + member = superClass.getMember(name) + if(member){ + break; + } + } + } + return member; + } + toString() { + return `Class: ${this._value.className}`; + } + +} + +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..f954f85d 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 { ClassInstanceObject, DataObject, DataTypes, 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,41 +59,62 @@ export default class Scope { return this._isContinueStatement; } - get(identifier: string): unknown { - if (this._variables.has(identifier)) { - return this._variables.get(identifier); + get(identifier: string): DataObject { + let idChain=identifier.split("."); + let id=idChain[0]; + if (this._variables.has(id)) { + let data = this._variables.get(id); + if(idChain.length>1&&data){ + if(data.getType()==DataTypes.ClassInsance){ + let classInstance=data as ClassInstanceObject; + let classScope=classInstance.getScope(); + classScope.get(idChain.slice(1).join(".")); + } + } + let value = sanatizeData(this._variables.get(identifier)); + return value; } if (this._parentScope !== null) { return this._parentScope.get(identifier); } - throw new RuntimeException(`Variable "${identifier}" bana to le pehle.`); + throw new RuntimeException(`Variable "${id}" bana to le pehle.`); } - assign(identifier: string, value: unknown) { - if (this._variables.has(identifier)) { + assign(identifier: string, value: DataObject) { + let idChain=identifier.split("."); + let id=idChain[0]; + if (this._variables.has(id)) { + let data = this._variables.get(id); + if(idChain.length>1&&data){ + if(data.getType()==DataTypes.ClassInsance){ + let classInstance=data as ClassInstanceObject; + let classScope=classInstance.getScope(); + classScope.assign(idChain.slice(1).join("."),value); + } + return + } this._variables.set(identifier, value); return; } - if (this._parentScope !== null) { this._parentScope.assign(identifier, value); return; } throw new RuntimeException( - `Variable "${identifier}" bana to le pehle fir assign karna.` + `Variable "${id}" bana to le pehle fir assign karna.` ); } - 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.` ); } - + value.setScopeRef(this); this._variables.set(identifier, value); } } diff --git a/packages/interpreter/src/components/visitor/assignmentExpression.ts b/packages/interpreter/src/components/visitor/assignmentExpression.ts index 8fc09cf7..1e8e3e34 100644 --- a/packages/interpreter/src/components/visitor/assignmentExpression.ts +++ b/packages/interpreter/src/components/visitor/assignmentExpression.ts @@ -1,11 +1,10 @@ import Visitor from "."; -import { ASTNode } from "bhai-lang-parser"; +import { ASTNode, NodeType} from "bhai-lang-parser"; import InvalidStateException from "../../exceptions/invalidStateException"; -import NallaPointerException from "../../exceptions/nallaPointerException"; -import RuntimeException from "../../exceptions/runtimeException"; import { getOperationValue } from "../../helpers"; import InterpreterModule from "../../module/interpreterModule"; +import { DataObject, NullObject } from "../dataClass"; export default class AssignmentExpression implements Visitor { @@ -14,9 +13,8 @@ export default class AssignmentExpression implements Visitor { throw new InvalidStateException( `left node not present while executing: ${node.type}` ); - - let identifier = node.left.name; - let value: unknown; + const identifier=this.getIdentifier(node.left); + let value: DataObject|null|void=null; const currentScope = InterpreterModule.getCurrentScope(); if (node.right) { @@ -24,20 +22,9 @@ export default class AssignmentExpression implements Visitor { node.right ); } + if(value==null) value =new NullObject() if (identifier && node.operator) { - const left = currentScope.get(identifier); - - if (left === null && node.operator !== "=") - throw new NallaPointerException( - `Nalla operand ni jamta "${node.operator}" ke sath` - ); - - if ((left === true || left === false) && node.operator !== "=") - throw new RuntimeException( - `Boolean operand ni jamta "${node.operator}" ke sath` - ); - const newValue = getOperationValue( { left: currentScope.get(identifier), right: value }, node.operator @@ -47,4 +34,15 @@ export default class AssignmentExpression implements Visitor { return currentScope.get(identifier); } } + getIdentifier(node: ASTNode): string { + if (node.type===NodeType.IdentifierExpression&&node.name){ + return node.name; + } + else if (node.type===NodeType.BinaryExpression&&node.operator==="."){ + if(node?.left?.name==null) throw new InvalidStateException(`Left Identifier not found for: ${node.type}`); + if(node?.right==null) throw new InvalidStateException(`right expression not found for: ${node.type}`); + if(node.left?.name&&node.right)return node.left.name+"."+this.getIdentifier(node.right); + } + throw new InvalidStateException(`Identifier not found for: ${node.type}`); + } } diff --git a/packages/interpreter/src/components/visitor/binaryExpression.ts b/packages/interpreter/src/components/visitor/binaryExpression.ts index c9909950..306239eb 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 { CallableObject, ClassInstanceObject, DataObject, DataTypes, NullObject, sanatizeData } from "../dataClass"; export default class BinaryExpression implements Visitor { @@ -16,28 +17,22 @@ 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 !== "!=") { + if (node.operator !== "==" && node.operator !== "!="&&node.operator !==".") { this._checkNalla(node); this._checkBoolean(node); } - left = this._getNodeValue(node.left); - right = this._getNodeValue(node.right); + else if (node.operator === ".") { + return this.performDotOperation(node); + } } 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 +55,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,29 +84,63 @@ 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; + private performDotOperation(node: ASTNode):DataObject { + if (!node.left || !node.right || !node.operator) { + throw new InvalidStateException( + `Left , right or operator not found for: ${node.type}` + ); } - if (node.type === NodeType.IdentifierExpression && node.name) - return InterpreterModule.getCurrentScope().get(node.name); + const left = sanatizeData(InterpreterModule.getVisitor(node.left.type).visitNode(node.left)); - return InterpreterModule.getVisitor(node.type).visitNode( - node + if (left instanceof NullObject) { + throw new NallaPointerException( + `Nalla operand ni jamta "${node.operator}" ke sath` + ); + } + if(!(left instanceof ClassInstanceObject)){ + console.log(left.getType()); + + throw new RuntimeException( + `Kya kar rha hai tu??..${left.toString()} operand ni jamta "${node.operator}" ke sath` + ); + } + if(node.right.type===NodeType.IdentifierExpression&&node?.right?.name){ + return left.getMember(node.right.name); + } + + if(node.right.type===NodeType.CallableExpression&&node?.right?.name){ + const member = left.getMember(node.right.name); + if(member.getType()!==DataTypes.Callable) + throw new RuntimeException( + `Kya kar rha hai tu??..${left.toString()} ka ${node.right.name} karke koi funda hai hi nahi` + ); + const args:{identifier:string,value:DataObject}[]=[]; + const providedArgs=node.right.args; + if(member.getValue().args){ + for (let i = 0; i < (member as CallableObject).getValue().args.length; i++) { + const identifier = (member as CallableObject).getValue().args[i]; + if(!identifier) throw new RuntimeException( + `Kya kar rha hai tu??..${left.toString()} ka ${node.right.name} karke koi funde ke argument me garbar hai` + ); + if(providedArgs&&providedArgs[i]){ + args.push({identifier,value:sanatizeData(InterpreterModule.getVisitor(providedArgs[i].type).visitNode(providedArgs[i]))}); + } + } + return (member as CallableObject).getValue().code(args); + } + } + throw new RuntimeException( + `Kya kar rha hai tu??..${left.toString()} operand ni jamta "${node.operator}" ke sath` ); } diff --git a/packages/interpreter/src/components/visitor/blockStatement.ts b/packages/interpreter/src/components/visitor/blockStatement.ts index cf0fc382..0f63edc6 100644 --- a/packages/interpreter/src/components/visitor/blockStatement.ts +++ b/packages/interpreter/src/components/visitor/blockStatement.ts @@ -11,9 +11,11 @@ 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 +23,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/classDeclaration.ts b/packages/interpreter/src/components/visitor/classDeclaration.ts new file mode 100644 index 00000000..62632981 --- /dev/null +++ b/packages/interpreter/src/components/visitor/classDeclaration.ts @@ -0,0 +1,97 @@ +import Visitor from "."; +import { ASTNode, NodeType } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; +import Scope from "../scope"; +import { CallableObject, ClassInstanceObject, ClassObject, DataObject, NullObject } from "../dataClass"; + + +export default class ClassDeclaration implements Visitor { + visitNode(node: ASTNode) { + if (!node.id || !node.body||!node) { + throw new InvalidStateException(`id or body not found for ${node.type}`); + } + + const identifier = node.id.name; + if (!identifier) { + throw new InvalidStateException(`name deke samjha na bhai isko, ${node.type}`); + } + + const inherits = node.inherits?.map(inherit => inherit?.name).filter(x=>x) ||[]; + if (!node.body || Array.isArray(node.body)) + throw new InvalidStateException(`body not found for class`); + + const {classMethods,classVariables} = this.getClassMembers(node.body); + let value:{args:(string|undefined)[],code:(args:{identifier:string,value:DataObject}[])=>any}; + const classConstructor=classMethods["janam"]; + let classConstructorArgs:string[]=[]; + if(classConstructor){ + classConstructorArgs=(classConstructor?.signature?.args?.map(param=>param?.id?.name).filter(arg => arg)||[])as string[]; + } + value={ + args:classConstructorArgs, + code:(args:{identifier:string,value:DataObject}[]):any=>{ + const classInsObj=this.createClassInstance(identifier,classVariables,classMethods,inherits as string[]); + if(classMethods["janam"]){ + const constructor=classInsObj.getMember("janam") + if(constructor&&constructor instanceof CallableObject){ + constructor.getValue().code(args); + } + } + return classInsObj + } + } + const currentScope = InterpreterModule.getCurrentScope(); + + if (identifier) { + currentScope.declare(identifier, new CallableObject(value)); + } + } + getClassMembers(body:ASTNode){ + if(!body){ + throw new InvalidStateException(`body not found for`); + } + if(body.type!=NodeType.BlockStatement){ + throw new InvalidStateException(`body not found for`); + } + let classBody=body.body; + if(!classBody){ + throw new InvalidStateException(`body not found for class`); + } + if(!Array.isArray(classBody)){ + throw new InvalidStateException(`body not found for class`); + } + let classMethods:{[identifier:string]:ASTNode}={} + let classVariables:string[]=[] + classBody.forEach(member=>{ + if(member.type!=NodeType.FunctionStatement&&member.type!=NodeType.VariableStatement){ + throw new InvalidStateException(`Sirf funda or variable me shamjha na bhai`); + } + if(member.type==NodeType.FunctionStatement){ + if(member?.declaration?.signature?.name) + classMethods[member?.declaration?.signature?.name]=member.declaration; + } + else { + let dataMembers=member.declarations?.map(declaration=>declaration?.id?.name)||[]; + dataMembers=dataMembers.filter(member=>member); + classVariables=classVariables.concat(dataMembers as string[]); + } + + }) + return {classMethods,classVariables}; + } + createClassInstance(identifier:string,classVariables:string[],classMethods:{[identifier:string]:ASTNode},inherits:string[]){ + const currentScope = InterpreterModule.getCurrentScope(); + let classScope=new Scope(currentScope); + InterpreterModule.setCurrentScope(classScope); + inherits.map(clsIds=>currentScope.get(clsIds)).filter(cls=>cls instanceof ClassObject) + + classVariables.forEach(member=>{ + classScope.declare(member,new NullObject()); + }) + let classInstance=new ClassInstanceObject({className:identifier,members:classScope,methods:classMethods}); + InterpreterModule.setCurrentScope(currentScope); + return classInstance; + } +} diff --git a/packages/interpreter/src/components/visitor/classStatement.ts b/packages/interpreter/src/components/visitor/classStatement.ts new file mode 100644 index 00000000..540a6404 --- /dev/null +++ b/packages/interpreter/src/components/visitor/classStatement.ts @@ -0,0 +1,16 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; + +export default class ClassStatement 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/functionDeclaration.ts b/packages/interpreter/src/components/visitor/functionDeclaration.ts new file mode 100644 index 00000000..3d8f0366 --- /dev/null +++ b/packages/interpreter/src/components/visitor/functionDeclaration.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 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 body = node.body; + if (body && !Array.isArray(body)) { + let scope=InterpreterModule.getCurrentScope() + const 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(oldScope) + return result + } + } + return new CallableObject(value); + } + else{ + throw new InvalidStateException(`body not found for ${node.type}`); + } + + } +} diff --git a/packages/interpreter/src/components/visitor/functionStatement.ts b/packages/interpreter/src/components/visitor/functionStatement.ts new file mode 100644 index 00000000..f2dc1b77 --- /dev/null +++ b/packages/interpreter/src/components/visitor/functionStatement.ts @@ -0,0 +1,22 @@ +import Visitor from "."; +import { ASTNode } from "bhai-lang-parser"; + +import InvalidStateException from "../../exceptions/invalidStateException"; +import InterpreterModule from "../../module/interpreterModule"; +import { sanatizeData } from "../dataClass"; + +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}` + ); + let functionObject = sanatizeData(InterpreterModule.getVisitor(node.declaration.type).visitNode(node.declaration)); + + const identifier = node?.declaration?.signature?.name + if (identifier) { + let scope = InterpreterModule.getCurrentScope(); + scope.declare(identifier, functionObject); + } + } +} 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..bafbd732 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.toString(); + } + ).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..83509a99 100644 --- a/packages/interpreter/src/helpers/index.ts +++ b/packages/interpreter/src/helpers/index.ts @@ -1,40 +1,44 @@ +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 "${operands.left.getType()}" aur "${operands.right.getType()}" nahi jamte.` ); switch (operator) { @@ -44,15 +48,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.toString() + operands.right.toString()); } throw exception; @@ -60,7 +64,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 +72,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 +92,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..8558ec77 100644 --- a/packages/interpreter/src/module/interpreterModule.ts +++ b/packages/interpreter/src/module/interpreterModule.ts @@ -8,9 +8,14 @@ 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 ClassDeclaration from "../components/visitor/classDeclaration"; +import ClassStatement from "../components/visitor/classStatement"; 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 +23,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 +53,13 @@ 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(), + [NodeType.ClassStatement]: new ClassStatement(), + [NodeType.ClassDeclaration]: new ClassDeclaration(), + } 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/classStatement.ts b/packages/parser/src/components/parser/statement/classStatement.ts new file mode 100644 index 00000000..392b8533 --- /dev/null +++ b/packages/parser/src/components/parser/statement/classStatement.ts @@ -0,0 +1,84 @@ +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"; + +export default class ClassStatement extends Statement { + + constructor(tokenExecutor: TokenExecutor) { + super(tokenExecutor); + } + + getStatement(): ASTNode { + this._tokenExecutor.eatTokenAndForwardLookahead( + TokenTypes.SAMAJH_LE_BHAI + ); + + const declaration = this._getClassDeclaration(); + + // this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.SEMI_COLON_TYPE); + + return { + type: NodeType.ClassStatement, + declaration, + }; + } + + + private _getClassDeclaration(): ASTNode { + const id = Expression.getExpressionImpl( + NodeType.IdentifierExpression + ).getExpression(); + let lookahead=this._tokenExecutor.getLookahead() + if (lookahead == null) { + throw new SyntaxError(`Unexpected end of "samajh le bhai" statement`); + } + + let superClasses:ASTNode[]=[] + if(lookahead.type===TokenTypes.JO_KI_EK_BHAI){ + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.JO_KI_EK_BHAI); + lookahead=this._tokenExecutor.getLookahead() + if (lookahead == null) { + throw new SyntaxError(`Unexpected end of "jo ki ek" statement`); + } + if(lookahead.type!==TokenTypes.IDENTIFIER_TYPE){ + throw new SyntaxError(`Unexpected token after "jo ki ek", got "${lookahead.value}" : expected "${TokenTypes.IDENTIFIER_TYPE}"`); + } + + do { + superClasses.push(Expression.getExpressionImpl(NodeType.IdentifierExpression).getExpression()); + } while ( + this._tokenExecutor.getLookahead()?.type === TokenTypes.COMMA_TYPE && + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.COMMA_TYPE) + ); + } + lookahead=this._tokenExecutor.getLookahead() + if (lookahead == null) { + throw new SyntaxError(`Unexpected end of "jo ki ek" statement`); + } + // removing optional hai bhai tokens + if(lookahead.type===TokenTypes.HAI_BHAI){ + this._tokenExecutor.eatTokenAndForwardLookahead(TokenTypes.HAI_BHAI) + lookahead=this._tokenExecutor.getLookahead() + if (lookahead == null) { + throw new SyntaxError(`Unexpected end of "hai bhai" statement`); + } + } + + if(lookahead.type!==TokenTypes.OPEN_CURLY_BRACE_TYPE){ + throw new SyntaxError(`Unexpected token after smajhana signature ${id.name}, got "${lookahead.value}" : expected "{"`); + } + const body=Statement.getStatementImpl(this._tokenExecutor.getLookahead()!).getStatement(); + + return { + type: NodeType.ClassDeclaration, + id, + body, + inherits:superClasses + }; + } +} diff --git a/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts b/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts index d7ab232e..9267722a 100644 --- a/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts +++ b/packages/parser/src/components/parser/statement/expression/assignmentExpression.ts @@ -57,7 +57,10 @@ export default class AssignmentExpression extends Expression { */ private _checkValidAssignmentTarget(node: any) { if (node.type === NodeType.IdentifierExpression) return node; - + if (node.type === NodeType.BinaryExpression + &&node.operator===TokenTypes.DOT_OPERATOR + &&node.right.type===NodeType.IdentifierExpression) return node; + throw new SyntaxError("Invalid left hand side in assignment expression"); } } 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/dotExpression.ts b/packages/parser/src/components/parser/statement/expression/dotExpression.ts new file mode 100644 index 00000000..6a43c890 --- /dev/null +++ b/packages/parser/src/components/parser/statement/expression/dotExpression.ts @@ -0,0 +1,14 @@ +import Expression from "."; + +import { TokenTypes } from "../../../../constants/bhaiLangSpec"; +import { NodeType } from "../../../../constants/constants"; +import { ASTNode } from "../../types/nodeTypes"; + +export default class DotExpression extends Expression { + getExpression(): ASTNode { + return this.getBinaryExpression( + NodeType.PrimaryExpression, + TokenTypes.DOT_OPERATOR + ); + } +} diff --git a/packages/parser/src/components/parser/statement/expression/index.ts b/packages/parser/src/components/parser/statement/expression/index.ts index 28849f74..ec4b4c0d 100644 --- a/packages/parser/src/components/parser/statement/expression/index.ts +++ b/packages/parser/src/components/parser/statement/expression/index.ts @@ -41,6 +41,12 @@ export default abstract class Expression { case NodeType.RelationalExpression: return BhaiLangModule.getRelationalExpression(); + + case NodeType.CallableExpression: + return BhaiLangModule.getCallableExpression(); + case NodeType.DotExpression: + return BhaiLangModule.getDotExpression(); + default: return BhaiLangModule.getIndentifierExpression(); diff --git a/packages/parser/src/components/parser/statement/expression/multiplicativeExpression.ts b/packages/parser/src/components/parser/statement/expression/multiplicativeExpression.ts index 6d300b47..ebbd0b7a 100644 --- a/packages/parser/src/components/parser/statement/expression/multiplicativeExpression.ts +++ b/packages/parser/src/components/parser/statement/expression/multiplicativeExpression.ts @@ -7,7 +7,7 @@ import { ASTNode } from "../../types/nodeTypes"; export default class MultiplicativeExpression extends Expression { getExpression(): ASTNode { return this.getBinaryExpression( - NodeType.PrimaryExpression, + NodeType.DotExpression, TokenTypes.MULTIPLICATIVE_OPERATOR_TYPE ); } 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..88255bdc 100644 --- a/packages/parser/src/components/parser/statement/index.ts +++ b/packages/parser/src/components/parser/statement/index.ts @@ -39,6 +39,12 @@ 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(); + case TokenTypes.SAMAJH_LE_BHAI: + return BhaiLangModule.getClassStatement(); 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..a3ea0815 100644 --- a/packages/parser/src/components/parser/types/nodeTypes.ts +++ b/packages/parser/src/components/parser/types/nodeTypes.ts @@ -11,7 +11,12 @@ export type ASTNode = { id?: ASTNode; init?: ASTNode | null; declarations?: ASTNode[]; + declaration?: ASTNode; test?: ASTNode; consequent?: ASTNode; alternates?: ASTNode[]; + args?: ASTNode[]; + inherits?: ASTNode[]; + accessMember?: ASTNode; + signature?: ASTNode; }; diff --git a/packages/parser/src/constants/bhaiLangSpec.ts b/packages/parser/src/constants/bhaiLangSpec.ts index 50525df9..6f56564b 100644 --- a/packages/parser/src/constants/bhaiLangSpec.ts +++ b/packages/parser/src/constants/bhaiLangSpec.ts @@ -21,6 +21,22 @@ 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 + + SAMAJH_LE_BHAI:"samajh le bhai",// class statement + + JO_KI_EK_BHAI:"jo ki ek bhai",// implements statement + + HAI_BHAI: "hai bhai", //implements ends statement + + ISKA: "iska", //This Operator + + CONSTRUCTOR_TYPE: "janam", //constructor + + DOT_OPERATOR: ".", + NALLA_TYPE: "NALLA", SEMI_COLON_TYPE: ";", @@ -39,6 +55,8 @@ export const TokenTypes = { IDENTIFIER_TYPE: "IDENTIFIER", + CALLABLE_TYPE: "CALLABLE", + SIMPLE_ASSIGN_TYPE: "SIMPLE_ASSIGN", COMPLEX_ASSIGN_TYPE: "COMPLEX_ASSIGN", @@ -91,6 +109,19 @@ 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 }, + + // object oriented programming + { regex: /^\bsamajh le bhai\b/, tokenType: TokenTypes.SAMAJH_LE_BHAI }, + { regex: /^\bjo ki ek (bhai|)\b/, tokenType: TokenTypes.JO_KI_EK_BHAI }, + { regex: /^\bhai (bhai|)\b/, tokenType: TokenTypes.HAI_BHAI }, + // { regex: /^\biska\b/, tokenType: TokenTypes.ISKA }, + // scope resolution accessing members + { regex: /^\.(?=\w+)/, tokenType: TokenTypes.DOT_OPERATOR }, + // Number { regex: /^-?\d+/, tokenType: TokenTypes.NUMBER_TYPE }, @@ -98,6 +129,8 @@ export const SPEC = [ { regex: /^\bsahi\b/, tokenType: TokenTypes.BOOLEAN_TYPE }, { regex: /^\bgalat\b/, tokenType: TokenTypes.BOOLEAN_TYPE }, + + // Identifier { regex: /^\w+/, tokenType: TokenTypes.IDENTIFIER_TYPE }, diff --git a/packages/parser/src/constants/constants.ts b/packages/parser/src/constants/constants.ts index f547d517..c9c11d89 100644 --- a/packages/parser/src/constants/constants.ts +++ b/packages/parser/src/constants/constants.ts @@ -11,6 +11,8 @@ export const NodeType = { LogicalORExpression: "LogicalORExpression", RelationalExpression: "RelationalExpression", EqualityExpression: "EqualityExpression", + CallableExpression: "CallableExpression", + DotExpression: "DotExpression", BlockStatement: "BlockStatement", EmptyStatement: "EmptyStatement", ExpressionStatement: "ExpressionStatement", @@ -26,5 +28,11 @@ export const NodeType = { StringLiteral: "StringLiteral", NullLiteral: "NullLiteral", VariableDeclaration: "VariableDeclaration", + FunctionStatement: "FunctionStatement", + FunctionDeclaration: "FunctionDeclaration", + FunctionSignature: "FunctionSignature", + ReturnStatement: "ReturnStatement", + ClassStatement: "ClassStatement", + ClassDeclaration: "ClassDeclaration", Program: "Program", } as const; diff --git a/packages/parser/src/module/bhaiLangModule.ts b/packages/parser/src/module/bhaiLangModule.ts index 6ddf3199..2583aead 100644 --- a/packages/parser/src/module/bhaiLangModule.ts +++ b/packages/parser/src/module/bhaiLangModule.ts @@ -2,6 +2,7 @@ import { Parser } from "../components/parser"; import Program from "../components/parser/program"; import BlockStatement from "../components/parser/statement/blockStatement"; import BreakStatement from "../components/parser/statement/breakStatement"; +import ClassStatement from "../components/parser/statement/classStatement"; import ContinueStatement from "../components/parser/statement/continueStatement"; import EmptyStatement from "../components/parser/statement/emptyStatement"; @@ -9,6 +10,9 @@ 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 DotExpression from "../components/parser/statement/expression/dotExpression"; import EqualityExpression from "../components/parser/statement/expression/equalityExpression"; import IdentifierExpression @@ -35,9 +39,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 +75,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 +85,10 @@ export default class BhaiLangModule { private static _breakStatement?: BreakStatement; private static _continueStatement?: ContinueStatement; private static _whileStatement?: WhileStatement; + private static _functionStatement: FunctionStatement; + private static _returnStatement: ReturnStatement; + private static _classStatement: ClassStatement; + private static _dotExpression: DotExpression; static getTokenizer() { if (!this._tokenizer) this._tokenizer = new TokenizerImpl(SPEC); @@ -187,6 +198,33 @@ 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 getClassStatement() { + if (!this._classStatement) + this._classStatement = new ClassStatement( + this.getTokenExecutor() + ); + + return this._classStatement; + } + static getAdditiveExpression() { if (!this._additiveExpression) { @@ -207,6 +245,15 @@ export default class BhaiLangModule { return this._multiplicativeExpression; } + static getDotExpression() { + if (!this._dotExpression) { + this._dotExpression = new DotExpression( + this.getTokenExecutor() + ); + } + + return this._dotExpression; + } static getPrimaryExpression() { if (!this._primaryExpression) { @@ -280,6 +327,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); }); +