diff --git a/src/assets/js/lib.d.ts b/src/assets/js/lib.d.ts index 72b9d1f..1cc226b 100644 --- a/src/assets/js/lib.d.ts +++ b/src/assets/js/lib.d.ts @@ -488,14 +488,17 @@ interface FileStat { interface File { readonly pointer: Promise; readonly length: Promise; - readonly mode: Promise<'' | 'r' | 'rw'>; read(n: number): Promise; write(buff: number[]): Promise; close(): Promise; - setPointer(val: number): Promise; + seek(offset: number, whence: number): Promise; } interface Filesystem { + readonly SEEK_SET: 0; + readonly SEEK_CUR: 1; + readonly SEEK_END: 2; + open(path: string, mode: 'r' | 'rw'): Promise; ls(path: string): AsyncIterableIterator; mkdir(path: string): Promise; @@ -503,6 +506,7 @@ interface Filesystem { rm(path: string, recursive?: boolean): Promise; stat(path: string): Promise; exists(path: string): Promise; + normalize(...paths: string[]): string; } interface Encoding { @@ -526,6 +530,7 @@ declare var parseInt: typeof Number.parseInt; declare var parseFloat: typeof Number.parseFloat; declare function log(...vals: any[]): void; +declare function require(name: string): any; declare var Array: ArrayConstructor; declare var Boolean: BooleanConstructor; diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index db9211c..ca32edd 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -22,6 +22,7 @@ import me.topchetoeu.jscript.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.lib.Internals; +import me.topchetoeu.jscript.modules.ModuleRepo; public class Main { public static class Printer implements Observer { @@ -119,7 +120,8 @@ public class Main { })); environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); - environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); + environment.filesystem.protocols.put("file", new PhysicalFilesystem(".")); + environment.modules.repos.put("file", ModuleRepo.ofFilesystem(environment.filesystem)); } private static void initEngine() { debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 0eb322e..8f6e1bc 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -18,10 +18,12 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeWrapperProvider; +import me.topchetoeu.jscript.modules.RootModuleRepo; import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.permissions.Permission; import me.topchetoeu.jscript.permissions.PermissionsProvider; +// TODO: Remove hardcoded extensions form environment public class Environment implements PermissionsProvider { private HashMap prototypes = new HashMap<>(); @@ -30,8 +32,11 @@ public class Environment implements PermissionsProvider { public GlobalScope global; public WrappersProvider wrappers; + public PermissionsProvider permissions = null; public final RootFilesystem filesystem = new RootFilesystem(this); + public final RootModuleRepo modules = new RootModuleRepo(); + public String moduleCwd = "/"; private static int nextId = 0; @@ -63,11 +68,6 @@ public class Environment implements PermissionsProvider { throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); }); - public Environment addData(Data data) { - this.data.addAll(data); - return this; - } - @Native public ObjectValue proto(String name) { return prototypes.get(name); } @@ -96,6 +96,9 @@ public class Environment implements PermissionsProvider { @Native public Environment child() { var res = fork(); res.global = res.global.globalChild(); + res.permissions = this.permissions; + res.filesystem.protocols.putAll(this.filesystem.protocols); + res.modules.repos.putAll(this.modules.repos); return res; } @@ -106,6 +109,10 @@ public class Environment implements PermissionsProvider { return permissions == null || permissions.hasPermission(perm); } + public Context context(Engine engine) { + return new Context(engine, this); + } + public static Symbol getSymbol(String name) { if (symbols.containsKey(name)) return symbols.get(name); else { diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 2bc6a26..2059e62 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -20,6 +20,12 @@ public class Internals { private static final DataKey> THREADS = new DataKey<>(); private static final DataKey I = new DataKey<>(); + @Native public static Object require(Context ctx, String name) { + var env = ctx.environment(); + var res = env.modules.getModule(ctx, env.moduleCwd, name); + res.load(ctx); + return res.value(); + } @Native public static Object log(Context ctx, Object ...args) { for (var arg : args) { diff --git a/src/me/topchetoeu/jscript/modules/Module.java b/src/me/topchetoeu/jscript/modules/Module.java new file mode 100644 index 0000000..938c721 --- /dev/null +++ b/src/me/topchetoeu/jscript/modules/Module.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.modules; + +import me.topchetoeu.jscript.engine.Context; + +public abstract class Module { + private Object value; + private boolean loaded; + + public Object value() { return value; } + public boolean loaded() { return loaded; } + + protected abstract Object onLoad(Context ctx); + + public void load(Context ctx) { + if (loaded) return; + this.value = onLoad(ctx); + this.loaded = true; + } +} + diff --git a/src/me/topchetoeu/jscript/modules/ModuleRepo.java b/src/me/topchetoeu/jscript/modules/ModuleRepo.java new file mode 100644 index 0000000..54a98e1 --- /dev/null +++ b/src/me/topchetoeu/jscript/modules/ModuleRepo.java @@ -0,0 +1,32 @@ +package me.topchetoeu.jscript.modules; + +import java.util.HashMap; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.filesystem.Filesystem; +import me.topchetoeu.jscript.filesystem.Mode; + +public interface ModuleRepo { + public Module getModule(Context ctx, String cwd, String name); + + public static ModuleRepo ofFilesystem(Filesystem fs) { + var modules = new HashMap(); + + return (ctx, cwd, name) -> { + name = fs.normalize(cwd, name); + var filename = Filename.parse(name); + var src = fs.open(name, Mode.READ).readToString(); + + if (modules.containsKey(name)) return modules.get(name); + + var env = ctx.environment().child(); + env.moduleCwd = fs.normalize(name, ".."); + + var mod = new SourceModule(filename, src, env); + modules.put(name, mod); + + return mod; + }; + } +} diff --git a/src/me/topchetoeu/jscript/modules/RootModuleRepo.java b/src/me/topchetoeu/jscript/modules/RootModuleRepo.java new file mode 100644 index 0000000..3b57a63 --- /dev/null +++ b/src/me/topchetoeu/jscript/modules/RootModuleRepo.java @@ -0,0 +1,30 @@ +package me.topchetoeu.jscript.modules; + +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class RootModuleRepo implements ModuleRepo { + public final HashMap repos = new HashMap<>(); + + @Override + public Module getModule(Context ctx, String cwd, String name) { + var i = name.indexOf(":"); + String repoName, modName; + + if (i < 0) { + repoName = "file"; + modName = name; + } + else { + repoName = name.substring(0, i); + modName = name.substring(i + 1); + } + + var repo = repos.get(repoName); + if (repo == null) throw EngineException.ofError("ModuleError", "Couldn't find module repo '" + repoName + "'."); + + return repo.getModule(ctx, cwd, modName); + } +} diff --git a/src/me/topchetoeu/jscript/modules/SourceModule.java b/src/me/topchetoeu/jscript/modules/SourceModule.java new file mode 100644 index 0000000..5971ce1 --- /dev/null +++ b/src/me/topchetoeu/jscript/modules/SourceModule.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.modules; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; + +public class SourceModule extends Module { + public final Filename filename; + public final String source; + public final Environment env; + + @Override + protected Object onLoad(Context ctx) { + var res = new Context(ctx.engine, env).compile(filename, source); + return res.call(ctx); + } + + public SourceModule(Filename filename, String source, Environment env) { + this.filename = filename; + this.source = source; + this.env = env; + } +}