Skip to content

Commit

Permalink
Add support for member expressions and refactor interpreter modules
Browse files Browse the repository at this point in the history
  • Loading branch information
binary-blazer committed Feb 8, 2025
1 parent 026e20f commit acf6f9d
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 55 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"typescript": "^5.7.3"
},
"dependencies": {
"axios": "^1.7.9",
"commander": "^13.0.0"
"commander": "^13.0.0",
"node-fetch": "^3.3.2"
}
}
19 changes: 18 additions & 1 deletion src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ export interface AwaitExpression extends ASTNode {
argument: ASTNode;
}

export interface MemberExpression extends ASTNode {
type: "MemberExpression";
object: ASTNode;
property: Identifier;
}

export function parse(code: string): Program {
const tokens = tokenize(code);
const parser = new Parser(tokens);
Expand Down Expand Up @@ -328,7 +334,18 @@ class Parser {

private parsePrimary(): ASTNode {
if (this.match(TokenType.Identifier)) {
return this.parseIdentifier();
const identifier = this.parseIdentifier();

// Handle member expressions (e.g., http.get, io.println)
if (this.match(TokenType.Operator) && this.previous().value === ".") {
return {
type: "MemberExpression",
object: identifier,
property: this.parseIdentifier()
};
}

return identifier;
}
if (this.match(TokenType.Literal)) {
return this.parseLiteral();
Expand Down
4 changes: 2 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Command } from "commander";
import { readFileSync } from "node:fs";
import process from "node:process";
import { interpret } from "./interpreter";
import { interpret } from "./interpreter.js";

async function main() {
const program = new Command();
Expand All @@ -25,4 +25,4 @@ if (require.main === module) {
console.error(error);
process.exit(1);
});
}
}
4 changes: 4 additions & 0 deletions src/interfaces/Module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface IModule {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
[key: string]: any;
}
97 changes: 48 additions & 49 deletions src/interpreter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios from "axios";
import { io } from "./stdlib/io.js";
import { http } from "./stdlib/http.js";
import IModule from "./interfaces/Module.js";
import {
parse,
type Program,
Expand All @@ -13,37 +15,19 @@ import {
type ForStatement,
type WhileStatement,
type AwaitExpression,
} from "./ast";
import { ParserError } from "./errors";

interface Module {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
[key: string]: any;
}

const io: Module = {
println: (message: any) => {
console.log(message);
},
};

const http: Module = {
get: async (url: string) => {
const response = await axios.get(url);
return response.data;
},
};
} from "./ast.js";
import { ParserError } from "./errors.js";

class Interpreter {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
[key: string]: any;
private modules: { [key: string]: Module } = {
private modules: { [key: string]: IModule } = {
io,
http,
};
private functions: { [key: string]: FunctionDeclaration } = {};

async interpret(ast: ASTNode) {
async interpret(ast: ASTNode): Promise<any> {
switch (ast.type) {
case "Program":
for (const statement of ast.body) {
Expand Down Expand Up @@ -80,13 +64,13 @@ class Interpreter {
handleImportDeclaration(ast: ImportDeclaration) {
for (const specifier of ast.specifiers) {
const localName = (specifier.local as Identifier).name;
const importedName = (specifier.imported as Identifier).name;
const moduleName = importedName.split("/")[0];
const source = (ast.source as Literal).value;
const moduleName = source.split("/")[1]; // Get 'io' or 'http' from 'nyx-stdlib/io'
const module = this.modules[moduleName];
if (!module) {
throw new Error(`Module not found: ${moduleName}`);
}
this[localName] = module[importedName.split("/")[1]];
this[localName] = module;
}
}

Expand All @@ -95,24 +79,30 @@ class Interpreter {
this.functions[functionName] = ast;
}

async handleCallExpression(ast: CallExpression) {
const callee = (ast.callee as Identifier).name;

if (this.functions[callee]) {
return await this.executeFunction(this.functions[callee], ast.arguments);
} else if (callee.includes(".") && this.modules[callee.split(".")[0]]) {
const moduleName = callee.split(".")[0];
const functionName = callee.split(".")[1];
const module = this.modules[moduleName];
if (module && module[functionName]) {
const args = await Promise.all(ast.arguments.map((arg) => this.evaluate(arg)));
return await module[functionName](...args);
async handleCallExpression(ast: CallExpression): Promise<any> {
const callee = ast.callee as any;

if (callee.type === "Identifier" && this.functions[callee.name]) {
return await this.executeFunction(this.functions[callee.name], ast.arguments);
}

// Handle module function calls (e.g., http.get, io.println)
if (callee.type === "MemberExpression") {
const obj = await this.evaluate(callee.object);
const prop = callee.property.name;
if (obj && typeof obj[prop] === "function") {
const args = await Promise.all(ast.arguments.map(arg => this.evaluate(arg)));
return await obj[prop].apply(obj, args);
}
} else if (this[callee]) {
return await this[callee](...await Promise.all(ast.arguments.map((arg) => this.evaluate(arg))));
} else {
throw new Error(`Unknown function: ${callee}`);
}

// Handle direct function calls
if (callee.type === "Identifier" && this[callee.name]) {
const args = await Promise.all(ast.arguments.map(arg => this.evaluate(arg)));
return await this[callee.name].apply(this, args);
}

throw new Error(`Unknown function: ${JSON.stringify(callee)}`);
}

handleVariableDeclaration(ast: VariableDeclaration) {
Expand Down Expand Up @@ -147,23 +137,28 @@ class Interpreter {
}
}

async handleAwaitExpression(ast: AwaitExpression) {
async handleAwaitExpression(ast: AwaitExpression): Promise<any> {
return await this.evaluate(ast.argument);
}

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
async evaluate(node: ASTNode): Promise<any> {
if (!node) return undefined;

switch (node.type) {
case "Literal":
return node.value;
case "Identifier":
return this[node.name];
case "MemberExpression":
const obj = await this.evaluate(node.object);
return obj[node.property.name];
case "BinaryExpression":
return this.evaluateBinaryExpression(node);
case "CallExpression":
return this.handleCallExpression(node as CallExpression);
return await this.handleCallExpression(node as CallExpression);
case "AwaitExpression":
return this.handleAwaitExpression(node as AwaitExpression);
return await this.handleAwaitExpression(node as AwaitExpression);
default:
throw new Error(`Unknown AST node type: ${node.type}`);
}
Expand All @@ -187,29 +182,33 @@ class Interpreter {
}
}

async executeFunction(func: FunctionDeclaration, args: ASTNode[]) {
async executeFunction(func: FunctionDeclaration, args: ASTNode[]): Promise<any> {
const params = func.params.map((param) => (param as Identifier).name);
const localScope: { [key: string]: any } = {};
for (let i = 0; i < params.length; i++) {
localScope[params[i]] = await this.evaluate(args[i]);
}
const previousScope = { ...this };
Object.assign(this, localScope);
await this.interpret(func.body);
const result: any = await this.interpret(func.body);
Object.assign(this, previousScope);
return result;
}
}

export async function interpret(code: string) {
try {
const now = new Date();
const ast: Program = parse(code);
const interpreter = new Interpreter();
await interpreter.interpret(ast);
await interpreter.interpret(ast).then(() => {
console.log(`Execution time: ${new Date().getTime() - now.getTime()}ms`);
});
} catch (error) {
if (error instanceof ParserError) {
console.error(error.toString());
} else {
console.error(error);
}
}
}
}
9 changes: 9 additions & 0 deletions src/stdlib/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import IModule from '../interfaces/Module.js';

export const http: IModule = {
async get(url: string) {
const response = await fetch(url);
const data = await response.json();
return { data };
}
};
7 changes: 7 additions & 0 deletions src/stdlib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { http } from './http.js';
import { io } from './io.js';

export const stdlib = {
http,
io,
};
7 changes: 7 additions & 0 deletions src/stdlib/io.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import IModule from '../interfaces/Module.js';

export const io: IModule = {
println(message: any) {
console.log(message);
}
};
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"module": "commonjs",
"module": "CommonJS",
"target": "es6",
"esModuleInterop": true,
"skipLibCheck": true,
Expand Down

0 comments on commit acf6f9d

Please sign in to comment.