diff --git a/.gitignore b/.gitignore index 33052da..a72302d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /doc/ bin/farpy +/tests/a.ts +/tests/a.php +/tests/click.ts diff --git a/Makefile b/Makefile index 09cd0a1..6ddb6df 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,4 @@ build: install: build @echo "Copying the binary to $(DESTDIR)..." - @sudo cp $(BUILD_PATH) $(DESTDIR) + @sudo cp -f $(BUILD_PATH) $(DESTDIR) diff --git a/README.md b/README.md index 332925c..c989128 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ farpy -v - Control flow structures (if) - Built-in constants -### In Progress 🚧 +### In Progress 🚧 - Type System - Complex types (objects, arrays, vectors) @@ -113,7 +113,7 @@ All PRs will be reviewed by the main maintainer (fernandothedev). MIT License -``` +``` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/deno.json b/deno.json index 6917ae5..5c84b97 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,11 @@ { "tasks": { - "compile": "deno compile -A -o bin/farpy main.ts" + "compile": "deno compile -A -o bin/farpy main.ts", + "compile_self": "deno compile -A main_compile.ts" }, "imports": { "@std/assert": "jsr:@std/assert@^1.0.11", - "@std/cli": "jsr:@std/cli@^1.0.13" + "@std/cli": "jsr:@std/cli@^1.0.13", + "@std/path": "jsr:@std/path" } } diff --git a/deno.lock b/deno.lock index b4859c6..2f1ff5b 100644 --- a/deno.lock +++ b/deno.lock @@ -3,7 +3,9 @@ "specifiers": { "jsr:@std/assert@^1.0.11": "1.0.11", "jsr:@std/cli@^1.0.13": "1.0.13", - "jsr:@std/internal@^1.0.5": "1.0.5" + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/path@*": "1.0.8", + "npm:@types/node@*": "22.5.4" }, "jsr": { "@std/assert@1.0.11": { @@ -17,12 +19,31 @@ }, "@std/internal@1.0.5": { "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" } }, + "npm": { + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + } + }, + "remote": { + "https://esm.sh/reflect-metadata@0.1.13": "9942ef607852eaa2fe9b807e237f31b207bf2dec8e30ab3661ceb12bc1c0f195", + "https://esm.sh/reflect-metadata@0.1.13/denonext/reflect-metadata.mjs": "7db7eab237ef485b940de57111f006676c5805480cb87f247fa738289a843edf" + }, "workspace": { "dependencies": [ "jsr:@std/assert@^1.0.11", - "jsr:@std/cli@^1.0.13" + "jsr:@std/cli@^1.0.13", + "jsr:@std/path@*" ] } } diff --git a/examples/array.farpy b/examples/array.farpy new file mode 100644 index 0000000..1a0f2cf --- /dev/null +++ b/examples/array.farpy @@ -0,0 +1,2 @@ +new arr: array = { 1, 2, 3 } +print(arr) diff --git a/examples/exotic.farpy b/examples/exotic.farpy new file mode 100644 index 0000000..890edd4 --- /dev/null +++ b/examples/exotic.farpy @@ -0,0 +1,17 @@ +new mut name: string = "Fernando" print(name) new lambda: void = fn |name|(): void { name = "Cabral" print(name) } lambda() print(name) + +/** +new mut name: string = "Fernando" + +print(name) + +new lambda: void = fn |name|(): void +{ + name = "Cabral" + print(name) +} + +lambda() + +print(name) +*/ diff --git a/examples/fib.farpy b/examples/fib.farpy index f8b47ec..740a0f3 100644 --- a/examples/fib.farpy +++ b/examples/fib.farpy @@ -1,11 +1,6 @@ fn fibonacci(n: int): int { - if n <= 0 { - return 0 - } elif n == 1 { - return 1 - } - + if n <= 1 { return n } return fibonacci(n - 1) + fibonacci(n - 2) } diff --git a/examples/fib_new.farpy b/examples/fib_new.farpy new file mode 100644 index 0000000..b588e7f --- /dev/null +++ b/examples/fib_new.farpy @@ -0,0 +1,21 @@ +import map as m + +new map: map = m.Map() + +fn fibonacci(n: int): int +{ + if map.has(n) + { + return map.get(n) + } + + if n <= 1 + { + return n + } + + map.set(n, fibonacci(n - 1) + fibonacci(n - 2)) + return map.get(n) +} + +print(fibonacci(100)) diff --git a/examples/for.fp b/examples/for.fp new file mode 100644 index 0000000..a8d4cfa --- /dev/null +++ b/examples/for.fp @@ -0,0 +1,13 @@ +import io + +new name: string = "Fernando" +new mut sum: int = 0 + +for new mut i: int = 0; i < name.length(); i++ { + print(name.at(i)) +} +for i = 0; i < 100; i++ { + sum = sum + i +} + +print(io.format("Sum = {sum}")) diff --git a/examples/import.farpy b/examples/import.farpy new file mode 100644 index 0000000..dfdeaf0 --- /dev/null +++ b/examples/import.farpy @@ -0,0 +1,2 @@ +import io +import math as m diff --git a/examples/io.farpy b/examples/io.farpy new file mode 100644 index 0000000..6a05681 --- /dev/null +++ b/examples/io.farpy @@ -0,0 +1,7 @@ +import io +import utils + +new name: string = io.readline("Name:") +new age: int = utils.toInt(io.readline("Age:")) + +print(io.format("Hello {name}, you are {age} years old.")) diff --git a/examples/member_call_expr.farpy b/examples/member_call_expr.farpy new file mode 100644 index 0000000..7d6578f --- /dev/null +++ b/examples/member_call_expr.farpy @@ -0,0 +1,5 @@ +new name: string = "Fernando" + +name.at(0).length() // F +name.length() // 8 +2 ** name.length() // 256 2⁸ diff --git a/examples/member_expr.farpy b/examples/member_expr.farpy new file mode 100644 index 0000000..aad840f --- /dev/null +++ b/examples/member_expr.farpy @@ -0,0 +1,8 @@ +import math as m +// import { fibonacci, PI } -> Do in the future + +print(m.PI) +print(m.fibonacci(100).len().string()) + +print(m.PI) + diff --git a/examples/member_expr2.fp b/examples/member_expr2.fp new file mode 100644 index 0000000..df7b71c --- /dev/null +++ b/examples/member_expr2.fp @@ -0,0 +1,5 @@ +new str: string = "Fernando is beautiful" +new x: string = str.at(0).length() + +print(x) + diff --git a/examples/object.farpy b/examples/object.farpy new file mode 100644 index 0000000..ad233dc --- /dev/null +++ b/examples/object.farpy @@ -0,0 +1,6 @@ +new obj: object = { + name: "Fernando", + age: 17 +} + +dd(obj.name) diff --git a/main.ts b/main.ts index 6abfbd2..e7c9c9e 100644 --- a/main.ts +++ b/main.ts @@ -7,16 +7,67 @@ import Context from "./src/runtime/context/Context.ts"; import { parseArgs } from "@std/cli"; import { Colorize } from "./src/utils/Colorize.ts"; import { define_env } from "./src/runtime/context/Context.ts"; +import { repl } from "./src/repl.ts"; const parsedArgs = parseArgs(Deno.args, { alias: { h: "help", v: "version", r: "repl", + o: "output", }, - boolean: ["help", "version", "repl"], + boolean: ["help", "version", "repl", "compile"], + string: ["output", "file"], }); +if (parsedArgs.compile) { + // Compile UWU + if (!parsedArgs.output || !parsedArgs.file) { + console.error("Error: No file specified or output."); + console.error("Usage --output and --file .fp"); + Deno.exit(1); + } + + const file = "main_compile.ts"; + const fileData: string = Deno.readTextFileSync(file); + let fileDataTmp = ""; + + let source: string; + try { + source = Deno.readTextFileSync(parsedArgs.file); + } catch (error: any) { + console.error("Error reading file:", error.message); + Deno.exit(1); + } + + fileDataTmp = fileData.replace(`_SOURCE_`, source); + fileDataTmp = fileDataTmp.replace("_FILENAME_", parsedArgs.file); + + Deno.writeFileSync(file, (new TextEncoder()).encode(fileDataTmp)); + + const command = new Deno.Command("deno", { + args: ["compile", "-A", `-o=${parsedArgs.output}`, "main_compile.ts"], + stdout: "piped", + stderr: "piped", + }); + + const { code, stdout, stderr } = command.outputSync(); + + if (code === 0) { + console.log( + `${Colorize.green("✓")} Successfully compiled to ${ + Colorize.bold(parsedArgs.output) + }`, + ); + } else { + console.error(`${Colorize.red("✗")} Compilation failed:`); + console.error(new TextDecoder().decode(stderr)); + } + + Deno.writeFileSync(file, (new TextEncoder()).encode(fileData)); + Deno.exit(1); +} + const VERSION = "v0.1.0"; if (parsedArgs.help) { @@ -25,9 +76,12 @@ if (parsedArgs.help) { farpy [options] ${Colorize.bold("Options:")} - -h, --help Show this help message. - -v, --version Shows version and runtime information. - -r, --repl Starts the interactive REPL.`, + -h, --help Show this help message. + -v, --version Shows version and runtime information. + -r, --repl Starts the interactive REPL. + -c, --compile Compile a .farpy/.fp file to an executable. + -o, --output Specify output filename for compilation. + --file Specify input file for compilation.`, ); Deno.exit(0); } @@ -35,7 +89,7 @@ ${Colorize.bold("Options:")} if (parsedArgs.version) { console.log( Colorize.bold( - `FarpyScript - ${Colorize.underline(Colorize.blue(VERSION))}`, + `Farpy - ${Colorize.underline(Colorize.blue(VERSION))}`, ), ); Deno.exit(0); @@ -44,64 +98,8 @@ if (parsedArgs.version) { const context: Context = define_env(new Context()); if (parsedArgs.repl) { - console.log( - Colorize.bold( - `FarpyScript ${Colorize.underline(Colorize.blue(VERSION))} - REPL`, - ), - ); - - let inputBuffer = ""; - let balance = 0; - while (true) { - // Se já há conteúdo acumulado, usamos um prompt com padding proporcional ao balance. - const promptSymbol = inputBuffer - ? ".".repeat(Math.max((balance * 3) == 0 ? 3 : balance * 3, 1)) - : ">"; - const line: string = prompt(promptSymbol) ?? ""; - - if (!inputBuffer && line.trim() === "exit") { - console.log("Bye"); - break; - } - - // Acumula a linha lida - inputBuffer += line + "\n"; - - // Atualiza o balance com base na linha atual - for (const char of line) { - if (char === "{") { - balance++; - } else if (char === "}") { - balance--; - } - } - - // Evita que o balance fique negativo - if (balance < 0) { - balance = 0; - } - - // Se ainda houver blocos abertos, continua a leitura - if (balance > 0) continue; - - const code = inputBuffer.trim(); - if (code) { - try { - const lexer: Lexer = new Lexer("repl", code); - const tokens: Token[] = lexer.tokenize(); - const parser: Parser = new Parser(tokens); - const program: Program = parser.parse(); - const runtime: Runtime = new Runtime(context); - runtime.evaluate(program); - } catch (error: any) { - console.error("Error processing code:", error.message); - } - } - // Reseta o buffer e o balance para o próximo bloco - inputBuffer = ""; - balance = 0; - } - Deno.exit(0); + repl(); + Deno.exit(); } if (parsedArgs._.length < 1) { @@ -112,8 +110,8 @@ if (parsedArgs._.length < 1) { const file: string = parsedArgs._[0] as string; -if (!file.endsWith(".farpy")) { - console.error("Error: The file must have a .farpy extension"); +if (!file.endsWith(".farpy") && !file.endsWith(".fp")) { + console.error("Error: The file must have a .farpy or .fp extension"); Deno.exit(1); } @@ -129,5 +127,7 @@ const lexer: Lexer = new Lexer(file, source); const tokens: Token[] = lexer.tokenize(); const parser: Parser = new Parser(tokens); const program: Program = parser.parse(); +console.log(program); + const runtime: Runtime = new Runtime(context); runtime.evaluate(program); diff --git a/main_compile.ts b/main_compile.ts new file mode 100644 index 0000000..0065074 --- /dev/null +++ b/main_compile.ts @@ -0,0 +1,18 @@ +import { Lexer } from "./src/frontend/Lexer.ts"; +import { Token } from "./src/frontend/Token.ts"; +import Parser from "./src/backend/Parser.ts"; +import { Program } from "./src/backend/AST.ts"; +import Runtime from "./src/runtime/Runtime.ts"; +import Context from "./src/runtime/context/Context.ts"; +import { define_env } from "./src/runtime/context/Context.ts"; + +const context: Context = define_env(new Context()); +const source: string = `_SOURCE_`; + +const lexer: Lexer = new Lexer("_FILENAME_", source); +const tokens: Token[] = lexer.tokenize(); +const parser: Parser = new Parser(tokens); +const program: Program = parser.parse(); + +const runtime: Runtime = new Runtime(context); +runtime.evaluate(program); diff --git a/src/backend/AST.ts b/src/backend/AST.ts index 05e2354..f71773b 100644 --- a/src/backend/AST.ts +++ b/src/backend/AST.ts @@ -2,173 +2,217 @@ import { Loc, Token } from "../frontend/Token.ts"; import { ArgsValue, TypesNative } from "../runtime/Values.ts"; export type NodeType = - | "Program" - | "CallExpr" - | "FunctionDeclaration" - | "LambdaExpr" - | "ReturnStatement" - | "IfStatement" - | "ElseStatement" - | "ElifStatement" - | "BlockStmt" - | "ReturnStatement" - | "VarDeclaration" - | "AssignmentDeclaration" - | "Identifier" - | "IntLiteral" - | "FloatLiteral" - | "BinaryLiteral" - | "StringLiteral" - | "NullLiteral" - | "IncrementExpr" - | "DecrementExpr" - | "BinaryExpr"; + | "Program" + | "CallExpr" + | "FunctionDeclaration" + | "LambdaExpr" + | "ReturnStatement" + | "IfStatement" + | "ElseStatement" + | "ElifStatement" + | "BlockStmt" + | "ImportStatement" + | "ForStatement" + | "ForEachStatement" + | "ReturnStatement" + | "VarDeclaration" + | "AssignmentDeclaration" + | "Identifier" + | "IntLiteral" + | "FloatLiteral" + | "BinaryLiteral" + | "StringLiteral" + | "NullLiteral" + | "IncrementExpr" + | "DecrementExpr" + | "IndexingExpr" + | "MemberCallExpr" + | "MemberExpr" + | "BinaryExpr" + | "ArrayLiteral" + | "ObjectLiteral" + | "BreakStatement"; export interface Stmt { - kind: NodeType; - type: TypesNative | TypesNative[]; - // deno-lint-ignore no-explicit-any - value: any; - loc: Loc; + kind: NodeType; + type: TypesNative | TypesNative[]; + // deno-lint-ignore no-explicit-any + value: any; + loc: Loc; } export interface Expr extends Stmt {} export interface Program extends Stmt { - kind: "Program"; - type: "null"; - body?: Stmt[]; + kind: "Program"; + type: "null"; + body?: Stmt[]; } export interface BinaryExpr extends Expr { - kind: "BinaryExpr"; - left: Expr; - right: Expr; - operator: string; + kind: "BinaryExpr"; + left: Expr; + right: Expr; + operator: string; } export interface IncrementExpr extends Expr { - kind: "IncrementExpr"; - value: Expr; + kind: "IncrementExpr"; + value: Expr; } export interface DecrementExpr extends Expr { - kind: "DecrementExpr"; - value: Expr; + kind: "DecrementExpr"; + value: Expr; } export interface Identifier extends Expr { - kind: "Identifier"; - value: string; + kind: "Identifier"; + value: string; } -export function AST_IDENTIFIER(id: string = "err", loc: Loc): Identifier { - return { - kind: "Identifier", - type: "id", - value: id, - loc: loc, - } as Identifier; +export function AST_IDENTIFIER(id: string, loc: Loc): Identifier { + return { + kind: "Identifier", + type: "id", + value: id, + loc: loc, + } as Identifier; } export interface StringLiteral extends Expr { - kind: "StringLiteral"; - value: string; + kind: "StringLiteral"; + value: string; } -export function AST_STRING(str: string = "err", loc: Loc): StringLiteral { - return { - kind: "StringLiteral", - type: "string", - value: str, - loc: loc, - } as StringLiteral; +export function AST_STRING(str: string, loc: Loc): StringLiteral { + return { + kind: "StringLiteral", + type: "string", + value: str, + loc: loc, + } as StringLiteral; } export interface IntLiteral extends Expr { - kind: "IntLiteral"; - value: number; + kind: "IntLiteral"; + value: number; } export function AST_INT(n: number = 0, loc: Loc): IntLiteral { - return { - kind: "IntLiteral", - type: "int", - value: n, - loc: loc, - } as IntLiteral; + return { + kind: "IntLiteral", + type: "int", + value: n, + loc: loc, + } as IntLiteral; } export interface FloatLiteral extends Expr { - kind: "FloatLiteral"; - value: number; + kind: "FloatLiteral"; + value: number; } export function AST_FLOAT(n: number = 0, loc: Loc): FloatLiteral { - return { - kind: "FloatLiteral", - type: "float", - value: n, - loc: loc, - } as FloatLiteral; + return { + kind: "FloatLiteral", + type: "float", + value: n, + loc: loc, + } as FloatLiteral; } export interface BinaryLiteral extends Expr { - kind: "BinaryLiteral"; - value: string; + kind: "BinaryLiteral"; + value: string; } export function AST_BINARY(n: string = "0b0", loc: Loc): BinaryLiteral { - return { - kind: "BinaryLiteral", - type: "binary", - value: n, - loc: loc, - } as BinaryLiteral; + return { + kind: "BinaryLiteral", + type: "binary", + value: n, + loc: loc, + } as BinaryLiteral; } export interface NullLiteral extends Expr { - kind: "NullLiteral"; - value: null; + kind: "NullLiteral"; + value: null; } export function AST_NULL(loc: Loc): NullLiteral { - return { - kind: "NullLiteral", - type: "null", - value: null, - loc: loc, - } as NullLiteral; + return { + kind: "NullLiteral", + type: "null", + value: null, + loc: loc, + } as NullLiteral; +} + +export interface ArrayLiteral extends Stmt { + kind: "ArrayLiteral"; + type: "array"; + value: Expr[]; + loc: Loc; +} + +export function AST_ARRAY(arr: Expr[] = [], loc: Loc): ArrayLiteral { + return { + kind: "ArrayLiteral", + type: "array", + value: arr, + loc: loc, + } as ArrayLiteral; +} + +export interface ObjectLiteral extends Stmt { + kind: "ObjectLiteral"; + type: "object"; + value: Map; + loc: Loc; +} + +export function AST_OBJECT( + obj: Map = new Map(), + loc: Loc, +): ObjectLiteral { + return { + kind: "ObjectLiteral", + type: "object", + value: obj, + loc: loc, + } as ObjectLiteral; } // Declarations {{ // new age: int = 17 | new mut name: string = "Fernando" export interface VarDeclaration extends Stmt { - kind: "VarDeclaration"; - type: TypesNative | TypesNative[]; // Type of variable - id: Identifier; - value: Stmt; - constant: boolean; + kind: "VarDeclaration"; + type: TypesNative | TypesNative[]; // Type of variable + id: Identifier; + value: Stmt; + constant: boolean; } // name = "Trump" export interface AssignmentDeclaration extends Stmt { - kind: "AssignmentDeclaration"; - type: TypesNative; // Type of variable - id: Identifier; - value: Stmt; + kind: "AssignmentDeclaration"; + type: TypesNative; // Type of variable + id: Identifier; + value: Stmt; } // = : , .... // fn (): // fn main(x: int, y: binary:float): int|float { return x * y } export interface FunctionDeclaration extends Stmt { - kind: "FunctionDeclaration"; - type: TypesNative | TypesNative[]; // Type of return - args: { id: Identifier; type: TypesNative | TypesNative[] }; // Args - id: Identifier; // name of function - block: Stmt[]; + kind: "FunctionDeclaration"; + type: TypesNative | TypesNative[]; // Type of return + args: { id: Identifier; type: TypesNative | TypesNative[] }; // Args + id: Identifier; // name of function + block: Stmt[]; } // }} @@ -177,19 +221,41 @@ export interface FunctionDeclaration extends Stmt { // print(n, z, x, y, ...) export interface CallExpr extends Expr { - kind: "CallExpr"; - type: TypesNative; // Type of return - id: Identifier; - args: Expr[]; + kind: "CallExpr"; + type: TypesNative; // Type of return + id: Identifier; + args: Expr[]; } export interface LambdaExpr extends Expr { - params: ArgsValue[]; - kind: "LambdaExpr"; - externalVars: Identifier[]; // Variables captured from the outer scope - args: { id: Identifier; type: TypesNative | TypesNative[] }[]; - type: TypesNative | TypesNative[]; - body: Stmt[]; + kind: "LambdaExpr"; + params: ArgsValue[]; + externalVars: Identifier[]; // Variables captured from the outer scope + args: { id: Identifier; type: TypesNative | TypesNative[] }[]; + type: TypesNative | TypesNative[]; + body: Stmt[]; +} + +// export interface IndexingExpr extends Expr { +// kind: "IndexingExpr"; +// type: TypesNative | TypesNative[]; +// target: Expr; +// index: Expr; +// } + +export interface MemberCallExpr extends Expr { + kind: "MemberCallExpr"; + type: TypesNative | TypesNative[]; + id: Expr; + member: CallExpr; +} + +export interface MemberExpr extends Expr { + kind: "MemberExpr"; + computed: boolean; // true if is computed + type: TypesNative | TypesNative[]; + id: Expr; + member: Identifier; } // }} @@ -197,40 +263,75 @@ export interface LambdaExpr extends Expr { // Statements {{ export interface ReturnStatement extends Stmt { - kind: "ReturnStatement"; - type: TypesNative | TypesNative[]; // Type of return - value: Expr; + kind: "ReturnStatement"; + type: TypesNative | TypesNative[]; // Type of return + value: Expr; } export interface IfStatement extends Stmt { - kind: "IfStatement"; - type: TypesNative | TypesNative[]; // Type of return if exists - value: Expr | Expr[] | Stmt; // Value of return if exists - condition: Expr | Expr[]; - primary: Stmt[]; // if {} - secondary: Stmt[]; // else {} | elif {} + kind: "IfStatement"; + type: TypesNative | TypesNative[]; // Type of return if exists + value: Expr | Expr[] | Stmt; // Value of return if exists + condition: Expr | Expr[]; + primary: Stmt[]; // if {} + secondary: Stmt[]; // else {} | elif {} } export interface ElseStatement extends Stmt { - kind: "ElseStatement"; - type: TypesNative | TypesNative[]; // Type of return if exists - value: Expr | Expr[] | Stmt; // Value of return if exists - primary: Stmt[]; // else {} + kind: "ElseStatement"; + type: TypesNative | TypesNative[]; // Type of return if exists + value: Expr | Expr[] | Stmt; // Value of return if exists + primary: Stmt[]; // else {} } export interface ElifStatement extends Stmt { - kind: "ElifStatement"; - type: TypesNative | TypesNative[]; // Type of return if exists - value: Expr | Expr[] | Stmt; // Value of return if exists - condition: Expr | Expr[]; - primary: Stmt[]; // elif {} - secondary: Stmt[]; // else {} | elif {} + kind: "ElifStatement"; + type: TypesNative | TypesNative[]; // Type of return if exists + value: Expr | Expr[] | Stmt; // Value of return if exists + condition: Expr | Expr[]; + primary: Stmt[]; // elif {} + secondary: Stmt[]; // else {} | elif {} } export interface BlockStmt extends Stmt { - kind: "BlockStmt"; - body: Stmt[]; // Lista de declarações dentro do bloco - endToken: Token; // Token de fechamento, útil para informações de localização + kind: "BlockStmt"; + body: Stmt[]; // Lista de declarações dentro do bloco + endToken: Token; // Token de fechamento, útil para informações de localização +} + +export interface ImportStatement extends Stmt { + kind: "ImportStatement"; + module: Identifier; + check: boolean; // check if is alias or not + alias: Identifier; +} + +// for ; ; {} +// ex.: for new mut i: int = 0; i < name.length(); i++ {} +// ex.: i = 0; i < name.length(); i++ {} +export interface ForStatement extends Stmt { + kind: "ForStatement"; + type: TypesNative | TypesNative[]; // Type of return if exists + value: Expr | Expr[] | Stmt; // Value of return if exists + init: VarDeclaration | AssignmentDeclaration; + test: BinaryExpr; + update: IncrementExpr | DecrementExpr; + body: Stmt[]; +} + +// for : {} +// ex.: for new mut user : users {} +export interface ForEachStatement extends Stmt { + kind: "ForEachStatement"; + type: TypesNative | TypesNative[]; // Type of return if exists + value: Expr | Expr[] | Stmt; // Value of return if exists + init: VarDeclaration; + from: Identifier; + body: Stmt[]; +} + +export interface BreakStatement extends Stmt { + kind: "BreakStatement"; } // }} diff --git a/src/backend/Parser.ts b/src/backend/Parser.ts index d0c3d72..f98e915 100644 --- a/src/backend/Parser.ts +++ b/src/backend/Parser.ts @@ -1,1000 +1,1291 @@ import { Loc, Token, TokenType } from "../frontend/Token.ts"; import { ErrorReporter } from "../error/ErrorReporter.ts"; import { - AssignmentDeclaration, - AST_BINARY, - AST_FLOAT, - AST_IDENTIFIER, - AST_INT, - AST_NULL, - AST_STRING, - BinaryExpr, - BinaryLiteral, - BlockStmt, - CallExpr, - ElifStatement, - ElseStatement, - Expr, - FloatLiteral, - FunctionDeclaration, - Identifier, - IfStatement, - IntLiteral, - LambdaExpr, - NullLiteral, - Program, - Stmt, - VarDeclaration, + ArrayLiteral, + AssignmentDeclaration, + AST_ARRAY, + AST_BINARY, + AST_FLOAT, + AST_IDENTIFIER, + AST_INT, + AST_NULL, + AST_OBJECT, + AST_STRING, + BinaryExpr, + BinaryLiteral, + BlockStmt, + BreakStatement, + CallExpr, + ElifStatement, + ElseStatement, + Expr, + FloatLiteral, + ForEachStatement, + ForStatement, + FunctionDeclaration, + Identifier, + IfStatement, + ImportStatement, + IntLiteral, + LambdaExpr, + MemberCallExpr, + MemberExpr, + NullLiteral, + ObjectLiteral, + Program, + Stmt, + VarDeclaration, } from "./AST.ts"; import { - TypesNative, - TypesNativeArray, - VALUE_STRING, + IParsedTypes, + TypesNative, + TypesNativeArray, } from "../runtime/Values.ts"; import { DecrementExpr, IncrementExpr } from "./AST.ts"; import { ReturnStatement } from "./AST.ts"; export default class Parser { - private tokens: Token[]; - - public constructor(tokens: Token[]) { - this.tokens = tokens; - } - - private is_end(index: number = 0): boolean { - return this.tokens[index] === undefined || - this.tokens[index].kind === TokenType.EOF; + private tokens: Token[] = []; + protected pos: number = 0; + private program: Program = { + kind: "Program", + type: "null", + value: null, + body: [], + loc: {} as Loc, + }; + protected lastParsed: Stmt = { + kind: "NullLiteral", + type: "null", + value: null, + loc: {} as Loc, + }; + + public constructor(tokens: Token[]) { + this.tokens = tokens; + } + + private is_end(index: number = this.pos): boolean { + return this.tokens[index] === undefined || + this.tokens[index].kind === TokenType.EOF; + } + + private peek(): Token { + return this.tokens[this.pos]; + } + + private next(): Token { + return this.tokens[this.pos + 1] ?? + { kind: TokenType.EOF, value: "\0", loc: {} as Loc }; + } + + private back(): Token { + return this.tokens[this.pos - 1] ?? + { kind: TokenType.EOF, value: "\0", loc: {} as Loc }; + } + + private eat(): Token { + return this.tokens[this.pos++] as Token; + } + + private consume(type: TokenType | TokenType[], err: string): Token { + const prev = this.eat(); + + if (Array.isArray(type)) { + if (type.some((t) => t === prev.kind)) { + return prev; + } + } else { + if (type === prev.kind) { + return prev; + } } - private peek(): Token { - return this.tokens[0]; - } + ErrorReporter.showError(err, prev.loc); + return {} as Token; + } - private next(): Token { - return this.tokens[1] ?? - { kind: TokenType.EOF, value: "\0", loc: {} as Loc }; + public parse(): Program { + while (!this.is_end()) { + const parsed: Stmt = this.parse_stmt(); + this.program.body?.push(parsed); + this.lastParsed = parsed; } - private eat(): Token { - return this.tokens.shift() as Token; + this.program.loc = this.getLocationFromTokens( + this.tokens[0], + this.tokens[this.tokens.length - 1], + ); + + return this.program; + } + + private parse_stmt(): Stmt { + return this.parse_expr(); + } + + private parse_expr(): Expr { + return this.parse_binary_expr(); + } + + private parse_additive_expr(): Expr { + let left: Expr = this.parse_mul_expr(); + + while (this.peek().value === "+" || this.peek().value === "-") { + const operatorToken = this.eat(); + const operator: string = operatorToken.value?.toString() ?? "ERROR"; + const right: Expr = this.parse_mul_expr(); + let type: TypesNative = "int"; + + if (left.type == "int" && right.type == "int") type = "int"; + if (left.type == "float" || right.type == "float") type = "float"; + if (left.type == "string" || right.type == "string") { + type = "string"; + } + + left = { + kind: "BinaryExpr", + type: type, + left, + right, + operator, + loc: this.getLocationFromTokens(left.loc, right.loc), + } as BinaryExpr; } - private consume(type: TokenType | TokenType[], err: string): Token { - const prev = this.eat(); + return left; + } - if (Array.isArray(type)) { - if (type.some((t) => t === prev.kind)) { - return prev; - } - } else { - if (type === prev.kind) { - return prev; - } - } + private parse_mul_expr(): Expr { + let left: Expr = this.parse_primary_expr(); - ErrorReporter.showError(err, prev.loc); - return {} as Token; + if (typeof this.peek().value == "undefined") { + return left; } - public parse(): Program { - const program: Program = { - kind: "Program", - type: "null", - value: null, - body: [], - loc: this.getLocationFromTokens( - this.tokens[0], - this.tokens[this.tokens.length - 1], - ), - }; - - while (!this.is_end()) { - program.body?.push(this.parse_stmt()); - } - - return program; + while ( + this.peek().value === "/" || + this.peek().value === "*" || + this.peek().value === "**" || + this.peek().value === "%" || + this.peek().value === "%%" + ) { + const operatorToken = this.eat(); + const operator: string = operatorToken.value?.toString() ?? "ERROR"; + const right: Expr = this.parse_mul_expr(); + let type: TypesNative = "int"; + + if (left.type == "int" && right.type == "int") type = "int"; + if (left.type == "float" || right.type == "float") type = "float"; + if (left.type == "string" || right.type == "string") { + type = "string"; + } + + left = { + kind: "BinaryExpr", + type: type, + left, + right, + operator, + loc: this.getLocationFromTokens(left.loc, right.loc), + } as BinaryExpr; } - private parse_stmt(): Stmt { - return this.parse_expr(); + return left; + } + + private parse_binary_expr(): Expr { + let left: Expr = this.parse_additive_expr(); + + while ( + this.peek().kind === TokenType.EQUALS_EQUALS || + this.peek().kind === TokenType.NOT_EQUALS || + this.peek().kind === TokenType.GREATER_THAN || + this.peek().kind === TokenType.LESS_THAN || + this.peek().kind === TokenType.GREATER_THAN_OR_EQUALS || + this.peek().kind === TokenType.LESS_THAN_OR_EQUALS || + this.peek().kind === TokenType.AND || + this.peek().kind === TokenType.OR + ) { + const operatorToken = this.eat(); + const operator: string = operatorToken.value?.toString() ?? "ERROR"; + const right: Expr = this.parse_additive_expr(); + + left = { + kind: "BinaryExpr", + left, + right, + operator, + loc: this.getLocationFromTokens(left.loc, right.loc), + } as BinaryExpr; } - private parse_expr(): Expr { - return this.parse_binary_expr(); + return left; + } + + private parse_primary_expr(): Expr { + const token = this.peek(); + + switch (token.kind) { + case TokenType.IDENTIFIER: + return this.parse_identifier(); + case TokenType.INT: + return AST_INT(token.value as number, this.eat().loc); + case TokenType.FLOAT: + return AST_FLOAT(token.value as number, this.eat().loc); + case TokenType.BINARY: + return this.parseBinaryLiteral(); + case TokenType.MINUS: + return this.parseNegativeValue(); + case TokenType.STRING: + return AST_STRING(token.value as string, this.eat().loc); + case TokenType.NULL: + return AST_NULL(this.eat().loc); + case TokenType.LPAREN: { + const startToken = this.eat(); + const value: Expr = this.parse_expr(); + const endToken = this.consume( + TokenType.RPAREN, + "Expected closing parenthesis.", + ); + return { + ...value, + loc: this.getLocationFromTokens(startToken, endToken), + }; + } + case TokenType.NEW: + return this.parse_var_declaration(); + case TokenType.FN: + return this.parse_function_or_lambda(); + case TokenType.RETURN: + return this.parse_return_stmt(); + case TokenType.IF: + return this.parse_if_statement(); + case TokenType.IMPORT: + return this.parse_import_statement(); + case TokenType.FOR: + return this.parse_for_statement(); + case TokenType.BREAK: + return this.parse_break_statement(); + case TokenType.LBRACE: // { + return this.parse_array_or_object(); + // case TokenType.DOT: + // return this.parse_member_and_call_expression(); + case TokenType.EOF: + return AST_NULL(token.loc); + default: + console.debug(token); + ErrorReporter.showError( + "Unexpected token found during parsing!", + token.loc, + ); + Deno.exit(1); } - - // private parse_and_expr(): Expr { - // let left: Expr = this.parse_additive_expr(); - - // while (this.peek().value === "&&" || this.peek().value === "||") { - // const operatorToken = this.eat(); - // const operator: string = operatorToken.value?.toString() ?? "ERROR"; - // const right: Expr = this.parse_mul_expr(); - // let type: TypesNative = "int"; - - // if (left.type == "int" && right.type == "int") type = "int"; - // if (left.type == "float" || right.type == "float") type = "float"; - // if (left.type == "string" || right.type == "string") { - // type = "string"; - // } - - // left = { - // kind: "BinaryExpr", - // type: type, - // left, - // right, - // operator, - // loc: this.getLocationFromTokens(left.loc, right.loc), - // } as BinaryExpr; - // } - - // return left; - // } - - private parse_additive_expr(): Expr { - let left: Expr = this.parse_mul_expr(); - - while (this.peek().value === "+" || this.peek().value === "-") { - const operatorToken = this.eat(); - const operator: string = operatorToken.value?.toString() ?? "ERROR"; - const right: Expr = this.parse_mul_expr(); - let type: TypesNative = "int"; - - if (left.type == "int" && right.type == "int") type = "int"; - if (left.type == "float" || right.type == "float") type = "float"; - if (left.type == "string" || right.type == "string") { - type = "string"; - } - - left = { - kind: "BinaryExpr", - type: type, - left, - right, - operator, - loc: this.getLocationFromTokens(left.loc, right.loc), - } as BinaryExpr; - } - - return left; + } + + private parse_member_and_call_expression(): MemberExpr | MemberCallExpr { + const id = this.lastParsed; + + this.eat(); // . + // deno-lint-ignore no-explicit-any + const expr: any = this.parse_expr(); + + if (expr.kind == "CallExpr") { // .(...) + return { + kind: "MemberCallExpr", + type: expr.type, + id: id, + member: expr as CallExpr, + loc: this.getLocationFromTokens(id.loc, expr.loc), + } as MemberCallExpr; } - private parse_mul_expr(): Expr { - let left: Expr = this.parse_primary_expr(); - - if (typeof this.peek().value == "undefined") { - return left; - } - - while ( - this.peek().value === "/" || - this.peek().value === "*" || - this.peek().value === "**" || - this.peek().value === "%" || - this.peek().value === "%%" - ) { - const operatorToken = this.eat(); - const operator: string = operatorToken.value?.toString() ?? "ERROR"; - const right: Expr = this.parse_mul_expr(); - let type: TypesNative = "int"; - - if (left.type == "int" && right.type == "int") type = "int"; - if (left.type == "float" || right.type == "float") type = "float"; - if (left.type == "string" || right.type == "string") { - type = "string"; - } - - left = { - kind: "BinaryExpr", - type: type, - left, - right, - operator, - loc: this.getLocationFromTokens(left.loc, right.loc), - } as BinaryExpr; - } - - return left; + if (expr.kind == "Identifier") { // . + return { + kind: "MemberExpr", + type: expr.type, + computed: false, + id: id, + member: expr as Identifier, + loc: this.getLocationFromTokens(id.loc, expr.loc), + } as MemberExpr; } - private parse_binary_expr(): Expr { - let left: Expr = this.parse_additive_expr(); - - while ( - this.peek().kind === TokenType.EQUALS_EQUALS || - this.peek().kind === TokenType.NOT_EQUALS || - this.peek().kind === TokenType.GREATER_THAN || - this.peek().kind === TokenType.LESS_THAN || - this.peek().kind === TokenType.GREATER_THAN_OR_EQUALS || - this.peek().kind === TokenType.LESS_THAN_OR_EQUALS || - this.peek().kind === TokenType.AND || - this.peek().kind === TokenType.OR - ) { - const operatorToken = this.eat(); - const operator: string = operatorToken.value?.toString() ?? "ERROR"; - const right: Expr = this.parse_additive_expr(); - - left = { - kind: "BinaryExpr", - left, - right, - operator, - loc: this.getLocationFromTokens(left.loc, right.loc), - } as BinaryExpr; - } - - return left; - } - - private parse_primary_expr(): Expr { - const token = this.peek(); - - switch (token.kind) { - case TokenType.IDENTIFIER: - return this.parse_identifier(); - case TokenType.INT: { - this.eat(); - return AST_INT(token.value as number, token.loc); - } - case TokenType.FLOAT: { - this.eat(); - return AST_FLOAT(token.value as number, token.loc); - } - case TokenType.BINARY: - return this.parseBinaryLiteral(); - case TokenType.MINUS: { - return this.parseNegativeValue(); - } - case TokenType.STRING: { - const str = this.eat(); - return AST_STRING(str.value as string, str.loc); - } - case TokenType.NULL: { - this.eat(); - return AST_NULL(token.loc); - } - case TokenType.LPAREN: { - const startToken = this.eat(); - const value: Expr = this.parse_expr(); - const endToken = this.consume( - TokenType.RPAREN, - "Expected closing parenthesis.", - ); - return { - ...value, - loc: this.getLocationFromTokens(startToken, endToken), - }; - } - case TokenType.NEW: - return this.parse_var_declaration(); - case TokenType.FN: - return this.parse_function_or_lambda(); - case TokenType.RETURN: - return this.parse_return_stmt(); - case TokenType.IF: - return this.parse_if_statement(); - default: - ErrorReporter.showError( - "Unexpected token found during parsing!", - token.loc, - ); - Deno.exit(1); + ErrorReporter.showError( + `After '${id.value}.' a member function call, or member call, was expected.`, + this.getLocationFromTokens(id.loc, expr.loc), + ); + Deno.exit(); + } + + // We will return whether it is an Array { EXPR, EXPR, ... } or object/dictionary { key: value, .... } + private parse_array_or_object(): ArrayLiteral | ObjectLiteral { + const startToken: Token = this.consume(TokenType.LBRACE, "Expected '{'."); + const values: Expr[] = []; + const dictionary: Map = new Map(); + + // Will be null until we determine the structure type (array or object) + let isArray: boolean = true; + + while (this.peek().kind !== TokenType.RBRACE) { + const left: Expr = this.parse_expr(); + const nextToken = this.peek(); + + if (nextToken.kind === TokenType.COLON) { + if (isArray === false) { + ErrorReporter.showError( + "Cannot mix array values and object entries.", + left.loc, + ); + Deno.exit(1); } - } - private parse_elif_statement(): ElifStatement { - const startToken: Token = this.eat(); // elif - const expr = this.parse_expr(); - this.consume(TokenType.LBRACE, "Era esperado um '{' após a expressão."); // { - let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); - const block: Stmt[] = []; - const secondBlock: Stmt[] = []; + this.eat(); + const right: Expr = this.parse_expr(); - while ( - this.is_end() == false && this.peek().kind !== TokenType.RBRACE - ) { - const expr_ = this.parse_expr(); + if (left.kind !== "Identifier") { + ErrorReporter.showError( + "The key of the object must be an identifier.", + left.loc, + ); + Deno.exit(1); + } - if (expr_.kind == "ReturnStatement") { - value = expr_; - } + const key: Identifier = left as Identifier; - block.push(expr_); + if (dictionary.has(key)) { + ErrorReporter.showError( + `Duplicate key '${key.value}' in object.`, + key.loc, + ); + Deno.exit(1); } - const endToken: Token = this.consume( - TokenType.RBRACE, - "Era esperado um '}' após o bloco de código.", - ); // } + dictionary.set(key, right); + isArray = false; - if (this.peek().kind == TokenType.ELSE) { - secondBlock.push(this.parse_else_statement()); + if (this.peek().kind === TokenType.COMMA) { + this.eat(); } - - if (this.peek().kind === TokenType.ELIF) { - secondBlock.push(this.parse_elif_statement()); + } else if ( + nextToken.kind === TokenType.COMMA || + nextToken.kind === TokenType.RBRACE + ) { + // Detected array value + if (isArray === true) { + ErrorReporter.showError( + "Cannot mix object entries and array values.", + left.loc, + ); + Deno.exit(1); } - return { - kind: "ElifStatement", - type: value.type, - value: value, - condition: expr, - primary: block, - secondary: secondBlock, - loc: this.getLocationFromTokens(startToken.loc, endToken.loc), - } as ElifStatement; - } - - private parse_else_statement(): ElseStatement { - const startToken: Token = this.eat(); // Consume 'else' - let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); - const block: Stmt[] = []; - - this.consume( - TokenType.LBRACE, - "Era esperado um '{' após a expressão.", - ); // { - - while ( - this.is_end() == false && this.peek().kind !== TokenType.RBRACE - ) { - const expr_ = this.parse_expr(); - - if (expr_.kind == "ReturnStatement") { - value = expr_; - } + values.push(left); + isArray = false; - block.push(expr_); + if (nextToken.kind === TokenType.COMMA) { + this.eat(); } + } else { + ErrorReporter.showError("Unexpected token.", nextToken.loc); + Deno.exit(1); + } + } - const endToken: Token = this.consume( - TokenType.RBRACE, - "Era esperado um '}' após o bloco de código.", - ); // { + const endToken: Token = this.consume( + TokenType.RBRACE, + "Expected '}' after array or object.", + ); + + const location = this.getLocationFromTokens(startToken.loc, endToken.loc); + return isArray + ? AST_ARRAY(values, location) + : AST_OBJECT(dictionary, location); + } + + private parse_break_statement(): BreakStatement { + const startToken: Token = this.eat(); + return { + kind: "BreakStatement", + loc: this.getLocationFromTokens(startToken.loc, startToken.loc), + } as BreakStatement; + } + + private parse_for_statement(): ForStatement | ForEachStatement { + const startToken: Token = this.eat(); + const declaration = this.parse_expr(); + const block: Stmt[] = []; + let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); + + if ( + declaration.kind !== "AssignmentDeclaration" && + declaration.kind !== "VarDeclaration" + ) { + ErrorReporter.showError( + "The declaration in the for loop can only be a VarDeclaration or AssignmentDeclaration", + this.getLocationFromTokens(startToken.loc, declaration.loc), + ); + Deno.exit(); + } - return { - kind: "ElseStatement", - type: value.type, - value: value, - primary: block, - loc: this.getLocationFromTokens(startToken.loc, endToken.loc), - } as ElseStatement; - } - - private parse_if_statement(): IfStatement { - const startToken: Token = this.eat(); // if - const expr = this.parse_expr(); - this.consume(TokenType.LBRACE, "Era esperado um '{' após a expressão."); // { - let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); - const block: Stmt[] = []; - const secondBlock: Stmt[] = []; - - while ( - this.is_end() == false && this.peek().kind !== TokenType.RBRACE - ) { - const expr_ = this.parse_expr(); + this.consume( + TokenType.SEMICOLON, + "Expected ';' after variable declaration or value assignment in for loop.", + ); - if (expr_.kind == "ReturnStatement") { - value = expr_; - } + if (this.peek().kind == TokenType.COLON) { + // + } - block.push(expr_); - } + const test = this.parse_expr(); - const endToken: Token = this.consume( - TokenType.RBRACE, - "Era esperado um '}' após o bloco de código.", - ); // } + if ( + test.kind !== "BinaryExpr" + ) { + ErrorReporter.showError( + "The for loop test can only be a binary expression.", + this.getLocationFromTokens(startToken.loc, declaration.loc), + ); + Deno.exit(); + } - if (this.peek().kind == TokenType.ELSE) { - secondBlock.push(this.parse_else_statement()); - } + this.consume( + TokenType.SEMICOLON, + "Expected ';' after test in for loop.", + ); + + const update = this.parse_expr(); + + if ( + update.kind !== "IncrementExpr" && update.kind !== "DecrementExpr" + ) { + ErrorReporter.showError( + "The for loop update can only be an increment or decrement..", + this.getLocationFromTokens(startToken.loc, declaration.loc), + ); + Deno.exit(); + } - if (this.peek().kind === TokenType.ELIF) { - secondBlock.push(this.parse_elif_statement()); - } + this.consume( + TokenType.LBRACE, + "Expected '{' after for loop.", + ); - return { - kind: "IfStatement", - type: value.type, - value: value, - condition: expr, - primary: block, - secondary: secondBlock, - loc: this.getLocationFromTokens(startToken.loc, endToken.loc), - } as IfStatement; - } - - private parse_return_stmt(): ReturnStatement { - const startToken: Token = this.eat(); - const value: Expr = this.parse_expr(); + while ( + this.is_end() == false && this.peek().kind !== TokenType.RBRACE + ) { + const expr_ = this.parse_expr(); - // console.log(value); - // console.log(this.peek()); + if (expr_.kind == "ReturnStatement") { + value = expr_; + } - return { - kind: "ReturnStatement", - type: value.type, - value: value, - loc: this.getLocationFromTokens(startToken.loc, value.loc), - } as ReturnStatement; - } - - // = : , .... - // fn (): - // fn main(x: int, y: binary:float): int|float { return x * y } - // Método que decide se é função nomeada ou lambda, depois de consumir 'fn' - private parse_function_or_lambda(): FunctionDeclaration | LambdaExpr { - const startToken: Token = this.eat(); // Consumes 'fn' - - // If next token is a pipe, it's a lambda (anonymous function) - if (this.peek().kind !== TokenType.IDENTIFIER) { - return this.parse_lambda_function(startToken); - } else { - // Otherwise, expect an identifier for the function name - const idToken: Token = this.consume( - TokenType.IDENTIFIER, - "Function name must be an IDENTIFIER", - ); - return this.parse_named_function(startToken, idToken); - } + block.push(expr_); } - // Parse a named function (regular function declaration) - private parse_named_function( - startToken: Token, - idToken: Token, - ): FunctionDeclaration { - const id: Identifier = AST_IDENTIFIER( - idToken.value as string, - idToken.loc, - ); - - // Parse function arguments using a helper - const args = this.parse_function_arguments(); + this.consume( + TokenType.RBRACE, + "Expected '}'after the for loop block.", + ); + + return { + kind: "ForStatement", + type: value.type, + value: value, + init: declaration, + test: test, + update: update, + body: block, + } as unknown as ForStatement; + } + + private parse_import_statement(): ImportStatement { + const startToken: Token = this.eat(); // import + const module: Token = this.consume( // + TokenType.IDENTIFIER, + "Module name as expected.", + ); + + let check = false; + let alias: Token = { + kind: "NULL", + value: null, + loc: module.loc, + } as unknown as Token; + + if (this.peek().kind == TokenType.AS) { + this.eat(); // as + check = true; + alias = this.consume(TokenType.IDENTIFIER, "Alias name expected ID."); // + } - // Parse return type(s) - this.consume( - TokenType.COLON, - "Expected ':' after parameters for return type.", - ); - const returnTypes: TypesNative[] = []; - const firstReturnTypeToken = this.consume( - TokenType.IDENTIFIER, - "Expected return type.", - ); - returnTypes.push(firstReturnTypeToken.value as TypesNative); - while (this.peek().kind === TokenType.PIPE) { - this.eat(); // Consume '|' - const typeToken = this.consume( - TokenType.IDENTIFIER, - "Expected type after '|'.", - ); - returnTypes.push(typeToken.value as TypesNative); - } - // Validate return types - returnTypes.forEach((type) => { - if (!TypesNativeArray.includes(type.toString())) { - ErrorReporter.showError( - `Invalid return type '${type}'.`, - firstReturnTypeToken.loc, - ); - Deno.exit(1); - } - if (type === "void" && returnTypes.length > 1) { - ErrorReporter.showError( - "'void' cannot be used in a union.", - firstReturnTypeToken.loc, - ); - Deno.exit(1); - } - }); - - // Parse function body (with return type checking inside) - const block = this.parse_function_body( - returnTypes.length === 1 ? returnTypes[0] : returnTypes, - ); - const endToken: Token = block.endToken; + return { + kind: "ImportStatement", + module: AST_IDENTIFIER(module.value as string, module.loc), + check: check, + alias: AST_IDENTIFIER(alias.value as string, alias.loc), + loc: this.getLocationFromTokens(startToken.loc, module.loc), + } as ImportStatement; + } + + private parse_elif_statement(): ElifStatement { + const startToken: Token = this.eat(); // elif + const expr = this.parse_expr(); + this.consume(TokenType.LBRACE, "Era esperado um '{' após a expressão."); // { + let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); + const block: Stmt[] = []; + const secondBlock: Stmt[] = []; + + while ( + this.is_end() == false && this.peek().kind !== TokenType.RBRACE + ) { + const expr_ = this.parse_expr(); + + if (expr_.kind == "ReturnStatement") { + value = expr_; + } + + block.push(expr_); + } - return { - kind: "FunctionDeclaration", - id: id, - args: args, - type: returnTypes.length === 1 ? returnTypes[0] : returnTypes, - block: block.body, // Assumes that parse_function_body returns a BlockStmt with property 'body' - loc: this.getLocationFromTokens(startToken.loc, endToken.loc), - } as unknown as FunctionDeclaration; - } - - // Helper para parsear a lista de argumentos de função/lambda - private parse_function_arguments(): { - id: Identifier; - type: TypesNative | TypesNative[]; - }[] { - const args: { id: Identifier; type: TypesNative | TypesNative[] }[] = - []; - this.consume( - TokenType.LPAREN, - "Expected '(' after function name or lambda capture list.", - ); + const endToken: Token = this.consume( + TokenType.RBRACE, + "Era esperado um '}' após o bloco de código.", + ); // } - while (this.peek().kind !== TokenType.RPAREN) { - const argToken: Token = this.consume( - TokenType.IDENTIFIER, - "Expected IDENTIFIER for argument name.", - ); - const argId: Identifier = AST_IDENTIFIER( - argToken.value as string, - argToken.loc, - ); + if (this.peek().kind == TokenType.ELSE) { + secondBlock.push(this.parse_else_statement()); + } - this.consume(TokenType.COLON, "Expected ':' after argument name."); + if (this.peek().kind === TokenType.ELIF) { + secondBlock.push(this.parse_elif_statement()); + } - // Parse argument type (supporting union types) - const argTypes: TypesNative[] = []; - const firstTypeToken = this.consume( - TokenType.IDENTIFIER, - "Expected type for argument.", - ); - argTypes.push(firstTypeToken.value as TypesNative); - - while (this.peek().kind === TokenType.PIPE) { - this.eat(); // Consume '|' - const typeToken = this.consume( - TokenType.IDENTIFIER, - "Expected type after '|'.", - ); - argTypes.push(typeToken.value as TypesNative); - } - - // Validate the argument types - argTypes.forEach((type) => { - if ( - !TypesNativeArray.includes(type.toString()) || - type === "void" - ) { - ErrorReporter.showError( - `Invalid type '${type}'.`, - firstTypeToken.loc, - ); - Deno.exit(1); - } - }); - - args.push({ - id: argId, - type: argTypes.length === 1 ? argTypes[0] : argTypes, - }); - - // Expect a comma or closing parenthesis - if (this.peek().kind === TokenType.COMMA) { - this.eat(); - } else if (this.peek().kind !== TokenType.RPAREN) { - ErrorReporter.showError( - "Expected ',' or ')' after argument.", - this.peek().loc, - ); - Deno.exit(1); - } - } + return { + kind: "ElifStatement", + type: value.type, + value: value, + condition: expr, + primary: block, + secondary: secondBlock, + loc: this.getLocationFromTokens(startToken.loc, endToken.loc), + } as ElifStatement; + } + + private parse_else_statement(): ElseStatement { + const startToken: Token = this.eat(); // Consume 'else' + let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); + const block: Stmt[] = []; + + this.consume( + TokenType.LBRACE, + "Era esperado um '{' após a expressão.", + ); // { + + while ( + this.is_end() == false && this.peek().kind !== TokenType.RBRACE + ) { + const expr_ = this.parse_expr(); + + if (expr_.kind == "ReturnStatement") { + value = expr_; + } + + block.push(expr_); + } - this.consume(TokenType.RPAREN, "Expected ')' after arguments."); - return args; + const endToken: Token = this.consume( + TokenType.RBRACE, + "Era esperado um '}' após o bloco de código.", + ); // { + + return { + kind: "ElseStatement", + type: value.type, + value: value, + primary: block, + loc: this.getLocationFromTokens(startToken.loc, endToken.loc), + } as ElseStatement; + } + + private parse_if_statement(): IfStatement { + const startToken: Token = this.eat(); // if + const expr = this.parse_expr(); + this.consume(TokenType.LBRACE, "Era esperado um '{' após a expressão."); // { + let value: NullLiteral | Expr | Stmt = AST_NULL({} as Loc); + const block: Stmt[] = []; + const secondBlock: Stmt[] = []; + + while ( + this.is_end() == false && this.peek().kind !== TokenType.RBRACE + ) { + const expr_ = this.parse_expr(); + + if (expr_.kind == "ReturnStatement") { + value = expr_; + } + + block.push(expr_); } - // Parse a lambda function (anonymous function with external variable capture) - // The 'startToken' is the token 'fn' already consumed. - private parse_lambda_function(startToken: Token): LambdaExpr { - // The lambda starts with a pipe for external variable capture list. - const externalVars: Identifier[] = []; - if (this.peek().kind == TokenType.PIPE) { - this.consume( - TokenType.PIPE, - "Expected '|' to start external variables capture list.", - ); + const endToken: Token = this.consume( + TokenType.RBRACE, + "Era esperado um '}' após o bloco de código.", + ); // } - // The capture list may be empty. If not, parse comma-separated identifiers. - if (this.peek().kind !== TokenType.PIPE) { - while (this.peek().kind !== TokenType.PIPE) { - const varToken = this.consume( - TokenType.IDENTIFIER, - "Expected identifier for external variable.", - ); - externalVars.push( - AST_IDENTIFIER(varToken.value as string, varToken.loc), - ); - - if (this.peek().kind === TokenType.COMMA) { - this.eat(); // Consume comma - } else if (this.peek().kind !== TokenType.PIPE) { - ErrorReporter.showError( - "Expected ',' or '|' after external variable.", - this.peek().loc, - ); - Deno.exit(1); - } - } - } - this.consume( - TokenType.PIPE, - "Expected '|' to end external variables capture list.", - ); - } + if (this.peek().kind == TokenType.ELSE) { + secondBlock.push(this.parse_else_statement()); + } - // Optionally parse lambda arguments if present - let args: { id: Identifier; type: TypesNative | TypesNative[] }[] = []; - if (this.peek().kind === TokenType.LPAREN) { - args = this.parse_function_arguments(); - } + if (this.peek().kind === TokenType.ELIF) { + secondBlock.push(this.parse_elif_statement()); + } - // Parse return type(s) – expect a colon - // console.log(this.peek()); - this.consume( - TokenType.COLON, - "Expected ':' after lambda arguments for return type.", - ); - const returnTypes: TypesNative[] = []; - const firstReturnTypeToken = this.consume( - TokenType.IDENTIFIER, - "Expected return type.", - ); - returnTypes.push(firstReturnTypeToken.value as TypesNative); - while (this.peek().kind === TokenType.PIPE) { - this.eat(); // Consume '|' - const typeToken = this.consume( - TokenType.IDENTIFIER, - "Expected type after '|'.", - ); - returnTypes.push(typeToken.value as TypesNative); - } - // Validate return types - returnTypes.forEach((type) => { - if (!TypesNativeArray.includes(type.toString())) { - ErrorReporter.showError( - `Invalid return type '${type}'.`, - firstReturnTypeToken.loc, - ); - Deno.exit(1); - } - if (type === "void" && returnTypes.length > 1) { - ErrorReporter.showError( - "'void' cannot be used in a union.", - firstReturnTypeToken.loc, - ); - Deno.exit(1); - } - }); - - // Parse lambda body - const block = this.parse_function_body( - returnTypes.length === 1 ? returnTypes[0] : returnTypes, + return { + kind: "IfStatement", + type: value.type, + value: value, + condition: expr, + primary: block, + secondary: secondBlock, + loc: this.getLocationFromTokens(startToken.loc, endToken.loc), + } as IfStatement; + } + + private parse_return_stmt(): ReturnStatement { + const startToken: Token = this.eat(); + const value: Expr = this.parse_expr(); + + // console.log(value); + // console.log(this.peek()); + + return { + kind: "ReturnStatement", + type: value.type, + value: value, + loc: this.getLocationFromTokens(startToken.loc, value.loc), + } as ReturnStatement; + } + + // = : , .... + // fn (): + // fn main(x: int, y: binary:float): int|float { return x * y } + // Método que decide se é função nomeada ou lambda, depois de consumir 'fn' + private parse_function_or_lambda(): FunctionDeclaration | LambdaExpr { + const startToken: Token = this.eat(); // Consumes 'fn' + + // If next token is a pipe, it's a lambda (anonymous function) + if (this.peek().kind !== TokenType.IDENTIFIER) { + return this.parse_lambda_function(startToken); + } else { + // Otherwise, expect an identifier for the function name + const idToken: Token = this.consume( + TokenType.IDENTIFIER, + "Function name must be an IDENTIFIER", + ); + return this.parse_named_function(startToken, idToken); + } + } + + // Parse a named function (regular function declaration) + private parse_named_function( + startToken: Token, + idToken: Token, + ): FunctionDeclaration { + const id: Identifier = AST_IDENTIFIER( + idToken.value as string, + idToken.loc, + ); + + // Parse function arguments using a helper + const args = this.parse_function_arguments(); + + // Parse return type(s) + this.consume( + TokenType.COLON, + "Expected ':' after parameters for return type.", + ); + const returnTypes: TypesNative[] = []; + const firstReturnTypeToken = this.consume( + TokenType.IDENTIFIER, + "Expected return type.", + ); + returnTypes.push(firstReturnTypeToken.value as TypesNative); + while (this.peek().kind === TokenType.PIPE) { + this.eat(); // Consume '|' + const typeToken = this.consume( + TokenType.IDENTIFIER, + "Expected type after '|'.", + ); + returnTypes.push(typeToken.value as TypesNative); + } + // Validate return types + returnTypes.forEach((type) => { + if (!TypesNativeArray.includes(type.toString())) { + ErrorReporter.showError( + `Invalid return type '${type}'.`, + firstReturnTypeToken.loc, ); - - return { - kind: "LambdaExpr", - externalVars: externalVars, - args: args, - type: returnTypes.length === 1 ? returnTypes[0] : returnTypes, - body: block.body, - loc: this.getLocationFromTokens(startToken.loc, block.endToken.loc), - } as unknown as LambdaExpr; - } - - // Example of parse_function_body that checks return statements against expected types - private parse_function_body( - expectedReturnTypes: TypesNative | TypesNative[], - ): BlockStmt { - const startToken: Token = this.consume( - TokenType.LBRACE, - "Expected '{' at the beginning of function body.", + Deno.exit(1); + } + if (type === "void" && returnTypes.length > 1) { + ErrorReporter.showError( + "'void' cannot be used in a union.", + firstReturnTypeToken.loc, ); - const bodyStatements: Stmt[] = []; - - while (this.peek().kind !== TokenType.RBRACE && !this.is_end()) { - const stmt = this.parse_stmt(); - - // If a ReturnStatement is found, check its return type. - if (stmt.kind === "ReturnStatement") { - const retStmt = stmt as ReturnStatement; - // If no expression is returned, consider type 'void' - const retExpr = retStmt.value; - const inferredType: TypesNative = retExpr - ? retExpr.type as TypesNative - : "void"; - - // Normalize expectedReturnTypes to an array for comparison. - const expectedTypes: TypesNative[] = - Array.isArray(expectedReturnTypes) - ? expectedReturnTypes - : [expectedReturnTypes]; - - if ( - !expectedTypes.includes(inferredType) && - inferredType != "id" - ) { - ErrorReporter.showError( - `Return type '${inferredType}' does not match declared return type. Expected one of: ${ - expectedTypes.join( - ", ", - ) - }.`, - stmt.loc, - ); - Deno.exit(1); - } - } - - bodyStatements.push(stmt); - } - - const endToken: Token = this.consume( - TokenType.RBRACE, - "Expected '}' after function body.", + Deno.exit(1); + } + }); + + // Parse function body (with return type checking inside) + const block = this.parse_function_body( + returnTypes.length === 1 ? returnTypes[0] : returnTypes, + ); + const endToken: Token = block.endToken; + + return { + kind: "FunctionDeclaration", + id: id, + args: args, + type: returnTypes.length === 1 ? returnTypes[0] : returnTypes, + block: block.body, // Assumes that parse_function_body returns a BlockStmt with property 'body' + loc: this.getLocationFromTokens(startToken.loc, endToken.loc), + } as unknown as FunctionDeclaration; + } + + // Helper para parsear a lista de argumentos de função/lambda + private parse_function_arguments(): { + id: Identifier; + type: TypesNative | TypesNative[]; + }[] { + const args: { id: Identifier; type: TypesNative | TypesNative[] }[] = []; + this.consume( + TokenType.LPAREN, + "Expected '(' after function name or lambda capture list.", + ); + + while (this.peek().kind !== TokenType.RPAREN) { + const argToken: Token = this.consume( + TokenType.IDENTIFIER, + "Expected IDENTIFIER for argument name.", + ); + const argId: Identifier = AST_IDENTIFIER( + argToken.value as string, + argToken.loc, + ); + + this.consume(TokenType.COLON, "Expected ':' after argument name."); + + // Parse argument type (supporting union types) + const argTypes: TypesNative[] = []; + const firstTypeToken = this.consume( + TokenType.IDENTIFIER, + "Expected type for argument.", + ); + argTypes.push(firstTypeToken.value as TypesNative); + + while (this.peek().kind === TokenType.PIPE) { + this.eat(); // Consume '|' + const typeToken = this.consume( + TokenType.IDENTIFIER, + "Expected type after '|'.", ); + argTypes.push(typeToken.value as TypesNative); + } - return { - kind: "BlockStmt", - body: bodyStatements, - loc: this.getLocationFromTokens(startToken.loc, endToken.loc), - endToken: endToken, // Used for location information - } as unknown as BlockStmt; - } - - private parse_identifier(): - | Identifier - | CallExpr - | AssignmentDeclaration - | IncrementExpr - | DecrementExpr { - const token = this.peek(); - - if (this.next().kind == TokenType.EQUALS) { // = - return this.parse_assignment_declaration(); - } - - if (this.next().kind == TokenType.LPAREN) { // () - return this.parse_call_expr(); + // Validate the argument types + argTypes.forEach((type) => { + if ( + !TypesNativeArray.includes(type.toString()) || + type === "void" + ) { + ErrorReporter.showError( + `Invalid type '${type}'.`, + firstTypeToken.loc, + ); + Deno.exit(1); } + }); - if (this.next().kind == TokenType.INCREMENT) { // ++ - this.eat(); // ID - this.eat(); // ++ - return { - kind: "IncrementExpr", - value: AST_IDENTIFIER(token.value as string, token.loc), - loc: token.loc, - } as IncrementExpr; - } + args.push({ + id: argId, + type: argTypes.length === 1 ? argTypes[0] : argTypes, + }); - if (this.next().kind == TokenType.DECREMENT) { // -- - this.eat(); // ID - this.eat(); // -- - return { - kind: "DecrementExpr", - value: AST_IDENTIFIER(token.value as string, token.loc), - loc: token.loc, - } as DecrementExpr; - } + // Expect a comma or closing parenthesis + if (this.peek().kind === TokenType.COMMA) { + this.eat(); + } else if (this.peek().kind !== TokenType.RPAREN) { + ErrorReporter.showError( + "Expected ',' or ')' after argument.", + this.peek().loc, + ); + Deno.exit(1); + } + } - if (this.next().kind == TokenType.DOT) { // . - // TODO: MemberExpr + this.consume(TokenType.RPAREN, "Expected ')' after arguments."); + return args; + } + + // Parse a lambda function (anonymous function with external variable capture) + // The 'startToken' is the token 'fn' already consumed. + private parse_lambda_function(startToken: Token): LambdaExpr { + // The lambda starts with a pipe for external variable capture list. + const externalVars: Identifier[] = []; + if (this.peek().kind == TokenType.PIPE) { + this.consume( + TokenType.PIPE, + "Expected '|' to start external variables capture list.", + ); + + // The capture list may be empty. If not, parse comma-separated identifiers. + if (this.peek().kind !== TokenType.PIPE) { + while (this.peek().kind !== TokenType.PIPE) { + const varToken = this.consume( + TokenType.IDENTIFIER, + "Expected identifier for external variable.", + ); + externalVars.push( + AST_IDENTIFIER(varToken.value as string, varToken.loc), + ); + + if (this.peek().kind === TokenType.COMMA) { + this.eat(); // Consume comma + } else if (this.peek().kind !== TokenType.PIPE) { + ErrorReporter.showError( + "Expected ',' or '|' after external variable.", + this.peek().loc, + ); + Deno.exit(1); + } } - - this.eat(); // ID - return AST_IDENTIFIER(token.value as string, token.loc); + } + this.consume( + TokenType.PIPE, + "Expected '|' to end external variables capture list.", + ); } - private parse_call_expr(): CallExpr { - const id: Token = this.consume( - TokenType.IDENTIFIER, - "Expected IDENTIFIER in function call name.", - ); + // Optionally parse lambda arguments if present + let args: { id: Identifier; type: TypesNative | TypesNative[] }[] = []; + if (this.peek().kind === TokenType.LPAREN) { + args = this.parse_function_arguments(); + } - this.consume( - TokenType.LPAREN, - "Expected (.", + // Parse return type(s) – expect a colon + // console.log(this.peek()); + this.consume( + TokenType.COLON, + "Expected ':' after lambda arguments for return type.", + ); + const returnTypes: TypesNative[] = []; + const firstReturnTypeToken = this.consume( + TokenType.IDENTIFIER, + "Expected return type.", + ); + returnTypes.push(firstReturnTypeToken.value as TypesNative); + while (this.peek().kind === TokenType.PIPE) { + this.eat(); // Consume '|' + const typeToken = this.consume( + TokenType.IDENTIFIER, + "Expected type after '|'.", + ); + returnTypes.push(typeToken.value as TypesNative); + } + // Validate return types + returnTypes.forEach((type) => { + if (!TypesNativeArray.includes(type.toString())) { + ErrorReporter.showError( + `Invalid return type '${type}'.`, + firstReturnTypeToken.loc, ); - - // Parse Args - const args: Expr[] = this.parse_args(); - - const endToken: Token = this.consume( - TokenType.RPAREN, - "Expected ).", + Deno.exit(1); + } + if (type === "void" && returnTypes.length > 1) { + ErrorReporter.showError( + "'void' cannot be used in a union.", + firstReturnTypeToken.loc, ); + Deno.exit(1); + } + }); + + // Parse lambda body + const block = this.parse_function_body( + returnTypes.length === 1 ? returnTypes[0] : returnTypes, + ); + + return { + kind: "LambdaExpr", + externalVars: externalVars, + args: args, + type: returnTypes.length === 1 ? returnTypes[0] : returnTypes, + body: block.body, + loc: this.getLocationFromTokens(startToken.loc, block.endToken.loc), + } as unknown as LambdaExpr; + } + + // Example of parse_function_body that checks return statements against expected types + private parse_function_body( + expectedReturnTypes: TypesNative | TypesNative[], + ): BlockStmt { + const startToken: Token = this.consume( + TokenType.LBRACE, + "Expected '{' at the beginning of function body.", + ); + const bodyStatements: Stmt[] = []; + + while (this.peek().kind !== TokenType.RBRACE && !this.is_end()) { + const stmt = this.parse_stmt(); + + // If a ReturnStatement is found, check its return type. + if (stmt.kind === "ReturnStatement") { + const retStmt = stmt as ReturnStatement; + // If no expression is returned, consider type 'void' + const retExpr = retStmt.value; + const inferredType: TypesNative = retExpr + ? retExpr.type as TypesNative + : "void"; + + // Normalize expectedReturnTypes to an array for comparison. + const expectedTypes: TypesNative[] = Array.isArray(expectedReturnTypes) + ? expectedReturnTypes + : [expectedReturnTypes]; - if (this.peek().kind == TokenType.SEMICOLON) { - this.eat(); // remove ; if exists + if ( + !expectedTypes.includes(inferredType) && + inferredType != "id" + ) { + ErrorReporter.showError( + `Return type '${inferredType}' does not match declared return type. Expected one of: ${ + expectedTypes.join( + ", ", + ) + }.`, + stmt.loc, + ); + Deno.exit(1); } + } - return { - kind: "CallExpr", - type: "id", - id: { - kind: "Identifier", - type: "id", - value: id.value, - loc: id.loc, - }, - args: args, - loc: this.getLocationFromTokens(id.loc, endToken.loc), - } as CallExpr; - } - - private parse_args(): Expr[] { - const args: Expr[] = []; - - while (this.peek().kind !== TokenType.RPAREN) { - const value = this.parse_expr(); - - if (this.peek().kind == TokenType.COMMA) { - args.push(value); - this.eat(); // , - - if ( - this.next().kind == TokenType.LBRACE || - this.next().kind == TokenType.SEMICOLON - ) { - ErrorReporter.showError( - `Expected EXPR after ','`, - this.peek().loc, - ); - Deno.exit(); - } - - continue; - } - - if (this.peek().kind == TokenType.RPAREN) { - args.push(value); - break; - } - - ErrorReporter.showError( - `Expected , receive ${this.peek().value}`, - this.peek().loc, - ); - Deno.exit(); - } + bodyStatements.push(stmt); + } - return args; + const endToken: Token = this.consume( + TokenType.RBRACE, + "Expected '}' after function body.", + ); + + return { + kind: "BlockStmt", + body: bodyStatements, + loc: this.getLocationFromTokens(startToken.loc, endToken.loc), + endToken: endToken, // Used for location information + } as unknown as BlockStmt; + } + + private parse_identifier(): + | Identifier + | CallExpr + | AssignmentDeclaration + | IncrementExpr + | DecrementExpr + | MemberCallExpr + | MemberExpr { + const token = this.peek(); + + if (this.next().kind == TokenType.EQUALS) { // = + return this.parse_assignment_declaration(); } - private parseBinaryLiteral(): BinaryLiteral { - const token: Token = this.eat(); - if (/^0b[01]+$/.test(token.value as string) == false) { - ErrorReporter.showError("Invalid binary value.", token.loc); - Deno.exit(); - } - return AST_BINARY(token.value as string, token.loc); + if (this.next().kind == TokenType.LPAREN) { // () + return this.parse_call_expr(); } - // new x: = EXPR - // new mut y: = x - private parse_var_declaration(): VarDeclaration { - const startToken = this.eat(); // new - let is_const: boolean = true; - const types: TypesNative[] = []; + const id_t = this.eat(); + const id: Identifier = AST_IDENTIFIER(id_t.value as string, id_t.loc); + + if (this.peek().kind == TokenType.LBRACKET) { // [] + this.eat(); // [ + const expr: any = this.parse_expr(); + this.consume(TokenType.RBRACKET, "Expected ']' after '['."); + + return { + kind: "MemberExpr", + type: expr.type, + computed: true, + id: id, + member: expr as Identifier, + loc: this.getLocationFromTokens(id.loc, expr.loc), + } as MemberExpr; + } - if (this.peek().kind === TokenType.MUT) { - this.eat(); - is_const = false; - } + if (this.peek().kind == TokenType.INCREMENT) { // ++ + this.eat(); // ++ + return { + kind: "IncrementExpr", + value: id, + loc: id.loc, + } as IncrementExpr; + } - const idToken = this.consume( - TokenType.IDENTIFIER, - "The name must be an IDENTIFIER.", - ); + if (this.peek().kind == TokenType.DECREMENT) { // -- + this.eat(); // -- + return { + kind: "DecrementExpr", + value: id, + loc: id.loc, + } as DecrementExpr; + } - this.consume(TokenType.COLON, "Expected ':'."); // : + if (this.peek().kind == TokenType.DOT) { // . | .(...) + this.eat(); // . + // deno-lint-ignore no-explicit-any + const expr: any = this.parse_expr(); - // new - while (this.peek().kind !== TokenType.EQUALS) { - const type_tk: Token = this.consume( - TokenType.IDENTIFIER, - "The type of the variable was expected but was not passed.", - ); - const type: TypesNative = type_tk.value as TypesNative; - - if (!TypesNativeArray.includes(type as string)) { - ErrorReporter.showError("Invalid type.", type_tk.loc); - Deno.exit(); - } - - types.push(type); - - if (this.peek().kind == TokenType.PIPE) { - this.eat(); // | - if (this.peek().kind == TokenType.EQUALS) { - ErrorReporter.showError( - "'=' is not expected after '|'", - this.peek().loc, - ); - Deno.exit(); - } - continue; - } - } - - this.consume(TokenType.EQUALS, "Expected '='."); - const value = this.parse_expr(); - if ( - value.kind != "Identifier" && value.kind != "IncrementExpr" && - value.kind != "DecrementExpr" - ) this.validateType(value, types); + // if (this.peek().kind == TokenType.DOT) { // encadeamento + // return this.parse_member_and_call_expression(); + // } + if (expr.kind == "CallExpr") { // .(...) + return { + kind: "MemberCallExpr", + type: expr.type, + id: id, + member: expr as CallExpr, + loc: this.getLocationFromTokens(id.loc, expr.loc), + } as unknown as MemberCallExpr; + } + + if (expr.kind == "Identifier") { // . return { - kind: "VarDeclaration", - type: types, - id: AST_IDENTIFIER(idToken.value as string, idToken.loc), - value, - constant: is_const, - loc: this.getLocationFromTokens(startToken, value.loc), - } as VarDeclaration; + kind: "MemberExpr", + type: expr.type, + id: id, + member: expr as Identifier, + loc: this.getLocationFromTokens(id.loc, expr.loc), + } as unknown as MemberExpr; + } + + ErrorReporter.showError( + `After '${id.value}.' a member function call, or member call, was expected.`, + this.getLocationFromTokens(id.loc, expr.loc), + ); + Deno.exit(); } - private parse_assignment_declaration(): AssignmentDeclaration { - const idToken = this.consume( - TokenType.IDENTIFIER, - "The name must be an IDENTIFIER.", + return id; + } + + private parse_call_expr(): CallExpr { + const id: Token = this.consume( + TokenType.IDENTIFIER, + "Expected IDENTIFIER in function call name.", + ); + + this.consume( + TokenType.LPAREN, + "Expected (.", + ); + + // Parse Args + const args: Expr[] = this.parse_args(); + + const endToken: Token = this.consume( + TokenType.RPAREN, + "Expected ).", + ); + + return { + kind: "CallExpr", + type: "id", + id: AST_IDENTIFIER(id.value as string, id.loc), + args: args, + loc: this.getLocationFromTokens(id.loc, endToken.loc), + } as CallExpr; + } + + private parse_args(): Expr[] { + const args: Expr[] = []; + + while (this.peek().kind !== TokenType.RPAREN) { + const value = this.parse_expr(); + + // Add the parsed expression to arguments + args.push(value); + + // Check for comma separator + if (this.peek().kind === TokenType.COMMA) { + this.eat(); // consume comma + + // Error if comma is followed by invalid tokens + if (this.peek().kind === TokenType.RPAREN) { + ErrorReporter.showError( + "Unexpected ',' before closing parenthesis", + this.peek().loc, + ); + Deno.exit(); + } + } else if (this.peek().kind !== TokenType.RPAREN) { + // If not a comma or closing paren, it's an error + ErrorReporter.showError( + `Expected ',' or ')' but got '${this.peek().value}'`, + this.peek().loc, ); + Deno.exit(); + } + } - this.consume(TokenType.EQUALS, "Expected '='."); - const value = this.parse_expr(); + return args; + } - return { - kind: "AssignmentDeclaration", - type: value.type, - id: AST_IDENTIFIER(idToken.value as string, idToken.loc), - value, - loc: this.getLocationFromTokens(idToken, value.loc), - } as AssignmentDeclaration; + private parseBinaryLiteral(): BinaryLiteral { + const token: Token = this.eat(); + if (/^0b[01]+$/.test(token.value as string) == false) { + ErrorReporter.showError("Invalid binary value.", token.loc); + Deno.exit(); + } + return AST_BINARY(token.value as string, token.loc); + } + + // new x: = EXPR + // new mut y: = x + private parse_var_declaration(): VarDeclaration { + const startToken = this.eat(); // new + let is_const: boolean = true; + + if (this.peek().kind === TokenType.MUT) { + this.eat(); + is_const = false; } - private parseNegativeValue(): IntLiteral | FloatLiteral { - if ( - this.next().kind === TokenType.INT || - this.next().kind === TokenType.FLOAT - ) { - this.eat(); // - - const numToken = this.eat(); - if (numToken.kind == TokenType.INT) { - return AST_INT(-Number(numToken.value), numToken.loc); - } - // Fallback for Float - return AST_FLOAT(-Number(numToken.value), numToken.loc); - } - - ErrorReporter.showError( - "Unexpected token found during parsing!", - this.next().loc, - ); - Deno.exit(1); + const idToken = this.consume( + TokenType.IDENTIFIER, + "The name must be an IDENTIFIER.", + ); + + this.consume(TokenType.COLON, "Expected ':'."); // : + + // new + const types: TypesNative[] = this.parse_types(TokenType.EQUALS).types; + + this.consume(TokenType.EQUALS, "Expected '='."); + const value = this.parse_expr(); + if ( + value.kind != "Identifier" && value.kind != "IncrementExpr" && + value.kind != "DecrementExpr" + ) this.validateType(value, types); + + return { + kind: "VarDeclaration", + type: types, + id: AST_IDENTIFIER(idToken.value as string, idToken.loc), + value, + constant: is_const, + loc: this.getLocationFromTokens(startToken, value.loc), + } as VarDeclaration; + } + + private parse_assignment_declaration(): AssignmentDeclaration { + const idToken = this.consume( + TokenType.IDENTIFIER, + "The name must be an IDENTIFIER.", + ); + + this.consume(TokenType.EQUALS, "Expected '='."); + const value = this.parse_expr(); + + return { + kind: "AssignmentDeclaration", + type: value.type, + id: AST_IDENTIFIER(idToken.value as string, idToken.loc), + value, + loc: this.getLocationFromTokens(idToken, value.loc), + } as AssignmentDeclaration; + } + + private parseNegativeValue(): IntLiteral | FloatLiteral { + if ( + this.next().kind === TokenType.INT || + this.next().kind === TokenType.FLOAT + ) { + this.eat(); // - + const numToken = this.eat(); + if (numToken.kind == TokenType.INT) { + return AST_INT(-Number(numToken.value), numToken.loc); + } + // Fallback for Float + return AST_FLOAT(-Number(numToken.value), numToken.loc); } - private validateType(value: Stmt, type: TypesNative[]): boolean { - if (!type.includes(value.type as TypesNative) && value.type !== "id") { - ErrorReporter.showError( - `The expected type does not match the type of the value. Expected one of ${ - type.join(", ") - } and received ${value.type}.`, - value.loc, - ); - Deno.exit(1); + ErrorReporter.showError( + "Unexpected token found during parsing!", + this.next().loc, + ); + Deno.exit(1); + } + + private parse_types(stopToken: TokenType): IParsedTypes { + const types: TypesNative[] = []; + + // Process tokens until the stop token (e.g., '=') + while (this.peek().kind !== stopToken) { + // Consume the token representing the type + const typeToken: Token = this.consume( + TokenType.IDENTIFIER, + "Expected a type for the variable but none was provided.", + ); + const typeValue = typeToken.value as TypesNative; + + // Validate the type against the allowed types + if (!TypesNativeArray.includes(typeValue as string)) { + ErrorReporter.showError("Invalid type.", typeToken.loc); + Deno.exit(); + } + + types.push(typeValue); + + // If a pipe ('|') token is found, consume it and continue with additional types + if (this.peek().kind === TokenType.PIPE) { + this.eat(); // Consume the '|' + if (this.peek().kind === stopToken) { + ErrorReporter.showError( + `'${this.peek().value}' is not expected after '|'`, + this.peek().loc, + ); + Deno.exit(); } - return true; + } else { + break; + } } - private getLocationFromTokens(start: Token | Loc, end: Token | Loc): Loc { - return { - file: "file" in start ? start.file : start.loc.file, - line: "line" in start ? start.line : start.loc.line, - start: "start" in start ? start.start : start.loc.start, - end: "end" in end ? end.end : end.loc.end, - line_string: "line_string" in start - ? start.line_string - : start.loc.line_string, - }; + return { types }; + } + + private validateType(value: Stmt, type: TypesNative[]): boolean { + if (!type.includes(value.type as TypesNative) && value.type !== "id") { + ErrorReporter.showError( + `The expected type does not match the type of the value. Expected one of ${ + type.join(", ") + } and received ${value.type}.`, + value.loc, + ); + Deno.exit(1); } + return true; + } + + private getLocationFromTokens(start: Token | Loc, end: Token | Loc): Loc { + return { + file: "file" in start ? start.file : start.loc.file, + line: "line" in start ? start.line : start.loc.line, + start: "start" in start ? start.start : start.loc.start, + end: "end" in end ? end.end : end.loc.end, + line_string: "line_string" in start + ? start.line_string + : start.loc.line_string, + }; + } } diff --git a/src/error/ErrorReporter.ts b/src/error/ErrorReporter.ts index 0334c3c..3db5486 100644 --- a/src/error/ErrorReporter.ts +++ b/src/error/ErrorReporter.ts @@ -2,47 +2,55 @@ import { Colorize } from "../utils/Colorize.ts"; import { Loc } from "../frontend/Token.ts"; export class ErrorReporter { - public static showError(message: string, loc: Loc): void { - this.displayErrorHeader(message, loc); - this.displayLineWithError( - loc.line, - loc.line_string, - loc.start, - loc.end, - ); - } + public static showError(message: string, loc: Loc): void { + this.displayErrorHeader(message, loc); - private static displayErrorHeader(message: string, loc: Loc): void { - console.error( - Colorize.bold( - `${Colorize.red("error")}: ${Colorize.bold(message)}`, - ), - ); - console.error( - `${Colorize.bold(Colorize.blue(" ---->"))} ${ - Colorize.bold(`${loc.file}:${loc.line}:${loc.start + 1}`) - }`, - ); + if (loc == undefined) { + return; } - private static displayLineWithError( - lineNumber: number, - lineContent: string, - start: number, - end: number, - ): void { - const markerLength = Math.max(end - start, 1); - - console.error( - `${ - Colorize.bold(Colorize.blue(`${lineNumber} |`)) - } ${lineContent}`, - ); - - console.error( - Colorize.bold(Colorize.blue(" |")) + - " ".repeat(start + 6) + - Colorize.bold(Colorize.red("^".repeat(markerLength))), - ); + this.displayLineWithError( + loc.line, + loc.line_string, + loc.start, + loc.end, + ); + } + + private static displayErrorHeader(message: string, loc: Loc): void { + console.error( + Colorize.bold( + `${Colorize.red("error")}: ${Colorize.bold(message)}`, + ), + ); + + if (loc == undefined) { + return; } + + console.error( + `${Colorize.bold(Colorize.blue(" ---->"))} ${ + Colorize.bold(`${loc.file}:${loc.line}:${loc.start + 1}`) + }`, + ); + } + + private static displayLineWithError( + lineNumber: number, + lineContent: string, + start: number, + end: number, + ): void { + const markerLength = Math.max(end - start, 1); + + console.error( + `${Colorize.bold(Colorize.blue(`${lineNumber} |`))} ${lineContent}`, + ); + + console.error( + Colorize.bold(Colorize.blue(" |")) + + " ".repeat(start + 6) + + Colorize.bold(Colorize.red("^".repeat(markerLength))), + ); + } } diff --git a/src/frontend/Lexer.ts b/src/frontend/Lexer.ts index df12003..b785c20 100644 --- a/src/frontend/Lexer.ts +++ b/src/frontend/Lexer.ts @@ -2,329 +2,331 @@ import { Keywords, Loc, NativeValue, Token, TokenType } from "./Token.ts"; import { ErrorReporter } from "../error/ErrorReporter.ts"; export class Lexer { - private source: string; - private file: string; - protected line: number = 1; - protected offset: number = 0; // Offset global - protected lineOffset: number = 0; // Offset relative to current line - protected start: number = 0; - protected tokens: Token[] = []; - private static SINGLE_CHAR_TOKENS: { [key: string]: TokenType } = { - "+": TokenType.PLUS, - "-": TokenType.MINUS, - "*": TokenType.ASTERISK, - "/": TokenType.SLASH, - ">": TokenType.GREATER_THAN, - "<": TokenType.LESS_THAN, - ",": TokenType.COMMA, - ";": TokenType.SEMICOLON, - ":": TokenType.COLON, - "(": TokenType.LPAREN, - ")": TokenType.RPAREN, - "{": TokenType.LBRACE, - "}": TokenType.RBRACE, - ".": TokenType.DOT, - "%": TokenType.PERCENT, - "|": TokenType.PIPE, - "=": TokenType.EQUALS, - }; - private static MULTI_CHAR_TOKENS: { [key: string]: TokenType } = { - "++": TokenType.INCREMENT, - "--": TokenType.DECREMENT, - "**": TokenType.EXPONENTIATION, - "%%": TokenType.REMAINDER, - "==": TokenType.EQUALS_EQUALS, - ">=": TokenType.GREATER_THAN_OR_EQUALS, - "<=": TokenType.LESS_THAN_OR_EQUALS, - "&&": TokenType.AND, - "||": TokenType.OR, - "!=": TokenType.NOT_EQUALS, - }; + private source: string; + private file: string; + protected line: number = 1; + protected offset: number = 0; // Offset global + protected lineOffset: number = 0; // Offset relative to current line + protected start: number = 0; + protected tokens: Token[] = []; + private static SINGLE_CHAR_TOKENS: { [key: string]: TokenType } = { + "+": TokenType.PLUS, + "-": TokenType.MINUS, + "*": TokenType.ASTERISK, + "/": TokenType.SLASH, + ">": TokenType.GREATER_THAN, + "<": TokenType.LESS_THAN, + ",": TokenType.COMMA, + ";": TokenType.SEMICOLON, + ":": TokenType.COLON, + "(": TokenType.LPAREN, + ")": TokenType.RPAREN, + "{": TokenType.LBRACE, + "}": TokenType.RBRACE, + ".": TokenType.DOT, + "%": TokenType.PERCENT, + "|": TokenType.PIPE, + "=": TokenType.EQUALS, + "[": TokenType.LBRACKET, + "]": TokenType.RBRACKET, + }; + private static MULTI_CHAR_TOKENS: { [key: string]: TokenType } = { + "++": TokenType.INCREMENT, + "--": TokenType.DECREMENT, + "**": TokenType.EXPONENTIATION, + "%%": TokenType.REMAINDER, + "==": TokenType.EQUALS_EQUALS, + ">=": TokenType.GREATER_THAN_OR_EQUALS, + "<=": TokenType.LESS_THAN_OR_EQUALS, + "&&": TokenType.AND, + "||": TokenType.OR, + "!=": TokenType.NOT_EQUALS, + }; + + public constructor(file: string, source: string) { + this.file = file; + this.source = source; + } + + public tokenize(): Token[] { + while (this.offset < this.source.length) { + this.start = this.offset - this.lineOffset; // Sets start to local offset + const char = this.source[this.offset]; + + if (char === "\n") { + this.line++; + this.offset++; + this.lineOffset = this.offset; + continue; + } - public constructor(file: string, source: string) { - this.file = file; - this.source = source; + if (char.trim() === "") { + this.offset++; + continue; + } + + if (char === "/") { + this.lexing_comment(); + continue; + } + + const muti = char.concat(this.source[this.offset + 1]); + const multiTokenType = Lexer + .MULTI_CHAR_TOKENS[ + muti + ]; + + if (multiTokenType) { + this.offset++; // skip second char + this.createToken(multiTokenType, muti); + continue; + } + + const singleTokenType = Lexer.SINGLE_CHAR_TOKENS[char]; + + if (singleTokenType) { + this.createToken(singleTokenType, char); + continue; + } + + // Checks if it is a string (unclosed string error) + if (char === '"') { + this.lexing_string(); + continue; + } + + // Check if it is a number (int, float or binary) + if (this.isDigit(char)) { + this.lexing_digit(); + continue; + } + + // Fernand0 + // print + if (this.isAlphaNumeric(char)) { + this.lexing_alphanumeric(); + continue; + } + + // If the character is not valid, it shows an error + ErrorReporter.showError( + `Invalid char '${char}'`, + this.getLocation(this.start, this.start + char.length), + ); + Deno.exit(1); } - public tokenize(): Token[] { - while (this.offset < this.source.length) { - this.start = this.offset - this.lineOffset; // Sets start to local offset - const char = this.source[this.offset]; - - if (char === "\n") { - this.line++; - this.offset++; - this.lineOffset = this.offset; - continue; - } - - if (char.trim() === "") { - this.offset++; - continue; - } - - if (char === "/") { - this.lexing_comment(); - continue; - } - - const muti = char.concat(this.source[this.offset + 1]); - const multiTokenType = Lexer - .MULTI_CHAR_TOKENS[ - muti - ]; - - if (multiTokenType) { - this.offset++; // skip second char - this.createToken(multiTokenType, muti); - continue; - } - - const singleTokenType = Lexer.SINGLE_CHAR_TOKENS[char]; - - if (singleTokenType) { - this.createToken(singleTokenType, char); - continue; - } - - // Checks if it is a string (unclosed string error) - if (char === '"') { - this.lexing_string(); - continue; - } - - // Check if it is a number (int, float or binary) - if (this.isDigit(char)) { - this.lexing_digit(); - continue; - } - - // Fernand0 - // print - if (this.isAlphaNumeric(char)) { - this.lexing_alphanumeric(); - continue; - } - - // If the character is not valid, it shows an error - ErrorReporter.showError( - `Invalid char '${char}'`, - this.getLocation(this.start, this.start + char.length), - ); - Deno.exit(1); - } - - this.createToken( - TokenType.EOF, - "\0", - ); - - return this.tokens; - } + this.createToken( + TokenType.EOF, + "\0", + ); - private lexing_alphanumeric(): void { - let id = ""; + return this.tokens; + } - while ( - this.offset < this.source.length && - this.isAlphaNumeric(this.source[this.offset]) - ) { - id += this.source[this.offset]; - this.offset++; - } + private lexing_alphanumeric(): void { + let id = ""; - if (Keywords[id] !== undefined) { - this.createToken( - Keywords[id], - id, - ); - } else { - this.createToken( - TokenType.IDENTIFIER, - id, - false, - ); - } + while ( + this.offset < this.source.length && + this.isAlphaNumeric(this.source[this.offset]) + ) { + id += this.source[this.offset]; + this.offset++; } - private lexing_digit(): void { - let number = ""; - - while ( - this.offset < this.source.length && - this.isDigit(this.source[this.offset]) - ) { - number += this.source[this.offset]; - this.offset++; - } - - // Check if is a float - if (this.source[this.offset] === ".") { - number += this.source[this.offset]; - this.offset++; - while ( - this.offset < this.source.length && - this.isDigit(this.source[this.offset]) - ) { - number += this.source[this.offset]; - this.offset++; - } - } - - // Check if is a binary - if (this.source[this.offset] === "b") { - number += this.source[this.offset]; - this.offset++; - while ( - this.offset < this.source.length && - this.isDigit(this.source[this.offset]) - ) { - number += this.source[this.offset]; - this.offset++; - } - } - - if (number.includes(".")) { - this.createToken( - TokenType.FLOAT, - Number(number), - false, - ); - return; - } - - if (number.includes("b")) { - this.createToken( - TokenType.BINARY, - number, - false, - ); - return; - } - - this.createToken( - TokenType.INT, - Number(number), - false, - ); + if (Keywords[id] !== undefined) { + this.createToken( + Keywords[id], + id, + ); + } else { + this.createToken( + TokenType.IDENTIFIER, + id, + false, + ); } + } - private lexing_string(): void { - let value = ""; - this.offset++; // jump " - - while ( - this.offset < this.source.length && - this.source[this.offset] !== '"' - ) { - if (this.source[this.offset] == "\n") { - break; - } - value += this.source[this.offset]; - this.offset++; - } - - if (this.source[this.offset] !== '"') { - ErrorReporter.showError( - "String not closed.", - this.getLocation( - this.start, - this.start + value.length + 1, - ), - ); - Deno.exit(1); - } + private lexing_digit(): void { + let number = ""; - this.createToken( - TokenType.STRING, - value, - ); + while ( + this.offset < this.source.length && + this.isDigit(this.source[this.offset]) + ) { + number += this.source[this.offset]; + this.offset++; } - private lexing_comment(): void { + // Check if is a float + if (this.source[this.offset] === ".") { + number += this.source[this.offset]; + this.offset++; + while ( + this.offset < this.source.length && + this.isDigit(this.source[this.offset]) + ) { + number += this.source[this.offset]; this.offset++; + } + } - if (this.source[this.offset] === "/") { - this.offset++; - while ( - this.offset < this.source.length && - this.source[this.offset] !== "\n" - ) { - this.offset++; - } - return; - } - - if (this.source[this.offset] === "*") { - this.offset++; - while ( - this.offset < this.source.length && - !(this.source[this.offset] === "*" && - this.source[this.offset + 1] === "/") - ) { - if (this.source[this.offset] === "\n") { - this.line++; - this.lineOffset = this.offset + 1; - } - this.offset++; - } - if (this.offset < this.source.length) { - this.offset += 2; - } else { - ErrorReporter.showError( - "Unclosed block comment", - this.getLocation(this.start, this.offset), - ); - Deno.exit(1); - } - return; - } + // Check if is a binary + if (this.source[this.offset] === "b") { + number += this.source[this.offset]; + this.offset++; + while ( + this.offset < this.source.length && + this.isDigit(this.source[this.offset]) + ) { + number += this.source[this.offset]; + this.offset++; + } + } - ErrorReporter.showError( - "Unexpected character after '/'", - this.getLocation(this.start, this.offset), - ); - Deno.exit(1); + if (number.includes(".")) { + this.createToken( + TokenType.FLOAT, + Number(number), + false, + ); + return; } - private isAlphaNumeric(token: string): boolean { - return /^[a-z0-9_]+$/i.test(token); + if (number.includes("b")) { + this.createToken( + TokenType.BINARY, + number, + false, + ); + return; } - private isDigit(char: string): boolean { - return /^[0-9]$/.test(char); + this.createToken( + TokenType.INT, + Number(number), + false, + ); + } + + private lexing_string(): void { + let value = ""; + this.offset++; // jump " + + while ( + this.offset < this.source.length && + this.source[this.offset] !== '"' + ) { + if (this.source[this.offset] == "\n") { + break; + } + value += this.source[this.offset]; + this.offset++; } - private getLocation(start: number, end: number): Loc { - return { - file: this.file, - line: this.line, - start, - end, - line_string: this.getLineText(this.line), - }; + if (this.source[this.offset] !== '"') { + ErrorReporter.showError( + "String not closed.", + this.getLocation( + this.start, + this.start + value.length + 1, + ), + ); + Deno.exit(1); } - private createToken( - kind: TokenType, - value: NativeValue, - jump: boolean = true, - ): Token { - const token: Token = { - kind, - value, - loc: this.getLocation( - this.start, - this.start + (String(value).length ?? 0), - ), - }; - this.tokens.push(token); - if (jump) this.offset++; - return token; + this.createToken( + TokenType.STRING, + value, + ); + } + + private lexing_comment(): void { + this.offset++; + + if (this.source[this.offset] === "/") { + this.offset++; + while ( + this.offset < this.source.length && + this.source[this.offset] !== "\n" + ) { + this.offset++; + } + return; } - private getLineText(line: number): string { - const lines = this.source.split("\n"); - return lines[line - 1] || ""; + if (this.source[this.offset] === "*") { + this.offset++; + while ( + this.offset < this.source.length && + !(this.source[this.offset] === "*" && + this.source[this.offset + 1] === "/") + ) { + if (this.source[this.offset] === "\n") { + this.line++; + this.lineOffset = this.offset + 1; + } + this.offset++; + } + if (this.offset < this.source.length) { + this.offset += 2; + } else { + ErrorReporter.showError( + "Unclosed block comment", + this.getLocation(this.start, this.offset), + ); + Deno.exit(1); + } + return; } + + ErrorReporter.showError( + "Unexpected character after '/'", + this.getLocation(this.start, this.offset), + ); + Deno.exit(1); + } + + private isAlphaNumeric(token: string): boolean { + return /^[a-z0-9_]+$/i.test(token); + } + + private isDigit(char: string): boolean { + return /^[0-9]$/.test(char); + } + + private getLocation(start: number, end: number): Loc { + return { + file: this.file, + line: this.line, + start, + end, + line_string: this.getLineText(this.line), + }; + } + + private createToken( + kind: TokenType, + value: NativeValue, + jump: boolean = true, + ): Token { + const token: Token = { + kind, + value, + loc: this.getLocation( + this.start, + this.start + (String(value).length ?? 0), + ), + }; + this.tokens.push(token); + if (jump) this.offset++; + return token; + } + + private getLineText(line: number): string { + const lines = this.source.split("\n"); + return lines[line - 1] || ""; + } } diff --git a/src/frontend/Token.ts b/src/frontend/Token.ts index 03069c4..61155ac 100644 --- a/src/frontend/Token.ts +++ b/src/frontend/Token.ts @@ -1,92 +1,97 @@ export enum TokenType { - // Keywords - NEW, // new x = EXPR 0 - MUT, // new mut x = EXPR 1 - IF, // if 2 - ELIF, // } elif () { 44 - ELSE, // else 3 - FOR, // for 4 - WHILE, // while 5 - FN, // fn x() {} 6 - RETURN, // return EXPR 7 - IMPORT, // import x 8 - AS, // import x as y 9 + // Keywords + NEW, // new x = EXPR 0 + MUT, // new mut x = EXPR 1 + IF, // if 2 + ELIF, // } elif () { 44 + ELSE, // else 3 + FOR, // for 4 + WHILE, // while 5 + FN, // fn x() {} 6 + RETURN, // return EXPR 7 + IMPORT, // import x 8 + AS, // import x as y 9 + BREAK, // break 44 - IDENTIFIER, // omg 10 + IDENTIFIER, // omg 10 - // Types - STRING, // "omg" 11 - INT, // 10 12 - FLOAT, // 10.1 13 - NULL, // null 14 + // Types + STRING, // "omg" 11 + INT, // 10 12 + FLOAT, // 10.1 13 + NULL, // null 14 - // Especials - BINARY, // 15 + // Especials + BINARY, // 15 - // Symbols - EQUALS, // = 16 - PLUS, // + 17 - INCREMENT, // ++ 18 - MINUS, // - 19 - DECREMENT, // -- 20 - SLASH, // / 21 - ASTERISK, // * 22 - EXPONENTIATION, // ** 23 - PERCENT, // % 24 - REMAINDER, // %% 25 - EQUALS_EQUALS, // == 26 - NOT_EQUALS, // != 27 - GREATER_THAN, // > 28 - LESS_THAN, // < 29 - GREATER_THAN_OR_EQUALS, // >= 30 - LESS_THAN_OR_EQUALS, // <= 31 - AND, // && 32 - OR, // || 33 - PIPE, // | // new x: | = 34 - COMMA, // , 35 - COLON, // : 36 - SEMICOLON, // ; 37 - DOT, // . 38 - LPAREN, // ( 39 - RPAREN, // ) 40 - LBRACE, // { 41 - RBRACE, // } 42 + // Symbols + EQUALS, // = 16 + PLUS, // + 17 + INCREMENT, // ++ 18 + MINUS, // - 19 + DECREMENT, // -- 20 + SLASH, // / 21 + ASTERISK, // * 22 + EXPONENTIATION, // ** 23 + PERCENT, // % 24 + REMAINDER, // %% 25 + EQUALS_EQUALS, // == 26 + NOT_EQUALS, // != 27 + GREATER_THAN, // > 28 + LESS_THAN, // < 29 + GREATER_THAN_OR_EQUALS, // >= 30 + LESS_THAN_OR_EQUALS, // <= 31 + AND, // && 32 + OR, // || 33 + PIPE, // | // new x: | = 34 + COMMA, // , 35 + COLON, // : 36 + SEMICOLON, // ; 37 + DOT, // . 38 + LPAREN, // ( 39 + RPAREN, // ) 40 + LBRACE, // { 41 + RBRACE, // } 42 + LBRACKET, // [ 43 + RBRACKET, // ] 44 - EOF, // EndOfFile 43 + EOF, // EndOfFile 45 } export type NativeValue = - | string - | boolean - | number - | null - | void - | CallableFunction; + | string + | boolean + | number + | null + | void + | object + | CallableFunction; export interface Loc { - file: string; - line: number; - line_string: string; - start: number; - end: number; + file: string; + line: number; + line_string: string; + start: number; + end: number; } export type Token = { - kind: TokenType; - value: NativeValue; // It already comes with the typed value - loc: Loc; + kind: TokenType; + value: NativeValue; // It already comes with the typed value + loc: Loc; }; export const Keywords: Record = { - "new": TokenType.NEW, - "mut": TokenType.MUT, - "if": TokenType.IF, - "else": TokenType.ELSE, - "elif": TokenType.ELIF, - "fn": TokenType.FN, - "return": TokenType.RETURN, - "for": TokenType.FOR, - "while": TokenType.WHILE, - "import": TokenType.IMPORT, - "as": TokenType.AS, + "new": TokenType.NEW, + "mut": TokenType.MUT, + "if": TokenType.IF, + "else": TokenType.ELSE, + "elif": TokenType.ELIF, + "fn": TokenType.FN, + "return": TokenType.RETURN, + "for": TokenType.FOR, + "while": TokenType.WHILE, + "import": TokenType.IMPORT, + "as": TokenType.AS, + "break": TokenType.BREAK, }; diff --git a/src/repl.ts b/src/repl.ts new file mode 100644 index 0000000..1aaa25b --- /dev/null +++ b/src/repl.ts @@ -0,0 +1,66 @@ +import { Program } from "./backend/AST.ts"; +import Parser from "./backend/Parser.ts"; +import { Lexer } from "./frontend/Lexer.ts"; +import { Token } from "./frontend/Token.ts"; +import Context, { define_env } from "./runtime/context/Context.ts"; +import Runtime from "./runtime/Runtime.ts"; +import { Colorize } from "./utils/Colorize.ts"; + +const VERSION = "v0.1.0"; +const context: Context = define_env(new Context()); + +export function repl(): void { + console.log( + Colorize.bold( + `Farpy ${Colorize.underline(Colorize.blue(VERSION))} - REPL`, + ), + ); + + let inputBuffer = ""; + let balance = 0; + while (true) { + const promptSymbol = inputBuffer + ? ".".repeat(Math.max((balance * 3) == 0 ? 3 : balance * 3, 1)) + : ">"; + const line: string = prompt(promptSymbol) ?? ""; + + if (!inputBuffer && line.trim() === "exit") { + console.log("Bye"); + break; + } + + inputBuffer += line + "\n"; + + for (const char of line) { + if (char === "{") { + balance++; + } else if (char === "}") { + balance--; + } + } + + if (balance < 0) { + balance = 0; + } + + if (balance > 0) continue; + + const code = inputBuffer.trim(); + if (code) { + try { + const lexer: Lexer = new Lexer("repl", code); + const tokens: Token[] = lexer.tokenize(); + const parser: Parser = new Parser(tokens); + const program: Program = parser.parse(); + const runtime: Runtime = new Runtime(context); + runtime.evaluate(program); + } catch (error: any) { + console.log(error); + console.error("Error processing code:", error); + } + } + inputBuffer = ""; + balance = 0; + } + Deno.exit(0); +} diff --git a/src/runtime/Runtime.ts b/src/runtime/Runtime.ts index 2f78b2c..5a75ee7 100644 --- a/src/runtime/Runtime.ts +++ b/src/runtime/Runtime.ts @@ -1,16 +1,23 @@ import { AssignmentDeclaration, + AST_IDENTIFIER, BinaryExpr, BinaryLiteral, + BreakStatement, CallExpr, DecrementExpr, ElifStatement, ElseStatement, + Expr, + ForStatement, FunctionDeclaration, Identifier, IfStatement, + ImportStatement, IncrementExpr, LambdaExpr, + MemberCallExpr, + MemberExpr, Program, Stmt, VarDeclaration, @@ -22,10 +29,12 @@ import { IntValue, NullValue, RuntimeValue, + VALUE_ARRAY, VALUE_BOOL, VALUE_FLOAT, VALUE_INT, VALUE_NULL, + VALUE_OBJECT, VALUE_STRING, } from "./Values.ts"; import VarDeclarationRuntime from "./declarations/VarDeclarationRuntime.ts"; @@ -40,6 +49,11 @@ import FunctionDeclarationRuntime from "./declarations/FunctionDeclarationRuntim import { IfStatementRuntime } from "./statements/IfStatementRuntime.ts"; import { ElseStatementRuntime } from "./statements/ElseStatementRuntime.ts"; import { LambdaExpressionRuntime } from "./expressions/LambdaExpressionRuntime.ts"; +import { MemberCallExpressionRuntime } from "./expressions/MemberCallExpressionRuntime.ts"; +import { ImportStatementRuntime } from "./statements/ImportStatementRuntime.ts"; +import { MemberExpressionRuntime } from "./expressions/MemberExpressionRuntime.ts"; +import { ForStatementRuntime } from "./statements/ForStatementRuntime.ts"; +import { BreakStatementRuntime } from "./statements/BreakStatementRuntime.ts"; export default class Runtime { private context: Context; @@ -62,6 +76,19 @@ export default class Runtime { return VALUE_NULL(null, stmt.loc); case "StringLiteral": return VALUE_STRING(stmt.value, stmt.loc); + case "ArrayLiteral": + return VALUE_ARRAY( + stmt.value.map((item: Expr) => this.evaluate(item)), + stmt.loc, + ); + case "ObjectLiteral": { + const newMap = new Map(); + for (const [key, value] of stmt.value.entries()) { + const evaluatedValue = this.evaluate(value as Stmt); + newMap.set(key, evaluatedValue); + } + return VALUE_OBJECT(newMap, stmt.loc); + } case "BinaryExpr": return this.evaluate_binary_expr(stmt as BinaryExpr); case "ReturnStatement": { @@ -87,6 +114,18 @@ export default class Runtime { this.context, this, ); + case "MemberCallExpr": + return MemberCallExpressionRuntime.evaluate( + stmt as MemberCallExpr, + this.context, + this, + ); + case "MemberExpr": + return MemberExpressionRuntime.evaluate( + stmt as MemberExpr, + this.context, + this, + ); case "AssignmentDeclaration": return AssignmentDeclarationRuntime.evaluate( stmt as AssignmentDeclaration, @@ -129,6 +168,24 @@ export default class Runtime { this.context, this, ); + case "ImportStatement": + return ImportStatementRuntime.evaluate( + stmt as ImportStatement, + this.context, + this, + ); + case "ForStatement": + return ForStatementRuntime.evaluate( + stmt as ForStatement, + this.context, + this, + ); + case "BreakStatement": + return BreakStatementRuntime.evaluate( + stmt as BreakStatement, + this.context, + this, + ); case "LambdaExpr": return LambdaExpressionRuntime.evaluate( stmt as LambdaExpr, @@ -288,13 +345,15 @@ export default class Runtime { loc: binary.loc, ret: true, } as BooleanValue; - case "<": + case "<": { + // console.log(Number(left.value) < Number(right.value)); return { type: "bool", value: Number(left.value) < Number(right.value), loc: binary.loc, ret: true, } as BooleanValue; + } case ">=": return { type: "bool", diff --git a/src/runtime/Type.ts b/src/runtime/Type.ts new file mode 100644 index 0000000..b88ca07 --- /dev/null +++ b/src/runtime/Type.ts @@ -0,0 +1,6 @@ +import { IntValue, RuntimeValue } from "./Values.ts"; + +export default abstract class Type { + abstract length(): IntValue; + abstract at(i: IntValue): RuntimeValue; +} diff --git a/src/runtime/Values.ts b/src/runtime/Values.ts index ef439a0..203df3a 100644 --- a/src/runtime/Values.ts +++ b/src/runtime/Values.ts @@ -2,148 +2,196 @@ import { Expr as _Expr, Identifier, Stmt } from "../backend/AST.ts"; import { Loc, NativeValue } from "../frontend/Token.ts"; import Context from "./context/Context.ts"; +// export interface GenericType { +// base: string; // ex: "lambda", "list", etc. +// typeParams: TypesNative[]; +// } + +export interface IParsedTypes { + types: TypesNative[]; +} + export type TypesNative = - | "string" - | "id" - | "int" - | "bool" - | "null" - | "float" - | "native-fn" - | "function" - | "binary" - | "void" - | "lambda" - | FunctionType; + | "string" + | "id" + | "int" + | "bool" + | "null" + | "float" + | "native-fn" + | "function" + | "binary" + | "void" + | "lambda" + | "object" + | "map" + | "array" + | FunctionType; +// GenericType; export const TypesNativeArray: string[] = [ - "string", - "id", - "int", - "bool", - "null", - "float", - "native-fn", - "function", - "binary", - "void", - "lambda", - "FunctionType", + "string", + "id", + "int", + "bool", + "null", + "float", + "native-fn", + "function", + "binary", + "void", + "lambda", + "object", + "map", + "array", + "FunctionType", ]; export interface RuntimeValue { - type?: TypesNative | TypesNative[]; - value: NativeValue; - ret: boolean; // Para controle de fluxo - loc: Loc; + kind?: string; + type?: TypesNative | TypesNative[]; + value: NativeValue; + ret: boolean; // Para controle de fluxo + loc: Loc; } export interface NullValue extends RuntimeValue { - type: "null"; - value: null; + type: "null"; + value: null; } export function VALUE_NULL(n: null = null, loc: Loc): NullValue { - return { type: "null", value: n, loc: loc, ret: false }; + return { type: "null", value: n, loc: loc, ret: false }; } export interface IntValue extends RuntimeValue { - type: "int"; - value: number; + type: "int"; + value: number; } export function VALUE_INT(n: number = 0, loc: Loc): IntValue { - return { type: "int", value: n, loc: loc, ret: false }; + return { type: "int", value: n, loc: loc, ret: false }; } export interface FloatValue extends RuntimeValue { - type: "float"; - value: number; + type: "float"; + value: number; } export function VALUE_FLOAT(n: number = 0, loc: Loc): FloatValue { - return { type: "float", value: n, loc: loc, ret: false }; + return { type: "float", value: n, loc: loc, ret: false }; } export interface StringValue extends RuntimeValue { - type: "string"; - value: string; + type: "string"; + value: string; } export function VALUE_STRING(n: string = "error", loc: Loc): StringValue { - return { type: "string", value: n, loc: loc, ret: false }; + return { type: "string", value: n, loc: loc, ret: false }; } export interface BooleanValue extends RuntimeValue { - type: "bool"; - value: boolean; + type: "bool"; + value: boolean; } export function VALUE_BOOL(n: boolean = false, loc: Loc): BooleanValue { - return { type: "bool", value: n, loc: loc, ret: false }; + return { type: "bool", value: n, loc: loc, ret: false }; } export interface VoidValue extends RuntimeValue { - type: "void"; - value: "void"; + type: "void"; + value: "void"; } export function VALUE_VOID(loc: Loc): VoidValue { - return { type: "void", value: "void", loc: loc, ret: false }; + return { type: "void", value: "void", loc: loc, ret: false }; } export interface VarDeclarationValue { - types: TypesNative[]; - value: RuntimeValue; + kind: "id"; + types: TypesNative[]; + value: RuntimeValue; } export type FunctionType = { - params: TypesNative[]; - return: TypesNative; + params: TypesNative[]; + return: TypesNative; }; export interface ArgsValue { - type: TypesNative[] | TypesNative; // Lista dos tipos esperados para cada parâmetro fixo - id?: Identifier; // Se definido, indica que a partir do último parâmetro todos devem ser deste tipo + type: TypesNative[] | TypesNative; // Lista dos tipos esperados para cada parâmetro fixo + id?: Identifier; // Se definido, indica que a partir do último parâmetro todos devem ser deste tipo } export type FunctionCall = ( - args: RuntimeValue[], - context: Context, + args: RuntimeValue[], + context: Context, ) => RuntimeValue; export interface NativeFnValue extends RuntimeValue { - kind: "native-fn"; - call: FunctionCall; + kind: "native-fn"; + call: FunctionCall; } export function NATIVE_FN(call: FunctionCall) { - return { kind: "native-fn", call } as NativeFnValue; + return { kind: "native-fn", call } as NativeFnValue; } export interface FunctionNativeDeclarationValue extends RuntimeValue { - kind: "native-fn"; - infinity: boolean; - args: ArgsValue[]; - type: TypesNative[]; - context: Context; - fn: NativeFnValue; + kind: "native-fn"; + infinity: boolean; + args: ArgsValue[]; + type: TypesNative[]; + context: Context; + fn: NativeFnValue; } export interface FunctionValue extends RuntimeValue { - kind: "function"; - id: Identifier; - type: TypesNative[]; - context: Context; - args: ArgsValue[]; - body: Stmt[]; + kind: "function"; + id: Identifier; + type: TypesNative[]; + context: Context; + args: ArgsValue[]; + body: Stmt[]; } export interface LambdaValue extends RuntimeValue { - kind: "lambda"; - type: TypesNative[]; - context: Context; - args: ArgsValue[]; - body: Stmt[]; - externalVars: Identifier[]; + kind: "lambda"; + type: TypesNative[]; + context: Context; + args: ArgsValue[]; + body: Stmt[]; + externalVars: Identifier[]; +} + +export interface ImportStatementValue extends RuntimeValue { + kind: "native-fn"; // + values: Map[]; +} + +export interface MapValue extends RuntimeValue { + type: "map"; + map: Map; +} + +export interface ArrayValue extends RuntimeValue { + type: "array"; + value: RuntimeValue[]; +} + +export function VALUE_ARRAY(n: RuntimeValue[] = [], loc: Loc): ArrayValue { + return { type: "array", value: n, loc: loc, ret: false }; +} + +export interface ObjectValue extends RuntimeValue { + type: "object"; + value: Map; +} +export function VALUE_OBJECT( + obj: Map = new Map(), + loc: Loc, +): ObjectValue { + return { type: "object", value: obj, loc: loc, ret: false }; } diff --git a/src/runtime/class_types/MapType.ts b/src/runtime/class_types/MapType.ts new file mode 100644 index 0000000..1657193 --- /dev/null +++ b/src/runtime/class_types/MapType.ts @@ -0,0 +1,85 @@ +import { Identifier } from "../../backend/AST.ts"; +import { ErrorReporter } from "../../error/ErrorReporter.ts"; +import { Loc } from "../../frontend/Token.ts"; +import Runtime from "../Runtime.ts"; +import Type from "../Type.ts"; +import { VALUE_BOOL, VALUE_VOID, VoidValue } from "../Values.ts"; +import { BooleanValue } from "../Values.ts"; +import { + IntValue, + MapValue, + StringValue, + VALUE_INT, + VALUE_STRING, +} from "../Values.ts"; + +export default class MapType extends Type { + public constructor( + private expr: MapValue, + private self: Runtime, + ) { + super(); + } + + public length(): IntValue { + return VALUE_INT(this.expr.map.size, this.expr.loc); + } + + public at(i: IntValue): StringValue { + const value = this.expr.map.get(i.value); + return VALUE_STRING(String(value ?? "null"), this.expr.loc); + } + + public has(i: IntValue | StringValue | Identifier): BooleanValue { + const key = i.kind == "Identifier" + ? this.self.evaluate(i as Identifier).value + : i.value; + + const value = this.expr.map.has(key); + return VALUE_BOOL(Boolean(value ?? false), this.expr.loc); + } + + public get( + i: IntValue | StringValue | Identifier, + ): StringValue | IntValue | VoidValue { + const key = i.kind == "Identifier" + ? this.self.evaluate(i as Identifier).value + : i.value; + + if (!this.expr.map.has(key)) { + return VALUE_VOID({} as Loc); + // ErrorReporter.showError( + // `Key '${key}' does not exist in the map.`, + // this.expr.loc, + // ); + // Deno.exit(); + } + + const value = this.expr.map.get(key); + + if (typeof value === "number") { + return VALUE_INT(value, this.expr.loc); + } else if (typeof value === "string") { + return VALUE_STRING(value, this.expr.loc); + } else { + ErrorReporter.showError( + `Invalid value type for key '${key}'.`, + this.expr.loc, + ); + Deno.exit(); + } + } + + public set(i: IntValue | StringValue | Identifier, newValue: any): any { + const key = i.kind == "Identifier" + ? this.self.evaluate(i as Identifier).value + : i.value; + + if (newValue && typeof newValue === "object" && "kind" in newValue) { + newValue = this.self.evaluate(newValue); + } + + this.expr.map.set(key, newValue.value); + return newValue; + } +} diff --git a/src/runtime/class_types/StringType.ts b/src/runtime/class_types/StringType.ts new file mode 100644 index 0000000..6c60347 --- /dev/null +++ b/src/runtime/class_types/StringType.ts @@ -0,0 +1,34 @@ +import { ErrorReporter } from "../../error/ErrorReporter.ts"; +import Runtime from "../Runtime.ts"; +import Type from "../Type.ts"; +import { IntValue, StringValue, VALUE_INT, VALUE_STRING } from "../Values.ts"; + +export default class StringType extends Type { + public constructor( + private expr: StringValue, + private self: Runtime, + ) { + super(); + } + + public length(): IntValue { + return VALUE_INT(this.expr.value.length, this.expr.loc); + } + + public at(i: any): StringValue { + const value = this.self.evaluate(i); + + if (value.type != "int") { + ErrorReporter.showError( + "The value passed in at() must be an integer.", + value.loc, + ); + Deno.exit(); + } + + return VALUE_STRING( + this.expr.value[value.value as number], + this.expr.loc, + ); + } +} diff --git a/src/runtime/context/Context.ts b/src/runtime/context/Context.ts index d65bca6..8ab30a5 100644 --- a/src/runtime/context/Context.ts +++ b/src/runtime/context/Context.ts @@ -4,17 +4,19 @@ import { FunctionNativeDeclarationValue, FunctionValue, NATIVE_FN, + RuntimeValue, TypesNative, VALUE_BOOL, - VALUE_FLOAT, - VALUE_INT, VALUE_NULL, - VALUE_STRING, VALUE_VOID, VarDeclarationValue, } from "../Values.ts"; import { ErrorReporter } from "../../error/ErrorReporter.ts"; import { Loc } from "../../frontend/Token.ts"; +import IoModule from "../modules/IoModule.ts"; +import MathModule from "../modules/MathModule.ts"; +import UtilsModule from "../modules/UtilsModule.ts"; +import MapModule from "../modules/MapModules.ts"; export function define_env(context: Context): Context { const loc = {} as Loc; @@ -47,26 +49,6 @@ export function define_env(context: Context): Context { true, ); - // Runtime version - context.new_var( - AST_IDENTIFIER("RUNTIME_VERSION", loc), - { - types: ["string"], - value: VALUE_STRING("0.1.0", loc), - } as VarDeclarationValue, - true, - ); - - // PI - context.new_var( - AST_IDENTIFIER("PI", loc), - { - types: ["float"], - value: VALUE_FLOAT(Math.PI, loc), - } as VarDeclarationValue, - true, - ); - // print(x, y, ...) context.new_function( AST_IDENTIFIER("print", loc), @@ -85,51 +67,58 @@ export function define_env(context: Context): Context { } as FunctionNativeDeclarationValue, ); - // sum(x, y) - // context.new_function( - // AST_IDENTIFIER("sum", loc), - // { - // kind: "native-fn", - // infinity: false, - // args: [ - // { type: ["int", "float"] }, - // { type: ["int", "float"] }, - // ] as ArgsValue[], - // type: ["int", "float"] as TypesNative[], - // context: new Context(context, true), - // fn: NATIVE_FN((args, _scope) => { - // const r = Number(args[0]?.value) + Number(args[1]?.value); - // return VALUE_INT(r, loc); - // }), - // } as FunctionNativeDeclarationValue, - // ); - - // fib(x) + // dd(x, y, ...) context.new_function( - AST_IDENTIFIER("fib", loc), + AST_IDENTIFIER("dd", loc), { kind: "native-fn", - infinity: false, - args: [{ type: ["int"] }] as ArgsValue[], - type: ["int"] as TypesNative[], + infinity: true, + args: [] as ArgsValue[], + type: ["null"] as TypesNative[], context: new Context(context, true), fn: NATIVE_FN((args, _scope) => { - const fib = (n: number): number => { - if (n <= 1) return n; - return fib(n - 1) + fib(n - 2); - }; - const n = Number(args[0]?.value); - return VALUE_INT(fib(n), loc); + for (const arg of args) { + console.log(arg); + } + return VALUE_NULL(null, loc); }), } as FunctionNativeDeclarationValue, ); + context.new_module( + AST_IDENTIFIER("math", loc), + new Map() + .set("PI", MathModule.PI()) + .set("fibonacci", MathModule.fibonacci(context)), + ); + + context.new_module( + AST_IDENTIFIER("io", loc), + new Map() + .set("readline", IoModule.readline(context)) + .set("format", IoModule.format(context)), + ); + + context.new_module( + AST_IDENTIFIER("utils", loc), + new Map() + .set("toInt", UtilsModule.toInt(context)), + ); + + context.new_module( + AST_IDENTIFIER("map", loc), + new Map() + .set("Map", MapModule.Map(context)), + ); + return context; } export default class Context { private parent?: Context; private variables: Map = new Map(); + private modules: Map> = new Map(); + private alias: Map = new Map(); private functions: Map< string, FunctionValue | FunctionNativeDeclarationValue @@ -171,6 +160,46 @@ export default class Context { return value; } + public new_module( + module: Identifier, + value: Map, + ): RuntimeValue { + if (this.constants.has(module.value)) { + ErrorReporter.showError( + `Module redeclaration '${module.value}'.`, + module.loc, + ); + Deno.exit(); + } + + this.modules.set(module.value, value); + return VALUE_VOID(module.loc); + } + + public new_alias( + alias: Identifier, + module: Identifier, + ): RuntimeValue { + if (!this.modules.has(module.value)) { + ErrorReporter.showError( + `Module is not exist '${module.value}'.`, + module.loc, + ); + Deno.exit(); + } + + if (this.alias.has(alias.value)) { + ErrorReporter.showError( + `Alias ​​already exists '${alias.value}'.`, + alias.loc, + ); + Deno.exit(); + } + + this.alias.set(alias.value, module.value); + return VALUE_VOID(alias.loc); + } + public new_function( name: Identifier, value: FunctionValue | FunctionNativeDeclarationValue, @@ -234,6 +263,22 @@ export default class Context { this.parent?.look_up_const(var_name) || undefined; } + public look_up_alias( + alias: Identifier, + ): Map | undefined { + const alias_get = this.alias.get(alias.value); + + return this.modules.get(alias_get ?? "") || + this.parent?.look_up_module(alias_get ?? "") || undefined; + } + + private look_up_module( + module: string, + ): Map | undefined { + return this.modules.get(module) || + this.parent?.look_up_module(module) || undefined; + } + public look_up_function( name: string, ): FunctionValue | FunctionNativeDeclarationValue | undefined { diff --git a/src/runtime/declarations/AssignmentDeclarationRuntime.ts b/src/runtime/declarations/AssignmentDeclarationRuntime.ts index 108cbb4..8f5d78b 100644 --- a/src/runtime/declarations/AssignmentDeclarationRuntime.ts +++ b/src/runtime/declarations/AssignmentDeclarationRuntime.ts @@ -2,39 +2,40 @@ import { AssignmentDeclaration as AD } from "../../backend/AST.ts"; import { ErrorReporter } from "../../error/ErrorReporter.ts"; import Context from "../context/Context.ts"; import Runtime from "../Runtime.ts"; -import { RuntimeValue, TypesNative } from "../Values.ts"; +import { RuntimeValue, TypesNative, VarDeclarationValue } from "../Values.ts"; export default class AssignmentDeclarationRuntime { - public static evaluate( - stmt: AD, - context: Context, - self: Runtime, - ): RuntimeValue { - const var_ = context.look_up_var(stmt.id.value); + public static evaluate( + stmt: AD, + context: Context, + self: Runtime, + ): RuntimeValue { + const var_ = context.look_up_var(stmt.id.value); - if (var_) { - const value: RuntimeValue = self.evaluate(stmt.value); - if (self.validateType(value, var_.types as TypesNative[])) { - context.assign_var( - stmt.id, - { types: var_.types, value: value }, - ); - return value; - } - } - - if (context.look_up_const(stmt.id.value)) { - ErrorReporter.showError( - `Constant redeclaration '${stmt.id.value}'.`, - stmt.loc, - ); - Deno.exit(); - } - - ErrorReporter.showError( - `Variable does not exist, so it cannot be updated '${stmt.id.value}'.`, - stmt.loc, + if (var_) { + const value: RuntimeValue = self.evaluate(stmt.value); + if (self.validateType(value, var_.types as TypesNative[])) { + context.assign_var( + stmt.id, + { types: var_.types, value: value } as VarDeclarationValue, ); - Deno.exit(); + value.ret = false; + return value; + } } + + if (context.look_up_const(stmt.id.value)) { + ErrorReporter.showError( + `Constant redeclaration '${stmt.id.value}'.`, + stmt.loc, + ); + Deno.exit(); + } + + ErrorReporter.showError( + `Variable does not exist, so it cannot be updated '${stmt.id.value}'.`, + stmt.loc, + ); + Deno.exit(); + } } diff --git a/src/runtime/declarations/VarDeclarationRuntime.ts b/src/runtime/declarations/VarDeclarationRuntime.ts index 3d09cbb..f47482f 100644 --- a/src/runtime/declarations/VarDeclarationRuntime.ts +++ b/src/runtime/declarations/VarDeclarationRuntime.ts @@ -1,23 +1,43 @@ import { VarDeclaration as VD } from "../../backend/AST.ts"; +import { ErrorReporter } from "../../error/ErrorReporter.ts"; import Context from "../context/Context.ts"; import Runtime from "../Runtime.ts"; -import { RuntimeValue, VarDeclarationValue } from "../Values.ts"; +import { RuntimeValue, TypesNative, VarDeclarationValue } from "../Values.ts"; export default class VarDeclarationRuntime { - public static evaluate( - stmt: VD, - context: Context, - self: Runtime, - ): RuntimeValue { - const value: RuntimeValue = self.evaluate(stmt.value); - context.new_var( - stmt.id, - { - types: stmt.type, - value: value, - } as VarDeclarationValue, - stmt.constant, - ); - return value; + public static evaluate( + stmt: VD, + context: Context, + self: Runtime, + ): RuntimeValue { + // console.log(stmt); + + if ( + context.look_up_alias(stmt.id) != undefined + ) { + ErrorReporter.showError( + `Your variable or constant has the same name as a module's alias, correct it.`, + stmt.id.loc, + ); + Deno.exit(); + } + + const value: RuntimeValue = self.evaluate(stmt.value); + value.ret = false; + + if (!self.validateType(value, stmt.type as TypesNative[])) { + return value; } + + context.new_var( + stmt.id, + { + types: stmt.type, + value: value, + } as VarDeclarationValue, + stmt.constant, + ); + + return value; + } } diff --git a/src/runtime/expressions/CallExpressionRuntime.ts b/src/runtime/expressions/CallExpressionRuntime.ts index 2b6eb2b..ed1b449 100644 --- a/src/runtime/expressions/CallExpressionRuntime.ts +++ b/src/runtime/expressions/CallExpressionRuntime.ts @@ -1,12 +1,13 @@ import Context from "../context/Context.ts"; import { - FunctionNativeDeclarationValue, - FunctionValue, - LambdaValue, - NativeFnValue as _NativeFnValue, - RuntimeValue, - TypesNative, - VALUE_NULL, + FunctionNativeDeclarationValue, + FunctionValue, + LambdaValue, + NativeFnValue as _NativeFnValue, + RuntimeValue, + TypesNative, + VALUE_NULL, + VarDeclarationValue, } from "../Values.ts"; import { CallExpr } from "../../backend/AST.ts"; import Runtime from "../Runtime.ts"; @@ -14,216 +15,221 @@ import { ErrorReporter } from "../../error/ErrorReporter.ts"; import { Loc } from "../../frontend/Token.ts"; export default class CallExpressionRuntime { - public static evaluate( - stmt: CallExpr, - context: Context, - _self: Runtime, - ): RuntimeValue { - const fn = context.look_up_function(stmt.id.value); - if (!fn) { - const lambda = context.look_up_var(stmt.id.value) ?? - context.look_up_const(stmt.id.value); - - if (lambda !== undefined) { - return this.evaluate_lambda( - stmt, - lambda.value as LambdaValue, - _self, - ); - } - - ErrorReporter.showError( - `Function does not exist '${stmt.id.value}'.`, - stmt.loc, - ); - Deno.exit(); - } + public static evaluate( + stmt: CallExpr, + context: Context, + self: Runtime, + ): RuntimeValue { + const fn = context.look_up_function(stmt.id.value); + if (!fn) { + const lambda = context.look_up_var(stmt.id.value) ?? + context.look_up_const(stmt.id.value); + + if (lambda !== undefined) { + return this.evaluate_lambda( + stmt, + lambda.value as LambdaValue, + self, + ); + } - if (fn.kind === "native-fn") { - return this.evaluate_function_native(stmt, fn, _self); - } else if (fn.kind === "function") { - return this.evaluate_function(stmt, fn, _self); - } + ErrorReporter.showError( + `Function does not exist '${stmt.id.value}'.`, + stmt.loc, + ); + Deno.exit(); + } - ErrorReporter.showError( - `Erro.`, - stmt.loc, - ); - Deno.exit(1); + if (fn.kind === "native-fn") { + return this.evaluate_function_native(stmt, fn, self); + } else if (fn.kind === "function") { + return this.evaluate_function(stmt, fn, self); } - private static evaluate_function_native( - stmt: CallExpr, - fn: FunctionNativeDeclarationValue, - _self: Runtime, - ): RuntimeValue { - const args = stmt.args.map((arg) => _self.evaluate(arg)); - - if (!fn.infinity) { - if (stmt.args.length !== fn.args.length) { - ErrorReporter.showError( - `The number of arguments passed does not match the expected number in the function.`, - stmt.loc, - ); - Deno.exit(); - } - - for (let i = 0; i < args.length; i++) { - const expectedParam = fn.args[i]; - const passedArg = args[i]; - - if ( - !(expectedParam.type as TypesNative[]).includes( - passedArg.type as TypesNative, - ) - ) { - ErrorReporter.showError( - `Type mismatch: expected '${expectedParam.type}' but got '${passedArg.type}'.`, - passedArg.loc, - ); - Deno.exit(1); - } - } + ErrorReporter.showError( + `Erro.`, + stmt.loc, + ); + Deno.exit(1); + } + + private static evaluate_function_native( + stmt: CallExpr, + fn: FunctionNativeDeclarationValue, + self: Runtime, + ): RuntimeValue { + const args = stmt.args.map((arg) => self.evaluate(arg)); + + if (!fn.infinity) { + if (stmt.args.length !== fn.args.length) { + ErrorReporter.showError( + `The number of arguments passed does not match the expected number in the function.`, + stmt.loc, + ); + Deno.exit(); + } + + for (let i = 0; i < args.length; i++) { + const expectedParam = fn.args[i]; + const passedArg = args[i]; + + if ( + !(expectedParam.type as TypesNative[]).includes( + passedArg.type as TypesNative, + ) + ) { + ErrorReporter.showError( + `Type mismatch: expected '${expectedParam.type}' but got '${passedArg.type}'.`, + passedArg.loc, + ); + Deno.exit(1); } + } + } - return fn.fn.call(args, fn.context); + return fn.fn.call(args, fn.context); + } + + private static evaluate_function( + stmt: CallExpr, + fn: FunctionValue, + self: Runtime, + ): RuntimeValue { + const args = stmt.args.map((arg) => self.evaluate(arg)); + const newContext = new Context(fn.context); + const runtime = new Runtime(newContext); + const defaultReturn: RuntimeValue = VALUE_NULL(null, {} as Loc); + + // Validation and binding of arguments to parameters + for (let i = 0; i < args.length; i++) { + const param = fn.args[i]; + const argValue = args[i]; + + if ( + !(param.type as TypesNative[]).includes( + argValue.type as TypesNative, + ) + ) { + ErrorReporter.showError( + `Type mismatch: expected '${param.type}' but got '${argValue.type}'.`, + argValue.loc, + ); + Deno.exit(1); + } + + if (newContext.look_up_var(param.id!.value)) { + newContext.assign_var(param.id!, { + types: param.type as TypesNative[], + value: argValue, + } as VarDeclarationValue); + } else { + newContext.new_var(param.id!, { + types: param.type as TypesNative[], + value: argValue, + } as VarDeclarationValue); + } } - private static evaluate_function( - stmt: CallExpr, - fn: FunctionValue, - _self: Runtime, - ): RuntimeValue { - const args = stmt.args.map((arg) => _self.evaluate(arg)); - const newContext = new Context(fn.context); - const runtime = new Runtime(newContext); - const defaultReturn: RuntimeValue = VALUE_NULL(null, {} as Loc); - - // Validação e vinculação dos argumentos aos parâmetros - for (let i = 0; i < args.length; i++) { - const param = fn.args[i]; - const argValue = args[i]; - - if ( - !(param.type as TypesNative[]).includes( - argValue.type as TypesNative, - ) - ) { - ErrorReporter.showError( - `Type mismatch: expected '${param.type}' but got '${argValue.type}'.`, - argValue.loc, - ); - Deno.exit(1); - } - - if (newContext.look_up_var(param.id!.value)) { - newContext.assign_var(param.id!, { - types: param.type as TypesNative[], - value: argValue, - }); - } else { - newContext.new_var(param.id!, { - types: param.type as TypesNative[], - value: argValue, - }); - } - } + // console.log(fn.body); - // Execução do corpo da função - for (const _stmt of fn.body) { - const evaluated = runtime.evaluate(_stmt); + // Execution of the function body + for (const _stmt of fn.body) { + const evaluated = runtime.evaluate(_stmt); - if (evaluated.ret) return evaluated; - if (_stmt.kind === "ReturnStatement") return evaluated; - if (_stmt.kind === "IfStatement" && evaluated.ret) return evaluated; - } + // console.log(evaluated); + // console.log(_stmt); - return defaultReturn; + if (evaluated.ret) return evaluated; + if (_stmt.kind === "ReturnStatement") return evaluated; + if (_stmt.kind === "IfStatement" && evaluated.ret) return evaluated; } - private static evaluate_lambda( - stmt: CallExpr, - fn: LambdaValue, - _self: Runtime, - ): RuntimeValue { - const args = stmt.args.map((arg) => _self.evaluate(arg)); - const newContext = new Context(fn.context); - const runtime = new Runtime(newContext); - const defaultReturn: RuntimeValue = VALUE_NULL(null, {} as Loc); - - // Verifica se alguma variável externa conflita com os parâmetros - for (const extVar of fn.externalVars) { - if ( - fn.args.some((param) => param.id!.value === extVar.value) - ) { - ErrorReporter.showError( - `External variable '${extVar.value}' cannot be used as a parameter in lambda.`, - extVar.loc, - ); - Deno.exit(1); - } - } + return defaultReturn; + } + + private static evaluate_lambda( + stmt: CallExpr, + fn: LambdaValue, + self: Runtime, + ): RuntimeValue { + const args = stmt.args.map((arg) => self.evaluate(arg)); + const newContext = new Context(fn.context); + const runtime = new Runtime(newContext); + const defaultReturn: RuntimeValue = VALUE_NULL(null, {} as Loc); + + // Checks if any external variable conflicts with the parameters + for (const extVar of fn.externalVars) { + if ( + fn.args.some((param) => param.id!.value === extVar.value) + ) { + ErrorReporter.showError( + `External variable '${extVar.value}' cannot be used as a parameter in lambda.`, + extVar.loc, + ); + Deno.exit(1); + } + } - for (const extVar of fn.externalVars) { - const extValue = fn.context.look_up_var(extVar.value) ?? - fn.context.look_up_const(extVar.value); - if (!extValue) { - ErrorReporter.showError( - `External variable '${extVar.value}' was not found in the lambda definition context.`, - extVar.loc, - ); - Deno.exit(1); - } - newContext.new_var(extVar, extValue); - } + for (const extVar of fn.externalVars) { + const extValue = fn.context.look_up_var(extVar.value) ?? + fn.context.look_up_const(extVar.value); + if (!extValue) { + ErrorReporter.showError( + `External variable '${extVar.value}' was not found in the lambda definition context.`, + extVar.loc, + ); + Deno.exit(1); + } + newContext.new_var(extVar, extValue); + } - // Valida o número de argumentos passados - if (stmt.args.length !== fn.args.length) { - ErrorReporter.showError( - `The number of arguments passed does not match the expected number in the lambda.`, - stmt.loc, - ); - Deno.exit(); - } + // Validates the number of arguments passed + if (stmt.args.length !== fn.args.length) { + ErrorReporter.showError( + `The number of arguments passed does not match the expected number in the lambda.`, + stmt.loc, + ); + Deno.exit(); + } - // Vincula os argumentos aos parâmetros - for (let i = 0; i < args.length; i++) { - const param = fn.args[i]; - const argValue = args[i]; - - if ( - !(param.type as TypesNative[]).includes( - argValue.type as TypesNative, - ) - ) { - ErrorReporter.showError( - `Type mismatch in lambda: expected '${param.type}' but got '${argValue.type}'.`, - argValue.loc, - ); - Deno.exit(1); - } - - if (newContext.look_up_var(param.id!.value)) { - newContext.assign_var(param.id!, { - types: param.type as TypesNative[], - value: argValue, - }); - } else { - newContext.new_var(param.id!, { - types: param.type as TypesNative[], - value: argValue, - }); - } - } + // Binds arguments to parameters + for (let i = 0; i < args.length; i++) { + const param = fn.args[i]; + const argValue = args[i]; - // Execução do corpo da lambda - for (const _stmt of fn.body) { - const evaluated = runtime.evaluate(_stmt); - if (evaluated.ret) return evaluated; - if (_stmt.kind === "ReturnStatement") return evaluated; - if (_stmt.kind === "IfStatement" && evaluated.ret) return evaluated; - } + if ( + !(param.type as TypesNative[]).includes( + argValue.type as TypesNative, + ) + ) { + ErrorReporter.showError( + `Type mismatch in lambda: expected '${param.type}' but got '${argValue.type}'.`, + argValue.loc, + ); + Deno.exit(1); + } + + if (newContext.look_up_var(param.id!.value)) { + newContext.assign_var(param.id!, { + types: param.type as TypesNative[], + value: argValue, + } as VarDeclarationValue); + } else { + newContext.new_var(param.id!, { + types: param.type as TypesNative[], + value: argValue, + } as VarDeclarationValue); + } + } - return defaultReturn; + // Execution of the lambda body + for (const _stmt of fn.body) { + const evaluated = runtime.evaluate(_stmt); + if (evaluated.ret) return evaluated; + if (_stmt.kind === "ReturnStatement") return evaluated; + if (_stmt.kind === "IfStatement" && evaluated.ret) return evaluated; } + + return defaultReturn; + } } diff --git a/src/runtime/expressions/DecrementExpressionRuntime.ts b/src/runtime/expressions/DecrementExpressionRuntime.ts index cb6308d..2141c91 100644 --- a/src/runtime/expressions/DecrementExpressionRuntime.ts +++ b/src/runtime/expressions/DecrementExpressionRuntime.ts @@ -3,51 +3,51 @@ import { ErrorReporter } from "../../error/ErrorReporter.ts"; import Context from "../context/Context.ts"; import Runtime from "../Runtime.ts"; import { - RuntimeValue, - VALUE_FLOAT, - VALUE_INT, - VarDeclarationValue, + RuntimeValue, + VALUE_FLOAT, + VALUE_INT, + VarDeclarationValue, } from "../Values.ts"; export class DecrementExpressionRuntime { - public static evaluate( - stmt: DecrementExpr, - context: Context, - _self: Runtime, - ): RuntimeValue { - const var_exists = context.look_up_var(stmt.value.value as string); + public static evaluate( + stmt: DecrementExpr, + context: Context, + _self: Runtime, + ): RuntimeValue { + const var_exists = context.look_up_var(stmt.value.value as string); - if (var_exists == undefined) { - ErrorReporter.showError( - `Variable does not exist or is a constant '${stmt.value.value}'`, - stmt.value.loc, - ); - Deno.exit(); - } + if (var_exists == undefined) { + ErrorReporter.showError( + `Variable does not exist or is a constant '${stmt.value.value}'`, + stmt.value.loc, + ); + Deno.exit(); + } - if ( - var_exists.value.type !== "float" && var_exists.value.type !== "int" - ) { - ErrorReporter.showError( - `The variable type can only be int or float for this operation ${stmt.value.type}`, - stmt.value.loc, - ); - Deno.exit(); - } + if ( + var_exists.value.type !== "float" && var_exists.value.type !== "int" + ) { + ErrorReporter.showError( + `The variable type can only be int or float for this operation ${stmt.value.type}`, + stmt.value.loc, + ); + Deno.exit(); + } - const result = (var_exists.value?.value as number ?? 0) - 1; + const result = (var_exists.value?.value as number ?? 0) - 1; - const var_declaration: VarDeclarationValue = { - types: var_exists.types, - value: (var_exists.value.type == "float") - ? VALUE_FLOAT(result, var_exists.value.loc) - : VALUE_INT(result, var_exists.value.loc), - }; + const var_declaration: VarDeclarationValue = { + types: var_exists.types, + value: (var_exists.value.type == "float") + ? VALUE_FLOAT(result, var_exists.value.loc) + : VALUE_INT(result, var_exists.value.loc), + } as VarDeclarationValue; - context.assign_var( - AST_IDENTIFIER(stmt.value.value, stmt.value.loc), - var_declaration, - ); - return var_declaration.value; - } + context.assign_var( + AST_IDENTIFIER(stmt.value.value, stmt.value.loc), + var_declaration, + ); + return var_declaration.value; + } } diff --git a/src/runtime/expressions/IdentifierExpressionRuntime.ts b/src/runtime/expressions/IdentifierExpressionRuntime.ts index 43501be..aca8d5e 100644 --- a/src/runtime/expressions/IdentifierExpressionRuntime.ts +++ b/src/runtime/expressions/IdentifierExpressionRuntime.ts @@ -5,22 +5,30 @@ import Runtime from "../Runtime.ts"; import { RuntimeValue, VALUE_NULL } from "../Values.ts"; export class IdentifierExpressionRuntime { - public static evaluate( - stmt: Identifier, - context: Context, - _self: Runtime, - ): RuntimeValue { - if (context.look_up_var(stmt.value) != undefined) { - return context.look_up_var(stmt.value)?.value ?? - VALUE_NULL(null, stmt.loc); - } else if (context.look_up_const(stmt.value) != undefined) { - return context.look_up_const(stmt.value)?.value ?? - VALUE_NULL(null, stmt.loc); - } - ErrorReporter.showError( - `Variable does not exist ${stmt.value}`, - stmt.loc, - ); - Deno.exit(); + public static evaluate( + stmt: Identifier, + context: Context, + _self: Runtime, + ): RuntimeValue { + const var_exists = context.look_up_var(stmt.value); + const const_exists = context.look_up_const(stmt.value); + + // console.log(stmt); + // console.log(var_exists); + // console.log(const_exists); + + if (var_exists != undefined) { + return var_exists?.value ?? + VALUE_NULL(null, stmt.loc); + } else if (const_exists != undefined) { + return const_exists?.value ?? + VALUE_NULL(null, stmt.loc); } + + ErrorReporter.showError( + `Variable does not exist '${stmt.value}'.`, + stmt.loc, + ); + Deno.exit(); + } } diff --git a/src/runtime/expressions/IncrementExpressionRuntime.ts b/src/runtime/expressions/IncrementExpressionRuntime.ts index e7ce6ac..85f850a 100644 --- a/src/runtime/expressions/IncrementExpressionRuntime.ts +++ b/src/runtime/expressions/IncrementExpressionRuntime.ts @@ -3,51 +3,51 @@ import { ErrorReporter } from "../../error/ErrorReporter.ts"; import Context from "../context/Context.ts"; import Runtime from "../Runtime.ts"; import { - RuntimeValue, - VALUE_FLOAT, - VALUE_INT, - VarDeclarationValue, + RuntimeValue, + VALUE_FLOAT, + VALUE_INT, + VarDeclarationValue, } from "../Values.ts"; export class IncrementExpressionRuntime { - public static evaluate( - stmt: IncrementExpr, - context: Context, - _self: Runtime, - ): RuntimeValue { - const var_exists = context.look_up_var(stmt.value.value as string); + public static evaluate( + stmt: IncrementExpr, + context: Context, + _self: Runtime, + ): RuntimeValue { + const var_exists = context.look_up_var(stmt.value.value as string); - if (var_exists == undefined) { - ErrorReporter.showError( - `Variable does not exist or is a constant '${stmt.value.value}'`, - stmt.value.loc, - ); - Deno.exit(); - } + if (var_exists == undefined) { + ErrorReporter.showError( + `Variable does not exist or is a constant '${stmt.value.value}'`, + stmt.value.loc, + ); + Deno.exit(); + } - if ( - var_exists.value.type !== "float" && var_exists.value.type !== "int" - ) { - ErrorReporter.showError( - `The variable type can only be int or float for this operation ${stmt.value.type}`, - stmt.value.loc, - ); - Deno.exit(); - } + if ( + var_exists.value.type !== "float" && var_exists.value.type !== "int" + ) { + ErrorReporter.showError( + `The variable type can only be int or float for this operation ${stmt.value.type}`, + stmt.value.loc, + ); + Deno.exit(); + } - const result = (var_exists.value?.value as number ?? 0) + 1; + const result = (var_exists.value?.value as number ?? 0) + 1; - const var_declaration: VarDeclarationValue = { - types: var_exists.types, - value: (var_exists.value.type == "float") - ? VALUE_FLOAT(result, var_exists.value.loc) - : VALUE_INT(result, var_exists.value.loc), - }; + const var_declaration: VarDeclarationValue = { + types: var_exists.types, + value: (var_exists.value.type == "float") + ? VALUE_FLOAT(result, var_exists.value.loc) + : VALUE_INT(result, var_exists.value.loc), + } as VarDeclarationValue; - context.assign_var( - AST_IDENTIFIER(stmt.value.value, stmt.value.loc), - var_declaration, - ); - return var_declaration.value; - } + context.assign_var( + AST_IDENTIFIER(stmt.value.value, stmt.value.loc), + var_declaration, + ); + return var_declaration.value; + } } diff --git a/src/runtime/expressions/LambdaExpressionRuntime.ts b/src/runtime/expressions/LambdaExpressionRuntime.ts index db847a5..da600f6 100644 --- a/src/runtime/expressions/LambdaExpressionRuntime.ts +++ b/src/runtime/expressions/LambdaExpressionRuntime.ts @@ -1,33 +1,32 @@ -import { Stmt } from "../../backend/AST.ts"; +// import { Stmt } from "../../backend/AST.ts"; import { LambdaExpr } from "../../backend/AST.ts"; -import { ErrorReporter } from "../../error/ErrorReporter.ts"; -import { Loc } from "../../frontend/Token.ts"; +// import { ErrorReporter } from "../../error/ErrorReporter.ts"; +// import { Loc } from "../../frontend/Token.ts"; import Context from "../context/Context.ts"; import Runtime from "../Runtime.ts"; import { - ArgsValue, - LambdaValue, - RuntimeValue, - TypesNative, - VALUE_NULL, + ArgsValue, + LambdaValue, + RuntimeValue, + TypesNative, } from "../Values.ts"; export class LambdaExpressionRuntime { - public static evaluate( - stmt: LambdaExpr, - _context: Context, - _self: Runtime, - ): RuntimeValue { - const lambdaValue = { - kind: "lambda", - type: stmt.type as TypesNative[], - args: stmt.args as ArgsValue[], - context: new Context(_context), - body: stmt.body, - externalVars: stmt.externalVars, - loc: stmt.loc, - } as LambdaValue; + public static evaluate( + stmt: LambdaExpr, + context: Context, + _self: Runtime, + ): RuntimeValue { + const lambdaValue = { + kind: "lambda", + type: stmt.type as TypesNative[], + args: stmt.args as ArgsValue[], + context: new Context(context), + body: stmt.body, + externalVars: stmt.externalVars, + loc: stmt.loc, + } as LambdaValue; - return lambdaValue; - } + return lambdaValue; + } } diff --git a/src/runtime/expressions/MemberCallExpressionRuntime.ts b/src/runtime/expressions/MemberCallExpressionRuntime.ts new file mode 100644 index 0000000..565aea2 --- /dev/null +++ b/src/runtime/expressions/MemberCallExpressionRuntime.ts @@ -0,0 +1,144 @@ +import { MemberCallExpr } from "../../backend/AST.ts"; +import { ErrorReporter } from "../../error/ErrorReporter.ts"; +import { Loc } from "../../frontend/Token.ts"; +import MapType from "../class_types/MapType.ts"; +import StringType from "../class_types/StringType.ts"; +import Context from "../context/Context.ts"; +import Runtime from "../Runtime.ts"; +import { MapValue, RuntimeValue, StringValue, TypesNative } from "../Values.ts"; + +export class MemberCallExpressionRuntime { + public static evaluate( + stmt: MemberCallExpr, + context: Context, + _self: Runtime, + ): RuntimeValue { + // console.log("Runtime:", expr); + + // @ts-ignore + const module_exists = context.look_up_alias(stmt.id); + + if (module_exists !== undefined) { + return this.evaluate_module_call(stmt, _self, module_exists); + } + + // console.log(stmt.id); + const expr: RuntimeValue = _self.evaluate(stmt.id); + // console.debug(stmt); + // console.debug(var_exists); + + // if (var_exists == undefined) { + // ErrorReporter.showError( + // `Variable does not exist '${stmt.id.value}'.`, + // stmt.id.loc, + // ); + // Deno.exit(); + // } + + const classType = this.check_class_type(stmt.loc, _self, expr!); + // @ts-ignore: Exists + const method = classType[stmt.member.id.value]; + + console.debug("Debug", stmt); + + if (typeof method !== "function") { + ErrorReporter.showError( + `You are trying to call an attribute that does not exist in the type of this variable '${stmt.id.value}'.`, + stmt.id.loc, + ); + Deno.exit(); + } + + const args = stmt.member.args; + + if (args.length != method.length) { + ErrorReporter.showError( + `Number of arguments do not match, you passed '${args.length}' and expected '${method.length}'.`, + stmt.id.loc, + ); + Deno.exit(); + } + + // I will add a decorator later to recognize type errors in case of an argument with an invalid type. + return method.apply(classType, args); + } + + private static evaluate_module_call( + stmt: MemberCallExpr, + self: Runtime, + module: Map, + ): RuntimeValue { + const func = module + .get(stmt.member.id.value); + + if (!func) { + ErrorReporter.showError( + `Function does not exist '${stmt.member.id.value}'`, + stmt.member.id.loc, + ); + Deno.exit(); + } + + if (func.kind !== "native-fn") { + ErrorReporter.showError( + `Not is a function '${stmt.member.id.value}'`, + stmt.member.id.loc, + ); + Deno.exit(); + } + + // @ts-ignore: Args from FunctionDeclarationValue + if (stmt.member.args.length != func.args.length) { + ErrorReporter.showError( + // @ts-ignore: Args from FunctionDeclarationValue + `Number of arguments do not match, you passed '${stmt.member.args.length}' and expected '${func.args.length}'.`, + stmt.id.loc, + ); + Deno.exit(); + } + + const args = stmt.member.args.map((arg) => self.evaluate(arg)); + + for (let i = 0; i < args.length; i++) { + // @ts-ignore: Args from FunctionDeclarationValue + const expectedParam = func.args[i]; + const passedArg = args[i]; + + if ( + !(expectedParam.type as TypesNative[]).includes( + passedArg.type as TypesNative, + ) + ) { + ErrorReporter.showError( + `Type mismatch: expected '${expectedParam.type}' but got '${passedArg.type}'.`, + passedArg.loc, + ); + Deno.exit(1); + } + } + + // @ts-ignore: All keys from FunctionDeclarationValue + return func.fn.call(args, func.context); + } + + private static check_class_type( + stmt_loc: Loc, + self: Runtime, + type: any, + ) { + switch (type.type!) { + case "string": + return new StringType(type as StringValue, self); + case "map": + return new MapType(type as MapValue, self); + default: { + ErrorReporter.showError( + `The Type of the value of this variable is not recognized '${type + .type!}'.`, + stmt_loc, + ); + Deno.exit(); + } + } + } +} diff --git a/src/runtime/expressions/MemberExpressionRuntime.ts b/src/runtime/expressions/MemberExpressionRuntime.ts new file mode 100644 index 0000000..8d8d9ec --- /dev/null +++ b/src/runtime/expressions/MemberExpressionRuntime.ts @@ -0,0 +1,61 @@ +import { MemberExpr } from "../../backend/AST.ts"; +import { ErrorReporter } from "../../error/ErrorReporter.ts"; +import Context from "../context/Context.ts"; +import Runtime from "../Runtime.ts"; +import { RuntimeValue, VALUE_NULL } from "../Values.ts"; + +export class MemberExpressionRuntime { + public static evaluate( + stmt: MemberExpr, + context: Context, + _self: Runtime, + ): RuntimeValue { + const var_exists = context.look_up_var(stmt.id.value as string) ?? + context.look_up_const(stmt.id.value as string); + + // @ts-ignore + const module_exists = context.look_up_alias(stmt.id); + + if (module_exists !== undefined) { + return this.evaluate_module_member(stmt, module_exists); + } + + console.debug("Stmt:", stmt); + + if (var_exists == undefined) { + ErrorReporter.showError( + `Variable does not exist '${stmt.id.value}'.`, + stmt.id.loc, + ); + Deno.exit(); + } + + if (var_exists.value.type != "object") { + ErrorReporter.showError( + `You are trying to access a member of a variable, which is neither an object nor a module.`, + stmt.id.loc, + ); + Deno.exit(); + } + + // When objects exist in the language, this area must check whether the member exists. + return var_exists.value; + } + + private static evaluate_module_member( + stmt: MemberExpr, + module: Map, + ): RuntimeValue { + const constant = module.get(stmt.member.value); + + if (constant == undefined) { + ErrorReporter.showError( + `Constant does not exist '${stmt.member.value}' in module '${stmt.id.value}'.`, + stmt.member.loc, + ); + Deno.exit(); + } + + return constant.value as RuntimeValue; + } +} diff --git a/src/runtime/modules/IoModule.ts b/src/runtime/modules/IoModule.ts new file mode 100644 index 0000000..4e78e5b --- /dev/null +++ b/src/runtime/modules/IoModule.ts @@ -0,0 +1,106 @@ +import { ErrorReporter } from "../../error/ErrorReporter.ts"; +import Context from "../context/Context.ts"; +import { + ArgsValue, + FunctionNativeDeclarationValue, + NATIVE_FN, + TypesNative, + VALUE_STRING, +} from "../Values.ts"; + +export default class IoModule { + public static readline(context: Context): FunctionNativeDeclarationValue { + return { + kind: "native-fn", + infinity: false, + args: [{ type: ["string"] }] as ArgsValue[], + type: ["string"] as TypesNative[], + context: new Context(context, true), + fn: NATIVE_FN((args, _scope) => { + return VALUE_STRING(prompt(args[0].value as string) ?? "", args[0].loc); + }), + } as FunctionNativeDeclarationValue; + } + + public static format(context: Context): FunctionNativeDeclarationValue { + return { + kind: "native-fn", + infinity: false, + args: [{ type: ["string"] }] as ArgsValue[], + type: ["string"] as TypesNative[], + context: new Context(context, true), + fn: NATIVE_FN((args, scope) => { + const input = args[0].value!.toString(); + let output = ""; + let i = 0; + + // Helper functions for validating identifiers + function isLetter(char: string): boolean { + return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z"); + } + function isDigit(char: string): boolean { + return char >= "0" && char <= "9"; + } + function isLetterOrDigit(char: string): boolean { + return isLetter(char) || isDigit(char); + } + + while (i < input.length) { + if (input[i] === "{") { + i++; // '{' + if (i >= input.length) { + ErrorReporter.showError( + "Incomplete match: expected identifier after '{'", + args[0].loc, + ); + Deno.exit(); + } + let id = ""; + if (!isLetter(input[i]) && input[i] !== "_") { + ErrorReporter.showError( + "Invalid identifier: must start with a letter or underscore", + args[0].loc, + ); + Deno.exit(); + } + while ( + i < input.length && + (isLetterOrDigit(input[i]) || input[i] === "_") + ) { + id += input[i]; + i++; + } + if (i >= input.length || input[i] !== "}") { + ErrorReporter.showError( + `Incomplete match: expected closing '}' for identifier ${id}`, + args[0].loc, + ); + Deno.exit(); + } + i++; // '}' + + const variable = scope.look_up_var(id); + const constant = scope.look_up_const(id); + if (variable === undefined && constant === undefined) { + ErrorReporter.showError( + `Variable '${id}' is not defined in the current scope`, + args[0].loc, + ); + Deno.exit(); + } + + const value = variable !== undefined + ? variable.value + : constant!.value; + output += value.value!.toString(); + } else { + output += input[i]; + i++; + } + } + + return VALUE_STRING(output, args[0].loc); + }), + } as FunctionNativeDeclarationValue; + } +} diff --git a/src/runtime/modules/MapModules.ts b/src/runtime/modules/MapModules.ts new file mode 100644 index 0000000..13fbe75 --- /dev/null +++ b/src/runtime/modules/MapModules.ts @@ -0,0 +1,29 @@ +import { ErrorReporter as _ErrorReporter } from "../../error/ErrorReporter.ts"; +import { NativeValue } from "../../frontend/Token.ts"; +import Context from "../context/Context.ts"; +import { + ArgsValue, + FunctionNativeDeclarationValue, + MapValue, + NATIVE_FN, + TypesNative, +} from "../Values.ts"; + +export default class MapModule { + public static Map(context: Context): FunctionNativeDeclarationValue { + return { + kind: "native-fn", + infinity: false, + args: [] as ArgsValue[], + type: ["map"] as TypesNative[], + context: new Context(context, true), + fn: NATIVE_FN((_args, _scope) => { + return { + type: "map", + map: new Map(), + // ret: true, + } as MapValue; + }), + } as FunctionNativeDeclarationValue; + } +} diff --git a/src/runtime/modules/MathModule.ts b/src/runtime/modules/MathModule.ts new file mode 100644 index 0000000..06f0352 --- /dev/null +++ b/src/runtime/modules/MathModule.ts @@ -0,0 +1,43 @@ +import { ErrorReporter as _ErrorReporter } from "../../error/ErrorReporter.ts"; +import { Loc } from "../../frontend/Token.ts"; +import Context from "../context/Context.ts"; +import { + ArgsValue, + FunctionNativeDeclarationValue, + NATIVE_FN, + TypesNative, + VALUE_FLOAT, + VALUE_INT, + VarDeclarationValue, +} from "../Values.ts"; + +export default class MathModule { + public static fibonacci(context: Context): FunctionNativeDeclarationValue { + return { + kind: "native-fn", + infinity: false, + args: [{ type: ["int"] }] as ArgsValue[], + type: ["int"] as TypesNative[], + context: new Context(context, true), + fn: NATIVE_FN((args, _scope) => { + const map: Map = new Map(); + const fib = (n: number) => { // O(n) + if (map.has(n)) { + return map.get(n)!; + } + if (n <= 1) return n; + map.set(n, fib(n - 1) + fib(n - 2)); + return map.get(n)!; + }; + return VALUE_INT(fib(Number(args[0]?.value)), {} as Loc); + }), + } as FunctionNativeDeclarationValue; + } + + public static PI(): VarDeclarationValue { + return { + types: ["float"], + value: VALUE_FLOAT(Math.PI, {} as Loc), + } as VarDeclarationValue; + } +} diff --git a/src/runtime/modules/UtilsModule.ts b/src/runtime/modules/UtilsModule.ts new file mode 100644 index 0000000..6e55d39 --- /dev/null +++ b/src/runtime/modules/UtilsModule.ts @@ -0,0 +1,24 @@ +import { ErrorReporter as _ErrorReporter } from "../../error/ErrorReporter.ts"; +import Context from "../context/Context.ts"; +import { + ArgsValue, + FunctionNativeDeclarationValue, + NATIVE_FN, + TypesNative, + VALUE_INT, +} from "../Values.ts"; + +export default class UtilsModule { + public static toInt(context: Context): FunctionNativeDeclarationValue { + return { + kind: "native-fn", + infinity: false, + args: [{ type: ["string", "bool", "float", "int"] }] as ArgsValue[], + type: ["int"] as TypesNative[], + context: new Context(context, true), + fn: NATIVE_FN((args, _scope) => { + return VALUE_INT(Math.round(Number(args[0].value)), args[0].loc); + }), + } as FunctionNativeDeclarationValue; + } +} diff --git a/src/runtime/statements/BreakStatementRuntime.ts b/src/runtime/statements/BreakStatementRuntime.ts new file mode 100644 index 0000000..2ec19bb --- /dev/null +++ b/src/runtime/statements/BreakStatementRuntime.ts @@ -0,0 +1,18 @@ +import { BreakStatement } from "../../backend/AST.ts"; +import { ErrorReporter as _ErrorReporter } from "../../error/ErrorReporter.ts"; +import { Loc } from "../../frontend/Token.ts"; +import Context from "../context/Context.ts"; +import Runtime from "../Runtime.ts"; +import { RuntimeValue, VALUE_VOID } from "../Values.ts"; + +export class BreakStatementRuntime { + public static evaluate( + _stmt: BreakStatement, + _context: Context, + _self: Runtime, + ): RuntimeValue { + const value = VALUE_VOID({} as Loc); + value.ret = true; + return value; + } +} diff --git a/src/runtime/statements/ForStatementRuntime.ts b/src/runtime/statements/ForStatementRuntime.ts new file mode 100644 index 0000000..a4d2005 --- /dev/null +++ b/src/runtime/statements/ForStatementRuntime.ts @@ -0,0 +1,35 @@ +import { Expr, ForStatement, Stmt } from "../../backend/AST.ts"; +import { ErrorReporter as _ErrorReporter } from "../../error/ErrorReporter.ts"; +import Context from "../context/Context.ts"; +import Runtime from "../Runtime.ts"; +import { RuntimeValue } from "../Values.ts"; + +export class ForStatementRuntime { + public static evaluate( + stmt: ForStatement, + _context: Context, + self: Runtime, + ): RuntimeValue { + self.evaluate(stmt.init); + const returnValue = self.evaluate(stmt.value as Expr); + + while (self.evaluate(stmt.test).value == true) { + for (let i = 0; i < stmt.body.length; i++) { + const currentStmt = stmt.body[i]; + const evaluated = self.evaluate(currentStmt as Stmt); + + if (evaluated.ret || currentStmt.kind === "ReturnStatement") { + return evaluated; + } + + if (evaluated.ret || currentStmt.kind === "BreakStatement") { + return returnValue; + } + } + + self.evaluate(stmt.update); + } + + return returnValue; + } +} diff --git a/src/runtime/statements/ImportStatementRuntime.ts b/src/runtime/statements/ImportStatementRuntime.ts new file mode 100644 index 0000000..b4b7688 --- /dev/null +++ b/src/runtime/statements/ImportStatementRuntime.ts @@ -0,0 +1,48 @@ +import { ImportStatement } from "../../backend/AST.ts"; +import { + ErrorReporter, + ErrorReporter as _ErrorReporter, +} from "../../error/ErrorReporter.ts"; +import { Loc } from "../../frontend/Token.ts"; +import Context from "../context/Context.ts"; +import Runtime from "../Runtime.ts"; +import { RuntimeValue, VALUE_VOID } from "../Values.ts"; + +export class ImportStatementRuntime { + public static evaluate( + stmt: ImportStatement, + context: Context, + _self: Runtime, + ): RuntimeValue { + const module = stmt.module; + const alias = stmt.check === true ? stmt.alias : module; + + // Check if the module name already exists as a constant (or variable if needed) + if ( + context.look_up_const(module.value) !== + undefined /* || context.look_up_var(module.value) !== undefined */ + ) { + // Verify if the alias also exists + if ( + context.look_up_const(alias.value) !== + undefined /* || context.look_up_var(alias.value) !== undefined */ + ) { + if (alias.value === module.value) { + ErrorReporter.showError( + `The imported module has the same name as an existing variable or constant. To fix this, create an alias for the module.`, + module.loc, + ); + } else { + ErrorReporter.showError( + `You declared an alias with the name of an existing variable or constant.`, + module.loc, + ); + } + Deno.exit(); + } + } + + context.new_alias(alias, module); + return VALUE_VOID({} as Loc); + } +} diff --git a/tests/tests.ts b/tests/tests.ts index dce3462..7647a4b 100644 --- a/tests/tests.ts +++ b/tests/tests.ts @@ -71,7 +71,8 @@ Deno.test( { name: "Check Binary Expr", fn() { - const code = "(2 ** 2 - (0b10 + PI)) + 1.1415926535897931"; + const code = + "import math (2 ** 2 - (0b10 + math.PI)) + 1.1415926535897931"; const evaluated = evaluate_code(code); const expected = VALUE_FLOAT(0, evaluated.loc); expected.ret = true; @@ -177,3 +178,31 @@ Deno.test( }, }, ); + +Deno.test( + { + name: "Increment Test", + fn() { + const code = ` + new mut x: int = 10 + x++ + `; + const evaluated = evaluate_code(code); + assert.assertEquals(evaluated, VALUE_INT(11, evaluated.loc)); + }, + }, +); + +Deno.test( + { + name: "Decrement Test", + fn() { + const code = ` + new mut x: int = 10 + x-- + `; + const evaluated = evaluate_code(code); + assert.assertEquals(evaluated, VALUE_INT(9, evaluated.loc)); + }, + }, +);