Merge pull request #11 from TopchetoEU/TopchetoEU/modules

Module support
This commit is contained in:
TopchetoEU 2023-12-26 14:20:55 +02:00 committed by GitHub
commit 09eb6507dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
/out
/build
/bin
/dst
/*.js
*
!/src
!/src/**/*
/src/assets/js/ts.js
!/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/build.js
/dead-code
/Metadata.java
!/LICENSE
!/README.md

View File

@ -2,44 +2,28 @@
**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.
## 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
var engine = new Engine(true /* false if you dont want debugging */);
var env = new Environment(null, null, null);
var debugger = new DebugServer();
// 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);
var engine = new Engine(false);
// Initialize a standard environment, with implementations of most basic standard libraries (Object, Array, Symbol, etc.)
var env = Internals.apply(new Environment());
// Queue code to load internal libraries and start engine
engine.pushMsg(false, null, new Internals().getApplier(env));
engine.start();
var awaitable = engine.pushMsg(false, env, new Filename("tmp", "eval"), "10 + Math.sqrt(5 / 3)", null);
// Run the engine on the same thread, until the event loop runs empty
engine.run(true);
while (true) {
try {
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) { }
}
// Get our result
System.out.println(awaitable.await());
```
## 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.

126
build.js
View File

@ -2,16 +2,8 @@ const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
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) {
const stat = await fs.stat(src);
@ -36,9 +28,9 @@ async function copy(src, dst, wildcard) {
await Promise.all(promises);
}
function run(cmd, ...args) {
function run(suppressOutput, cmd, ...args) {
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 => {
if (code === 0) res(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 {
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
.replace('${VERSION}', conf.version)
@ -57,8 +126,10 @@ async function compileJava() {
if (argv[2] === 'debug') args.push('-g');
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);
await run(conf.javahome + 'javac', ...args);
await run(false, conf.javahome + 'javac', ...args);
console.log('Compiled java project!');
}
finally {
await fs.rm('Metadata.java');
@ -67,10 +138,31 @@ async function compileJava() {
(async () => {
try {
try { await fs.rm('dst', { recursive: true }); } catch {}
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
await compileJava();
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
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 {}
await Promise.all([
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) {
if (argv[2] === 'debug') throw e;

View File

@ -63,7 +63,7 @@
version++;
if (!environments[env.id]) environments[env.id] = []
declSnapshots = environments[env.id];
var decls = declSnapshots = environments[env.id];
var emit = service.getEmitOutput("/src.ts");
var diagnostics = []
@ -94,7 +94,7 @@
return {
function: function () {
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;
},
breakpoints: compiled.breakpoints,

View File

@ -105,7 +105,7 @@ interface AsyncIterableIterator<T> extends AsyncIterator<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>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
@ -118,7 +118,7 @@ interface GeneratorFunction {
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>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
@ -488,14 +488,17 @@ interface FileStat {
interface File {
readonly pointer: Promise<number>;
readonly length: Promise<number>;
readonly mode: Promise<'' | 'r' | 'rw'>;
read(n: number): Promise<number[]>;
write(buff: number[]): Promise<void>;
close(): Promise<void>;
setPointer(val: number): Promise<void>;
seek(offset: number, whence: number): Promise<void>;
}
interface Filesystem {
readonly SEEK_SET: 0;
readonly SEEK_CUR: 1;
readonly SEEK_END: 2;
open(path: string, mode: 'r' | 'rw'): Promise<File>;
ls(path: string): AsyncIterableIterator<string>;
mkdir(path: string): Promise<void>;
@ -503,6 +506,7 @@ interface Filesystem {
rm(path: string, recursive?: boolean): Promise<void>;
stat(path: string): Promise<FileStat>;
exists(path: string): Promise<boolean>;
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;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript;
import java.io.File;
import java.nio.file.Path;
public class Filename {
public final String protocol;
@ -40,9 +41,7 @@ public class Filename {
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
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());
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.PhysicalFilesystem;
import me.topchetoeu.jscript.lib.Internals;
import me.topchetoeu.jscript.modules.ModuleRepo;
public class Main {
public static class Printer implements Observer<Object> {
@ -57,7 +58,7 @@ public class Main {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, new Context(engine, environment),
false, environment,
Filename.fromFile(file.toFile()),
raw, null
).await();
@ -73,7 +74,7 @@ public class Main {
if (raw == null) break;
var res = engine.pushMsg(
false, new Context(engine, environment),
false, environment,
new Filename("jscript", "repl/" + i + ".js"),
raw, null
).await();
@ -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));
@ -135,18 +137,16 @@ public class Main {
bsEnv.stackVisible = false;
engine.pushMsg(
false, new Context(engine, tsEnv),
false, tsEnv,
new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
).await();
System.out.println("Loaded typescript!");
var ctx = new Context(engine, bsEnv);
engine.pushMsg(
false, ctx,
false, bsEnv,
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();
}
catch (EngineException e) {

View File

@ -26,11 +26,11 @@ public class Context {
return env.empty() ? null : env.peek();
}
public Context pushEnv(Environment env) {
private Context pushEnv(Environment env) {
this.env.push(env);
return this;
}
public void popEnv() {
private void popEnv() {
if (!env.empty()) this.env.pop();
}
@ -119,7 +119,6 @@ public class Context {
}
public Context(Engine engine, Environment env) {
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;
}
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(new Context(this, env), func, thisArg, args, micro);
tasks.add(msg);
return msg.notifier;
}
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
}
@Override

View File

@ -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<String, ObjectValue> 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;
@ -53,7 +58,6 @@ public class Environment implements PermissionsProvider {
res.defineProperty(ctx, "function", target.func(env));
res.defineProperty(ctx, "mapChain", new ArrayValue());
if (isDebug) {
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);
});
public Environment addData(Data data) {
this.data.addAll(data);
return this;
}
@Native public ObjectValue proto(String name) {
return prototypes.get(name);
}
@ -97,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;
}
@ -108,7 +110,7 @@ public class Environment implements PermissionsProvider {
}
public Context context(Engine engine) {
return new Context(engine).pushEnv(this);
return new Context(engine, this);
}
public static Symbol getSymbol(String name) {
@ -128,4 +130,7 @@ public class Environment implements PermissionsProvider {
this.wrappers = nativeConverter;
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);
var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
var ctx = new Context(engine, env);
var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true);

View File

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

View File

@ -741,7 +741,7 @@ public class Values {
try {
if (err instanceof EngineException) {
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) {
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 (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.function = function;
}

View File

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

View File

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

View File

@ -12,7 +12,8 @@ public class FilesystemException extends RuntimeException {
NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8);
FOLDER_EXISTS(0x8),
UNSUPPORTED_OPERATION(0x9);
public final int code;
@ -27,7 +28,8 @@ public class FilesystemException extends RuntimeException {
"No permissions to read '%s'",
"No permissions to write '%s'",
"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;

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

View File

@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem {
@ -12,66 +13,71 @@ public class MemoryFilesystem implements Filesystem {
private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private Path getPath(String name) {
return Path.of("/" + name.replace("\\", "/")).normalize();
private Path realPath(String path) {
return Filename.normalize(path);
}
@Override
public void create(String path, EntryType type) {
var _path = getPath(path);
public String normalize(String... path) {
return Paths.normalize(path);
}
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
switch (type) {
case FILE:
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)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer());
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
files.put(path, new Buffer());
break;
case FOLDER:
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);
folders.add(_path);
if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
folders.add(path);
break;
default:
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
public File open(String path, Mode perms) {
var _path = getPath(path);
var pcount = _path.getNameCount();
public File open(String _path, Mode perms) {
var path = realPath(_path);
var pcount = path.getNameCount();
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
else if (folders.contains(_path)) {
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
else if (folders.contains(path)) {
var res = new StringBuilder();
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(_path)) continue;
if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n');
}
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(_path)) continue;
if (!file.startsWith(path)) continue;
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
public FileStat stat(String path) {
var _path = getPath(path);
public FileStat stat(String _path) {
var path = realPath(_path);
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER);
else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = getPath(path);
var _path = realPath(path);
var _curr = "/";
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 {
private String filename;
private RandomAccessFile file;
private Mode perms;
private Mode mode;
@Override
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); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
}
@Override
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); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
}
@Override
public long getPtr() {
if (file == null || !perms.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);
public long seek(long offset, int pos) {
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
try {
if (pos == 1) pos += file.getFilePointer();
else if (pos == 2) pos += file.length();
file.seek(pos);
if (pos == 1) offset += file.getFilePointer();
else if (pos == 2) offset += file.length();
file.seek(offset);
return offset;
}
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
}
@ -48,16 +43,15 @@ public class PhysicalFile implements File {
try { file.close(); }
catch (IOException e) {} // SHUT
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;
else try { file = new RandomAccessFile(path, mode.name); }
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
perms = mode;
}
}

View File

@ -1,74 +1,88 @@
package me.topchetoeu.jscript.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem {
public final Path root;
private Path getPath(String name) {
return root.resolve(name.replace("\\", "/")).normalize();
}
public final String root;
private void checkMode(Path path, Mode mode) {
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
public File open(String path, Mode perms) {
var _path = getPath(path);
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); }
public String normalize(String... paths) {
return Paths.normalize(paths);
}
@Override
public void create(String path, EntryType type) {
var _path = getPath(path);
var f = _path.toFile();
public File open(String _path, Mode perms) {
_path = normalize(_path);
var path = realPath(_path);
switch (type) {
case FILE:
try {
if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
else break;
}
catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); }
case FOLDER:
if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
else break;
case NONE:
default:
if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
else break;
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 FileStat stat(String path) {
var _path = getPath(path);
var f = _path.toFile();
public void create(String _path, EntryType type) {
var path = realPath(_path);
if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
checkMode(_path, Mode.READ);
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
try {
switch (type) {
case FILE:
Files.createFile(path);
break;
case FOLDER:
Files.createDirectories(path);
break;
case NONE:
default:
Files.delete(path);
}
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
}
@Override
public FileStat stat(String _path) {
var path = realPath(_path);
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
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(
f.canWrite() ? Mode.READ_WRITE : Mode.READ,
f.isFile() ? EntryType.FILE : EntryType.FOLDER
perms,
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
);
}
public PhysicalFilesystem(Path root) {
this.root = root.toAbsolutePath().normalize();
public PhysicalFilesystem(String root) {
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);
}
@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 {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
@ -45,9 +57,8 @@ public class RootFilesystem implements Filesystem {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
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); }
}

View File

@ -267,14 +267,12 @@ public class NativeWrapperProvider implements WrappersProvider {
else return null;
}));
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);
proto.defineProperty(null, "constructor", constr, 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);
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) {
Object res = null;
ctx.pushFrame(frame);
ctx.pushEnv(frame.function.environment);
awaiting = false;
while (!awaiting) {

View File

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

View File

@ -20,6 +20,10 @@ import me.topchetoeu.jscript.interop.Native;
@Native("Filesystem")
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) {
var env = ctx.environment();
if (env != null) {
@ -29,24 +33,28 @@ public class FilesystemLib {
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) {
var filename = Filename.parse(_path);
var path = fs(ctx).normalize(_path);
var _mode = Mode.parse(mode);
return PromiseLib.await(ctx, () -> {
try {
if (fs(ctx).stat(filename.path).type != EntryType.FILE) {
throw new FilesystemException(filename.toString(), FSCode.NOT_FILE);
if (fs(ctx).stat(path).type != EntryType.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);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
@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<>() {
private boolean failed, done;
@ -57,11 +65,11 @@ public class FilesystemLib {
if (done) return;
if (!failed) {
if (file == null) {
if (fs(ctx).stat(filename.path).type != EntryType.FOLDER) {
throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER);
if (fs(ctx).stat(path).type != EntryType.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) {
@ -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, () -> {
try {
fs(ctx).create(Filename.parse(_path).toString(), EntryType.FILE);
fs(ctx).create(path, EntryType.FILE);
return null;
}
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, () -> {
try {
if (!recursive) fs(ctx).create(Filename.parse(_path).toString(), EntryType.NONE);
if (!recursive) fs(ctx).create(path, EntryType.NONE);
else {
var stack = new Stack<String>();
stack.push(_path);
stack.push(path);
while (!stack.empty()) {
var path = Filename.parse(stack.pop()).toString();
var currPath = stack.pop();
FileStat stat;
try { stat = fs(ctx).stat(path); }
try { stat = fs(ctx).stat(currPath); }
catch (FilesystemException e) { continue; }
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;
@ -143,10 +151,10 @@ public class FilesystemLib {
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, () -> {
try {
var stat = fs(ctx).stat(_path);
var stat = fs(ctx).stat(path);
var res = new ObjectValue();
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<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) {
for (var arg : args) {
@ -51,7 +57,7 @@ public class Internals {
}
catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx, func, null, args);
ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
});
thread.start();
@ -71,7 +77,7 @@ public class Internals {
}
catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx, func, null, args);
ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
}
});
thread.start();

View File

@ -253,7 +253,7 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val;
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) {
handle.fulfilled.call(handle.ctx, null, val);
}
@ -288,7 +288,7 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val;
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);
if (!handled) {
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) {
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) {
ctx.engine.pushMsg(true, ctx, reject, null, val);
ctx.engine.pushMsg(true, ctx.environment(), reject, null, val);
handled = true;
}
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) {
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) {
var str = passThis(ctx, "charCodeAt", thisArg);
if (i < 0 || i >= str.length()) return Double.NaN;
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) {
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) {
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) {
@ -238,6 +235,12 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@Native(thisArg = true) public static String trim(Context ctx, Object thisArg) {
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) {
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
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> error = ParseRes.failed();

View File

@ -216,8 +216,10 @@ public class Parsing {
currToken.append(c);
continue;
case CURR_SCIENTIFIC_NOT:
if (c == '-') currStage = CURR_NEG_SCIENTIFIC_NOT;
else if (!isDigit(c)) {
if (c == '-') {
if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT;
}
if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) {
i--; start--;
break;
}
@ -565,7 +567,8 @@ public class Parsing {
}
private static double parseNumber(Location loc, String 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;
}
@ -1022,9 +1025,8 @@ public class Parsing {
return ParseRes.res(res.result, n);
}
@SuppressWarnings("all")
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) {
res.add(parseObject(filename, tokens, i));
@ -1047,7 +1049,7 @@ public class Parsing {
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) {