├── .vscode └── settings.json ├── examples ├── fibonaci.kocha └── don_don_ziki.kocha ├── runtime ├── errors │ └── panic.ts ├── values.ts ├── std │ └── functions.ts ├── environment │ └── env.ts ├── interpreter.ts └── evaluate │ ├── statements.ts │ └── expressions.ts ├── frontend ├── lexer │ ├── misc.ts │ ├── types.ts │ └── index.ts └── parser │ ├── ast.ts │ └── index.ts ├── LICENSE ├── main.ts └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "editor.formatOnSave": true, 5 | "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" } 6 | } -------------------------------------------------------------------------------- /examples/fibonaci.kocha: -------------------------------------------------------------------------------- 1 | tema fib(n){ 2 | xullas a = [1, 1]; 3 | xullas i = 0; 4 | 5 | aylan(i> "); 13 | 14 | if (input?.trim() == 'exit') { 15 | Deno.exit(1); 16 | } 17 | 18 | const program = parser.createAST(input); 19 | const result = interpret(program, env); 20 | console.log(result.value); 21 | } 22 | } 23 | 24 | async function runCode(path: string) { 25 | const parser = new Parser(); 26 | const env = createGlobalEnv(); 27 | 28 | const code = await Deno.readTextFile(path); 29 | const program = parser.createAST(code); 30 | console.log(program); 31 | interpret(program, env); 32 | } 33 | 34 | function main() { 35 | if (Deno.args.length == 0) { 36 | repl(); 37 | return; 38 | } 39 | 40 | if (Deno.args.length == 1) { 41 | runCode(Deno.args[0]); 42 | } 43 | } 44 | 45 | main(); 46 | -------------------------------------------------------------------------------- /frontend/lexer/types.ts: -------------------------------------------------------------------------------- 1 | export enum TokenType { 2 | // literal types 3 | Number, 4 | Identifier, 5 | String, 6 | 7 | // grouping operators 8 | BinaryOperator, 9 | Equals, 10 | OpenParen, // ( 11 | CloseParen, // ) 12 | OpenBrace, // { 13 | CloseBrace, // } 14 | OpenBracket, // [ 15 | CloseBracket, // ] 16 | Colon, 17 | Semicolon, 18 | Comma, 19 | Dot, 20 | EOF, // tells the end of file 21 | 22 | // keywords 23 | Let, 24 | Const, 25 | Fn, 26 | Return, 27 | If, 28 | ElseIf, 29 | Else, 30 | // conditions 31 | While, 32 | For, 33 | Continue, 34 | Break, 35 | } 36 | 37 | export interface Token { 38 | value: string; 39 | type: TokenType; 40 | line: number; 41 | } 42 | 43 | export const KEYWORDS: Record = { 44 | "xullas": TokenType.Let, 45 | "jovob": TokenType.Const, 46 | "endi": TokenType.Equals, 47 | "tema": TokenType.Fn, 48 | "qaytar": TokenType.Return, 49 | // conditions 50 | "agar": TokenType.If, 51 | "yemasa": TokenType.ElseIf, 52 | "oxiri": TokenType.Else, 53 | "va": TokenType.BinaryOperator, 54 | "yoki": TokenType.BinaryOperator, 55 | // loops 56 | "aylan": TokenType.While, 57 | "qarama": TokenType.Continue, 58 | "toxta": TokenType.Break, 59 | }; 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Kocha dasturlash tili - Birinchi o‘zbek ko‘cha tilidan ilhomlangan dasturlash tili 8 | 9 | 10 | [Website][Kocha] | [Boshlash] | [Qollanma] | [Qoshimcha] 11 | 12 | _Birinchi o‘zbek ko‘cha tilidan ilhomlangan dasturlash tili_ 13 | 14 |
15 | 16 | [Kocha]: https://www.kocha-lang.uz/ 17 | [Boshlash]: https://www.kocha-lang.uz/start/install/ 18 | [Qollanma]: https://www.kocha-lang.uz/start/install/ 19 | [Qoshimcha]: https://github.com/kocha-lang/extension 20 | 21 | ## Kocha Lang o'zi nima? 22 | 23 | Kocha Lang - bu xobi sifatida yaratilgan open source loyiha. Bu tilning asosiy 24 | maqsadi dasturlashni o’zgacha tarzda tajriba qilishdir. 25 | U interpretatsiya qilinadigan til bo’lib, u [Deno](https://deno.com/) asosida 26 | ishlaydi. 27 | 28 | ```js 29 | xullas a = 5; 30 | jovob b = 4; 31 | 32 | agar(a > b){ 33 | korsat("A katta"); 34 | } 35 | yemasa(a < b){ 36 | korsat("B katta"); 37 | } 38 | 39 | oxiri{ 40 | korsat("Teng"); 41 | } 42 | ``` 43 | 44 | ## Contributors 45 | 46 | - [Bobomurod](https://github.com/mmnvb) - Assosiy maintainer 47 | 48 |
49 | © Bobomurod, 2025 ⭐️ 50 |
51 | -------------------------------------------------------------------------------- /examples/don_don_ziki.kocha: -------------------------------------------------------------------------------- 1 | korsat(""); 2 | korsat("===== DON-DON ZIKI ====="); 3 | korsat("stop - tugatish uchun"); 4 | korsat(""); 5 | 6 | jovob sxema = { 7 | qaychi: { 8 | yutadi: "varro", 9 | yutqazadi: "tosh" 10 | }, 11 | varro: { 12 | yutadi: "tosh", 13 | yutqazadi: "qaychi" 14 | }, 15 | tosh: { 16 | yutadi: "qaychi", 17 | yutqazadi: "varro" 18 | } 19 | } 20 | 21 | # xisob 22 | xullas odam = 0; 23 | xullas komp = 0; 24 | 25 | # oyin 26 | xullas oyinchi endi ""; 27 | xullas round = 1; 28 | jovob yurishla = ["qaychi", "tosh", "varro"]; 29 | 30 | 31 | tema oyin(yurish){ 32 | # komputerni tasodif yurishi 33 | xullas index = kelishtir(shara(0,2)); 34 | xullas kompYurish = yurishla[index]; 35 | korsat(" Man yurdim", kompYurish); 36 | 37 | # kim yutdi? 38 | agar(sxema[yurish].yutadi == kompYurish){ 39 | odam = odam + 1; 40 | korsat("-> Xullas yuttin ukam"); 41 | } 42 | yemasa(sxema[yurish].yutqazadi == kompYurish){ 43 | komp = komp + 1; 44 | korsat("-> Yutqazdin, bo'r organ oynawwi..."); 45 | } 46 | oxiri{ 47 | korsat("-> Dostli"); 48 | } 49 | } 50 | 51 | aylan(oyinchi != "stop"){ 52 | korsat("------- ROUND", round, "-------"); 53 | korsat(" ", odam, ":", komp, " ") 54 | korsat("Yur endi", yurishla, ":") 55 | oyinchi = gapir(""); 56 | 57 | agar(oyinchi == "tosh" yoki oyinchi == "qaychi" yoki oyinchi == "varro"){ 58 | oyin(oyinchi); 59 | round = round + 1; 60 | } oxiri{ 61 | korsat("Norm narsa yozgin..."); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /runtime/values.ts: -------------------------------------------------------------------------------- 1 | import { Statement } from "../frontend/parser/ast.ts"; 2 | import Environment from "./environment/env.ts"; 3 | 4 | export type ValueType = 5 | | "null" 6 | | "string" 7 | | "number" 8 | | "boolean" 9 | | "object" 10 | | "native-fn" 11 | | "function" 12 | | "array" 13 | | "flow"; 14 | 15 | export interface RuntimeValue { 16 | type: ValueType; 17 | value: null | number | boolean | string; // temp line 18 | } 19 | 20 | export interface NullValue extends RuntimeValue { 21 | type: "null"; 22 | value: null; 23 | } 24 | 25 | export interface NumberValue extends RuntimeValue { 26 | type: "number"; 27 | value: number; 28 | } 29 | 30 | export interface StringValue extends RuntimeValue { 31 | type: "string"; 32 | value: string; 33 | } 34 | 35 | export interface BoolValue extends RuntimeValue { 36 | type: "boolean"; 37 | value: boolean; 38 | } 39 | 40 | export interface FlowValue extends RuntimeValue { 41 | type: "flow"; 42 | catched: boolean; 43 | skip: boolean; 44 | stop: boolean; 45 | } 46 | 47 | export function MK_NUMBER(n: number) { 48 | return { type: "number", value: n } as NumberValue; 49 | } 50 | 51 | export function MK_NULL() { 52 | return { type: "null", value: null } as NullValue; 53 | } 54 | 55 | export function MK_BOOL(value = true) { 56 | return { type: "boolean", value: value } as BoolValue; 57 | } 58 | 59 | export function MK_STR(value: string) { 60 | return { type: "string", value } as StringValue; 61 | } 62 | 63 | /** 64 | * @param catched did it catch by children of if stmt or not 65 | * @param skip did it faced a continue stmt 66 | * @param stop did it faced a break stmt 67 | * @returns 68 | */ 69 | export function MK_FLOW(catched: boolean, skip: boolean, stop: boolean) { 70 | return { type: "flow", catched, skip, stop } as FlowValue; 71 | } 72 | 73 | export interface ObjectValue extends RuntimeValue { 74 | type: "object"; 75 | props: Map; 76 | } 77 | 78 | export interface ArrayValue extends RuntimeValue { 79 | type: "array"; 80 | values: RuntimeValue[]; 81 | } 82 | 83 | export type FunctionCall = ( 84 | args: RuntimeValue[], 85 | env: Environment, 86 | ) => RuntimeValue; 87 | 88 | export interface NativeFnValue extends RuntimeValue { 89 | type: "native-fn"; 90 | call: FunctionCall; 91 | } 92 | 93 | export function MK_NATIVE_FN(call: FunctionCall) { 94 | return { type: "native-fn", call } as NativeFnValue; 95 | } 96 | 97 | export interface FnValue extends RuntimeValue { 98 | type: "function"; 99 | name: string; 100 | params: string[]; 101 | declarationEnv: Environment; 102 | body: Statement[]; 103 | } 104 | -------------------------------------------------------------------------------- /runtime/std/functions.ts: -------------------------------------------------------------------------------- 1 | import Environment from "../environment/env.ts"; 2 | import { stdPanic } from "../errors/panic.ts"; 3 | import { 4 | type ArrayValue, 5 | MK_NUMBER, 6 | MK_STR, 7 | type NumberValue, 8 | RuntimeValue, 9 | StringValue, 10 | } from "../values.ts"; 11 | import { MK_NULL } from "../values.ts"; 12 | import { ObjectValue } from "../values.ts"; 13 | 14 | export function korsat(args: RuntimeValue[], _env: Environment) { 15 | console.log(...args.map((arg) => { 16 | // validate 17 | if (arg == undefined) { 18 | return; 19 | } 20 | 21 | // handle array print 22 | if (arg.type == "array") { 23 | const temp: string[] = []; 24 | (arg as ArrayValue).values.forEach((el) => { 25 | temp.push((el.value ?? "null").toString()); 26 | }); 27 | return "[ " + temp.join(", ") + " ]"; 28 | } 29 | 30 | // handle object print 31 | // shittiest code of my codebase :) 32 | if (arg.type == "object") { 33 | return [...(arg as ObjectValue).props.entries()].toString(); 34 | } 35 | 36 | // primitive print 37 | return arg.value; 38 | })); 39 | 40 | return MK_NULL(); 41 | } 42 | 43 | export function gapir(args: RuntimeValue[]): RuntimeValue { 44 | if (args.length < 2 && args[0].type == "string") { 45 | const text = args[0] as StringValue; 46 | 47 | const input = prompt(text.value); 48 | 49 | return MK_STR(input ?? ""); 50 | } 51 | 52 | // todo: make proper error handling 53 | return MK_NULL(); 54 | } 55 | 56 | export function son(args: RuntimeValue[]) { 57 | if (args.length < 2 && args[0].type == "string") { 58 | const num = Number(args[0].value); 59 | return MK_NUMBER(num); 60 | } 61 | 62 | // todo: make proper error handling 63 | return MK_NULL(); 64 | } 65 | 66 | export function shara(args: RuntimeValue[]) { 67 | if (args.length != 2) { 68 | stdPanic( 69 | `Random function needs 2 arguments, but ${args.length} were passed`, 70 | ); 71 | } 72 | 73 | if (args[0].type != "number" || args[1].type != "number") { 74 | stdPanic("Arguments for random function must be of a number type"); 75 | } 76 | 77 | // at this point we know that we 2 args that are numbers 78 | const min = (args[0] as NumberValue).value; 79 | const max = (args[1] as NumberValue).value; 80 | 81 | return MK_NUMBER(Math.random() * (max - min) + min); 82 | } 83 | 84 | export function kelishtir(args: RuntimeValue[]) { 85 | if (args.length != 1) { 86 | stdPanic( 87 | `Round function needs 1 argument, but ${args.length} were passed`, 88 | ); 89 | } 90 | 91 | if (args[0].type != "number") { 92 | stdPanic("Arguments for round function must be of a number type"); 93 | } 94 | 95 | // bro really thinks he wrote a programming language 96 | // it is just a wrapper of js functions... 97 | return MK_NUMBER(Math.round((args[0] as NumberValue).value)); 98 | } 99 | -------------------------------------------------------------------------------- /runtime/environment/env.ts: -------------------------------------------------------------------------------- 1 | import { MK_NATIVE_FN, RuntimeValue } from "../values.ts"; 2 | import { gapir, kelishtir, korsat, shara, son } from "../std/functions.ts"; 3 | import { MK_BOOL, MK_NULL } from "../values.ts"; 4 | import panic from "../errors/panic.ts"; 5 | 6 | export function createGlobalEnv(): Environment { 7 | const env = new Environment(); 8 | 9 | // std vars 10 | env.declareVariable("true", MK_BOOL(), true, -1); 11 | env.declareVariable("lagmon", MK_BOOL(false), true, -1); 12 | env.declareVariable("pustoy", MK_NULL(), true, -1); 13 | 14 | // std funcs 15 | // I/O 16 | env.declareVariable("korsat", MK_NATIVE_FN(korsat), true, -1); // print() 17 | env.declareVariable("gapir", MK_NATIVE_FN(gapir), true, -1); // input() 18 | // Math 19 | env.declareVariable("son", MK_NATIVE_FN(son), true, -1); // converts str to number 20 | env.declareVariable("shara", MK_NATIVE_FN(shara), true, -1); // random(min, max) 21 | env.declareVariable("kelishtir", MK_NATIVE_FN(kelishtir), true, -1); // round(n) 22 | 23 | return env; 24 | } 25 | 26 | export default class Environment { 27 | private parent?: Environment; 28 | private variables: Map; 29 | private constants: Set; 30 | 31 | constructor(parentEnv?: Environment) { 32 | this.parent = parentEnv; 33 | this.variables = new Map(); 34 | this.constants = new Set(); 35 | } 36 | 37 | public declareVariable( 38 | name: string, 39 | value: RuntimeValue, 40 | isConst: boolean, 41 | line: number, 42 | ) { 43 | if (this.variables.has(name)) { 44 | panic( 45 | `ENV: Varibale "${name}" already defined. Cannot declare twice`, 46 | line, 47 | ); 48 | } 49 | 50 | if (isConst) { 51 | this.constants.add(name); 52 | } 53 | this.variables.set(name, value); 54 | return value; 55 | } 56 | 57 | private resolve(name: string, line: number): Environment { 58 | if (this.variables.has(name)) { 59 | return this; 60 | } 61 | 62 | if (this.parent == undefined) { 63 | panic(`ENV: Variable "${name}" is not declared. Cannot assign`, line); 64 | } 65 | 66 | // recursive call of a parent 67 | return this.parent.resolve(name, line); 68 | } 69 | 70 | public assignVariable(name: string, value: RuntimeValue, line: number) { 71 | const env = this.resolve(name, line); 72 | 73 | // cannot mutate 74 | if (env.constants.has(name)) { 75 | panic(`Const ${name} cannot be mutated`, line); 76 | } 77 | env.variables.set(name, value); 78 | return value; 79 | } 80 | 81 | public getVariable(name: string, line: number): RuntimeValue { 82 | const env = this.resolve(name, line); 83 | // never gonna be undefined thanks to resolve 84 | return env.variables.get(name) as RuntimeValue; 85 | } 86 | 87 | public updateVariable( 88 | name: string, 89 | updated: RuntimeValue, 90 | line: number, 91 | ): RuntimeValue { 92 | const env = this.resolve(name, line); 93 | env.variables.set(name, updated); 94 | 95 | return updated; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /runtime/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { MK_FLOW, MK_NUMBER, MK_STR, RuntimeValue } from "./values.ts"; 2 | import { 3 | type ArrayLiteral, 4 | AssignmentExpression, 5 | BinaryExpression, 6 | CallExpression, 7 | ElifStatement, 8 | ElseStatement, 9 | FunctionDeclaration, 10 | Identifier, 11 | IfStatement, 12 | MemberExpression, 13 | NumericLiteral, 14 | ObjectLiteral, 15 | Program, 16 | ReturnStatement, 17 | Statement, 18 | StringLiteral, 19 | VariableDeclaration, 20 | type WhileStatement, 21 | } from "../frontend/parser/ast.ts"; 22 | import Environment from "./environment/env.ts"; 23 | import { 24 | evalArrayExpression, 25 | evalAssignment, 26 | evalBinaryExpression, 27 | evalCallExpression, 28 | evalIdentifier, 29 | evalMemberExpression, 30 | evalObjectExpression, 31 | } from "./evaluate/expressions.ts"; 32 | import { 33 | evalElifStatement, 34 | evalElseStatement, 35 | evalFnDeclaration, 36 | evalIfStatement, 37 | evalProgram, 38 | evalReturnStatement, 39 | evalVarDeclaration, 40 | evalWhileStatement, 41 | } from "./evaluate/statements.ts"; 42 | 43 | export function interpret(astNode: Statement, env: Environment): RuntimeValue { 44 | switch (astNode.kind) { 45 | case "NumericLiteral": 46 | return MK_NUMBER((astNode as NumericLiteral).value); 47 | case "StringLiteral": 48 | return MK_STR((astNode as StringLiteral).value); 49 | case "Identifier": 50 | return evalIdentifier(astNode as Identifier, env); 51 | case "BinaryExpression": 52 | return evalBinaryExpression(astNode as BinaryExpression, env); 53 | case "Program": 54 | return evalProgram(astNode as Program, env); 55 | case "VariableDeclaration": 56 | return evalVarDeclaration(astNode as VariableDeclaration, env); 57 | case "FunctionDeclaration": 58 | return evalFnDeclaration(astNode as FunctionDeclaration, env); 59 | case "ReturnStatement": 60 | return evalReturnStatement(astNode as ReturnStatement, env); 61 | case "AssignmentExpression": 62 | return evalAssignment(astNode as AssignmentExpression, env); 63 | case "ObjectLiteral": 64 | return evalObjectExpression(astNode as ObjectLiteral, env); 65 | case "CallExpression": 66 | return evalCallExpression(astNode as CallExpression, env); 67 | case "MemberExpression": 68 | return evalMemberExpression(astNode as MemberExpression, env); 69 | case "IfStatement": 70 | return evalIfStatement(astNode as IfStatement, env); 71 | case "ElifStatement": 72 | return evalElifStatement(astNode as ElifStatement, env); 73 | case "ElseStatement": 74 | return evalElseStatement(astNode as ElseStatement, env); 75 | case "ArrayLiteral": 76 | return evalArrayExpression(astNode as ArrayLiteral, env); 77 | case "WhileStatement": 78 | return evalWhileStatement(astNode as WhileStatement, env); 79 | case "ContinueStatement": 80 | return MK_FLOW(false, true, false); 81 | case "BreakStatement": 82 | return MK_FLOW(false, false, true); 83 | default: 84 | console.error( 85 | "Interpreter: AST type not handled yet type:", 86 | astNode, 87 | ); 88 | Deno.exit(0); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /frontend/parser/ast.ts: -------------------------------------------------------------------------------- 1 | export type NodeType = 2 | // Statements 3 | | "Program" 4 | | "VariableDeclaration" 5 | | "FunctionDeclaration" 6 | | "ReturnStatement" 7 | | "IfStatement" 8 | | "ElifStatement" 9 | | "ElseStatement" 10 | | "WhileStatement" 11 | | "ContinueStatement" 12 | | "BreakStatement" 13 | // Expressions 14 | | "BinaryExpression" 15 | | "AssignmentExpression" 16 | | "MemberExpression" 17 | | "CallExpression" 18 | | "UnaryExpression" // not used yet 19 | // Literals 20 | | "Property" 21 | | "ObjectLiteral" 22 | | "ArrayLiteral" 23 | | "NumericLiteral" 24 | | "Identifier" 25 | | "StringLiteral"; 26 | 27 | export interface Statement { 28 | kind: NodeType; 29 | line: number; 30 | } 31 | 32 | export interface Program extends Statement { 33 | kind: "Program"; 34 | body: Statement[]; 35 | } 36 | 37 | export interface VariableDeclaration extends Statement { 38 | kind: "VariableDeclaration"; 39 | isConst: boolean; 40 | identifier: string; 41 | value?: Expression; 42 | } 43 | 44 | export interface FunctionDeclaration extends Statement { 45 | kind: "FunctionDeclaration"; 46 | name: string; 47 | params: string[]; 48 | body: Statement[]; 49 | } 50 | 51 | export interface ReturnStatement extends Statement { 52 | kind: "ReturnStatement"; 53 | value: Expression; 54 | } 55 | 56 | export interface IfStatement extends Statement { 57 | kind: "IfStatement"; 58 | condition: BinaryExpression | Identifier; 59 | body: Statement[]; 60 | children?: Statement[]; 61 | } 62 | 63 | export interface ElifStatement extends Statement { 64 | kind: "ElifStatement"; 65 | condition: BinaryExpression | Identifier; 66 | body: Statement[]; 67 | } 68 | 69 | export interface ElseStatement extends Statement { 70 | kind: "ElseStatement"; 71 | body: Statement[]; 72 | } 73 | 74 | export interface WhileStatement extends Statement { 75 | kind: "WhileStatement"; 76 | condition: BinaryExpression | Identifier; 77 | body: Statement[]; 78 | } 79 | 80 | export interface ContinueStatement extends Statement { 81 | kind: "ContinueStatement"; 82 | } 83 | 84 | export interface BreakStatement extends Statement { 85 | kind: "BreakStatement"; 86 | } 87 | 88 | export interface Expression extends Statement {} 89 | 90 | export interface BinaryExpression extends Expression { 91 | kind: "BinaryExpression"; 92 | left: Expression; 93 | right: Expression; 94 | operator: string; 95 | } 96 | 97 | export interface AssignmentExpression extends Expression { 98 | kind: "AssignmentExpression"; 99 | owner: Expression; 100 | value: Expression; 101 | } 102 | 103 | export interface MemberExpression extends Expression { 104 | kind: "MemberExpression"; 105 | object: Expression; 106 | prop: Expression; 107 | computed: boolean; 108 | } 109 | 110 | export interface CallExpression extends Expression { 111 | kind: "CallExpression"; 112 | args: Expression[]; 113 | caller: Expression; 114 | } 115 | 116 | export interface Identifier extends Expression { 117 | kind: "Identifier"; 118 | symbol: string; 119 | } 120 | 121 | export interface NumericLiteral extends Expression { 122 | kind: "NumericLiteral"; 123 | value: number; 124 | } 125 | 126 | export interface StringLiteral extends Expression { 127 | kind: "StringLiteral"; 128 | value: string; 129 | } 130 | 131 | export interface Property extends Expression { 132 | kind: "Property"; 133 | key: string; 134 | value?: Expression; 135 | } 136 | 137 | export interface ObjectLiteral extends Expression { 138 | kind: "ObjectLiteral"; 139 | props: Property[]; 140 | } 141 | 142 | export interface ArrayLiteral extends Expression { 143 | kind: "ArrayLiteral"; 144 | values: Expression[]; 145 | } 146 | -------------------------------------------------------------------------------- /frontend/lexer/index.ts: -------------------------------------------------------------------------------- 1 | import { KEYWORDS, Token, TokenType } from "./types.ts"; 2 | import { isAlpha, isEscapeChar, isInt } from "./misc.ts"; 3 | 4 | export function tokenize(srcCode: string): Token[] { 5 | const tokens = new Array(); 6 | const src = srcCode.split(""); 7 | let tempWord = ""; 8 | let line: number = 1; 9 | 10 | // =========== Methods ============ 11 | // append whole int or alpha word to token functions 12 | const saveInt = () => { 13 | tokens.push(token(tempWord, TokenType.Number)); 14 | }; 15 | 16 | const saveAlpha = () => { 17 | const reserved = KEYWORDS[tempWord]; 18 | if (typeof reserved == "number") { 19 | tokens.push(token(tempWord, reserved)); 20 | return; 21 | } 22 | tokens.push(token(tempWord, TokenType.Identifier)); 23 | }; 24 | 25 | const finalHandle = (index: number) => { 26 | // handling numeric saved word 27 | if (isInt(tempWord)) { 28 | saveInt(); 29 | } // handling aphabetical saved word 30 | else if (isAlpha(tempWord)) { 31 | saveAlpha(); 32 | } // handling escape characters word 33 | else if (!isEscapeChar(src[index]) && tempWord) { 34 | console.log("Unhandled character:", src[index]); 35 | Deno.exit(1); 36 | } 37 | tempWord = ""; 38 | }; 39 | 40 | const findTheEnd = (i: number): number => { 41 | while (src[i] != "\n" && src[i] != undefined) { 42 | i++; 43 | } 44 | return i; 45 | }; 46 | 47 | const getString = (i: number): number => { 48 | let str: string = ""; 49 | i++; // advance 50 | while (src[i] != '"') { 51 | str += src[i]; 52 | i++; 53 | } 54 | 55 | tokens.push(token(str, TokenType.String)); 56 | return i; 57 | }; 58 | 59 | function token(value: string, type: TokenType): Token { 60 | return { value, type, line }; 61 | } 62 | 63 | // =========== Loop ============ 64 | for (let i = 0; i < src.length; i++) { 65 | // check if a new line appeared 66 | if (src[i] == "\n") { 67 | line++; 68 | } 69 | // check if comment 70 | if (src[i] == "#") { 71 | i = findTheEnd(i); 72 | continue; 73 | } 74 | if (src[i] == '"') { 75 | i = getString(i); 76 | continue; 77 | } 78 | if (src[i] == "(") { 79 | finalHandle(i); 80 | tokens.push(token(src[i], TokenType.OpenParen)); 81 | continue; 82 | } 83 | if (src[i] == ")") { 84 | finalHandle(i); 85 | tokens.push(token(src[i], TokenType.CloseParen)); 86 | continue; 87 | } 88 | if (src[i] == "{") { 89 | finalHandle(i); 90 | tokens.push(token(src[i], TokenType.OpenBrace)); 91 | continue; 92 | } 93 | if (src[i] == "}") { 94 | finalHandle(i); 95 | tokens.push(token(src[i], TokenType.CloseBrace)); 96 | continue; 97 | } 98 | if (src[i] == "[") { 99 | finalHandle(i); 100 | tokens.push(token(src[i], TokenType.OpenBracket)); 101 | continue; 102 | } 103 | if (src[i] == "]") { 104 | finalHandle(i); 105 | tokens.push(token(src[i], TokenType.CloseBracket)); 106 | continue; 107 | } 108 | if (["+", "-", "*", "/", "%"].includes(src[i])) { 109 | finalHandle(i); 110 | tokens.push(token(src[i], TokenType.BinaryOperator)); 111 | continue; 112 | } 113 | if (src[i] == ";") { 114 | finalHandle(i); 115 | tokens.push(token(src[i], TokenType.Semicolon)); 116 | continue; 117 | } 118 | 119 | if (src[i] == ":") { 120 | finalHandle(i); 121 | tokens.push(token(src[i], TokenType.Colon)); 122 | continue; 123 | } 124 | 125 | if (src[i] == ",") { 126 | finalHandle(i); 127 | tokens.push(token(src[i], TokenType.Comma)); 128 | continue; 129 | } 130 | 131 | if (src[i] == ".") { 132 | finalHandle(i); 133 | tokens.push(token(src[i], TokenType.Dot)); 134 | continue; 135 | } 136 | 137 | // if int or last 138 | if (isInt(src[i])) { 139 | tempWord += src[i]; 140 | if (i + 1 == src.length) { 141 | saveInt(); 142 | } 143 | continue; 144 | } 145 | 146 | // if alpha or last 147 | if (isAlpha(src[i])) { 148 | tempWord += src[i]; 149 | if (i + 1 == src.length) { 150 | saveAlpha(); 151 | } 152 | continue; 153 | } 154 | 155 | if ([">", "<", "!", "="].includes(src[i])) { 156 | if (src[i + 1] == "=") { 157 | finalHandle(i); 158 | tokens.push(token(`${src[i]}${src[i + 1]}`, TokenType.BinaryOperator)); 159 | i++; // skip the next char 160 | continue; 161 | } 162 | if (src[i] == "=") { 163 | finalHandle(i); 164 | tokens.push(token(src[i], TokenType.Equals)); 165 | continue; 166 | } 167 | 168 | finalHandle(i); 169 | tokens.push(token(src[i], TokenType.BinaryOperator)); 170 | continue; 171 | } 172 | 173 | // clean the temp word 174 | finalHandle(i); 175 | } 176 | 177 | tokens.push({ value: "EndOfFile", type: TokenType.EOF, line }); 178 | return tokens; 179 | } 180 | 181 | // const code = await Deno.readTextFile("./test.kocha"); 182 | // for (const token of tokenize(code)) { 183 | // console.log(token); 184 | // } 185 | 186 | // console.log(tokenize("xullas a endi (4 + 23)")); 187 | -------------------------------------------------------------------------------- /runtime/evaluate/statements.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type FlowValue, 3 | FnValue, 4 | MK_FLOW, 5 | MK_NULL, 6 | RuntimeValue, 7 | } from "../values.ts"; 8 | import { 9 | ElifStatement, 10 | ElseStatement, 11 | FunctionDeclaration, 12 | IfStatement, 13 | Program, 14 | ReturnStatement, 15 | VariableDeclaration, 16 | type WhileStatement, 17 | } from "../../frontend/parser/ast.ts"; 18 | import Environment from "../environment/env.ts"; 19 | import { interpret } from "../interpreter.ts"; 20 | 21 | export function evalProgram(program: Program, env: Environment): RuntimeValue { 22 | let lastInterpreted: RuntimeValue = MK_NULL(); 23 | 24 | for (const statement of program.body) { 25 | lastInterpreted = interpret(statement, env); 26 | } 27 | 28 | return lastInterpreted; 29 | } 30 | 31 | export function evalVarDeclaration( 32 | declaration: VariableDeclaration, 33 | env: Environment, 34 | ): RuntimeValue { 35 | const value = declaration.value 36 | ? interpret(declaration.value, env) 37 | : MK_NULL(); 38 | 39 | return env.declareVariable( 40 | declaration.identifier, 41 | value, 42 | declaration.isConst, 43 | declaration.line, 44 | ); 45 | } 46 | 47 | export function evalFnDeclaration( 48 | declaration: FunctionDeclaration, 49 | env: Environment, 50 | ): RuntimeValue { 51 | const fn = { 52 | type: "function", 53 | name: declaration.name, 54 | params: declaration.params, 55 | declarationEnv: env, 56 | body: declaration.body, 57 | } as FnValue; 58 | 59 | return env.declareVariable(declaration.name, fn, true, declaration.line); 60 | } 61 | 62 | export function evalReturnStatement( 63 | statement: ReturnStatement, 64 | env: Environment, 65 | ): RuntimeValue { 66 | return interpret(statement.value, env); 67 | } 68 | 69 | export function evalIfStatement( 70 | statement: IfStatement, 71 | env: Environment, 72 | ): FlowValue { 73 | const scope = new Environment(env); 74 | const condition = interpret(statement.condition, env); 75 | // in case parent if statement must be called 76 | if (condition.value) { 77 | // execute line by line 78 | // can customize to handle break and continue keywords 79 | // or return a value from a statement but don't need it now 80 | for (const stmt of statement.body) { 81 | const val = interpret(stmt, scope); 82 | 83 | if (val.type == "flow") { 84 | const flow = val as FlowValue; 85 | 86 | if (flow.skip || flow.stop) { 87 | return MK_FLOW(true, flow.skip, flow.stop); 88 | } 89 | } 90 | } 91 | return MK_FLOW(true, false, false); 92 | } 93 | 94 | if (statement.children) { 95 | for (const child of statement.children) { 96 | // run everychild till one of them will catch 97 | const result = interpret(child, env); 98 | 99 | if (result.type == "flow") { 100 | const flow = result as FlowValue; 101 | 102 | if (flow.catched) { 103 | if (flow.skip || flow.stop) { 104 | return MK_FLOW(true, flow.skip, flow.stop); 105 | } 106 | break; 107 | } 108 | } 109 | } 110 | } 111 | 112 | return MK_FLOW(false, false, false); 113 | } 114 | 115 | export function evalElifStatement( 116 | statement: ElifStatement, 117 | env: Environment, 118 | ): FlowValue { 119 | const scope = new Environment(env); 120 | const condition = interpret(statement.condition, env); 121 | 122 | if (condition.value) { 123 | // execute line by line 124 | // can customize to handle break and continue keywords 125 | // or return a value from a statement but don't need it now 126 | for (const stmt of statement.body) { 127 | const val = interpret(stmt, scope); 128 | 129 | if (val.type == "flow") { 130 | const flow = val as FlowValue; 131 | 132 | if (flow.skip || flow.stop) { 133 | return MK_FLOW(true, flow.skip, flow.stop); 134 | } 135 | } 136 | } 137 | 138 | return MK_FLOW(true, false, false); 139 | } 140 | 141 | return MK_FLOW(false, false, false); 142 | } 143 | 144 | export function evalElseStatement( 145 | statement: ElseStatement, 146 | env: Environment, 147 | ): FlowValue { 148 | const scope = new Environment(env); 149 | 150 | for (const stmt of statement.body) { 151 | const val = interpret(stmt, scope); 152 | 153 | if (val.type == "flow") { 154 | const flow = val as FlowValue; 155 | 156 | if (flow.skip || flow.stop) { 157 | return MK_FLOW(true, flow.skip, flow.stop); 158 | } 159 | } 160 | } 161 | 162 | return MK_FLOW(true, false, false); 163 | } 164 | 165 | // bro thinks he wrote a programming language 166 | // isn't it just a wrapper of js functions? 167 | // bro write some llvm instad... 168 | 169 | export function evalWhileStatement( 170 | statement: WhileStatement, 171 | env: Environment, 172 | ) { 173 | const scope = new Environment(env); 174 | 175 | while (interpret(statement.condition, env).value) { 176 | for (const stmt of statement.body) { 177 | const val = interpret(stmt, scope); 178 | 179 | if (val.type == "flow") { 180 | const flow = val as FlowValue; 181 | 182 | if (flow.skip) { 183 | break; 184 | } else if (flow.stop) { 185 | return MK_NULL(); 186 | } 187 | } 188 | } 189 | } 190 | 191 | return MK_NULL(); 192 | } 193 | -------------------------------------------------------------------------------- /runtime/evaluate/expressions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArrayValue, 3 | FnValue, 4 | MK_NULL, 5 | MK_NUMBER, 6 | MK_STR, 7 | NativeFnValue, 8 | NumberValue, 9 | ObjectValue, 10 | RuntimeValue, 11 | } from "../values.ts"; 12 | import { 13 | type ArrayLiteral, 14 | AssignmentExpression, 15 | BinaryExpression, 16 | CallExpression, 17 | Identifier, 18 | MemberExpression, 19 | type NumericLiteral, 20 | ObjectLiteral, 21 | type StringLiteral, 22 | } from "../../frontend/parser/ast.ts"; 23 | import Environment from "../environment/env.ts"; 24 | import { interpret } from "../interpreter.ts"; 25 | import { StringValue } from "../values.ts"; 26 | import { BoolValue } from "../values.ts"; 27 | import panic from "../errors/panic.ts"; 28 | 29 | function evalNumericBinaryExpression( 30 | left: NumberValue, 31 | right: NumberValue, 32 | operator: string, 33 | ): NumberValue { 34 | let result: number; 35 | 36 | switch (operator) { 37 | case "+": 38 | result = left.value + right.value; 39 | break; 40 | case "-": 41 | result = left.value - right.value; 42 | break; 43 | case "*": 44 | result = left.value * right.value; 45 | break; 46 | case "/": 47 | if (right.value == 0) { 48 | console.error("Cannot divide by zero!"); 49 | Deno.exit(1); 50 | } 51 | result = left.value / right.value; 52 | break; 53 | default: 54 | result = left.value % right.value; 55 | } 56 | 57 | return { type: "number", value: result }; 58 | } 59 | 60 | function evalStringBinaryExpression( 61 | left: StringValue, 62 | right: StringValue, 63 | operator: string, 64 | line: number = -1 65 | ): StringValue { 66 | let result: string; 67 | 68 | switch (operator) { 69 | case "+": 70 | result = left.value + right.value; 71 | break; 72 | default: 73 | panic("Faqat concat qilish mumkin!", line); 74 | } 75 | 76 | return { type: "string", value: result }; 77 | } 78 | 79 | function evalRelationalBinaryExpr( 80 | left: NumberValue | StringValue | BoolValue, 81 | right: NumberValue | StringValue | BoolValue, 82 | operator: string, 83 | ): RuntimeValue { 84 | let result: boolean; 85 | 86 | switch (operator) { 87 | case ">": 88 | result = left.value > right.value; 89 | break; 90 | case "<": 91 | result = left.value < right.value; 92 | break; 93 | case "==": 94 | result = left.value == right.value; 95 | break; 96 | case "!=": 97 | result = left.value != right.value; 98 | break; 99 | case ">=": 100 | result = left.value >= right.value; 101 | break; 102 | default: 103 | result = left.value <= right.value; 104 | break; 105 | } 106 | 107 | return { type: "boolean", value: result } as BoolValue; 108 | } 109 | 110 | function evalLogicalBinaryExpr( 111 | left: BoolValue, 112 | right: BoolValue, 113 | operator: string, 114 | ): BoolValue { 115 | let result: boolean; 116 | 117 | switch (operator) { 118 | case "va": 119 | result = left.value && right.value; 120 | break; 121 | default: 122 | result = left.value || right.value; 123 | } 124 | 125 | return { type: "boolean", value: result } as BoolValue; 126 | } 127 | 128 | export function evalBinaryExpression( 129 | binop: BinaryExpression, 130 | env: Environment, 131 | ): RuntimeValue { 132 | const left = interpret(binop.left, env); 133 | const right = interpret(binop.right, env); 134 | 135 | const bothNumbers = () => { 136 | return left.type == "number" && right.type == "number"; 137 | }; 138 | 139 | const bothStrings = () => { 140 | return left.type == "string" && right.type == "string"; 141 | }; 142 | 143 | const bothBools = () => { 144 | return left.type == "boolean" && right.type == "boolean"; 145 | }; 146 | 147 | // handling relational operators 148 | if ([">=", "<=", ">", "<", "==", "!="].includes(binop.operator)) { 149 | if (bothNumbers()) { 150 | return evalRelationalBinaryExpr( 151 | left as NumberValue, 152 | right as NumberValue, 153 | binop.operator, 154 | ); 155 | } 156 | if (bothStrings()) { 157 | return evalRelationalBinaryExpr( 158 | left as StringValue, 159 | right as StringValue, 160 | binop.operator, 161 | ); 162 | } 163 | panic( 164 | "Bir biriga tog'ri keladigan ishni qilin. Son bilan son, gap bilan gap solishtirin faqat", 165 | binop.line, 166 | ); 167 | } 168 | 169 | if (binop.operator == "va" || binop.operator == "yoki") { 170 | if (bothBools()) { 171 | return evalLogicalBinaryExpr( 172 | left as BoolValue, 173 | right as BoolValue, 174 | binop.operator, 175 | ); 176 | } 177 | panic("va/yoki faqat boolni orasida bo'lishi mumkin aka", binop.line); 178 | } 179 | 180 | // arithmetic operators 181 | if (bothNumbers()) { 182 | return evalNumericBinaryExpression( 183 | left as NumberValue, 184 | right as NumberValue, 185 | binop.operator, 186 | ); 187 | } 188 | 189 | if (bothStrings()) { 190 | return evalStringBinaryExpression( 191 | left as StringValue, 192 | right as StringValue, 193 | binop.operator, 194 | binop.line, 195 | ); 196 | } 197 | 198 | return MK_NULL(); 199 | } 200 | 201 | export function evalIdentifier( 202 | ident: Identifier, 203 | env: Environment, 204 | ): RuntimeValue { 205 | const varibale = env.getVariable(ident.symbol, ident.line); 206 | return varibale; 207 | } 208 | 209 | export function evalAssignment( 210 | node: AssignmentExpression, 211 | env: Environment, 212 | ): RuntimeValue { 213 | // we only support identifier rn, maybe some support like a,b = b,a will be added in the future 214 | // would be awesome to implement a var switch using XOR gate 215 | 216 | if (node.owner.kind !== "Identifier") { 217 | panic("Cannot assign to anything rather than an identifier", node.line); 218 | } 219 | 220 | const varname = (node.owner as Identifier).symbol; 221 | return env.assignVariable(varname, interpret(node.value, env), node.line); 222 | } 223 | 224 | export function evalObjectExpression( 225 | obj: ObjectLiteral, 226 | env: Environment, 227 | ): RuntimeValue { 228 | const object = { type: "object", props: new Map() } as ObjectValue; 229 | 230 | for (const { key, value } of obj.props) { 231 | const runtimeVal = (value == undefined) 232 | ? env.getVariable(key, obj.line) 233 | : interpret(value, env); 234 | 235 | object.props.set(key, runtimeVal); 236 | } 237 | 238 | return object; 239 | } 240 | 241 | export function evalArrayExpression( 242 | arr: ArrayLiteral, 243 | env: Environment, 244 | ): RuntimeValue { 245 | const array = { type: "array", values: [] as RuntimeValue[] } as ArrayValue; 246 | 247 | for (const element of arr.values) { 248 | array.values.push(interpret(element, env)); 249 | } 250 | 251 | return array; 252 | } 253 | 254 | export function evalCallExpression( 255 | call: CallExpression, 256 | env: Environment, 257 | ): RuntimeValue { 258 | const args = call.args.map((arg) => interpret(arg, env)); 259 | const fn = interpret(call.caller, env); 260 | 261 | if (fn.type == "native-fn") { 262 | const result = (fn as NativeFnValue).call(args, env); 263 | return result; 264 | } 265 | 266 | if (fn.type == "function") { 267 | const func = fn as FnValue; 268 | const scope = new Environment(func.declarationEnv); 269 | 270 | if (args.length == func.params.length) { 271 | // declare variables to the function's scope 272 | for (let i = 0; i < func.params.length; i++) { 273 | scope.declareVariable(func.params[i], args[i], false, call.line); 274 | } 275 | 276 | let result: RuntimeValue = MK_NULL(); 277 | // execute line by line 278 | for (const statement of func.body) { 279 | if (statement.kind == "ReturnStatement") { 280 | result = interpret(statement, scope); 281 | return result; 282 | } 283 | result = interpret(statement, scope); 284 | } 285 | return result; 286 | } 287 | 288 | panic( 289 | `The number of args must match calling the function\n 290 | You gave ${args.length}\n 291 | Should be: ${func.params.length}`, 292 | call.line, 293 | ); 294 | } 295 | 296 | if (call.caller.kind == "MemberExpression") { 297 | const obj = (call.caller as MemberExpression).object as Identifier; 298 | const cur = env.getVariable(obj.symbol, call.line) as ArrayValue; 299 | if (fn.value == "push" && args.length == 1) { 300 | cur.values.push(args[0]); 301 | return env.updateVariable(obj.symbol, cur, call.line); 302 | } else if (fn.value == "pop" && args.length == 0) { 303 | cur.values.pop(); 304 | return env.updateVariable(obj.symbol, cur, call.line); 305 | } else if (fn.value == "shift" && args.length == 0) { 306 | cur.values.shift(); 307 | return env.updateVariable(obj.symbol, cur, call.line); 308 | } else if (fn.value == "clear" && args.length == 0) { 309 | cur.values = []; 310 | return env.updateVariable(obj.symbol, cur, call.line); 311 | } else if (fn.value == "getlength" && args.length == 0) { 312 | return MK_NUMBER(cur.values.length); 313 | } 314 | } 315 | 316 | panic( 317 | `${JSON.stringify(fn)} is not a function. So we can't call it!`, 318 | call.line, 319 | ); 320 | } 321 | 322 | function makeKeysToCall(expr: MemberExpression, env: Environment): string[] { 323 | if (expr.object.kind == "MemberExpression") { 324 | const last = makeKeysToCall(expr.object as MemberExpression, env); 325 | 326 | if (expr.prop.kind == "Identifier") { 327 | last.push((expr.prop as Identifier).symbol); 328 | } else if (expr.prop.kind == "StringLiteral") { 329 | last.push((expr.prop as StringLiteral).value); 330 | } 331 | 332 | return last; 333 | } 334 | 335 | if (expr.object.kind == "Identifier") { 336 | const ident = expr.object as Identifier; 337 | const prop = expr.prop; 338 | 339 | if (prop.kind == "Identifier") { 340 | const val = env.getVariable((prop as Identifier).symbol, expr.line); 341 | 342 | if (val.type != "string") { 343 | panic("Only string computed is supported", expr.line); 344 | } 345 | 346 | return [ident.symbol, (val as StringValue).value]; 347 | } 348 | 349 | return expr.prop.kind == "Identifier" 350 | ? [ident.symbol, (expr.prop as Identifier).symbol] 351 | : [ident.symbol, (expr.prop as StringLiteral).value]; 352 | } 353 | 354 | panic( 355 | `Other kinds for objects are not supported yet\n Given: ${expr.object}`, 356 | expr.line, 357 | ); 358 | } 359 | 360 | /** 361 | * It handles a lot of types of member expr syntax \ 362 | * *~That's why it messy (pls fix that)* 363 | * 364 | * **Arrays** 365 | * - `a[0]` - get by index 366 | * - `a[i+x]` - binary expr as an index 367 | * - *todo: `a[x][b]` - multi dimensional array calls* 368 | * 369 | * **Objects** 370 | * - `b.x` - dot notation syntax 371 | * - `b.x.m.n` - nested call syntax 372 | * - `b["x"]` - computed syntax 373 | * - `b["x"]["m"]["n"]` - nested call computed syntax 374 | * `` 375 | */ 376 | export function evalMemberExpression( 377 | expr: MemberExpression, 378 | env: Environment, 379 | ): RuntimeValue { 380 | // todo: massive refactor needed. Messy and unreadable if stmts 381 | // this function must handle a lot of types of syntax 382 | // give array's elements 383 | if (expr.prop.kind == "NumericLiteral") { 384 | const arr = env.getVariable( 385 | (expr.object as Identifier).symbol, 386 | expr.line, 387 | ) as ArrayValue; 388 | 389 | // check if an object was here 390 | if (arr.values == undefined) { 391 | panic("This syntax is supported by arrays only", expr.line); 392 | } 393 | const index = expr.prop as NumericLiteral; 394 | 395 | if (index.value >= arr.values.length) { 396 | panic("Index out of bounds", expr.line); 397 | } 398 | 399 | return arr.values[index.value]; 400 | } else if ( 401 | expr.prop.kind == "Identifier" && expr.object.kind == "Identifier" 402 | ) { 403 | const symbol = (expr.prop as Identifier).symbol; 404 | 405 | if (symbol == "qosh") { 406 | return MK_STR("push"); 407 | } else if (symbol == "chop") { 408 | return MK_STR("pop"); 409 | } else if (symbol == "sur") { 410 | return MK_STR("shift"); 411 | } else if (symbol == "yuqot") { 412 | return MK_STR("clear"); 413 | } else if (symbol == "razmer") { 414 | return MK_STR("getlength"); 415 | } 416 | } 417 | 418 | if ( 419 | expr.prop.kind == "BinaryExpression" && expr.object.kind == "Identifier" 420 | ) { 421 | const binexpr = expr.prop as BinaryExpression; 422 | const index = evalBinaryExpression(binexpr, env) as NumberValue; 423 | 424 | const arr = env.getVariable( 425 | (expr.object as Identifier).symbol, 426 | expr.line, 427 | ) as ArrayValue; 428 | 429 | // check if an object was here 430 | if (arr.values == undefined) { 431 | panic("This syntax is supported by arrays only", expr.line); 432 | } 433 | 434 | if (index.value >= arr.values.length) { 435 | panic("Index out of bounds", expr.line); 436 | } 437 | 438 | return arr.values[index.value]; 439 | } 440 | 441 | if (expr.computed && expr.prop.kind == "Identifier") { 442 | const symbol = (expr.object as Identifier).symbol; 443 | const index = env.getVariable( 444 | (expr.prop as Identifier).symbol, 445 | expr.line, 446 | ) as NumberValue; 447 | const arr = env.getVariable( 448 | symbol, 449 | expr.line, 450 | ) as ArrayValue; 451 | 452 | if (arr.values != undefined) { 453 | if (index.value >= arr.values.length) { 454 | panic("Index out of bounds", expr.line); 455 | } 456 | 457 | return arr.values[index.value]; 458 | } 459 | } 460 | // it just finds an array of keys in the order where first element is a declared variable 461 | // and others are top -> bottom keys 462 | // for struct like a.x.y we will have an array of strings [a,x,y] 463 | const keys = makeKeysToCall(expr, env); 464 | let variable = env.getVariable(keys[0], expr.line) as ObjectValue; 465 | 466 | // emulate recursion with for loop 467 | for (let i = 1; i < keys.length; i++) { 468 | variable = variable.props.get(keys[i]) as ObjectValue; 469 | } 470 | 471 | return variable; 472 | } 473 | -------------------------------------------------------------------------------- /frontend/parser/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArrayLiteral, 3 | AssignmentExpression, 4 | BinaryExpression, 5 | type BreakStatement, 6 | CallExpression, 7 | type ContinueStatement, 8 | ElifStatement, 9 | ElseStatement, 10 | Expression, 11 | FunctionDeclaration, 12 | Identifier, 13 | IfStatement, 14 | MemberExpression, 15 | NumericLiteral, 16 | ObjectLiteral, 17 | Program, 18 | Property, 19 | ReturnStatement, 20 | Statement, 21 | StringLiteral, 22 | VariableDeclaration, 23 | type WhileStatement, 24 | } from "./ast.ts"; 25 | import { tokenize } from "../lexer/index.ts"; 26 | import { Token, TokenType } from "../lexer/types.ts"; 27 | 28 | export default class Parser { 29 | private tokens: Token[] = []; 30 | private index: number = 0; 31 | 32 | private notEOF(): boolean { 33 | return this.tokens[this.index].type != TokenType.EOF; 34 | } 35 | 36 | /** Returns the current token */ 37 | private at(): Token { 38 | return this.tokens[this.index] as Token; 39 | } 40 | /** Moves one step forward and returns the previous token (came from) */ 41 | private next(): Token { 42 | this.index++; 43 | return this.tokens[this.index - 1]; 44 | } 45 | 46 | /** Gives the line we are at */ 47 | private line(): number { 48 | return this.at().line; 49 | } 50 | 51 | /** 52 | * Expects some type and moves one step forward \ 53 | * Returns prev values before move 54 | */ 55 | private expect(type: TokenType, error: string): Token { 56 | this.index++; 57 | const prev = this.tokens[this.index - 1]; 58 | if (!prev || prev.type != type) { 59 | console.error( 60 | `Parser error: ${error} -> 61 | ${prev} - 62 | Expected: ${type} 63 | Line: ${this.line()}`, 64 | ); 65 | Deno.exit(1); 66 | } 67 | return prev; 68 | } 69 | 70 | /** Terminates process with given error */ 71 | private panic(msg: string): never { 72 | throw `Parse Error: ${msg} Line: ${this.line()}`; 73 | } 74 | 75 | private parseStatement(): Statement { 76 | switch (this.at().type) { 77 | // variables 78 | case TokenType.Let: 79 | case TokenType.Const: 80 | return this.parseVarDeclaration(); 81 | 82 | // functions 83 | case TokenType.Fn: 84 | return this.parseFnDeclaration(); 85 | case TokenType.Return: 86 | return this.parseReturnStatement(); 87 | 88 | // conditions 89 | case TokenType.If: 90 | return this.parseIfStatement(); 91 | case TokenType.ElseIf: 92 | this.panic("Elif must have a parent IF stmt!"); 93 | /* falls through */ 94 | case TokenType.Else: 95 | this.panic("Else must have a parent IF stmt!"); 96 | /* falls through */ 97 | 98 | // Loops 99 | case TokenType.While: 100 | return this.parseWhileStatement(); 101 | case TokenType.Continue: 102 | this.skipMove(); 103 | return { kind: "ContinueStatement" } as ContinueStatement; 104 | case TokenType.Break: 105 | this.skipMove(); 106 | return { kind: "BreakStatement" } as BreakStatement; 107 | 108 | default: 109 | return this.parseExpression(); 110 | } 111 | } 112 | 113 | /** 114 | * Formats: 115 | * 1. (Let | Const) Identifier Equal Expression Semicolon 116 | * ``` 117 | * xullas a endi 4; 118 | * aniq a endi 4; 119 | * ``` 120 | * 2. Let Identifier Semicolon 121 | * ``` 122 | * xullas a; 123 | * ``` 124 | */ 125 | private parseVarDeclaration(): Statement { 126 | const isConst = this.next().type == TokenType.Const; 127 | const identifier = this.expect( 128 | TokenType.Identifier, 129 | "PARSER: Expected identifier after let | const", 130 | ).value; 131 | 132 | if (this.at().type == TokenType.Semicolon) { 133 | if (isConst) { 134 | this.panic("Const must contain a value fool!"); 135 | } 136 | 137 | this.next(); 138 | 139 | return { 140 | kind: "VariableDeclaration", 141 | identifier, 142 | isConst, 143 | } as VariableDeclaration; 144 | } 145 | 146 | this.expect(TokenType.Equals, "Expected equals token"); 147 | const declaration = { 148 | kind: "VariableDeclaration", 149 | identifier, 150 | value: this.parseExpression(), 151 | isConst, 152 | } as VariableDeclaration; 153 | 154 | if (this.at().type == TokenType.Semicolon) { 155 | this.next(); 156 | } 157 | 158 | return declaration; 159 | } 160 | 161 | private parseFnDeclaration(): Statement { 162 | this.next(); // skipping fn keyword 163 | const name = this.expect( 164 | TokenType.Identifier, 165 | "Expceted function name following fn keyword", 166 | ).value; // looking for func name 167 | 168 | // parsing params | fn foo (a,b) 169 | const args = this.parseArgs(); 170 | const params: string[] = []; 171 | 172 | for (const arg of args) { 173 | if (arg.kind !== "Identifier") { 174 | this.panic("Expected identifier on func declaration"); 175 | } 176 | params.push((arg as Identifier).symbol); 177 | } 178 | 179 | // parsing body | fn foo (a,b) {} 180 | this.expect(TokenType.OpenBrace, "Expected '{' after function params"); 181 | const body: Statement[] = []; 182 | 183 | while ( 184 | this.at().type != TokenType.EOF && this.at().type != TokenType.CloseBrace 185 | ) { 186 | body.push(this.parseStatement()); 187 | } 188 | 189 | this.expect(TokenType.CloseBrace, "Expected '}' after function body"); 190 | const fn = { 191 | kind: "FunctionDeclaration", 192 | name, 193 | body, 194 | params, 195 | } as FunctionDeclaration; 196 | 197 | return fn; 198 | } 199 | 200 | private parseReturnStatement(): Statement { 201 | this.next(); 202 | const value = this.parseExpression(); 203 | this.expect(TokenType.Semicolon, "Expected semicolon after return"); 204 | return { kind: "ReturnStatement", value } as ReturnStatement; 205 | } 206 | 207 | private parseIfStatement(): Statement { 208 | // if (a > 5 va b < 4) {} 209 | this.next(); // advance 210 | this.expect(TokenType.OpenParen, "Expected '(' after IF statement"); 211 | const condition = this.parseLogicalExpression(); 212 | this.expect(TokenType.CloseParen, "Expected ')' after IF statement"); 213 | this.expect(TokenType.OpenBrace, "Expected '{' after IF statement"); 214 | 215 | const body: Statement[] = []; 216 | while ( 217 | this.at().type != TokenType.EOF && this.at().type != TokenType.CloseBrace 218 | ) { 219 | body.push(this.parseStatement()); 220 | } 221 | 222 | this.expect(TokenType.CloseBrace, "Expected '}' after if body"); 223 | 224 | // check for children 225 | const children: Statement[] = []; 226 | 227 | let counter = 0; 228 | const checkChildren = () => { 229 | if (this.at().type == TokenType.ElseIf) { 230 | children.push(this.parseElifStatement()); 231 | checkChildren(); 232 | } else if (this.at().type == TokenType.Else) { 233 | if (counter > 0) { 234 | this.panic("If can contain only one else statement"); 235 | } 236 | children.push(this.parseElseStatement()); 237 | counter++; 238 | checkChildren(); 239 | } 240 | }; 241 | // recursive check 242 | checkChildren(); 243 | 244 | const ifStmt = { 245 | kind: "IfStatement", 246 | condition, 247 | body, 248 | children: children.length > 0 ? children : undefined, 249 | } as IfStatement; 250 | 251 | return ifStmt; 252 | } 253 | 254 | private parseElifStatement(): Statement { 255 | // elif (a > 5 va b < 4) {} 256 | this.next(); // advance 257 | this.expect(TokenType.OpenParen, "Expected '(' after elif statement"); 258 | const condition = this.parseLogicalExpression(); 259 | this.expect(TokenType.CloseParen, "Expected ')' after elif statement"); 260 | this.expect(TokenType.OpenBrace, "Expected '{' after elif statement"); 261 | 262 | const body: Statement[] = []; 263 | while ( 264 | this.at().type != TokenType.EOF && this.at().type != TokenType.CloseBrace 265 | ) { 266 | body.push(this.parseStatement()); 267 | } 268 | 269 | this.expect(TokenType.CloseBrace, "Expected '}' after if body"); 270 | 271 | const elifStmt = { 272 | kind: "ElifStatement", 273 | condition, 274 | body, 275 | } as ElifStatement; 276 | 277 | return elifStmt; 278 | } 279 | 280 | private parseElseStatement(): Statement { 281 | // else {} 282 | this.next(); // advance 283 | this.expect(TokenType.OpenBrace, "Expected '{' after else statement"); 284 | 285 | const body: Statement[] = []; 286 | while ( 287 | this.at().type != TokenType.EOF && this.at().type != TokenType.CloseBrace 288 | ) { 289 | body.push(this.parseStatement()); 290 | } 291 | 292 | this.expect(TokenType.CloseBrace, "Expected '}' after if body"); 293 | 294 | const elseStmt = { 295 | kind: "ElseStatement", 296 | body, 297 | } as ElseStatement; 298 | 299 | return elseStmt; 300 | } 301 | 302 | private parseWhileStatement(): Statement { 303 | this.next(); 304 | this.expect(TokenType.OpenParen, "Expected open paren on while loop"); 305 | const condition = this.parseLogicalExpression(); 306 | this.expect(TokenType.CloseParen, "Expected ')' after while statement"); 307 | this.expect(TokenType.OpenBrace, "Expected '{' after while statement"); 308 | 309 | const body: Statement[] = []; 310 | while ( 311 | this.at().type != TokenType.EOF && this.at().type != TokenType.CloseBrace 312 | ) { 313 | body.push(this.parseStatement()); 314 | } 315 | 316 | this.expect(TokenType.CloseBrace, "Expected '}' after while's body"); 317 | 318 | return { 319 | kind: "WhileStatement", 320 | condition, 321 | body, 322 | } as WhileStatement; 323 | } 324 | 325 | /** 326 | * Moves one step forward and after that skips semicolon if faced. 327 | */ 328 | private skipMove(): void { 329 | this.next(); 330 | if (this.at().type == TokenType.Semicolon) { 331 | this.next(); 332 | } 333 | } 334 | 335 | // - Orders Of Prescidence - 336 | // Assignment 337 | // Object 338 | // Logical 339 | // Relational 340 | // AdditiveExpr 341 | // MultiplicitaveExpr 342 | // CallMember 343 | // Member 344 | // PrimaryExpr 345 | 346 | private parseExpression(): Expression { 347 | return this.parseAssignmentExpression(); 348 | } 349 | 350 | private parseAssignmentExpression(): Expression { 351 | // syntax: var = expr(); 352 | const left = this.parseArrayExpression(); 353 | 354 | if (this.at().type == TokenType.Equals) { 355 | this.next(); 356 | const value = this.parseAssignmentExpression(); 357 | 358 | if (this.at().type == TokenType.Semicolon) { 359 | this.next(); 360 | } 361 | 362 | return { 363 | value, 364 | owner: left, 365 | kind: "AssignmentExpression", 366 | } as AssignmentExpression; 367 | } 368 | 369 | return left; 370 | } 371 | 372 | private parseArrayExpression(): Expression { 373 | if (this.at().type !== TokenType.OpenBracket) { 374 | return this.parseObjectExpression(); 375 | } 376 | 377 | this.next(); 378 | const values = new Array(); 379 | 380 | while (this.notEOF() && this.at().type != TokenType.CloseBracket) { 381 | if (this.at().type == TokenType.Comma) { 382 | this.next(); 383 | continue; 384 | } 385 | values.push(this.parseExpression()); 386 | } 387 | 388 | this.expect(TokenType.CloseBracket, "Closing bracket expected on array"); 389 | 390 | if (this.at().type == TokenType.Semicolon) { 391 | this.next(); 392 | } 393 | 394 | return { kind: "ArrayLiteral", values } as ArrayLiteral; 395 | } 396 | 397 | private parseObjectExpression(): Expression { 398 | if (this.at().type !== TokenType.OpenBrace) { 399 | return this.parseLogicalExpression(); 400 | } 401 | 402 | this.next(); 403 | const props = new Array(); 404 | 405 | while (this.notEOF() && this.at().type != TokenType.CloseBrace) { 406 | const key = this.expect( 407 | TokenType.Identifier, 408 | "Object literal key expected!", 409 | ).value; 410 | 411 | // shorthand for an empty value key { key, } 412 | if (this.at().type == TokenType.Comma) { 413 | this.next(); 414 | props.push({ kind: "Property", key } as Property); 415 | continue; 416 | } 417 | 418 | // shorthand for { key } 419 | if (this.at().type == TokenType.CloseBrace) { 420 | props.push({ kind: "Property", key } as Property); 421 | continue; 422 | } 423 | 424 | // { key: value } 425 | this.expect(TokenType.Colon, "Missing colon after key"); 426 | const value = this.parseExpression(); 427 | props.push({ kind: "Property", key, value, line: this.line() }); 428 | 429 | if (this.at().type != TokenType.CloseBrace) { 430 | this.expect( 431 | TokenType.Comma, 432 | "Comma or closing brace expected on Object", 433 | ); 434 | } 435 | } 436 | 437 | this.expect(TokenType.CloseBrace, "Object closing brace missing!"); 438 | return { kind: "ObjectLiteral", props } as ObjectLiteral; 439 | } 440 | 441 | private parseLogicalExpression(): Expression { 442 | let left = this.parseRelationalExpression(); 443 | 444 | while (["va", "yoki"].includes(this.at().value)) { 445 | const operator = this.next().value; 446 | const right = this.parseRelationalExpression(); 447 | 448 | left = { 449 | kind: "BinaryExpression", 450 | left, 451 | right, 452 | operator, 453 | } as BinaryExpression; 454 | } 455 | 456 | return left; 457 | } 458 | 459 | private parseRelationalExpression(): Expression { 460 | let left = this.parseAdditiveExpression(); 461 | 462 | while ([">", "<", "==", ">=", "<=", "!="].includes(this.at().value)) { 463 | const operator = this.next().value; 464 | const right = this.parseAdditiveExpression(); 465 | 466 | left = { 467 | kind: "BinaryExpression", 468 | left, 469 | right, 470 | operator, 471 | } as BinaryExpression; 472 | } 473 | 474 | return left; 475 | } 476 | 477 | private parseAdditiveExpression(): Expression { 478 | let left = this.parseMultiplicativeExpression(); 479 | 480 | while (this.at().value == "+" || this.at().value == "-") { 481 | const operator = this.next().value; 482 | const right = this.parseMultiplicativeExpression(); 483 | 484 | left = { 485 | kind: "BinaryExpression", 486 | left, 487 | right, 488 | operator, 489 | } as BinaryExpression; 490 | } 491 | 492 | return left; 493 | } 494 | 495 | private parseMultiplicativeExpression(): Expression { 496 | let left = this.parseCallMemeberExpression(); 497 | 498 | while ( 499 | this.at().value == "*" || this.at().value == "/" || this.at().value == "%" 500 | ) { 501 | const operator = this.next().value; 502 | const right = this.parseCallMemeberExpression(); 503 | 504 | left = { 505 | kind: "BinaryExpression", 506 | left, 507 | right, 508 | operator, 509 | } as BinaryExpression; 510 | } 511 | 512 | return left; 513 | } 514 | 515 | private parseCallMemeberExpression(): Expression { 516 | const member = this.parseMemberExpression(); 517 | 518 | if (this.at().type == TokenType.OpenParen) { 519 | return this.parseCallExpression(member); 520 | } 521 | 522 | return member; 523 | } 524 | 525 | private parseCallExpression(caller: Expression): Expression { 526 | let callExpr: Expression = { 527 | kind: "CallExpression", 528 | caller, 529 | args: this.parseArgs(), 530 | } as CallExpression; 531 | 532 | // support for x()() syntax - calling a function that was returned 533 | if (this.at().type == TokenType.OpenParen) { 534 | callExpr = this.parseCallExpression(callExpr); 535 | } 536 | 537 | return callExpr; 538 | } 539 | 540 | private parseArgs(): Expression[] { 541 | this.expect(TokenType.OpenParen, "Exprected open paren"); 542 | const args = this.at().type == TokenType.CloseParen 543 | ? [] 544 | : this.parseArgList(); 545 | 546 | this.expect(TokenType.CloseParen, "Exprected close paren"); 547 | 548 | if (this.at().type == TokenType.Semicolon) { 549 | this.next(); 550 | } 551 | // this.expect(TokenType.Semicolon, "Expected semicolon after statement"); 552 | 553 | return args; 554 | } 555 | 556 | private parseArgList(): Expression[] { 557 | const args = [this.parseAssignmentExpression()]; 558 | 559 | while (this.at().type == TokenType.Comma && this.next()) { 560 | args.push(this.parseAssignmentExpression()); 561 | } 562 | 563 | return args; 564 | } 565 | 566 | private parseMemberExpression(): Expression { 567 | let object = this.parsePrimaryExpression(); 568 | 569 | while ( 570 | this.at().type == TokenType.Dot || this.at().type == TokenType.OpenBracket 571 | ) { 572 | const operator = this.next(); 573 | let prop: Expression; 574 | let computed: boolean; 575 | 576 | // syntax obj.expression 577 | if (operator.type == TokenType.Dot) { 578 | computed = false; 579 | prop = this.parsePrimaryExpression(); 580 | 581 | if (prop.kind != "Identifier") { 582 | this.panic("Nuxtadan keyin normalniy klichka berin"); 583 | } 584 | } // syntax: obj[computedValue] 585 | else { 586 | computed = true; 587 | prop = this.parseExpression(); 588 | this.expect( 589 | TokenType.CloseBracket, 590 | "Missing close bracker after computed value", 591 | ); 592 | } 593 | 594 | object = { 595 | kind: "MemberExpression", 596 | object, 597 | prop, 598 | computed, 599 | } as MemberExpression; 600 | } 601 | 602 | return object; 603 | } 604 | 605 | private parsePrimaryExpression(): Expression { 606 | const tk = this.at().type; 607 | 608 | switch (tk) { 609 | case TokenType.Identifier: 610 | return { 611 | kind: "Identifier", 612 | symbol: this.next().value, 613 | } as Identifier; 614 | case TokenType.Number: 615 | return { 616 | kind: "NumericLiteral", 617 | value: parseFloat(this.next().value), 618 | } as NumericLiteral; 619 | case TokenType.String: 620 | return { 621 | kind: "StringLiteral", 622 | value: this.next().value, 623 | } as StringLiteral; 624 | 625 | case TokenType.OpenParen: { 626 | this.next(); 627 | const value = this.parseExpression(); 628 | this.expect(TokenType.CloseParen, "Unexpected token inside skobki"); 629 | return value; 630 | } 631 | 632 | case TokenType.BinaryOperator: { 633 | if (this.at().value == "-") { 634 | this.next(); 635 | const num = this.expect( 636 | TokenType.Number, 637 | "Expected a number after a minus sign", 638 | ).value; 639 | 640 | return { 641 | kind: "NumericLiteral", 642 | value: (-1 * parseFloat(num)), 643 | } as NumericLiteral; 644 | } 645 | 646 | this.panic(`Error occured in parsing a token ${this.at()}`); 647 | break; 648 | } 649 | 650 | default: 651 | this.panic(`Error occured in parsing a token ${this.at()}`); 652 | } 653 | } 654 | 655 | /** 656 | * @param srcCode 657 | * @returns Program to interpert 658 | * 659 | * Takes the source code and makes Abstract 660 | * Syntax Tree to be interpreted 661 | */ 662 | public createAST(srcCode: string): Program { 663 | this.tokens = tokenize(srcCode); 664 | 665 | const program: Program = { 666 | kind: "Program", 667 | body: [], 668 | line: 0, 669 | }; 670 | 671 | this.index = 0; 672 | 673 | // parse 674 | while (this.notEOF()) { 675 | const start = this.tokens[this.index].line; 676 | const stmt = this.parseStatement(); 677 | 678 | stmt.line = start; 679 | program.body.push(stmt); 680 | } 681 | 682 | return program; 683 | } 684 | } 685 | --------------------------------------------------------------------------------