Module support #11

Merged
TopchetoEU merged 20 commits from TopchetoEU/modules into master 2023-12-26 12:20:55 +00:00
117 changed files with 10528 additions and 10189 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* -text

27
.gitignore vendored
View File

@ -1,11 +1,18 @@
.vscode *
.gradle
.ignore !/src
/out !/src/**/*
/build
/bin /src/assets/js/ts.js
/dst
/*.js !/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/build.js !/build.js
/dead-code !/LICENSE
/Metadata.java !/README.md

View File

@ -2,44 +2,28 @@
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript** **NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.** **WARNING: Currently, this code is undocumented. Proceed with caution and a psychiatrist.**
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code. JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
## Example ## Example
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`: The following is going to execute a simple javascript statement:
```java ```java
var engine = new Engine(true /* false if you dont want debugging */); var engine = new Engine(false);
var env = new Environment(null, null, null); // Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
var debugger = new DebugServer(); var env = Internals.apply(new Environment());
// Create one target for the engine and start debugging server
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
// Queue code to load internal libraries and start engine // Queue code to load internal libraries and start engine
engine.pushMsg(false, null, new Internals().getApplier(env)); var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
engine.start(); // Run the engine on the same thread, until the event loop runs empty
engine.run(true);
while (true) { // Get our result
try { System.out.println(awaitable.await());
var raw = Reading.read();
if (raw == null) break;
// Push a message to the engine with the raw REPL code
var res = engine.pushMsg(
false, new Context(engine).pushEnv(env),
new Filename("jscript", "repl.js"), raw, null
).await();
Values.printValue(null, res);
}
catch (EngineException e) { Values.printError(e, ""); }
catch (SyntaxException ex) {
System.out.println("Syntax error:" + ex.msg);
}
catch (IOException e) { }
}
``` ```
## NOTE:
To setup the typescript bundle in your sources, run `node build.js init-ts`. This will download the latest version of typescript, minify it, and add it to your src/assets folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.

124
build.js
View File

@ -2,16 +2,8 @@ const { spawn } = require('child_process');
const fs = require('fs/promises'); const fs = require('fs/promises');
const pt = require('path'); const pt = require('path');
const { argv, exit } = require('process'); const { argv, exit } = require('process');
const { Readable } = require('stream');
const conf = {
name: "java-jscript",
author: "TopchetoEU",
javahome: "",
version: argv[3]
};
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
async function* find(src, dst, wildcard) { async function* find(src, dst, wildcard) {
const stat = await fs.stat(src); const stat = await fs.stat(src);
@ -36,9 +28,9 @@ async function copy(src, dst, wildcard) {
await Promise.all(promises); await Promise.all(promises);
} }
function run(cmd, ...args) { function run(suppressOutput, cmd, ...args) {
return new Promise((res, rej) => { return new Promise((res, rej) => {
const proc = spawn(cmd, args, { stdio: 'inherit' }); const proc = spawn(cmd, args, { stdio: suppressOutput ? 'ignore' : 'inherit' });
proc.once('exit', code => { proc.once('exit', code => {
if (code === 0) res(code); if (code === 0) res(code);
else rej(new Error(`Process ${cmd} exited with code ${code}.`)); else rej(new Error(`Process ${cmd} exited with code ${code}.`));
@ -46,7 +38,84 @@ function run(cmd, ...args) {
}) })
} }
async function compileJava() { async function downloadTypescript(outFile) {
try {
// Import the required libraries, without the need of a package.json
console.log('Importing modules...');
await run(true, 'npm', 'i', 'tar', 'zlib', 'uglify-js');
await fs.mkdir(pt.dirname(outFile), { recursive: true });
await fs.mkdir('tmp', { recursive: true });
const tar = require('tar');
const zlib = require('zlib');
const { minify } = await import('uglify-js');
// Download the package.json file of typescript
const packageDesc = await (await fetch('https://registry.npmjs.org/typescript/latest')).json();
const url = packageDesc.dist.tarball;
console.log('Extracting typescript...');
await new Promise(async (res, rej) => Readable.fromWeb((await fetch(url)).body)
.pipe(zlib.createGunzip())
.pipe(tar.x({ cwd: 'tmp', filter: v => v === 'package/lib/typescript.js' }))
.on('end', res)
.on('error', rej)
);
console.log('Compiling typescript to ES5...');
const ts = require('./tmp/package/lib/typescript');
const program = ts.createProgram([ 'tmp/package/lib/typescript.js' ], {
outFile: "tmp/typescript-es5.js",
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.None,
downlevelIteration: true,
allowJs: true,
});
program.emit();
console.log('Minifying typescript...');
const minified = { code: (await fs.readFile('tmp/typescript-es5.js')).toString() };
// if (minified.error) throw minified.error;
// Patch unsupported regex syntax
minified.code = minified.code.replaceAll('[-/\\\\^$*+?.()|[\\]{}]', '[-/\\\\^$*+?.()|\\[\\]{}]');
const stream = await fs.open(outFile, 'w');
// Write typescript's license
await stream.write(new TextEncoder().encode(`
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
The following is a minified version of the unmodified Typescript 5.2
***************************************************************************** */
`));
await stream.write(minified.code);
console.log('Typescript bundling done!');
}
finally {
// Clean up all stuff left from typescript bundling
await fs.rm('tmp', { recursive: true, force: true });
await fs.rm('package.json');
await fs.rm('package-lock.json');
await fs.rm('node_modules', { recursive: true });
}
}
async function compileJava(conf) {
try { try {
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString() await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
.replace('${VERSION}', conf.version) .replace('${VERSION}', conf.version)
@ -57,8 +126,10 @@ async function compileJava() {
if (argv[2] === 'debug') args.push('-g'); if (argv[2] === 'debug') args.push('-g');
args.push('-d', 'dst/classes', 'Metadata.java'); args.push('-d', 'dst/classes', 'Metadata.java');
console.log('Compiling java project...');
for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path); for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path);
await run(conf.javahome + 'javac', ...args); await run(false, conf.javahome + 'javac', ...args);
console.log('Compiled java project!');
} }
finally { finally {
await fs.rm('Metadata.java'); await fs.rm('Metadata.java');
@ -67,10 +138,31 @@ async function compileJava() {
(async () => { (async () => {
try { try {
if (argv[2] === 'init-ts') {
await downloadTypescript('src/assets/js/ts.js');
}
else {
const conf = {
name: "java-jscript",
author: "TopchetoEU",
javahome: "",
version: argv[3]
};
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
try { await fs.rm('dst', { recursive: true }); } catch {} try { await fs.rm('dst', { recursive: true }); } catch {}
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
await compileJava(); await Promise.all([
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.'); downloadTypescript('dst/classes/assets/js/ts.js'),
copy('src', 'dst/classes', v => !v.endsWith('.java')),
compileJava(conf),
]);
await run(true, 'jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
console.log('Done!');
}
} }
catch (e) { catch (e) {
if (argv[2] === 'debug') throw e; if (argv[2] === 'debug') throw e;

View File

@ -63,7 +63,7 @@
version++; version++;
if (!environments[env.id]) environments[env.id] = [] if (!environments[env.id]) environments[env.id] = []
declSnapshots = environments[env.id]; var decls = declSnapshots = environments[env.id];
var emit = service.getEmitOutput("/src.ts"); var emit = service.getEmitOutput("/src.ts");
var diagnostics = [] var diagnostics = []
@ -94,7 +94,7 @@
return { return {
function: function () { function: function () {
var val = compiled.function.apply(this, arguments); var val = compiled.function.apply(this, arguments);
if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); if (declaration !== '') decls.push(ts.ScriptSnapshot.fromString(declaration));
return val; return val;
}, },
breakpoints: compiled.breakpoints, breakpoints: compiled.breakpoints,

View File

@ -105,7 +105,7 @@ interface AsyncIterableIterator<T> extends AsyncIterator<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<T>; [Symbol.asyncIterator](): AsyncIterableIterator<T>;
} }
interface Generator<T = unknown, TReturn = unknown, TNext = unknown> extends Iterator<T, TReturn, TNext> { interface Generator<T = unknown, TReturn = void, TNext = unknown> extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): Generator<T, TReturn, TNext>; [Symbol.iterator](): Generator<T, TReturn, TNext>;
return(value: TReturn): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>;
@ -118,7 +118,7 @@ interface GeneratorFunction {
readonly prototype: Generator; readonly prototype: Generator;
} }
interface AsyncGenerator<T = unknown, TReturn = unknown, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> { interface AsyncGenerator<T = unknown, TReturn = void, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>; return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>; throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>; [Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
@ -488,14 +488,17 @@ interface FileStat {
interface File { interface File {
readonly pointer: Promise<number>; readonly pointer: Promise<number>;
readonly length: Promise<number>; readonly length: Promise<number>;
readonly mode: Promise<'' | 'r' | 'rw'>;
read(n: number): Promise<number[]>; read(n: number): Promise<number[]>;
write(buff: number[]): Promise<void>; write(buff: number[]): Promise<void>;
close(): Promise<void>; close(): Promise<void>;
setPointer(val: number): Promise<void>; seek(offset: number, whence: number): Promise<void>;
} }
interface Filesystem { interface Filesystem {
readonly SEEK_SET: 0;
readonly SEEK_CUR: 1;
readonly SEEK_END: 2;
open(path: string, mode: 'r' | 'rw'): Promise<File>; open(path: string, mode: 'r' | 'rw'): Promise<File>;
ls(path: string): AsyncIterableIterator<string>; ls(path: string): AsyncIterableIterator<string>;
mkdir(path: string): Promise<void>; mkdir(path: string): Promise<void>;
@ -503,6 +506,7 @@ interface Filesystem {
rm(path: string, recursive?: boolean): Promise<void>; rm(path: string, recursive?: boolean): Promise<void>;
stat(path: string): Promise<FileStat>; stat(path: string): Promise<FileStat>;
exists(path: string): Promise<boolean>; exists(path: string): Promise<boolean>;
normalize(...paths: string[]): string;
} }
interface Encoding { interface Encoding {
@ -526,6 +530,7 @@ declare var parseInt: typeof Number.parseInt;
declare var parseFloat: typeof Number.parseFloat; declare var parseFloat: typeof Number.parseFloat;
declare function log(...vals: any[]): void; declare function log(...vals: any[]): void;
declare function require(name: string): any;
declare var Array: ArrayConstructor; declare var Array: ArrayConstructor;
declare var Boolean: BooleanConstructor; declare var Boolean: BooleanConstructor;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.File; import java.io.File;
import java.nio.file.Path;
public class Filename { public class Filename {
public final String protocol; public final String protocol;
@ -40,9 +41,7 @@ public class Filename {
return true; return true;
} }
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) { public Filename(String protocol, String path) {
@ -57,4 +56,10 @@ public class Filename {
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim()); if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim()); else return new Filename("file", val.trim());
} }
public static Path normalize(String path) {
return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
} }

View File

@ -22,6 +22,7 @@ import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.filesystem.Mode;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.lib.Internals;
import me.topchetoeu.jscript.modules.ModuleRepo;
public class Main { public class Main {
public static class Printer implements Observer<Object> { public static class Printer implements Observer<Object> {
@ -57,7 +58,7 @@ public class Main {
var file = Path.of(arg); var file = Path.of(arg);
var raw = Files.readString(file); var raw = Files.readString(file);
var res = engine.pushMsg( var res = engine.pushMsg(
false, new Context(engine, environment), false, environment,
Filename.fromFile(file.toFile()), Filename.fromFile(file.toFile()),
raw, null raw, null
).await(); ).await();
@ -73,7 +74,7 @@ public class Main {
if (raw == null) break; if (raw == null) break;
var res = engine.pushMsg( var res = engine.pushMsg(
false, new Context(engine, environment), false, environment,
new Filename("jscript", "repl/" + i + ".js"), new Filename("jscript", "repl/" + i + ".js"),
raw, null raw, null
).await(); ).await();
@ -119,7 +120,8 @@ public class Main {
})); }));
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); 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() { private static void initEngine() {
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
@ -135,18 +137,16 @@ public class Main {
bsEnv.stackVisible = false; bsEnv.stackVisible = false;
engine.pushMsg( engine.pushMsg(
false, new Context(engine, tsEnv), false, tsEnv,
new Filename("jscript", "ts.js"), new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null Reading.resourceToString("js/ts.js"), null
).await(); ).await();
System.out.println("Loaded typescript!"); System.out.println("Loaded typescript!");
var ctx = new Context(engine, bsEnv);
engine.pushMsg( engine.pushMsg(
false, ctx, false, bsEnv,
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) tsEnv.global.get(new Context(engine, bsEnv), "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await(); ).await();
} }
catch (EngineException e) { catch (EngineException e) {

View File

@ -26,11 +26,11 @@ public class Context {
return env.empty() ? null : env.peek(); return env.empty() ? null : env.peek();
} }
public Context pushEnv(Environment env) { private Context pushEnv(Environment env) {
this.env.push(env); this.env.push(env);
return this; return this;
} }
public void popEnv() { private void popEnv() {
if (!env.empty()) this.env.pop(); if (!env.empty()) this.env.pop();
} }
@ -119,7 +119,6 @@ public class Context {
} }
public Context(Engine engine, Environment env) { public Context(Engine engine, Environment env) {
this(engine); this(engine);
this.pushEnv(env); if (env != null) this.pushEnv(env);
} }
} }

View File

@ -141,13 +141,13 @@ public class Engine implements DebugController {
return this.thread != null; return this.thread != null;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro); var msg = new Task(new Context(this, env), func, thisArg, args, micro);
tasks.add(msg); tasks.add(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
} }
@Override @Override

View File

@ -18,10 +18,12 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.modules.RootModuleRepo;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.permissions.Permission; import me.topchetoeu.jscript.permissions.Permission;
import me.topchetoeu.jscript.permissions.PermissionsProvider; import me.topchetoeu.jscript.permissions.PermissionsProvider;
// TODO: Remove hardcoded extensions form environment
public class Environment implements PermissionsProvider { public class Environment implements PermissionsProvider {
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
@ -30,8 +32,11 @@ public class Environment implements PermissionsProvider {
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappers; public WrappersProvider wrappers;
public PermissionsProvider permissions = null; public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this); public final RootFilesystem filesystem = new RootFilesystem(this);
public final RootModuleRepo modules = new RootModuleRepo();
public String moduleCwd = "/";
private static int nextId = 0; private static int nextId = 0;
@ -53,7 +58,6 @@ public class Environment implements PermissionsProvider {
res.defineProperty(ctx, "function", target.func(env)); res.defineProperty(ctx, "function", target.func(env));
res.defineProperty(ctx, "mapChain", new ArrayValue()); res.defineProperty(ctx, "mapChain", new ArrayValue());
if (isDebug) { if (isDebug) {
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))); res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
} }
@ -64,11 +68,6 @@ public class Environment implements PermissionsProvider {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); 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) { @Native public ObjectValue proto(String name) {
return prototypes.get(name); return prototypes.get(name);
} }
@ -97,6 +96,9 @@ public class Environment implements PermissionsProvider {
@Native public Environment child() { @Native public Environment child() {
var res = fork(); var res = fork();
res.global = res.global.globalChild(); 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; return res;
} }
@ -108,7 +110,7 @@ public class Environment implements PermissionsProvider {
} }
public Context context(Engine engine) { public Context context(Engine engine) {
return new Context(engine).pushEnv(this); return new Context(engine, this);
} }
public static Symbol getSymbol(String name) { public static Symbol getSymbol(String name) {
@ -128,4 +130,7 @@ public class Environment implements PermissionsProvider {
this.wrappers = nativeConverter; this.wrappers = nativeConverter;
this.global = global; this.global = global;
} }
public Environment() {
this(null, null, null);
}
} }

View File

@ -482,8 +482,8 @@ public class SimpleDebugger implements Debugger {
env.global = new GlobalScope(codeFrame.local); env.global = new GlobalScope(codeFrame.local);
var ctx = new Context(engine).pushEnv(env); var ctx = new Context(engine, env);
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true); engine.run(true);

View File

@ -254,6 +254,7 @@ public class CodeFrame {
break; break;
} }
else { else {
popTryFlag = false;
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {

View File

@ -741,7 +741,7 @@ public class Values {
try { try {
if (err instanceof EngineException) { if (err instanceof EngineException) {
var ee = ((EngineException)err); var ee = ((EngineException)err);
System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env))); System.out.println(prefix + " " + ee.toString(new Context(ee.engine, ee.env)));
} }
else if (err instanceof SyntaxException) { else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); System.out.println("Syntax error:" + ((SyntaxException)err).msg);

View File

@ -37,7 +37,7 @@ public class EngineException extends RuntimeException {
if (function.equals("")) function = null; if (function.equals("")) function = null;
if (ctx == null) this.ctx = null; if (ctx == null) this.ctx = null;
else this.ctx = new Context(ctx.engine).pushEnv(ctx.environment()); else this.ctx = new Context(ctx.engine, ctx.environment());
this.location = location; this.location = location;
this.function = function; this.function = function;
} }

View File

@ -5,20 +5,16 @@ import me.topchetoeu.jscript.Buffer;
public interface File { public interface File {
int read(byte[] buff); int read(byte[] buff);
void write(byte[] buff); void write(byte[] buff);
long getPtr(); long seek(long offset, int pos);
void setPtr(long offset, int pos);
void close(); void close();
Mode mode();
default String readToString() { default String readToString() {
setPtr(0, 2); long len = seek(0, 2);
long len = getPtr();
if (len < 0) return null; if (len < 0) return null;
seek(0, 0);
setPtr(0, 0);
byte[] res = new byte[(int)len]; byte[] res = new byte[(int)len];
read(res); len = read(res);
return new String(res); return new String(res);
} }

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
public interface Filesystem { public interface Filesystem {
String normalize(String... path);
File open(String path, Mode mode) throws FilesystemException; File open(String path, Mode mode) throws FilesystemException;
void create(String path, EntryType type) throws FilesystemException; void create(String path, EntryType type) throws FilesystemException;
FileStat stat(String path) throws FilesystemException; FileStat stat(String path) throws FilesystemException;

View File

@ -12,7 +12,8 @@ public class FilesystemException extends RuntimeException {
NO_PERMISSIONS_RW(0x5), NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6), FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7), ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8); FOLDER_EXISTS(0x8),
UNSUPPORTED_OPERATION(0x9);
public final int code; public final int code;
@ -27,7 +28,8 @@ public class FilesystemException extends RuntimeException {
"No permissions to read '%s'", "No permissions to read '%s'",
"No permissions to write '%s'", "No permissions to write '%s'",
"Can't delete '%s', since it is a full folder.", "Can't delete '%s', since it is a full folder.",
"'%s' already exists." "'%s' already exists.",
"An unsupported operation was performed on the file '%s'."
}; };
public final String message, filename; public final String message, filename;

View File

@ -0,0 +1,73 @@
package me.topchetoeu.jscript.filesystem;
import java.io.IOException;
import java.util.Iterator;
import java.util.stream.Stream;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class ListFile implements File {
private Iterator<String> it;
private String filename;
private byte[] currFile;
private long ptr = 0, start = 0, end = 0;
private void next() {
if (it != null && it.hasNext()) {
start = end;
currFile = (it.next() + "\n").getBytes();
end = start + currFile.length;
}
else {
it = null;
currFile = null;
end = -1;
}
}
@Override
public void close() {
it = null;
currFile = null;
}
@Override
public int read(byte[] buff) {
if (ptr < start) return 0;
if (it == null) return 0;
var i = 0;
while (i < buff.length) {
while (i + ptr >= end) {
next();
if (it == null) return 0;
}
int cpyN = Math.min(currFile.length, buff.length - i);
System.arraycopy(currFile, (int)(ptr + i - start), buff, i, cpyN);
i += cpyN;
}
ptr += i;
return i;
}
@Override
public long seek(long offset, int pos) {
if (pos == 2) throw new FilesystemException(filename, FSCode.UNSUPPORTED_OPERATION);
if (pos == 1) offset += ptr;
return ptr = offset;
}
@Override
public void write(byte[] buff) {
throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
}
public ListFile(String filename, Stream<String> stream) throws IOException {
this.it = stream.iterator();
this.filename = filename;
}
}

View File

@ -27,17 +27,17 @@ public class MemoryFile implements File {
} }
@Override @Override
public long getPtr() { public long seek(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
return ptr;
}
@Override
public void setPtr(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
if (pos == 0) ptr = (int)offset; if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset; else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = data.length() - (int)offset; else if (pos == 2) ptr = data.length() - (int)offset;
if (ptr < 0) ptr = 0;
if (ptr > data.length()) ptr = data.length();
return pos;
} }
@Override @Override
@ -45,11 +45,6 @@ public class MemoryFile implements File {
mode = Mode.NONE; mode = Mode.NONE;
ptr = 0; ptr = 0;
} }
@Override
public Mode mode() {
if (data == null) return Mode.NONE;
return mode;
}
public MemoryFile(String filename, Buffer buff, Mode mode) { public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename; this.filename = filename;

View File

@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import me.topchetoeu.jscript.Buffer; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem { public class MemoryFilesystem implements Filesystem {
@ -12,66 +13,71 @@ public class MemoryFilesystem implements Filesystem {
private HashMap<Path, Buffer> files = new HashMap<>(); private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>(); private HashSet<Path> folders = new HashSet<>();
private Path getPath(String name) { private Path realPath(String path) {
return Path.of("/" + name.replace("\\", "/")).normalize(); return Filename.normalize(path);
} }
@Override @Override
public void create(String path, EntryType type) { public String normalize(String... path) {
var _path = getPath(path); return Paths.normalize(path);
}
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
switch (type) { switch (type) {
case FILE: case FILE:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer()); files.put(path, new Buffer());
break; break;
case FOLDER: case FOLDER:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
folders.add(_path); folders.add(path);
break; break;
default: default:
case NONE: case NONE:
if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.remove(path) && files.remove(path) == null) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
} }
} }
@Override @Override
public File open(String path, Mode perms) { public File open(String _path, Mode perms) {
var _path = getPath(path); var path = realPath(_path);
var pcount = _path.getNameCount(); var pcount = path.getNameCount();
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms); if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
else if (folders.contains(_path)) { else if (folders.contains(path)) {
var res = new StringBuilder(); var res = new StringBuilder();
for (var folder : folders) { for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue; if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(_path)) continue; if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n'); res.append(folder.toFile().getName()).append('\n');
} }
for (var file : files.keySet()) { for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue; if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(_path)) continue; if (!file.startsWith(path)) continue;
res.append(file.toFile().getName()).append('\n'); res.append(file.toFile().getName()).append('\n');
} }
return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); return new MemoryFile(path.toString(), new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
} }
else throw new FilesystemException(path, FSCode.DOESNT_EXIST); else throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
} }
@Override @Override
public FileStat stat(String path) { public FileStat stat(String _path) {
var _path = getPath(path); var path = realPath(_path);
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else throw new FilesystemException(path, FSCode.DOESNT_EXIST); else return new FileStat(Mode.NONE, EntryType.NONE);
} }
public MemoryFilesystem put(String path, byte[] data) { public MemoryFilesystem put(String path, byte[] data) {
var _path = getPath(path); var _path = realPath(path);
var _curr = "/"; var _curr = "/";
for (var seg : _path) { for (var seg : _path) {

View File

@ -0,0 +1,52 @@
package me.topchetoeu.jscript.filesystem;
import java.util.ArrayList;
public class Paths {
public static String normalize(String... path) {
var parts = String.join("/", path).split("[\\\\/]");
var res = new ArrayList<String>();
for (var part : parts) {
if (part.equals("...")) res.clear();
else if (part.equals("..")) {
if (res.size() > 0) res.remove(res.size() - 1);
}
else if (!part.equals(".") && !part.isEmpty()) res.add(part);
}
var sb = new StringBuilder();
for (var el : res) sb.append("/").append(el);
if (sb.isEmpty()) return "/";
else return sb.toString();
}
public static String chroot(String root, String path) {
return normalize(root) + normalize(path);
}
public static String cwd(String cwd, String path) {
return normalize(cwd + "/" + path);
}
public static String filename(String path) {
var i = path.lastIndexOf('/');
if (i < 0) i = path.lastIndexOf('\\');
if (i < 0) return path;
else return path.substring(i + 1);
}
public static String extension(String path) {
var i = path.lastIndexOf('.');
if (i < 0) return "";
else return path.substring(i + 1);
}
public static String dir(String path) {
return normalize(path + "/..");
}
}

View File

@ -9,35 +9,30 @@ import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class PhysicalFile implements File { public class PhysicalFile implements File {
private String filename; private String filename;
private RandomAccessFile file; private RandomAccessFile file;
private Mode perms; private Mode mode;
@Override @Override
public int read(byte[] buff) { public int read(byte[] buff) {
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
else try { return file.read(buff); } else try { return file.read(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
} }
@Override @Override
public void write(byte[] buff) { public void write(byte[] buff) {
if (file == null || !perms.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); if (file == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
else try { file.write(buff); } else try { file.write(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); } catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
} }
@Override @Override
public long getPtr() { public long seek(long offset, int pos) {
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
else try { return file.getFilePointer(); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
}
@Override
public void setPtr(long offset, int pos) {
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
try { try {
if (pos == 1) pos += file.getFilePointer(); if (pos == 1) offset += file.getFilePointer();
else if (pos == 2) pos += file.length(); else if (pos == 2) offset += file.length();
file.seek(pos); file.seek(offset);
return offset;
} }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
} }
@ -48,16 +43,15 @@ public class PhysicalFile implements File {
try { file.close(); } try { file.close(); }
catch (IOException e) {} // SHUT catch (IOException e) {} // SHUT
file = null; file = null;
perms = Mode.NONE; mode = Mode.NONE;
} }
@Override
public Mode mode() { return perms; }
public PhysicalFile(String path, Mode mode) throws FileNotFoundException { public PhysicalFile(String name, String path, Mode mode) throws FileNotFoundException {
this.filename = name;
this.mode = mode;
if (mode == Mode.NONE) file = null; if (mode == Mode.NONE) file = null;
else try { file = new RandomAccessFile(path, mode.name); } else try { file = new RandomAccessFile(path, mode.name); }
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); } catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
perms = mode;
} }
} }

View File

@ -1,74 +1,88 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem { public class PhysicalFilesystem implements Filesystem {
public final Path root; public final String root;
private Path getPath(String name) {
return root.resolve(name.replace("\\", "/")).normalize();
}
private void checkMode(Path path, Mode mode) { private void checkMode(Path path, Mode mode) {
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R); if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.readable && !path.toFile().canRead()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.writable && !path.toFile().canWrite()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
}
private Path realPath(String path) {
return Path.of(Paths.chroot(root, path));
} }
@Override @Override
public File open(String path, Mode perms) { public String normalize(String... paths) {
var _path = getPath(path); return Paths.normalize(paths);
var f = _path.toFile();
checkMode(_path, perms);
if (f.isDirectory()) return MemoryFile.fromFileList(path, f.listFiles());
else try { return new PhysicalFile(path, perms); }
catch (FileNotFoundException e) { throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); }
} }
@Override @Override
public void create(String path, EntryType type) { public File open(String _path, Mode perms) {
var _path = getPath(path); _path = normalize(_path);
var f = _path.toFile(); var path = realPath(_path);
checkMode(path, perms);
try {
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString())));
else return new PhysicalFile(_path, path.toString(), perms);
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); }
}
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
try {
switch (type) { switch (type) {
case FILE: case FILE:
try { Files.createFile(path);
if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); break;
else break;
}
catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); }
case FOLDER: case FOLDER:
if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); Files.createDirectories(path);
else break; break;
case NONE: case NONE:
default: default:
if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); Files.delete(path);
else break;
} }
} }
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
}
@Override @Override
public FileStat stat(String path) { public FileStat stat(String _path) {
var _path = getPath(path); var path = realPath(_path);
var f = _path.toFile();
if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
checkMode(_path, Mode.READ);
var perms = Mode.NONE;
if (Files.isReadable(path)) {
if (Files.isWritable(path)) perms = Mode.READ_WRITE;
else perms = Mode.READ;
}
if (perms == Mode.NONE) return new FileStat(Mode.NONE, EntryType.NONE);
return new FileStat( return new FileStat(
f.canWrite() ? Mode.READ_WRITE : Mode.READ, perms,
f.isFile() ? EntryType.FILE : EntryType.FOLDER Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
); );
} }
public PhysicalFilesystem(Path root) { public PhysicalFilesystem(String root) {
this.root = root.toAbsolutePath().normalize(); this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());
} }
} }

View File

@ -23,6 +23,18 @@ public class RootFilesystem implements Filesystem {
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
} }
@Override public String normalize(String... paths) {
if (paths.length == 0) return "file://";
else {
var filename = Filename.parse(paths[0]);
var protocol = protocols.get(filename.protocol);
paths[0] = filename.path;
if (protocol == null) return Paths.normalize(paths);
else return filename.protocol + "://" + protocol.normalize(paths);
}
}
@Override public File open(String path, Mode perms) throws FilesystemException { @Override public File open(String path, Mode perms) throws FilesystemException {
var filename = Filename.parse(path); var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol); var protocol = protocols.get(filename.protocol);
@ -45,9 +57,8 @@ public class RootFilesystem implements Filesystem {
var filename = Filename.parse(path); var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol); var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), Mode.READ);
try { return protocol.stat(path); } try { return protocol.stat(filename.path); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
} }

View File

@ -267,14 +267,12 @@ public class NativeWrapperProvider implements WrappersProvider {
else return null; else return null;
})); }));
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass()))); proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
proto.defineProperty(null, "toString", new NativeFunction("toString", (ctx, thisArg, args) -> thisArg.toString()));
var constr = makeConstructor(null, Throwable.class); var constr = makeConstructor(null, Throwable.class);
proto.defineProperty(null, "constructor", constr, true, false, false); proto.defineProperty(null, "constructor", constr, true, false, false);
constr.defineProperty(null, "prototype", proto, true, false, false); constr.defineProperty(null, "prototype", proto, true, false, false);
proto.setPrototype(null, getProto(Object.class));
constr.setPrototype(null, getConstr(Object.class));
setProto(Throwable.class, proto); setProto(Throwable.class, proto);
setConstr(Throwable.class, constr); setConstr(Throwable.class, constr);
} }

View File

@ -21,7 +21,6 @@ import me.topchetoeu.jscript.interop.Native;
private void next(Context ctx, Object inducedValue, Object inducedError) { private void next(Context ctx, Object inducedValue, Object inducedError) {
Object res = null; Object res = null;
ctx.pushFrame(frame); ctx.pushFrame(frame);
ctx.pushEnv(frame.function.environment);
awaiting = false; awaiting = false;
while (!awaiting) { while (!awaiting) {

View File

@ -15,7 +15,7 @@ public class FileLib {
@NativeGetter public PromiseLib pointer(Context ctx) { @NativeGetter public PromiseLib pointer(Context ctx) {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
return file.getPtr(); return file.seek(0, 1);
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
@ -23,23 +23,14 @@ public class FileLib {
@NativeGetter public PromiseLib length(Context ctx) { @NativeGetter public PromiseLib length(Context ctx) {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
long curr = file.getPtr(); long curr = file.seek(0, 1);
file.setPtr(0, 2); long res = file.seek(0, 2);
long res = file.getPtr(); file.seek(curr, 0);
file.setPtr(curr, 0);
return res; return res;
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
} }
@NativeGetter public PromiseLib getMode(Context ctx) {
return PromiseLib.await(ctx, () -> {
try {
return file.mode().name;
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@Native public PromiseLib read(Context ctx, int n) { @Native public PromiseLib read(Context ctx, int n) {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
@ -73,11 +64,10 @@ public class FileLib {
return null; return null;
}); });
} }
@Native public PromiseLib setPointer(Context ctx, long ptr) { @Native public PromiseLib seek(Context ctx, long ptr, int whence) {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
file.setPtr(ptr, 0); return file.seek(ptr, whence);
return null;
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });

View File

@ -20,6 +20,10 @@ import me.topchetoeu.jscript.interop.Native;
@Native("Filesystem") @Native("Filesystem")
public class FilesystemLib { public class FilesystemLib {
@Native public static final int SEEK_SET = 0;
@Native public static final int SEEK_CUR = 1;
@Native public static final int SEEK_END = 2;
private static Filesystem fs(Context ctx) { private static Filesystem fs(Context ctx) {
var env = ctx.environment(); var env = ctx.environment();
if (env != null) { if (env != null) {
@ -29,24 +33,28 @@ public class FilesystemLib {
throw EngineException.ofError("Current environment doesn't have a file system."); throw EngineException.ofError("Current environment doesn't have a file system.");
} }
@Native public static String normalize(Context ctx, String... paths) {
return fs(ctx).normalize(paths);
}
@Native public static PromiseLib open(Context ctx, String _path, String mode) { @Native public static PromiseLib open(Context ctx, String _path, String mode) {
var filename = Filename.parse(_path); var path = fs(ctx).normalize(_path);
var _mode = Mode.parse(mode); var _mode = Mode.parse(mode);
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
if (fs(ctx).stat(filename.path).type != EntryType.FILE) { if (fs(ctx).stat(path).type != EntryType.FILE) {
throw new FilesystemException(filename.toString(), FSCode.NOT_FILE); throw new FilesystemException(path, FSCode.NOT_FILE);
} }
var file = fs(ctx).open(filename.path, _mode); var file = fs(ctx).open(path, _mode);
return new FileLib(file); return new FileLib(file);
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
} }
@Native public static ObjectValue ls(Context ctx, String _path) throws IOException { @Native public static ObjectValue ls(Context ctx, String _path) throws IOException {
var filename = Filename.parse(_path); var path = fs(ctx).normalize(_path);
return Values.toJSAsyncIterator(ctx, new Iterator<>() { return Values.toJSAsyncIterator(ctx, new Iterator<>() {
private boolean failed, done; private boolean failed, done;
@ -57,11 +65,11 @@ public class FilesystemLib {
if (done) return; if (done) return;
if (!failed) { if (!failed) {
if (file == null) { if (file == null) {
if (fs(ctx).stat(filename.path).type != EntryType.FOLDER) { if (fs(ctx).stat(path).type != EntryType.FOLDER) {
throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER); throw new FilesystemException(path, FSCode.NOT_FOLDER);
} }
file = fs(ctx).open(filename.path, Mode.READ); file = fs(ctx).open(path, Mode.READ);
} }
if (nextLine == null) { if (nextLine == null) {
@ -108,34 +116,34 @@ public class FilesystemLib {
}); });
} }
@Native public static PromiseLib mkfile(Context ctx, String _path) throws IOException { @Native public static PromiseLib mkfile(Context ctx, String path) throws IOException {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
fs(ctx).create(Filename.parse(_path).toString(), EntryType.FILE); fs(ctx).create(path, EntryType.FILE);
return null; return null;
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
} }
@Native public static PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException { @Native public static PromiseLib rm(Context ctx, String path, boolean recursive) throws IOException {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
if (!recursive) fs(ctx).create(Filename.parse(_path).toString(), EntryType.NONE); if (!recursive) fs(ctx).create(path, EntryType.NONE);
else { else {
var stack = new Stack<String>(); var stack = new Stack<String>();
stack.push(_path); stack.push(path);
while (!stack.empty()) { while (!stack.empty()) {
var path = Filename.parse(stack.pop()).toString(); var currPath = stack.pop();
FileStat stat; FileStat stat;
try { stat = fs(ctx).stat(path); } try { stat = fs(ctx).stat(currPath); }
catch (FilesystemException e) { continue; } catch (FilesystemException e) { continue; }
if (stat.type == EntryType.FOLDER) { if (stat.type == EntryType.FOLDER) {
for (var el : fs(ctx).open(path, Mode.READ).readToString().split("\n")) stack.push(el); for (var el : fs(ctx).open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
} }
else fs(ctx).create(path, EntryType.NONE); else fs(ctx).create(currPath, EntryType.NONE);
} }
} }
return null; return null;
@ -143,10 +151,10 @@ public class FilesystemLib {
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
} }
@Native public static PromiseLib stat(Context ctx, String _path) throws IOException { @Native public static PromiseLib stat(Context ctx, String path) throws IOException {
return PromiseLib.await(ctx, () -> { return PromiseLib.await(ctx, () -> {
try { try {
var stat = fs(ctx).stat(_path); var stat = fs(ctx).stat(path);
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(ctx, "type", stat.type.name); res.defineProperty(ctx, "type", stat.type.name);

View File

@ -20,6 +20,12 @@ public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>(); private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
private static final DataKey<Integer> I = new DataKey<>(); private static final DataKey<Integer> 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) { @Native public static Object log(Context ctx, Object ...args) {
for (var arg : args) { for (var arg : args) {
@ -51,7 +57,7 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx, func, null, args); ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
}); });
thread.start(); thread.start();
@ -71,7 +77,7 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx, func, null, args); ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
} }
}); });
thread.start(); thread.start();

View File

@ -253,7 +253,7 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val; this.val = val;
this.state = STATE_FULFILLED; this.state = STATE_FULFILLED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) { for (var handle : handles) {
handle.fulfilled.call(handle.ctx, null, val); handle.fulfilled.call(handle.ctx, null, val);
} }
@ -288,7 +288,7 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val; this.val = val;
this.state = STATE_REJECTED; this.state = STATE_REJECTED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) handle.rejected.call(handle.ctx, null, val); for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (!handled) { if (!handled) {
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
@ -305,9 +305,9 @@ import me.topchetoeu.jscript.interop.Native;
} }
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment(), fulfill, null, val);
else if (state == STATE_REJECTED) { else if (state == STATE_REJECTED) {
ctx.engine.pushMsg(true, ctx, reject, null, val); ctx.engine.pushMsg(true, ctx.environment(), reject, null, val);
handled = true; handled = true;
} }
else handles.add(new Handle(ctx, fulfill, reject)); else handles.add(new Handle(ctx, fulfill, reject));

View File

@ -57,19 +57,16 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
return passThis(ctx, "charAt", thisArg).charAt(i) + ""; return passThis(ctx, "charAt", thisArg).charAt(i) + "";
} }
// @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) {
// return passThis(ctx, "charCodeAt", thisArg).charAt(i);
// }
// @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
// var str = passThis(ctx, "charAt", thisArg);
// if (i < 0 || i >= str.length()) return "";
// else return str.charAt(i) + "";
// }
@Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) { @Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) {
var str = passThis(ctx, "charCodeAt", thisArg); var str = passThis(ctx, "charCodeAt", thisArg);
if (i < 0 || i >= str.length()) return Double.NaN; if (i < 0 || i >= str.length()) return Double.NaN;
else return str.charAt(i); else return str.charAt(i);
} }
@Native(thisArg = true) public static double codePointAt(Context ctx, Object thisArg, int i) {
var str = passThis(ctx, "codePointAt", thisArg);
if (i < 0 || i >= str.length()) return Double.NaN;
else return str.codePointAt(i);
}
@Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) {
return passThis(ctx, "startsWith", thisArg).startsWith(term, pos); return passThis(ctx, "startsWith", thisArg).startsWith(term, pos);
@ -105,7 +102,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
} }
@Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) { @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) {
return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0; return indexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0;
} }
@Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) { @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) {
@ -238,6 +235,12 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@Native(thisArg = true) public static String trim(Context ctx, Object thisArg) { @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) {
return passThis(ctx, "trim", thisArg).trim(); return passThis(ctx, "trim", thisArg).trim();
} }
@Native(thisArg = true) public static String trimStart(Context ctx, Object thisArg) {
return passThis(ctx, "trimStart", thisArg).replaceAll("^\\s+", "");
}
@Native(thisArg = true) public static String trimEnd(Context ctx, Object thisArg) {
return passThis(ctx, "trimEnd", thisArg).replaceAll("\\s+$", "");
}
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
val = Values.toString(ctx, val); val = Values.toString(ctx, val);

View File

@ -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;
}
}

View File

@ -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<String, Module>();
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;
};
}
}

View File

@ -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<String, ModuleRepo> 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);
}
}

View File

@ -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;
}
}

View File

@ -69,6 +69,9 @@ public class ParseRes<T> {
@SafeVarargs @SafeVarargs
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) { public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
return any(List.of(parsers));
}
public static <T> ParseRes<? extends T> any(List<ParseRes<? extends T>> parsers) {
ParseRes<? extends T> best = null; ParseRes<? extends T> best = null;
ParseRes<? extends T> error = ParseRes.failed(); ParseRes<? extends T> error = ParseRes.failed();

View File

@ -216,8 +216,10 @@ public class Parsing {
currToken.append(c); currToken.append(c);
continue; continue;
case CURR_SCIENTIFIC_NOT: case CURR_SCIENTIFIC_NOT:
if (c == '-') currStage = CURR_NEG_SCIENTIFIC_NOT; if (c == '-') {
else if (!isDigit(c)) { if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT;
}
if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) {
i--; start--; i--; start--;
break; break;
} }
@ -565,7 +567,8 @@ public class Parsing {
} }
private static double parseNumber(Location loc, String value) { private static double parseNumber(Location loc, String value) {
var res = parseNumber(false, value); var res = parseNumber(false, value);
if (res == null) throw new SyntaxException(loc, "Invalid number format."); if (res == null)
throw new SyntaxException(loc, "Invalid number format.");
else return res; else return res;
} }
@ -1022,9 +1025,8 @@ public class Parsing {
return ParseRes.res(res.result, n); return ParseRes.res(res.result, n);
} }
@SuppressWarnings("all")
public static ParseRes<? extends Statement> parseSimple(Filename filename, List<Token> tokens, int i, boolean statement) { public static ParseRes<? extends Statement> parseSimple(Filename filename, List<Token> tokens, int i, boolean statement) {
var res = new ArrayList<>(); var res = new ArrayList<ParseRes<? extends Statement>>();
if (!statement) { if (!statement) {
res.add(parseObject(filename, tokens, i)); res.add(parseObject(filename, tokens, i));
@ -1047,7 +1049,7 @@ public class Parsing {
parseDelete(filename, tokens, i) parseDelete(filename, tokens, i)
)); ));
return ParseRes.any(res.toArray(ParseRes[]::new)); return ParseRes.any(res);
} }
public static ParseRes<VariableStatement> parseVariable(Filename filename, List<Token> tokens, int i) { public static ParseRes<VariableStatement> parseVariable(Filename filename, List<Token> tokens, int i) {