Compare commits

...

91 Commits

Author SHA1 Message Date
3e6214659b fix: use new global API 2024-03-05 15:54:51 +02:00
7c6622c53d fix: separate scope records from scopes 2024-03-05 15:45:02 +02:00
70d5871091 fix: properly check permissions 2024-03-03 20:47:54 +02:00
7b9bbe576b feat: add std streams as global variables 2024-03-03 20:31:20 +02:00
e6399c1546 refactor: remove unused var 2024-03-02 14:01:58 +02:00
c8253795b2 fix: make debugging work again 2024-03-02 13:56:48 +02:00
49dd725669 refactor: fully separate event loop from context 2024-02-29 00:23:14 +02:00
52489ad3a8 feat: separate compilation and runtime 2024-02-26 13:22:56 +02:00
c4d44547c8 fix: call move when passing same array to copyTo 2024-02-21 11:03:19 +02:00
c6dc031cfd fix: respect return value of constructors 2024-02-21 11:01:33 +02:00
285960bdd6 refactor: rework fs error system 2024-02-09 13:46:57 +02:00
cf99845f6b refactor: rework permission system 2024-01-13 11:05:43 +02:00
48bd1e2015 bump 2024-01-12 09:54:32 +02:00
304665904f feat: extract log API to console 2024-01-12 09:53:56 +02:00
56ae3a85a6 build: improve build scripts 2024-01-12 09:48:20 +02:00
0178cb2194 build: specify java toolchain 2024-01-11 11:46:51 +02:00
a2cb5cd473 build: add gradle wrapper props 2024-01-11 11:46:41 +02:00
c123427e77 bump 2024-01-11 10:59:07 +02:00
7ac5ded185 build: set main class in jar manifest 2024-01-11 10:58:40 +02:00
769d6ae8fc version unbump 2024-01-11 10:55:43 +02:00
afb99ffc70 action attempt 1 2024-01-11 10:52:18 +02:00
46136e77e2 build: improve build scripts 2024-01-11 10:47:41 +02:00
b460b87318 refactor: remove old build script 2024-01-11 09:57:41 +02:00
e772f0b50d feat: change custom build script to gradle 2024-01-11 09:56:50 +02:00
187ad55291 fix: fully remove typescript 2024-01-10 17:02:45 +02:00
8156a1733f refactor: move debugging logic out of core 2024-01-10 17:01:24 +02:00
d1937fdb63 remove typescript 2024-01-10 16:51:40 +02:00
3f826cc85d remove old test 2024-01-10 16:51:17 +02:00
af35d7f20b fix: oops 2024-01-10 11:25:29 +02:00
cfa0e001b9 refactor: split up code in 4 modules 2024-01-10 11:21:49 +02:00
c10d071346 feat: some API improvements 2024-01-10 11:21:35 +02:00
89eea7d62b refactor: remove old useless exceptions 2024-01-06 19:51:48 +02:00
18d22a1282 Merge pull request #12 from TopchetoEU/TopchetoEU/cleanup
Major codebase cleanup
2024-01-06 18:28:11 +02:00
72a0d39d0b fix: make java 11 compatible 2024-01-06 18:27:36 +02:00
d8585a20bf refactor: don't require ctx in frame.push 2024-01-06 18:23:34 +02:00
e4c9a8756e fix: debugger hanging sometimes 2024-01-06 18:23:20 +02:00
c6e6425c7e fix: bring back ts minification 2024-01-06 17:53:42 +02:00
292ca64cb9 fix: wrong behavior in Number.toString (which somehow broke typescript) 2024-01-06 17:50:06 +02:00
4572db5c46 feat: some array tests 2024-01-06 17:49:36 +02:00
0251c4689d fix: use Values to access members in ObjectLib, instead of direct access 2024-01-06 17:49:27 +02:00
3173919b49 fix: implement proper parseInt logic 2024-01-06 17:48:35 +02:00
45f133c6b0 fix: use Values.call instead of direct calling 2024-01-06 17:48:10 +02:00
34276d720c fix: remove sparse call arguments 2024-01-06 17:47:38 +02:00
2c634778c3 fix: report proper function name in String.length errors 2024-01-06 17:47:07 +02:00
4aa757e625 fix: Function.bind now passess this argument, instead of the function itself 2024-01-06 17:46:39 +02:00
918f2623cd fix: small issue with sparse arrays 2024-01-06 17:46:13 +02:00
a321fc14bc fix: wrong signature of Map.forEach 2024-01-06 17:45:56 +02:00
07a6f18b16 refactor: some spring cleaning in array lib, fix small issue with join 2024-01-06 17:45:52 +02:00
5f4011aa0c refactor: move NO_RETURN to Values, remove some casters from Values 2024-01-06 17:45:44 +02:00
71f735b812 fix: some more libs fixes 2024-01-04 13:58:04 +02:00
e575b3287e fix: try-catch-finally fix #457846982 2024-01-04 13:57:41 +02:00
4fa5f5a815 feat: use new wrapper API in libs 2024-01-04 10:02:14 +02:00
a61c6a494e fix: some Argument and Engine API improvements, 2024-01-04 10:02:01 +02:00
978ee8db79 feat: make better native wrapper API 2023-12-28 16:55:57 +02:00
e372941e99 refactor: generalize Reading class 2023-12-27 20:18:41 +02:00
c36a0db860 refactor: remove more dead code 2023-12-27 20:18:23 +02:00
d6ee59363f refactor: remove unneeded event system 2023-12-27 20:10:11 +02:00
d5fd6e650e fix: clean up debugger API 2023-12-27 20:02:45 +02:00
c0b895e00a feat: greatly improve Context API 2023-12-27 14:22:18 +02:00
9ea5cd9277 refactor: remove old Data API 2023-12-27 14:21:52 +02:00
aaf9a6fa45 fix: pass environment to compiler via simple environment wrapper 2023-12-27 13:59:19 +02:00
579f09c837 fix: pass arguments to regex constructor in LOAD_REGEX 2023-12-27 13:28:17 +02:00
3343262e72 fix: main now uses new env API 2023-12-27 13:16:21 +02:00
153a1a9a49 feat: readd permissions API via new env API 2023-12-27 13:16:12 +02:00
bf38587271 feat: readd module API via env API 2023-12-27 13:15:46 +02:00
21534efd60 feat: readd FS API via new env API 2023-12-27 13:14:46 +02:00
802f2f3f52 fix: access env via context 2023-12-27 13:14:09 +02:00
38acc20a6f refactor: greatly improve Environment API 2023-12-26 17:12:20 +02:00
d7f6010319 fix: make code java 11 compatible 2023-12-26 14:22:35 +02:00
87f8975275 build: minify typescript code 2023-12-26 14:22:25 +02:00
09eb6507dc Merge pull request #11 from TopchetoEU/TopchetoEU/modules
Module support
2023-12-26 14:20:55 +02:00
2f58f6b245 feat: implement basic module system 2023-12-26 14:12:41 +02:00
4bc363485f fix: losts of FS API improvements 2023-12-26 14:02:32 +02:00
8e01db637b fix: improve path resolutions and FS API 2023-12-26 11:27:40 +02:00
1c64912786 feat: create memory-light directory list file 2023-12-26 11:27:09 +02:00
28265a8f44 fix: some bug fixes and improvements with File interface 2023-12-26 11:26:08 +02:00
e9e020512e fix: environment pushed when it shouldn't be 2023-12-24 15:17:38 +02:00
4b0bbf5190 fix: correctly convert virtual to real path 2023-12-24 14:40:27 +02:00
031f78ebf1 fix: don't include ts's code in repo anymore 2023-12-24 14:32:37 +02:00
562f1f9425 fix: remove major mistakes in README 2023-12-24 14:31:50 +02:00
82a09e8865 fix: some config files cleanup 2023-12-24 14:31:33 +02:00
90da2db1fb fix: error now is not null-prototyped 2023-12-24 14:30:48 +02:00
3d275c52c0 refactor: fix code to not result in compile warning 2023-12-24 14:30:31 +02:00
797585f539 feat: implement some stdlib functions 2023-12-24 14:28:12 +02:00
7a301eba8f fix: some stupid mistakes in FS 2023-12-24 14:27:27 +02:00
1b2068a274 fix: never-ending try-catch issues 2023-12-24 14:27:00 +02:00
078d7ed95f refactor: improve Engine API 2023-12-24 14:26:42 +02:00
93973c12b1 fix: change default return type to void for generators 2023-12-24 14:10:03 +02:00
cad4f34b51 fix: use correct env declarations in bootstrap.js 2023-12-24 14:08:20 +02:00
d3571d6ee2 fix: make .gitignore more restrictive 2023-12-22 10:46:30 +02:00
caf9131cde refactor: replace all CRLF with LF 2023-12-22 10:45:04 +02:00
295 changed files with 14492 additions and 14198 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@@ -16,15 +16,17 @@ jobs:
with:
distribution: 'adopt'
java-version: '11'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main
with:
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
branch: 'master'
owner: 'TopchetoEU'
repository: 'java-jscript'
- name: "Build"
- name: Build
run: |
cd java-jscript; node ./build.js release ${{ github.ref }}
cd java-jscript; gradle build
- uses: "marvinpinto/action-automatic-releases@latest"
with:
@@ -32,4 +34,4 @@ jobs:
prerelease: false
files: |
java-jscript/LICENSE
java-jscript/dst/*.jar
java-jscript/build/libs/*.jar

35
.gitignore vendored
View File

@@ -1,11 +1,24 @@
.vscode
.gradle
.ignore
/out
/build
/bin
/dst
/*.js
!/build.js
/dead-code
/Metadata.java
*
!/src
!/src/**/*
!/doc
!/doc/**/*
!/tests
!/tests/**/*
!/.github
!/.github/**/*
!/.gitignore
!/.gitattributes
!/LICENSE
!/README.md
!/settings.gradle
!/build.gradle
!/gradle.properties
!/gradle
!/gradle/wrapper
!/gradle/wrapper/gradle-wrapper.properties

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();
// 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 folder. If you are going to work with the `node build.js debug|release` command, this is not a necessary step.

32
build.gradle Normal file
View File

@@ -0,0 +1,32 @@
plugins {
id "application"
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
toolchain.languageVersion = JavaLanguageVersion.of(11)
withSourcesJar()
}
jar {
manifest.attributes["Main-class"] = project.main_class
}
sourceSets {
main.java.srcDirs = [ "src/java" ]
main.resources.srcDirs = [ "src/assets" ]
}
processResources {
filesMatching "metadata.json", {
expand(
version: project.project_version,
name: project.project_name
)
}
}
base.archivesName = project.project_name
version = project.project_version
group = project.project_group

View File

@@ -1,80 +0,0 @@
const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
const { argv, exit } = require('process');
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);
if (stat.isDirectory()) {
for (const el of await fs.readdir(src)) {
for await (const res of find(pt.join(src, el), dst ? pt.join(dst, el) : undefined, wildcard)) yield res;
}
}
else if (stat.isFile() && wildcard(src)) yield dst ? { src, dst } : src;
}
async function copy(src, dst, wildcard) {
const promises = [];
for await (const el of find(src, dst, wildcard)) {
promises.push((async () => {
await fs.mkdir(pt.dirname(el.dst), { recursive: true });
await fs.copyFile(el.src, el.dst);
})());
}
await Promise.all(promises);
}
function run(cmd, ...args) {
return new Promise((res, rej) => {
const proc = spawn(cmd, args, { stdio: 'inherit' });
proc.once('exit', code => {
if (code === 0) res(code);
else rej(new Error(`Process ${cmd} exited with code ${code}.`));
});
})
}
async function compileJava() {
try {
await fs.writeFile('Metadata.java', (await fs.readFile('src/me/topchetoeu/jscript/Metadata.java')).toString()
.replace('${VERSION}', conf.version)
.replace('${NAME}', conf.name)
.replace('${AUTHOR}', conf.author)
);
const args = ['--release', '11', ];
if (argv[2] === 'debug') args.push('-g');
args.push('-d', 'dst/classes', 'Metadata.java');
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);
}
finally {
await fs.rm('Metadata.java');
}
}
(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', '.');
}
catch (e) {
if (argv[2] === 'debug') throw e;
console.log(e.toString());
exit(-1);
}
})();

4
gradle.properties Normal file
View File

@@ -0,0 +1,4 @@
project_group = me.topchetoeu
project_name = jscript
project_version = 0.8.6-beta
main_class = me.topchetoeu.jscript.utils.JScriptRepl

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

5
settings.gradle Normal file
View File

@@ -0,0 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = properties.project_name

View File

@@ -1,113 +0,0 @@
(function (ts, env, libs) {
var src = '', version = 0;
var lib = libs.concat([
'declare function exit(): never;',
'declare function go(): any;',
'declare function getTsDeclarations(): string[];'
]).join('');
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var environments = {};
var declSnapshots = [];
var settings = {
outDir: "/out",
declarationDir: "/out",
target: ts.ScriptTarget.ES5,
lib: [ ],
module: ts.ModuleKind.None,
declaration: true,
stripInternal: true,
downlevelIteration: true,
forceConsistentCasingInFileNames: true,
experimentalDecorators: true,
strict: true,
sourceMap: true,
};
var reg = ts.createDocumentRegistry();
var service = ts.createLanguageService({
getCurrentDirectory: function() { return "/"; },
getDefaultLibFileName: function() { return "/lib.d.ts"; },
getScriptFileNames: function() {
var res = [ "/src.ts", "/lib.d.ts" ];
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
return res;
},
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) {
if (filename === "/lib.d.ts") return libSnapshot;
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
return declSnapshots[index - 1];
}
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
else return version;
},
}, reg);
service.getEmitOutput("/lib.d.ts");
log("Loaded libraries!");
var oldCompile = env.compile;
function compile(code, filename, env) {
src = code;
version++;
if (!environments[env.id]) environments[env.id] = []
declSnapshots = environments[env.id];
var emit = service.getEmitOutput("/src.ts");
var diagnostics = []
.concat(service.getCompilerOptionsDiagnostics())
.concat(service.getSyntacticDiagnostics("/src.ts"))
.concat(service.getSemanticDiagnostics("/src.ts"))
.map(function (diagnostic) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file) {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
var file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return message;
});
if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join("\n"));
}
var map = JSON.parse(emit.outputFiles[0].text);
var result = emit.outputFiles[1].text;
var declaration = emit.outputFiles[2].text;
var compiled = oldCompile(result, filename, env);
return {
function: function () {
var val = compiled.function.apply(this, arguments);
if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
return val;
},
breakpoints: compiled.breakpoints,
mapChain: compiled.mapChain.concat(map.mappings),
};
}
function apply(env) {
env.compile = compile;
env.global.getTsDeclarations = function() {
return environments[env.id];
}
}
apply(env);
})(arguments[0], arguments[1], arguments[2]);

618
src/assets/js/lib.d.ts vendored
View File

@@ -1,618 +0,0 @@
type PropertyDescriptor<T, ThisT> = {
value: any;
writable?: boolean;
enumerable?: boolean;
configurable?: boolean;
} | {
get?(this: ThisT): T;
set(this: ThisT, val: T): void;
enumerable?: boolean;
configurable?: boolean;
} | {
get(this: ThisT): T;
set?(this: ThisT, val: T): void;
enumerable?: boolean;
configurable?: boolean;
};
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT }
type ReplaceFunc = (match: string, ...args: any[]) => string;
type PromiseResult<T> = { type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
// wippidy-wine, this code is now mine :D
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T;
type IteratorYieldResult<TReturn> =
{ done?: false; } &
(TReturn extends undefined ? { value?: undefined; } : { value: TReturn; });
type IteratorReturnResult<TReturn> =
{ done: true } &
(TReturn extends undefined ? { value?: undefined; } : { value: TReturn; });
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Thenable<T> {
then<NextT = void>(onFulfilled?: (val: T) => NextT, onRejected?: (err: any) => NextT): Promise<Awaited<NextT>>;
}
interface RegExpResultIndices extends Array<[number, number]> {
groups?: { [name: string]: [number, number]; };
}
interface RegExpResult extends Array<string> {
groups?: { [name: string]: string; };
index: number;
input: string;
indices?: RegExpResultIndices;
escape(raw: string, flags: string): RegExp;
}
interface Matcher {
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
}
interface Splitter {
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
}
interface Replacer {
[Symbol.replace](target: string, replacement: string | ReplaceFunc): string;
}
interface Searcher {
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
}
type FlatArray<Arr, Depth extends number> = {
"done": Arr,
"recur": Arr extends Array<infer InnerArr>
? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
: Arr
}[Depth extends -1 ? "done" : "recur"];
interface IArguments {
[i: number]: any;
length: number;
}
interface Iterator<T, TReturn = any, TNext = undefined> {
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
interface AsyncIterator<T, TReturn = any, TNext = undefined> {
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return?(value?: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
}
interface AsyncIterable<T> {
[Symbol.asyncIterator](): AsyncIterator<T>;
}
interface AsyncIterableIterator<T> extends AsyncIterator<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
}
interface Generator<T = unknown, TReturn = unknown, 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>;
}
interface GeneratorFunction {
new (...args: any[]): Generator;
(...args: any[]): Generator;
readonly length: number;
readonly name: string;
readonly prototype: Generator;
}
interface AsyncGenerator<T = unknown, TReturn = unknown, 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>;
}
interface AsyncGeneratorFunction {
new (...args: any[]): AsyncGenerator;
(...args: any[]): AsyncGenerator;
readonly length: number;
readonly name: string;
readonly prototype: AsyncGenerator;
}
interface MathObject {
readonly E: number;
readonly PI: number;
readonly SQRT2: number;
readonly SQRT1_2: number;
readonly LN2: number;
readonly LN10: number;
readonly LOG2E: number;
readonly LOG10E: number;
asin(x: number): number;
acos(x: number): number;
atan(x: number): number;
atan2(y: number, x: number): number;
asinh(x: number): number;
acosh(x: number): number;
atanh(x: number): number;
sin(x: number): number;
cos(x: number): number;
tan(x: number): number;
sinh(x: number): number;
cosh(x: number): number;
tanh(x: number): number;
sqrt(x: number): number;
cbrt(x: number): number;
hypot(...vals: number[]): number;
imul(a: number, b: number): number;
exp(x: number): number;
expm1(x: number): number;
pow(x: number, y: number): number;
log(x: number): number;
log10(x: number): number;
log1p(x: number): number;
log2(x: number): number;
ceil(x: number): number;
floor(x: number): number;
round(x: number): number;
fround(x: number): number;
trunc(x: number): number;
abs(x: number): number;
max(...vals: number[]): number;
min(...vals: number[]): number;
sign(x: number): number;
random(): number;
clz32(x: number): number;
}
interface Array<T> extends IterableIterator<T> {
[i: number]: T;
length: number;
toString(): string;
// toLocaleString(): string;
join(separator?: string): string;
fill(val: T, start?: number, end?: number): T[];
pop(): T | undefined;
push(...items: T[]): number;
concat(...items: (T | T[])[]): T[];
concat(...items: (T | T[])[]): T[];
join(separator?: string): string;
reverse(): T[];
shift(): T | undefined;
slice(start?: number, end?: number): T[];
sort(compareFn?: (a: T, b: T) => number): this;
splice(start: number, deleteCount?: number | undefined, ...items: T[]): T[];
unshift(...items: T[]): number;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
includes(el: any, start?: number): boolean;
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
find(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
findIndex(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): number;
findLast(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
findLastIndex(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): number;
flat<D extends number = 1>(depth?: D): FlatArray<T, D>;
flatMap(func: (val: T, i: number, arr: T[]) => T | T[], thisAarg?: any): FlatArray<T[], 1>;
sort(func?: (a: T, b: T) => number): this;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
entries(): IterableIterator<[number, T]>;
values(): IterableIterator<T>;
keys(): IterableIterator<number>;
}
interface ArrayConstructor {
new <T>(arrayLength?: number): T[];
new <T>(...items: T[]): T[];
<T>(arrayLength?: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is any[];
prototype: Array<any>;
}
interface Boolean {
toString(): string;
valueOf(): boolean;
}
interface BooleanConstructor {
(val: any): boolean;
new (val: any): Boolean;
prototype: Boolean;
}
interface Error {
name: string;
message: string;
stack: string[];
toString(): string;
}
interface ErrorConstructor {
(msg?: any): Error;
new (msg?: any): Error;
prototype: Error;
}
interface TypeErrorConstructor extends ErrorConstructor {
(msg?: any): TypeError;
new (msg?: any): TypeError;
prototype: Error;
}
interface TypeError extends Error {
name: 'TypeError';
}
interface RangeErrorConstructor extends ErrorConstructor {
(msg?: any): RangeError;
new (msg?: any): RangeError;
prototype: Error;
}
interface RangeError extends Error {
name: 'RangeError';
}
interface SyntaxErrorConstructor extends ErrorConstructor {
(msg?: any): RangeError;
new (msg?: any): RangeError;
prototype: Error;
}
interface SyntaxError extends Error {
name: 'SyntaxError';
}
interface Function {
apply(this: Function, thisArg: any, argArray?: any): any;
call(this: Function, thisArg: any, ...argArray: any[]): any;
bind(this: Function, thisArg: any, ...argArray: any[]): Function;
toString(): string;
prototype: any;
readonly length: number;
name: string;
}
interface CallableFunction extends Function {
(...args: any[]): any;
apply<ThisArg, Args extends any[], RetT>(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, argArray?: Args): RetT;
call<ThisArg, Args extends any[], RetT>(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, ...argArray: Args): RetT;
bind<ThisArg, Args extends any[], Rest extends any[], RetT>(this: (this: ThisArg, ...args: [ ...Args, ...Rest ]) => RetT, thisArg: ThisArg, ...argArray: Args): (this: void, ...args: Rest) => RetT;
}
interface NewableFunction extends Function {
new(...args: any[]): any;
apply<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, argArray?: Args): RetT;
call<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): RetT;
bind<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): new (...args: Args) => RetT;
}
interface FunctionConstructor extends Function {
(...args: string[]): (...args: any[]) => any;
new (...args: string[]): (...args: any[]) => any;
prototype: Function;
async<ArgsT extends any[], RetT>(
func: (await: <T>(val: T) => Awaited<T>) => (...args: ArgsT) => RetT
): (...args: ArgsT) => Promise<RetT>;
asyncGenerator<ArgsT extends any[], RetT>(
func: (await: <T>(val: T) => Awaited<T>, _yield: <T>(val: T) => void) => (...args: ArgsT) => RetT
): (...args: ArgsT) => AsyncGenerator<RetT>;
generator<ArgsT extends any[], T = unknown, RetT = unknown, TNext = unknown>(
func: (_yield: <T>(val: T) => TNext) => (...args: ArgsT) => RetT
): (...args: ArgsT) => Generator<T, RetT, TNext>;
}
interface Number {
toString(): string;
valueOf(): number;
}
interface NumberConstructor {
(val: any): number;
new (val: any): Number;
prototype: Number;
parseInt(val: unknown): number;
parseFloat(val: unknown): number;
}
interface Object {
constructor: NewableFunction;
[Symbol.typeName]: string;
valueOf(): this;
toString(): string;
hasOwnProperty(key: any): boolean;
}
interface ObjectConstructor {
(arg: string): String;
(arg: number): Number;
(arg: boolean): Boolean;
(arg?: undefined | null): {};
<T extends object>(arg: T): T;
new (arg: string): String;
new (arg: number): Number;
new (arg: boolean): Boolean;
new (arg?: undefined | null): {};
new <T extends object>(arg: T): T;
prototype: Object;
assign<T extends object>(target: T, ...src: object[]): T;
create<T extends object>(proto: T, props?: { [key: string]: PropertyDescriptor<any, T> }): T;
keys<T extends object>(obj: T, onlyString?: true): (keyof T)[];
keys<T extends object>(obj: T, onlyString: false): any[];
entries<T extends object>(obj: T, onlyString?: true): [keyof T, T[keyof T]][];
entries<T extends object>(obj: T, onlyString: false): [any, any][];
values<T extends object>(obj: T, onlyString?: true): (T[keyof T])[];
values<T extends object>(obj: T, onlyString: false): any[];
fromEntries(entries: Iterable<[any, any]>): object;
defineProperty<T, ThisT extends object>(obj: ThisT, key: any, desc: PropertyDescriptor<T, ThisT>): ThisT;
defineProperties<ThisT extends object>(obj: ThisT, desc: { [key: string]: PropertyDescriptor<any, ThisT> }): ThisT;
getOwnPropertyNames<T extends object>(obj: T): (keyof T)[];
getOwnPropertySymbols<T extends object>(obj: T): (keyof T)[];
hasOwn<T extends object, KeyT>(obj: T, key: KeyT): boolean;
getOwnPropertyDescriptor<T extends object, KeyT extends keyof T>(obj: T, key: KeyT): PropertyDescriptor<T[KeyT], T>;
getOwnPropertyDescriptors<T extends object>(obj: T): { [x in keyof T]: PropertyDescriptor<T[x], T> };
getPrototypeOf(obj: any): object | null;
setPrototypeOf<T>(obj: T, proto: object | null): T;
preventExtensions<T extends object>(obj: T): T;
seal<T extends object>(obj: T): T;
freeze<T extends object>(obj: T): T;
isExtensible(obj: object): boolean;
isSealed(obj: object): boolean;
isFrozen(obj: object): boolean;
}
interface String {
[i: number]: string;
toString(): string;
valueOf(): string;
charAt(pos: number): string;
charCodeAt(pos: number): number;
substring(start?: number, end?: number): string;
slice(start?: number, end?: number): string;
substr(start?: number, length?: number): string;
startsWith(str: string, pos?: number): string;
endsWith(str: string, pos?: number): string;
replace(pattern: string | Replacer, val: string | ReplaceFunc): string;
replaceAll(pattern: string | Replacer, val: string | ReplaceFunc): string;
match(pattern: string | Matcher): RegExpResult | string[] | null;
matchAll(pattern: string | Matcher): IterableIterator<RegExpResult>;
split(pattern: string | Splitter, limit?: number, sensible?: boolean): string;
concat(...others: string[]): string;
indexOf(term: string | Searcher, start?: number): number;
lastIndexOf(term: string | Searcher, start?: number): number;
toLowerCase(): string;
toUpperCase(): string;
trim(): string;
includes(term: string, start?: number): boolean;
length: number;
}
interface StringConstructor {
(val: any): string;
new (val: any): String;
fromCharCode(val: number): string;
prototype: String;
}
interface Symbol {
valueOf(): symbol;
}
interface SymbolConstructor {
(val?: any): symbol;
new(...args: any[]): never;
prototype: Symbol;
for(key: string): symbol;
keyFor(sym: symbol): string;
readonly typeName: unique symbol;
readonly match: unique symbol;
readonly matchAll: unique symbol;
readonly split: unique symbol;
readonly replace: unique symbol;
readonly search: unique symbol;
readonly iterator: unique symbol;
readonly asyncIterator: unique symbol;
}
interface Promise<T> extends Thenable<T> {
catch<ResT = void>(func: (err: unknown) => ResT): Promise<ResT>;
finally(func: () => void): Promise<T>;
constructor: PromiseConstructor;
}
interface PromiseConstructorLike {
new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Thenable<Awaited<T>>;
}
interface PromiseConstructor extends PromiseConstructorLike {
prototype: Promise<any>;
new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Promise<Awaited<T>>;
resolve<T>(val: T): Promise<Awaited<T>>;
reject(val: any): Promise<never>;
isAwaitable(val: unknown): val is Thenable<any>;
any<T>(promises: T[]): Promise<Awaited<T>>;
race<T>(promises: (Promise<T>|T)[]): Promise<T>;
all<T extends any[]>(promises: T): Promise<{ [Key in keyof T]: Awaited<T[Key]> }>;
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
}
interface FileStat {
type: 'file' | 'folder';
mode: 'r' | 'rw';
}
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>;
}
interface Filesystem {
open(path: string, mode: 'r' | 'rw'): Promise<File>;
ls(path: string): AsyncIterableIterator<string>;
mkdir(path: string): Promise<void>;
mkfile(path: string): Promise<void>;
rm(path: string, recursive?: boolean): Promise<void>;
stat(path: string): Promise<FileStat>;
exists(path: string): Promise<boolean>;
}
interface Encoding {
encode(val: string): number[];
decode(val: number[]): string;
}
declare var String: StringConstructor;
//@ts-ignore
declare const arguments: IArguments;
declare var NaN: number;
declare var Infinity: number;
declare var setTimeout: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
declare var setInterval: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
declare var clearTimeout: (id: number) => void;
declare var clearInterval: (id: number) => void;
declare var parseInt: typeof Number.parseInt;
declare var parseFloat: typeof Number.parseFloat;
declare function log(...vals: any[]): void;
declare var Array: ArrayConstructor;
declare var Boolean: BooleanConstructor;
declare var Promise: PromiseConstructor;
declare var Function: FunctionConstructor;
declare var Number: NumberConstructor;
declare var Object: ObjectConstructor;
declare var Symbol: SymbolConstructor;
declare var Promise: PromiseConstructor;
declare var Math: MathObject;
declare var Encoding: Encoding;
declare var Filesystem: Filesystem;
declare var Error: ErrorConstructor;
declare var RangeError: RangeErrorConstructor;
declare var TypeError: TypeErrorConstructor;
declare var SyntaxError: SyntaxErrorConstructor;
declare var self: typeof globalThis;
declare class Map<KeyT, ValueT> {
public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>;
public clear(): void;
public delete(key: KeyT): boolean;
public entries(): IterableIterator<[KeyT, ValueT]>;
public keys(): IterableIterator<KeyT>;
public values(): IterableIterator<ValueT>;
public get(key: KeyT): ValueT;
public set(key: KeyT, val: ValueT): this;
public has(key: KeyT): boolean;
public get size(): number;
public forEach(func: (key: KeyT, val: ValueT, map: Map<KeyT, ValueT>) => void, thisArg?: any): void;
public constructor();
}
declare class Set<T> {
public [Symbol.iterator](): IterableIterator<T>;
public entries(): IterableIterator<[T, T]>;
public keys(): IterableIterator<T>;
public values(): IterableIterator<T>;
public clear(): void;
public add(val: T): this;
public delete(val: T): boolean;
public has(key: T): boolean;
public get size(): number;
public forEach(func: (key: T, set: Set<T>) => void, thisArg?: any): void;
public constructor();
}
declare class RegExp implements Matcher, Splitter, Replacer, Searcher {
static escape(raw: any, flags?: string): RegExp;
prototype: RegExp;
exec(val: string): RegExpResult | null;
test(val: string): boolean;
toString(): string;
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
[Symbol.replace](target: string, replacement: string | ReplaceFunc): string;
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
readonly dotAll: boolean;
readonly global: boolean;
readonly hasIndices: boolean;
readonly ignoreCase: boolean;
readonly multiline: boolean;
readonly sticky: boolean;
readonly unicode: boolean;
readonly source: string;
readonly flags: string;
lastIndex: number;
constructor(pattern?: string, flags?: string);
constructor(pattern?: RegExp, flags?: string);
}

File diff suppressed because one or more lines are too long

5
src/assets/metadata.json Normal file
View File

@@ -0,0 +1,5 @@
{
"version": "${version}",
"name": "${name}",
"author": "TopchetoEU"
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
public class Buffer {
private byte[] data;

View File

@@ -1,27 +1,23 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
import java.io.File;
import java.nio.file.Path;
public class Filename {
public final String protocol;
public final String path;
public String toString() {
@Override public String toString() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
@Override public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
@Override public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
@@ -40,11 +36,6 @@ public class Filename {
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
@@ -57,4 +48,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

@@ -0,0 +1,14 @@
package me.topchetoeu.jscript.common;
public class FunctionBody {
public final FunctionBody[] children;
public final Instruction[] instructions;
public final int localsN, argsN;
public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) {
this.children = children;
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
}
}

View File

@@ -0,0 +1,369 @@
package me.topchetoeu.jscript.common;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
NOP(0),
RETURN(1),
THROW(2),
THROW_SYNTAX(3),
DELETE(4),
TRY_START(5),
TRY_END(6),
CALL(7),
CALL_NEW(8),
JMP_IF(9),
JMP_IFN(10),
JMP(11),
PUSH_UNDEFINED(12),
PUSH_NULL(13),
PUSH_BOOL(14),
PUSH_NUMBER(15),
PUSH_STRING(16),
LOAD_VAR(17),
LOAD_MEMBER(18),
LOAD_GLOB(20),
LOAD_FUNC(21),
LOAD_ARR(22),
LOAD_OBJ(23),
STORE_SELF_FUNC(24),
LOAD_REGEX(25),
DUP(26),
STORE_VAR(27),
STORE_MEMBER(28),
DISCARD(29),
MAKE_VAR(30),
DEF_PROP(31),
KEYS(32),
TYPEOF(33),
OPERATION(34);
private static final HashMap<Integer, Type> types = new HashMap<>();
public final int numeric;
static {
for (var val : Type.values()) types.put(val.numeric, val);
}
private Type(int numeric) {
this.numeric = numeric;
}
public static Type fromNumeric(int i) {
return types.get(i);
}
}
public static enum BreakpointType {
NONE,
STEP_OVER,
STEP_IN;
public boolean shouldStepIn() {
return this != NONE;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type;
public final Object[] params;
@SuppressWarnings("unchecked")
public <T> T get(int i) {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
@SuppressWarnings("unchecked")
public <T> T get(int i, T defaultVal) {
if (i >= params.length || i < 0) return defaultVal;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
var a = params[i];
var b = args[i];
if (a == null || b == null) {
if (!(a == null && b == null)) return false;
}
if (!a.equals(b)) return false;
}
return true;
}
public boolean is(int i, Object arg) {
if (params.length <= i) return false;
return params[i].equals(arg);
}
public void write(DataOutputStream writer) throws IOException {
var rawType = type.numeric;
switch (type) {
case KEYS:
case PUSH_BOOL:
case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break;
case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break;
case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
default:
}
writer.writeByte(rawType);
switch (type) {
case CALL: writer.writeInt(get(0)); break;
case CALL_NEW: writer.writeInt(get(0)); break;
case DUP: writer.writeInt(get(0)); break;
case JMP: writer.writeInt(get(0)); break;
case JMP_IF: writer.writeInt(get(0)); break;
case JMP_IFN: writer.writeInt(get(0)); break;
case LOAD_ARR: writer.writeInt(get(0)); break;
case LOAD_FUNC: {
writer.writeInt(params.length - 1);
for (var i = 0; i < params.length; i++) {
writer.writeInt(get(i + 1));
}
writer.writeInt(get(0));
break;
}
case LOAD_REGEX: writer.writeUTF(get(0)); break;
case LOAD_VAR: writer.writeInt(get(0)); break;
case MAKE_VAR: writer.writeUTF(get(0)); break;
case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break;
case PUSH_NUMBER: writer.writeDouble(get(0)); break;
case PUSH_STRING: writer.writeUTF(get(0)); break;
case STORE_SELF_FUNC: writer.writeInt(get(0)); break;
case STORE_VAR: writer.writeInt(get(0)); break;
case THROW_SYNTAX: writer.writeUTF(get(0));
case TRY_START:
writer.writeInt(get(0));
writer.writeInt(get(1));
writer.writeInt(get(2));
break;
case TYPEOF:
if (params.length > 0) writer.writeUTF(get(0));
break;
default:
}
}
private Instruction(Type type, Object ...params) {
this.type = type;
this.params = params;
}
public static Instruction read(DataInputStream stream) throws IOException {
var rawType = stream.readUnsignedByte();
var type = Type.fromNumeric(rawType & 127);
var flag = (rawType & 128) != 0;
switch (type) {
case CALL: return call(stream.readInt());
case CALL_NEW: return callNew(stream.readInt());
case DEF_PROP: return defProp();
case DELETE: return delete();
case DISCARD: return discard();
case DUP: return dup(stream.readInt());
case JMP: return jmp(stream.readInt());
case JMP_IF: return jmpIf(stream.readInt());
case JMP_IFN: return jmpIfNot(stream.readInt());
case KEYS: return keys(flag);
case LOAD_ARR: return loadArr(stream.readInt());
case LOAD_FUNC: {
var captures = new int[stream.readInt()];
for (var i = 0; i < captures.length; i++) {
captures[i] = stream.readInt();
}
return loadFunc(stream.readInt(), captures);
}
case LOAD_GLOB: return loadGlob();
case LOAD_MEMBER: return loadMember();
case LOAD_OBJ: return loadObj();
case LOAD_REGEX: return loadRegex(stream.readUTF(), null);
case LOAD_VAR: return loadVar(stream.readInt());
case MAKE_VAR: return makeVar(stream.readUTF());
case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
case PUSH_NULL: return pushNull();
case PUSH_UNDEFINED: return pushUndefined();
case PUSH_BOOL: return pushValue(flag);
case PUSH_NUMBER: return pushValue(stream.readDouble());
case PUSH_STRING: return pushValue(stream.readUTF());
case RETURN: return ret();
case STORE_MEMBER: return storeMember(flag);
case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt());
case STORE_VAR: return storeVar(stream.readInt(), flag);
case THROW: return throwInstr();
case THROW_SYNTAX: return throwSyntax(stream.readUTF());
case TRY_END: return tryEnd();
case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt());
case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
case NOP:
if (flag) return null;
else return nop();
default: return null;
}
}
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
}
public static Instruction tryEnd() {
return new Instruction(Type.TRY_END);
}
public static Instruction throwInstr() {
return new Instruction(Type.THROW);
}
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction throwSyntax(String err) {
return new Instruction(Type.THROW_SYNTAX, err);
}
public static Instruction delete() {
return new Instruction(Type.DELETE);
}
public static Instruction ret() {
return new Instruction(Type.RETURN);
}
public static Instruction debug() {
return new Instruction(Type.NOP, "debug");
}
public static Instruction nop(Object ...params) {
return new Instruction(Type.NOP, params);
}
public static Instruction call(int argn) {
return new Instruction(Type.CALL, argn);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn);
}
public static Instruction jmp(int offset) {
return new Instruction(Type.JMP, offset);
}
public static Instruction jmpIf(int offset) {
return new Instruction(Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(int offset) {
return new Instruction(Type.JMP_IFN, offset);
}
public static Instruction pushUndefined() {
return new Instruction(Type.PUSH_UNDEFINED);
}
public static Instruction pushNull() {
return new Instruction(Type.PUSH_NULL);
}
public static Instruction pushValue(boolean val) {
return new Instruction(Type.PUSH_BOOL, val);
}
public static Instruction pushValue(double val) {
return new Instruction(Type.PUSH_NUMBER, val);
}
public static Instruction pushValue(String val) {
return new Instruction(Type.PUSH_STRING, val);
}
public static Instruction makeVar(String name) {
return new Instruction(Type.MAKE_VAR, name);
}
public static Instruction loadVar(Object i) {
return new Instruction(Type.LOAD_VAR, i);
}
public static Instruction loadGlob() {
return new Instruction(Type.LOAD_GLOB);
}
public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER);
}
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(int id, int[] captures) {
var args = new Object[1 + captures.length];
args[0] = id;
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
return new Instruction(Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {
return new Instruction(Type.LOAD_OBJ);
}
public static Instruction loadArr(int count) {
return new Instruction(Type.LOAD_ARR, count);
}
public static Instruction dup() {
return new Instruction(Type.DUP, 1);
}
public static Instruction dup(int count) {
return new Instruction(Type.DUP, count);
}
public static Instruction storeSelfFunc(int i) {
return new Instruction(Type.STORE_SELF_FUNC, i);
}
public static Instruction storeVar(Object i) {
return new Instruction(Type.STORE_VAR, i, false);
}
public static Instruction storeVar(Object i, boolean keep) {
return new Instruction(Type.STORE_VAR, i, keep);
}
public static Instruction storeMember() {
return new Instruction(Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(Type.STORE_MEMBER, keep);
}
public static Instruction discard() {
return new Instruction(Type.DISCARD);
}
public static Instruction typeof() {
return new Instruction(Type.TYPEOF);
}
public static Instruction typeof(String varName) {
return new Instruction(Type.TYPEOF, varName);
}
public static Instruction keys(boolean forInFormat) {
return new Instruction(Type.KEYS, forInFormat);
}
public static Instruction defProp() {
return new Instruction(Type.DEF_PROP);
}
public static Instruction operation(Operation op) {
return new Instruction(Type.OPERATION, op);
}
@Override
public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@@ -1,7 +1,9 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
import java.util.ArrayList;
public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native"));
private int line;
private int start;
private Filename filename;
@@ -12,7 +14,13 @@ public class Location implements Comparable<Location> {
@Override
public String toString() {
return filename.toString() + ":" + line + ":" + start;
var res = new ArrayList<String>();
if (filename != null) res.add(filename.toString());
if (line >= 0) res.add(line + "");
if (start >= 0) res.add(start + "");
return String.join(":", res);
}
public Location add(int n, boolean clone) {

View File

@@ -1,9 +1,18 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.json.JSON;
public class Metadata {
private static final String VERSION = "${VERSION}";
private static final String AUTHOR = "${AUTHOR}";
private static final String NAME = "${NAME}";
private static final String VERSION;
private static final String AUTHOR;
private static final String NAME;
static {
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
VERSION = data.string("version");
AUTHOR = data.string("author");
NAME = data.string("name");
}
public static String version() {
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript.common;
import java.util.HashMap;
public enum Operation {
INSTANCEOF(1, 2),
IN(2, 2),
MULTIPLY(3, 2),
DIVIDE(4, 2),
MODULO(5, 2),
ADD(6, 2),
SUBTRACT(7, 2),
USHIFT_RIGHT(8, 2),
SHIFT_RIGHT(9, 2),
SHIFT_LEFT(10, 2),
GREATER(11, 2),
LESS(12, 2),
GREATER_EQUALS(13, 2),
LESS_EQUALS(14, 2),
LOOSE_EQUALS(15, 2),
LOOSE_NOT_EQUALS(16, 2),
EQUALS(17, 2),
NOT_EQUALS(18, 2),
AND(19, 2),
OR(20, 2),
XOR(21, 2),
NEG(23, 1),
POS(24, 1),
NOT(25, 1),
INVERSE(26, 1);
private static final HashMap<Integer, Operation> operations = new HashMap<>();
static {
for (var val : Operation.values()) operations.put(val.numeric, val);
}
public final int numeric;
public final int operands;
private Operation(int numeric, int n) {
this.numeric = numeric;
this.operands = n;
}
public static Operation fromNumeric(int i) {
return operations.get(i);
}
}

View File

@@ -1,25 +1,26 @@
package me.topchetoeu.jscript;
package me.topchetoeu.jscript.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import java.io.UncheckedIOException;
public class Reading {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static synchronized String read() throws IOException {
public static synchronized String readline() throws IOException {
return reader.readLine();
}
public static String streamToString(InputStream in) {
try { return new String(in.readAllBytes()); }
catch (Throwable e) { throw new UncheckedException(e); }
try {
return new String(in.readAllBytes());
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/assets/" + name);
return Reading.class.getResourceAsStream("/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.common;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class RefTracker {
public static void onDestroy(Object obj, Runnable runnable) {
var queue = new ReferenceQueue<>();
var ref = new WeakReference<>(obj, queue);
obj = null;
var th = new Thread(() -> {
try {
queue.remove();
ref.get();
runnable.run();
}
catch (InterruptedException e) { return; }
});
th.setDaemon(true);
th.start();
}
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.common;
public interface ResultRunnable<T> {
T run();
}

View File

@@ -1,34 +1,31 @@
package me.topchetoeu.jscript.events;
public class DataNotifier<T> implements Awaitable<T> {
private Notifier notifier = new Notifier();
private boolean isErr;
private T val;
private RuntimeException err;
public void error(RuntimeException t) {
err = t;
isErr = true;
notifier.next();
}
public void error(Throwable t) {
error(new RuntimeException(t));
}
public void next(T val) {
this.val = val;
isErr = false;
notifier.next();
}
public T await() {
notifier.await();
try {
if (isErr) throw err;
else return val;
}
finally {
this.err = null;
this.val = null;
}
}
}
package me.topchetoeu.jscript.common.events;
public class DataNotifier<T> {
private Notifier notifier = new Notifier();
private boolean isErr;
private T val;
private RuntimeException err;
public void error(RuntimeException t) {
err = t;
isErr = true;
notifier.next();
}
public void next(T val) {
this.val = val;
isErr = false;
notifier.next();
}
public T await() {
notifier.await();
try {
if (isErr) throw err;
else return val;
}
finally {
this.err = null;
this.val = null;
}
}
}

View File

@@ -1,19 +1,19 @@
package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Notifier {
private boolean ok = false;
public synchronized void next() {
ok = true;
notifyAll();
}
public synchronized void await() {
try {
while (!ok) wait();
ok = false;
}
catch (InterruptedException e) { throw new InterruptException(e); }
}
}
package me.topchetoeu.jscript.common.events;
import me.topchetoeu.jscript.core.exceptions.InterruptException;
public class Notifier {
private boolean ok = false;
public synchronized void next() {
ok = true;
notifyAll();
}
public synchronized void await() {
try {
while (!ok) wait();
ok = false;
}
catch (InterruptedException e) { throw new InterruptException(e); }
}
}

View File

@@ -1,243 +1,243 @@
package me.topchetoeu.jscript.json;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.Operator;
import me.topchetoeu.jscript.parsing.ParseRes;
import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.parsing.Token;
public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Context ctx, Object val) {
return fromJs(ctx, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
if (minus) i++;
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
else return res.transform();
}
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(true, 1);
else if (id.result.equals("false")) return ParseRes.res(false, 1);
else return ParseRes.failed();
}
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
parseBool(filename, tokens, i),
parseMap(filename, tokens, i),
parseList(filename, tokens, i)
);
}
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
var values = new JSONMap();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
var name = ParseRes.any(
parseIdentifier(tokens, i + n),
parseString(filename, tokens, i + n),
parseNumber(filename, tokens, i + n)
);
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
else n += name.n;
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
}
n++;
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.put(name.result.toString(), JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
var values = new JSONList();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.add(JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(el.list().get(i)));
}
res.append("]");
return res.toString();
}
if (el.isMap()) {
var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":");
res.append(stringify(entries.get(i).getValue()));
}
res.append("}");
return res.toString();
}
return null;
}
public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map));
}
public static String stringify(JSONList list) {
return stringify(JSONElement.of(list));
}
}
package me.topchetoeu.jscript.common.json;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.compilation.parsing.Operator;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Token;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.values.ArrayValue;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : Values.getMembers(ctx, val, false, false)) {
var jsonEl = fromJs(ctx, Values.getMember(ctx, val, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Context ctx, Object val) {
return fromJs(ctx, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
if (minus) i++;
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
else return res.transform();
}
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(true, 1);
else if (id.result.equals("false")) return ParseRes.res(false, 1);
else return ParseRes.failed();
}
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
parseBool(filename, tokens, i),
parseMap(filename, tokens, i),
parseList(filename, tokens, i)
);
}
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
var values = new JSONMap();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
var name = ParseRes.any(
parseIdentifier(tokens, i + n),
parseString(filename, tokens, i + n),
parseNumber(filename, tokens, i + n)
);
if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name);
else n += name.n;
if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) {
return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name);
}
n++;
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.put(name.result.toString(), JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
var values = new JSONList();
while (true) {
if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
var res = parseValue(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res);
else n += res.n;
values.add(JSONElement.of(res.result));
if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++;
else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) {
n++;
break;
}
}
return ParseRes.res(values, n);
}
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
else return JSONElement.of(res.result);
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(el.list().get(i)));
}
res.append("]");
return res.toString();
}
if (el.isMap()) {
var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":");
res.append(stringify(entries.get(i).getValue()));
}
res.append("}");
return res.toString();
}
return null;
}
public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map));
}
public static String stringify(JSONList list) {
return stringify(JSONElement.of(list));
}
}

View File

@@ -1,88 +1,88 @@
package me.topchetoeu.jscript.json;
public class JSONElement {
public static enum Type {
STRING,
NUMBER,
BOOLEAN,
NULL,
LIST,
MAP,
}
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val);
}
public static JSONElement list(JSONList val) {
return new JSONElement(Type.LIST, val);
}
public static JSONElement string(String val) {
return new JSONElement(Type.STRING, val);
}
public static JSONElement number(double val) {
return new JSONElement(Type.NUMBER, val);
}
public static JSONElement bool(boolean val) {
return new JSONElement(Type.BOOLEAN, val);
}
public static JSONElement of(Object val) {
if (val instanceof JSONMap) return map((JSONMap)val);
else if (val instanceof JSONList) return list((JSONList)val);
else if (val instanceof String) return string((String)val);
else if (val instanceof Boolean) return bool((Boolean)val);
else if (val instanceof Number) return number(((Number)val).doubleValue());
else if (val == null) return NULL;
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
}
public final Type type;
private final Object value;
public boolean isMap() { return type == Type.MAP; }
public boolean isList() { return type == Type.LIST; }
public boolean isString() { return type == Type.STRING; }
public boolean isNumber() { return type == Type.NUMBER; }
public boolean isBoolean() { return type == Type.BOOLEAN; }
public boolean isNull() { return type == Type.NULL; }
public JSONMap map() {
if (!isMap()) throw new IllegalStateException("Element is not a map.");
return (JSONMap)value;
}
public JSONList list() {
if (!isList()) throw new IllegalStateException("Element is not a map.");
return (JSONList)value;
}
public String string() {
if (!isString()) throw new IllegalStateException("Element is not a string.");
return (String)value;
}
public double number() {
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
return (double)value;
}
public boolean bool() {
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
@Override
public String toString() {
if (isMap()) return "{...}";
if (isList()) return "[...]";
if (isString()) return (String)value;
if (isString()) return (String)value;
if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null";
return "";
}
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;
}
}
package me.topchetoeu.jscript.common.json;
public class JSONElement {
public static enum Type {
STRING,
NUMBER,
BOOLEAN,
NULL,
LIST,
MAP,
}
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val);
}
public static JSONElement list(JSONList val) {
return new JSONElement(Type.LIST, val);
}
public static JSONElement string(String val) {
return new JSONElement(Type.STRING, val);
}
public static JSONElement number(double val) {
return new JSONElement(Type.NUMBER, val);
}
public static JSONElement bool(boolean val) {
return new JSONElement(Type.BOOLEAN, val);
}
public static JSONElement of(Object val) {
if (val instanceof JSONMap) return map((JSONMap)val);
else if (val instanceof JSONList) return list((JSONList)val);
else if (val instanceof String) return string((String)val);
else if (val instanceof Boolean) return bool((Boolean)val);
else if (val instanceof Number) return number(((Number)val).doubleValue());
else if (val == null) return NULL;
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap.");
}
public final Type type;
private final Object value;
public boolean isMap() { return type == Type.MAP; }
public boolean isList() { return type == Type.LIST; }
public boolean isString() { return type == Type.STRING; }
public boolean isNumber() { return type == Type.NUMBER; }
public boolean isBoolean() { return type == Type.BOOLEAN; }
public boolean isNull() { return type == Type.NULL; }
public JSONMap map() {
if (!isMap()) throw new IllegalStateException("Element is not a map.");
return (JSONMap)value;
}
public JSONList list() {
if (!isList()) throw new IllegalStateException("Element is not a map.");
return (JSONList)value;
}
public String string() {
if (!isString()) throw new IllegalStateException("Element is not a string.");
return (String)value;
}
public double number() {
if (!isNumber()) throw new IllegalStateException("Element is not a number.");
return (double)value;
}
public boolean bool() {
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
@Override
public String toString() {
if (isMap()) return "{...}";
if (isList()) return "[...]";
if (isString()) return (String)value;
if (isString()) return (String)value;
if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null";
return "";
}
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;
}
}

View File

@@ -1,26 +1,26 @@
package me.topchetoeu.jscript.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class JSONList extends ArrayList<JSONElement> {
public JSONList() {}
public JSONList(JSONElement ...els) {
super(List.of(els));
}
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
}
package me.topchetoeu.jscript.common.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class JSONList extends ArrayList<JSONElement> {
public JSONList() {}
public JSONList(JSONElement ...els) {
super(List.of(els));
}
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
}

View File

@@ -1,150 +1,150 @@
package me.topchetoeu.jscript.json;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JSONMap implements Map<String, JSONElement> {
private Map<String, JSONElement> elements = new HashMap<>();
public JSONElement get(String path) {
var curr = this;
var segs = path.split("\\.");
var i = 0;
while (true) {
var tmp = curr.elements.get(segs[i++]);
if (i == segs.length) return tmp;
if (!tmp.isMap()) return null;
curr = tmp.map();
}
}
public boolean isMap(String path) {
var el = get(path);
return el != null && el.isMap();
}
public boolean isList(String path) {
var el = get(path);
return el != null && el.isList();
}
public boolean isString(String path) {
var el = get(path);
return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isMap()) return el.map();
return defaultVal;
}
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isList()) return el.list();
return defaultVal;
}
public String string(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isString()) return el.string();
return defaultVal;
}
public double number(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isNumber()) return el.number();
return defaultVal;
}
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool();
return defaultVal;
}
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override
public int size() { return elements.size(); }
@Override
public boolean isEmpty() { return elements.isEmpty(); }
@Override
public boolean containsKey(Object key) { return elements.containsKey(key); }
@Override
public boolean containsValue(Object value) { return elements.containsValue(value); }
@Override
public JSONElement get(Object key) { return elements.get(key); }
@Override
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
@Override
public JSONElement remove(Object key) { return elements.remove(key); }
@Override
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override
public void clear() { elements.clear(); }
@Override
public Set<String> keySet() { return elements.keySet(); }
@Override
public Collection<JSONElement> values() { return elements.values(); }
@Override
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
public JSONMap() { }
public JSONMap(Map<String, JSONElement> els) {
this.elements = new HashMap<>(els);
}
}
package me.topchetoeu.jscript.common.json;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JSONMap implements Map<String, JSONElement> {
private Map<String, JSONElement> elements = new HashMap<>();
public JSONElement get(String path) {
var curr = this;
var segs = path.split("\\.");
var i = 0;
while (true) {
var tmp = curr.elements.get(segs[i++]);
if (i == segs.length) return tmp;
if (!tmp.isMap()) return null;
curr = tmp.map();
}
}
public boolean isMap(String path) {
var el = get(path);
return el != null && el.isMap();
}
public boolean isList(String path) {
var el = get(path);
return el != null && el.isList();
}
public boolean isString(String path) {
var el = get(path);
return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isMap()) return el.map();
return defaultVal;
}
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isList()) return el.list();
return defaultVal;
}
public String string(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isString()) return el.string();
return defaultVal;
}
public double number(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isNumber()) return el.number();
return defaultVal;
}
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool();
return defaultVal;
}
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override
public int size() { return elements.size(); }
@Override
public boolean isEmpty() { return elements.isEmpty(); }
@Override
public boolean containsKey(Object key) { return elements.containsKey(key); }
@Override
public boolean containsValue(Object value) { return elements.containsValue(value); }
@Override
public JSONElement get(Object key) { return elements.get(key); }
@Override
public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
@Override
public JSONElement remove(Object key) { return elements.remove(key); }
@Override
public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override
public void clear() { elements.clear(); }
@Override
public Set<String> keySet() { return elements.keySet(); }
@Override
public Collection<JSONElement> values() { return elements.values(); }
@Override
public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
public JSONMap() { }
public JSONMap(Map<String, JSONElement> els) {
this.elements = new HashMap<>(els);
}
}

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.common.mapping;
public enum ConvertType {
Exact,
Lower,
Upper,
Both,
}

View File

@@ -0,0 +1,190 @@
package me.topchetoeu.jscript.common.mapping;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.utils.mapping.SourceMap;
public class FunctionMap {
public static class FunctionMapBuilder {
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
public Location toLocation(int pc) {
return sourceMap.headMap(pc, true).firstEntry().getValue();
}
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
if (loc == null || type == null || type == BreakpointType.NONE) return this;
breakpoints.put(loc, type);
return this;
}
public FunctionMapBuilder setLocation(int i, Location loc) {
if (loc == null || i < 0) return this;
sourceMap.put(i, loc);
return this;
}
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
setDebug(loc, type);
setLocation(i, loc);
return this;
}
public Location first() {
if (sourceMap.size() == 0) return null;
return sourceMap.firstEntry().getValue();
}
public Location last() {
if (sourceMap.size() == 0) return null;
return sourceMap.lastEntry().getValue();
}
public FunctionMap build(String[] localNames, String[] captureNames) {
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
}
public FunctionMap build(LocalScopeRecord scope) {
return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures());
}
public FunctionMap build() {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
private FunctionMapBuilder() { }
}
public static final FunctionMap EMPTY = new FunctionMap();
private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
public final String[] localNames, captureNames;
public Location toLocation(int pc, boolean approxiamte) {
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
public Location toLocation(int pc) {
return toLocation(pc, false);
}
public BreakpointType getBreakpoint(int pc) {
return bps.getOrDefault(pc, BreakpointType.NONE);
}
public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
var candidates = new HashMap<Filename, TreeSet<Location>>();
for (var name : bpLocs.keySet()) {
if (filename.matcher(name.toString()).matches()) {
candidates.put(name, bpLocs.get(name));
}
}
var res = new ArrayList<Location>(candidates.size());
for (var candidate : candidates.entrySet()) {
res.add(candidate.getValue().ceiling(new Location(line, column, candidate.getKey())));
}
return res;
}
public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return List.of();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return List.of();
if (start != null) set = set.tailSet(start, true);
if (end != null) set = set.headSet(end, true);
return set.stream().collect(Collectors.toList());
}
public Location start() {
if (pcToLoc.size() == 0) return null;
return pcToLoc.firstEntry().getValue();
}
public Location end() {
if (pcToLoc.size() == 0) return null;
return pcToLoc.lastEntry().getValue();
}
public FunctionMap apply(SourceMap map) {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
for (var el : pcToLoc.entrySet()) {
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
}
res.bps.putAll(bps);
for (var el : bpLocs.entrySet()) {
for (var loc : el.getValue()) {
loc = map.toCompiled(loc);
if (loc == null) continue;
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
res.bpLocs.get(loc.filename()).add(loc);
}
}
return res;
}
public FunctionMap clone() {
var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
res.pcToLoc.putAll(this.pcToLoc);
res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res;
}
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
var locToPc = new HashMap<Location, Integer>();
for (var el : map.entrySet()) {
pcToLoc.put(el.getKey(), el.getValue());
locToPc.putIfAbsent(el.getValue(), el.getKey());
}
for (var el : breakpoints.entrySet()) {
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
bps.put(locToPc.get(el.getKey()), el.getValue());
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
bpLocs.get(el.getKey().filename()).add(el.getKey());
}
this.localNames = localNames;
this.captureNames = captureNames;
}
private FunctionMap() {
localNames = new String[0];
captureNames = new String[0];
}
public static FunctionMapBuilder builder() {
return new FunctionMapBuilder();
}
}

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
public abstract class AssignableStatement extends Statement {
public abstract Statement toAssign(Statement val, Operation operation);

View File

@@ -0,0 +1,78 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.LinkedList;
import java.util.Vector;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
public class CompileResult {
public final Vector<Instruction> instructions = new Vector<>();
public final List<CompileResult> children = new LinkedList<>();
public final FunctionMapBuilder map = FunctionMap.builder();
public final LocalScopeRecord scope;
public int length = 0;
public int temp() {
instructions.add(null);
return instructions.size() - 1;
}
public CompileResult add(Instruction instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public Instruction get(int i) {
return instructions.get(i);
}
public int size() { return instructions.size(); }
public void setDebug(Location loc, BreakpointType type) {
map.setDebug(loc, type);
}
public void setLocation(int i, Location loc) {
map.setLocation(i, loc);
}
public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
map.setLocationAndDebug(i, loc, type);
}
public void setDebug(BreakpointType type) {
setDebug(map.last(), type);
}
public void setLocation(Location type) {
setLocation(instructions.size() - 1, type);
}
public void setLocationAndDebug(Location loc, BreakpointType type) {
setLocationAndDebug(instructions.size() - 1, loc, type);
}
public CompileResult addChild(CompileResult child) {
this.children.add(child);
return child;
}
public FunctionMap map() {
return map.build(scope);
}
public FunctionBody body() {
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren);
}
public CompileResult(LocalScopeRecord scope) {
this.scope = scope;
}
}

View File

@@ -3,10 +3,10 @@ package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement {
public final Statement[] statements;
@@ -22,16 +22,16 @@ public class CompoundStatement extends Statement {
}
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : statements) stm.declare(varsScope);
public void declare(CompileResult target) {
for (var stm : statements) stm.declare(target);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
List<Statement> statements = new Vector<Statement>();
if (separateFuncs) for (var stm : this.statements) {
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
stm.compile(target, scope, false);
stm.compile(target, false);
}
else statements.add(stm);
}
@@ -42,12 +42,12 @@ public class CompoundStatement extends Statement {
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
}
if (!polluted && pollute) {
target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.pushUndefined());
}
}

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
public abstract class Statement {
private Location _loc;
public boolean pure() { return false; }
public void declare(CompileResult target) { }
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
int start = target.size();
compile(target, pollute);
if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
}
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.NONE);
}
public Location loc() { return _loc; }
public void setLoc(Location loc) { _loc = loc; }
protected Statement(Location loc) {
this._loc = loc;
}
}

View File

@@ -0,0 +1,18 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class ThrowSyntaxStatement extends Statement {
public final String name;
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.throwSyntax(name));
}
public ThrowSyntaxStatement(SyntaxException e) {
super(e.loc);
this.name = e.msg;
}
}

View File

@@ -1,52 +1,52 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableDeclareStatement extends Statement {
public static class Pair {
public final String name;
public final Statement value;
public final Location location;
public Pair(String name, Statement value, Location location) {
this.name = name;
this.value = value;
this.location = location;
}
}
public final List<Pair> values;
@Override
public void declare(ScopeRecord varsScope) {
for (var key : values) {
varsScope.define(key.name);
}
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var entry : values) {
if (entry.name == null) continue;
var key = scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
if (entry.value != null) {
FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
target.add(Instruction.storeVar(entry.location, key));
}
}
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public VariableDeclareStatement(Location loc, List<Pair> values) {
super(loc);
this.values = values;
}
}
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
public class VariableDeclareStatement extends Statement {
public static class Pair {
public final String name;
public final Statement value;
public final Location location;
public Pair(String name, Statement value, Location location) {
this.name = name;
this.value = value;
this.location = location;
}
}
public final List<Pair> values;
@Override
public void declare(CompileResult target) {
for (var key : values) {
target.scope.define(key.name);
}
}
@Override
public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
if (entry.name == null) continue;
var key = target.scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
if (entry.value != null) {
FunctionStatement.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
target.add(Instruction.storeVar(key));
}
}
if (pollute) target.add(Instruction.pushUndefined());
}
public VariableDeclareStatement(Location loc, List<Pair> values) {
super(loc);
this.values = values;
}
}

View File

@@ -0,0 +1,21 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class BreakStatement extends Statement {
public final String label;
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.nop("break", label));
if (pollute) target.add(Instruction.pushUndefined());
}
public BreakStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@@ -1,22 +1,21 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ContinueStatement extends Statement {
public final String label;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public ContinueStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ContinueStatement extends Statement {
public final String label;
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.pushUndefined());
}
public ContinueStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@@ -0,0 +1,18 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class DebugStatement extends Statement {
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.debug());
if (pollute) target.add(Instruction.pushUndefined());
}
public DebugStatement(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,26 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class DeleteStatement extends Statement {
public final Statement key;
public final Statement value;
@Override
public void compile(CompileResult target, boolean pollute) {
value.compile(target, true);
key.compile(target, true);
target.add(Instruction.delete());
if (pollute) target.add(Instruction.pushValue(true));
}
public DeleteStatement(Location loc, Statement key, Statement value) {
super(loc);
this.key = key;
this.value = value;
}
}

View File

@@ -0,0 +1,36 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class DoWhileStatement extends Statement {
public final Statement condition, body;
public final String label;
@Override
public void declare(CompileResult target) {
body.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute) {
int start = target.size();
body.compile(target, false, BreakpointType.STEP_OVER);
int mid = target.size();
condition.compile(target, true, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end));
}
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
}

View File

@@ -0,0 +1,69 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ForInStatement extends Statement {
public final String varName;
public final boolean isDeclaration;
public final Statement varValue, object, body;
public final String label;
public final Location varLocation;
@Override
public void declare(CompileResult target) {
body.declare(target);
if (isDeclaration) target.scope.define(varName);
}
@Override
public void compile(CompileResult target, boolean pollute) {
var key = target.scope.getKey(varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
if (varValue != null) {
varValue.compile(target, true);
target.add(Instruction.storeVar(target.scope.getKey(varName)));
}
object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(true));
int start = target.size();
target.add(Instruction.dup());
target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS));
int mid = target.temp();
target.add(Instruction.pushValue("value")).setLocation(varLocation);
target.add(Instruction.loadMember()).setLocation(varLocation);
target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
body.compile(target, false, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end));
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(end - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc);
this.varLocation = varLocation;
this.label = label;
this.isDeclaration = isDecl;
this.varName = varName;
this.varValue = varValue;
this.object = object;
this.body = body;
}
}

View File

@@ -0,0 +1,45 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body;
public final String label;
@Override
public void declare(CompileResult target) {
declaration.declare(target);
body.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute) {
declaration.compile(target, false, BreakpointType.STEP_OVER);
int start = target.size();
condition.compile(target, true, BreakpointType.STEP_OVER);
int mid = target.temp();
body.compile(target, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size();
assignment.compile(target, false, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
}

View File

@@ -0,0 +1,48 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class IfStatement extends Statement {
public final Statement condition, body, elseBody;
@Override
public void declare(CompileResult target) {
body.declare(target);
if (elseBody != null) elseBody.declare(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, true, breakpoint);
if (elseBody == null) {
int i = target.temp();
body.compile(target, pollute, breakpoint);
int endI = target.size();
target.set(i, Instruction.jmpIfNot(endI - i));
}
else {
int start = target.temp();
body.compile(target, pollute, breakpoint);
int mid = target.temp();
elseBody.compile(target, pollute, breakpoint);
int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start + 1));
target.set(mid, Instruction.jmp(end - mid));
}
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
super(loc);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
}
}

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ReturnStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileResult target, boolean pollute) {
if (value == null) target.add(Instruction.pushUndefined());
else value.compile(target, true);
target.add(Instruction.ret()).setLocation(loc());
}
public ReturnStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@@ -1,87 +1,83 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.HashMap;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class SwitchStatement extends Statement {
public static class SwitchCase {
public final Statement value;
public final int statementI;
public SwitchCase(Statement value, int statementI) {
this.value = value;
this.statementI = statementI;
}
}
public final Statement value;
public final SwitchCase[] cases;
public final Statement[] body;
public final int defaultI;
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : body) stm.declare(varsScope);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, scope, true, BreakpointType.STEP_OVER);
for (var ccase : cases) {
target.add(Instruction.dup(loc()));
ccase.value.compile(target, scope, true);
target.add(Instruction.operation(loc(), Operation.EQUALS));
caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop(null));
}
int start = target.size();
target.add(Instruction.nop(null));
for (var stm : body) {
statementToIndex.put(statementToIndex.size(), target.size());
stm.compile(target, scope, false, BreakpointType.STEP_OVER);
}
int end = target.size();
target.add(Instruction.discard(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null));
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
for (int i = start; i < end; i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
}
}
for (var el : caseToStatement.entrySet()) {
var i = statementToIndex.get(el.getValue());
if (i == null) i = end;
target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
}
}
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
super(loc);
this.value = value;
this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
}
package me.topchetoeu.jscript.compilation.control;
import java.util.HashMap;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class SwitchStatement extends Statement {
public static class SwitchCase {
public final Statement value;
public final int statementI;
public SwitchCase(Statement value, int statementI) {
this.value = value;
this.statementI = statementI;
}
}
public final Statement value;
public final SwitchCase[] cases;
public final Statement[] body;
public final int defaultI;
@Override
public void declare(CompileResult target) {
for (var stm : body) stm.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute) {
var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, true, BreakpointType.STEP_OVER);
for (var ccase : cases) {
target.add(Instruction.dup());
ccase.value.compile(target, true);
target.add(Instruction.operation(Operation.EQUALS));
caseToStatement.put(target.temp(), ccase.statementI);
}
int start = target.temp();
for (var stm : body) {
statementToIndex.put(statementToIndex.size(), target.size());
stm.compile(target, false, BreakpointType.STEP_OVER);
}
int end = target.size();
target.add(Instruction.discard());
if (pollute) target.add(Instruction.pushUndefined());
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start));
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
for (int i = start; i < end; i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
target.set(i, Instruction.jmp(end - i));
}
}
for (var el : caseToStatement.entrySet()) {
var i = statementToIndex.get(el.getValue());
if (i == null) i = end;
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
}
}
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
super(loc);
this.value = value;
this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
}

View File

@@ -0,0 +1,21 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ThrowStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileResult target, boolean pollute) {
value.compile(target, true);
target.add(Instruction.throwInstr()).setLocation(loc());
}
public ThrowStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@@ -0,0 +1,58 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class TryStatement extends Statement {
public final Statement tryBody;
public final Statement catchBody;
public final Statement finallyBody;
public final String name;
@Override
public void declare(CompileResult target) {
tryBody.declare(target);
if (catchBody != null) catchBody.declare(target);
if (finallyBody != null) finallyBody.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
int replace = target.temp();
int start = replace + 1, catchStart = -1, finallyStart = -1;
tryBody.compile(target, false);
target.add(Instruction.tryEnd());
if (catchBody != null) {
catchStart = target.size() - start;
target.scope.define(name, true);
catchBody.compile(target, false);
target.scope.undefine();
target.add(Instruction.tryEnd());
}
if (finallyBody != null) {
finallyStart = target.size() - start;
finallyBody.compile(target, false);
target.add(Instruction.tryEnd());
}
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.pushUndefined());
}
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
super(loc);
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.name = name;
}
}

View File

@@ -0,0 +1,52 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class WhileStatement extends Statement {
public final Statement condition, body;
public final String label;
@Override
public void declare(CompileResult target) {
body.declare(target);
}
@Override
public void compile(CompileResult target, boolean pollute) {
int start = target.size();
condition.compile(target, true);
int mid = target.temp();
body.compile(target, false, BreakpointType.STEP_OVER);
int end = target.size();
replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) {
for (int i = start; i < end; i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(continuePoint - i));
}
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(breakPoint - i));
}
}
}
}

View File

@@ -1,113 +1,113 @@
package me.topchetoeu.jscript.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.engine.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13),
DIVIDE("/", Operation.DIVIDE, 12),
MODULO("%", Operation.MODULO, 12),
SUBTRACT("-", Operation.SUBTRACT, 11),
ADD("+", Operation.ADD, 11),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
GREATER(">", Operation.GREATER, 9),
LESS("<", Operation.LESS, 9),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
EQUALS("==", Operation.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Operation.EQUALS, 8),
AND("&", Operation.AND, 7),
XOR("^", Operation.XOR, 6),
OR("|", Operation.OR, 5),
LAZY_AND("&&", 4),
LAZY_OR("||", 3),
ASSIGN_SHIFT_LEFT("<<=", 2, true),
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
ASSIGN_AND("&=", 2, true),
ASSIGN_OR("|=", 2, true),
ASSIGN_XOR("^=", 2, true),
ASSIGN_MODULO("%=", 2, true),
ASSIGN_DIVIDE("/=", 2, true),
ASSIGN_MULTIPLY("*=", 2, true),
ASSIGN_SUBTRACT("-=", 2, true),
ASSIGN_ADD("+=", 2, true),
ASSIGN("=", 2, true),
SEMICOLON(";"),
COLON(":"),
PAREN_OPEN("("),
PAREN_CLOSE(")"),
BRACKET_OPEN("["),
BRACKET_CLOSE("]"),
BRACE_OPEN("{"),
BRACE_CLOSE("}"),
DOT("."),
COMMA(","),
NOT("!"),
QUESTION("?"),
INVERSE("~"),
INCREASE("++"),
DECREASE("--");
public final String value;
public final Operation operation;
public final int precedence;
public final boolean reverse;
private static final Map<String, Operator> ops = new HashMap<>();
static {
for (var el : Operator.values()) {
ops.put(el.value, el);
}
}
public boolean isAssign() { return precedence == 2; }
public static Operator parse(String val) {
return ops.get(val);
}
private Operator() {
this.value = null;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value) {
this. value = value;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value, int precedence) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, int precedence, boolean reverse) {
this. value = value;
this.operation = null;
this.precedence = precedence;
this.reverse = reverse;
}
private Operator(String value, Operation funcName, int precedence) {
this. value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = reverse;
}
package me.topchetoeu.jscript.compilation.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.common.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13),
DIVIDE("/", Operation.DIVIDE, 12),
MODULO("%", Operation.MODULO, 12),
SUBTRACT("-", Operation.SUBTRACT, 11),
ADD("+", Operation.ADD, 11),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
GREATER(">", Operation.GREATER, 9),
LESS("<", Operation.LESS, 9),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
EQUALS("==", Operation.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Operation.EQUALS, 8),
AND("&", Operation.AND, 7),
XOR("^", Operation.XOR, 6),
OR("|", Operation.OR, 5),
LAZY_AND("&&", 4),
LAZY_OR("||", 3),
ASSIGN_SHIFT_LEFT("<<=", 2, true),
ASSIGN_SHIFT_RIGHT(">>=", 2, true),
ASSIGN_USHIFT_RIGHT(">>>=", 2, true),
ASSIGN_AND("&=", 2, true),
ASSIGN_OR("|=", 2, true),
ASSIGN_XOR("^=", 2, true),
ASSIGN_MODULO("%=", 2, true),
ASSIGN_DIVIDE("/=", 2, true),
ASSIGN_MULTIPLY("*=", 2, true),
ASSIGN_SUBTRACT("-=", 2, true),
ASSIGN_ADD("+=", 2, true),
ASSIGN("=", 2, true),
SEMICOLON(";"),
COLON(":"),
PAREN_OPEN("("),
PAREN_CLOSE(")"),
BRACKET_OPEN("["),
BRACKET_CLOSE("]"),
BRACE_OPEN("{"),
BRACE_CLOSE("}"),
DOT("."),
COMMA(","),
NOT("!"),
QUESTION("?"),
INVERSE("~"),
INCREASE("++"),
DECREASE("--");
public final String readable;
public final Operation operation;
public final int precedence;
public final boolean reverse;
private static final Map<String, Operator> ops = new HashMap<>();
static {
for (var el : Operator.values()) {
ops.put(el.readable, el);
}
}
public boolean isAssign() { return precedence == 2; }
public static Operator parse(String val) {
return ops.get(val);
}
private Operator() {
this.readable = null;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value) {
this.readable = value;
this.operation = null;
this.precedence = -1;
this.reverse = false;
}
private Operator(String value, int precedence) {
this.readable = value;
this.operation = null;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, int precedence, boolean reverse) {
this.readable = value;
this.operation = null;
this.precedence = precedence;
this.reverse = reverse;
}
private Operator(String value, Operation funcName, int precedence) {
this.readable = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.readable = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = reverse;
}
}

View File

@@ -1,97 +1,100 @@
package me.topchetoeu.jscript.parsing;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.parsing.Parsing.Parser;
public class ParseRes<T> {
public static enum State {
SUCCESS,
FAILED,
ERROR;
public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; }
}
public final ParseRes.State state;
public final String error;
public final T result;
public final int n;
private ParseRes(ParseRes.State state, String error, T result, int readN) {
this.result = result;
this.n = readN;
this.state = state;
this.error = error;
}
public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, i);
}
public ParseRes<T> addN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, this.n + i);
}
public <T2> ParseRes<T2> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, error, null, 0);
}
public TestRes toTest() {
if (isSuccess()) return TestRes.res(n);
else if (isError()) return TestRes.error(null, error);
else return TestRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new ParseRes<>(State.ERROR, error, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
return new ParseRes<>(State.ERROR, other.error, null, 0);
}
public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, val, i);
}
@SafeVarargs
public static <T> ParseRes<? extends T> any(ParseRes<? extends T> ...parsers) {
ParseRes<? extends T> best = null;
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
if (parser.isSuccess()) {
if (best == null || best.n < parser.n) best = parser;
}
else if (parser.isError() && error.isFailed()) error = parser.transform();
}
if (best != null) return best;
else return error;
}
@SafeVarargs
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
var res = parser.parse(null, tokens, 0);
if (res.isSuccess()) return res;
else if (res.isError() && error.isFailed()) error = res.transform();
}
return error;
}
package me.topchetoeu.jscript.compilation.parsing;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser;
public class ParseRes<T> {
public static enum State {
SUCCESS,
FAILED,
ERROR;
public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; }
}
public final ParseRes.State state;
public final String error;
public final T result;
public final int n;
private ParseRes(ParseRes.State state, String error, T result, int readN) {
this.result = result;
this.n = readN;
this.state = state;
this.error = error;
}
public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, i);
}
public ParseRes<T> addN(int i) {
if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, result, this.n + i);
}
public <T2> ParseRes<T2> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, error, null, 0);
}
public TestRes toTest() {
if (isSuccess()) return TestRes.res(n);
else if (isError()) return TestRes.error(null, error);
else return TestRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new ParseRes<>(State.ERROR, error, null, 0);
}
public static <T> ParseRes<T> error(Location loc, String error, ParseRes<?> other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0);
return new ParseRes<>(State.ERROR, other.error, null, 0);
}
public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, val, i);
}
@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();
for (var parser : parsers) {
if (parser.isSuccess()) {
if (best == null || best.n < parser.n) best = parser;
}
else if (parser.isError() && error.isFailed()) error = parser.transform();
}
if (best != null) return best;
else return error;
}
@SafeVarargs
public static <T> ParseRes<? extends T> first(String filename, List<Token> tokens, Map<String, Parser<T>> named, Parser<? extends T> ...parsers) {
ParseRes<? extends T> error = ParseRes.failed();
for (var parser : parsers) {
var res = parser.parse(null, tokens, 0);
if (res.isSuccess()) return res;
else if (res.isError() && error.isFailed()) error = res.transform();
}
return error;
}
}

View File

@@ -1,15 +1,15 @@
package me.topchetoeu.jscript.parsing;
public class RawToken {
public final String value;
public final TokenType type;
public final int line;
public final int start;
public RawToken(String value, TokenType type, int line, int start) {
this.value = value;
this.type = type;
this.line = line;
this.start = start;
}
package me.topchetoeu.jscript.compilation.parsing;
public class RawToken {
public final String value;
public final TokenType type;
public final int line;
public final int start;
public RawToken(String value, TokenType type, int line, int start) {
this.value = value;
this.type = type;
this.line = line;
this.start = start;
}
}

View File

@@ -1,45 +1,45 @@
package me.topchetoeu.jscript.parsing;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.parsing.ParseRes.State;
public class TestRes {
public final State state;
public final String error;
public final int i;
private TestRes(ParseRes.State state, String error, int i) {
this.i = i;
this.state = state;
this.error = error;
}
public TestRes add(int n) {
return new TestRes(state, null, this.i + n);
}
public <T> ParseRes<T> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed.");
else if (isError()) return ParseRes.error(null, error);
else return ParseRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static TestRes failed() {
return new TestRes(State.FAILED, null, 0);
}
public static TestRes error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new TestRes(State.ERROR, error, 0);
}
public static TestRes error(Location loc, String error, TestRes other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new TestRes(State.ERROR, error, 0);
return new TestRes(State.ERROR, other.error, 0);
}
public static TestRes res(int i) {
return new TestRes(State.SUCCESS, null, i);
}
package me.topchetoeu.jscript.compilation.parsing;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.parsing.ParseRes.State;
public class TestRes {
public final State state;
public final String error;
public final int i;
private TestRes(ParseRes.State state, String error, int i) {
this.i = i;
this.state = state;
this.error = error;
}
public TestRes add(int n) {
return new TestRes(state, null, this.i + n);
}
public <T> ParseRes<T> transform() {
if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed.");
else if (isError()) return ParseRes.error(null, error);
else return ParseRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); }
public static TestRes failed() {
return new TestRes(State.FAILED, null, 0);
}
public static TestRes error(Location loc, String error) {
if (loc != null) error = loc + ": " + error;
return new TestRes(State.ERROR, error, 0);
}
public static TestRes error(Location loc, String error, TestRes other) {
if (loc != null) error = loc + ": " + error;
if (!other.isError()) return new TestRes(State.ERROR, error, 0);
return new TestRes(State.ERROR, other.error, 0);
}
public static TestRes res(int i) {
return new TestRes(State.SUCCESS, null, i);
}
}

View File

@@ -1,55 +1,58 @@
package me.topchetoeu.jscript.parsing;
public class Token {
public final Object value;
public final boolean isString;
public final boolean isRegex;
public final int line;
public final int start;
private Token(int line, int start, Object value, boolean isString, boolean isRegex) {
this.value = value;
this.line = line;
this.start = start;
this.isString = isString;
this.isRegex = isRegex;
}
private Token(int line, int start, Object value) {
this.value = value;
this.line = line;
this.start = start;
this.isString = false;
this.isRegex = false;
}
public boolean isString() { return isString; }
public boolean isRegex() { return isRegex; }
public boolean isNumber() { return value instanceof Number; }
public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; }
public boolean isOperator() { return value instanceof Operator; }
public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); }
public boolean isOperator(Operator op) { return value.equals(op); }
public String string() { return (String)value; }
public String regex() { return (String)value; }
public double number() { return (double)value; }
public String identifier() { return (String)value; }
public Operator operator() { return (Operator)value; }
public static Token regex(int line, int start, String val) {
return new Token(line, start, val, false, true);
}
public static Token string(int line, int start, String val) {
return new Token(line, start, val, true, false);
}
public static Token number(int line, int start, double val) {
return new Token(line, start, val);
}
public static Token identifier(int line, int start, String val) {
return new Token(line, start, val);
}
public static Token operator(int line, int start, Operator val) {
return new Token(line, start, val);
}
package me.topchetoeu.jscript.compilation.parsing;
public class Token {
public final Object value;
public final String rawValue;
public final boolean isString;
public final boolean isRegex;
public final int line;
public final int start;
private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) {
this.value = value;
this.rawValue = rawValue;
this.line = line;
this.start = start;
this.isString = isString;
this.isRegex = isRegex;
}
private Token(int line, int start, Object value, String rawValue) {
this.value = value;
this.rawValue = rawValue;
this.line = line;
this.start = start;
this.isString = false;
this.isRegex = false;
}
public boolean isString() { return isString; }
public boolean isRegex() { return isRegex; }
public boolean isNumber() { return value instanceof Number; }
public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; }
public boolean isOperator() { return value instanceof Operator; }
public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); }
public boolean isOperator(Operator op) { return value.equals(op); }
public String string() { return (String)value; }
public String regex() { return (String)value; }
public double number() { return (double)value; }
public String identifier() { return (String)value; }
public Operator operator() { return (Operator)value; }
public static Token regex(int line, int start, String val, String rawValue) {
return new Token(line, start, val, rawValue, false, true);
}
public static Token string(int line, int start, String val, String rawValue) {
return new Token(line, start, val, rawValue, true, false);
}
public static Token number(int line, int start, double val, String rawValue) {
return new Token(line, start, val, rawValue);
}
public static Token identifier(int line, int start, String val) {
return new Token(line, start, val, val);
}
public static Token operator(int line, int start, Operator val) {
return new Token(line, start, val, val.readable);
}
}

View File

@@ -1,9 +1,9 @@
package me.topchetoeu.jscript.parsing;
enum TokenType {
REGEX,
STRING,
NUMBER,
LITERAL,
OPERATOR,
package me.topchetoeu.jscript.compilation.parsing;
enum TokenType {
REGEX,
STRING,
NUMBER,
LITERAL,
OPERATOR,
}

View File

@@ -1,77 +1,77 @@
package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent;
private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() {
return locals.toArray(String[]::new);
}
public LocalScopeRecord child() {
return new LocalScopeRecord(this);
}
public int localsCount() {
return locals.size();
}
public int capturesCount() {
return captures.size();
}
public int[] getCaptures() {
var buff = new int[captures.size()];
var i = 0;
for (var name : captures) {
var index = parent.getKey(name);
if (index instanceof Integer) buff[i++] = (int)index;
}
var res = new int[i];
System.arraycopy(buff, 0, res, 0, i);
return res;
}
public Object getKey(String name) {
var capI = captures.indexOf(name);
var locI = locals.lastIndexOf(name);
if (locI >= 0) return locI;
if (capI >= 0) return ~capI;
if (parent != null) {
var res = parent.getKey(name);
if (res != null && res instanceof Integer) {
captures.add(name);
return -captures.size();
}
}
return name;
}
public Object define(String name, boolean force) {
if (!force && locals.contains(name)) return locals.indexOf(name);
locals.add(name);
return locals.size() - 1;
}
public Object define(String name) {
return define(name, false);
}
public void undefine() {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord() {
this.parent = null;
}
public LocalScopeRecord(LocalScopeRecord parent) {
this.parent = parent;
}
}
package me.topchetoeu.jscript.compilation.scope;
import java.util.ArrayList;
public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent;
private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() {
return locals.toArray(String[]::new);
}
public LocalScopeRecord child() {
return new LocalScopeRecord(this);
}
public int localsCount() {
return locals.size();
}
public int capturesCount() {
return captures.size();
}
public int[] getCaptures() {
var buff = new int[captures.size()];
var i = 0;
for (var name : captures) {
var index = parent.getKey(name);
if (index instanceof Integer) buff[i++] = (int)index;
}
var res = new int[i];
System.arraycopy(buff, 0, res, 0, i);
return res;
}
public Object getKey(String name) {
var capI = captures.indexOf(name);
var locI = locals.lastIndexOf(name);
if (locI >= 0) return locI;
if (capI >= 0) return ~capI;
if (parent != null) {
var res = parent.getKey(name);
if (res != null && res instanceof Integer) {
captures.add(name);
return -captures.size();
}
}
return name;
}
public Object define(String name, boolean force) {
if (!force && locals.contains(name)) return locals.indexOf(name);
locals.add(name);
return locals.size() - 1;
}
public Object define(String name) {
return define(name, false);
}
public void undefine() {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord() {
this.parent = null;
}
public LocalScopeRecord(LocalScopeRecord parent) {
this.parent = parent;
}
}

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public LocalScopeRecord child();
}
package me.topchetoeu.jscript.compilation.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public LocalScopeRecord child();
}

View File

@@ -0,0 +1,40 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ArrayStatement extends Statement {
public final Statement[] statements;
@Override public boolean pure() {
for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadArr(statements.length));
for (var i = 0; i < statements.length; i++) {
var el = statements[i];
if (el != null) {
target.add(Instruction.dup());
target.add(Instruction.pushValue(i));
el.compile(target, true);
target.add(Instruction.storeMember());
}
}
if (!pollute) target.add(Instruction.discard());
}
public ArrayStatement(Location loc, Statement[] statements) {
super(loc);
this.statements = statements;
}
}

View File

@@ -0,0 +1,43 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class CallStatement extends Statement {
public final Statement func;
public final Statement[] args;
public final boolean isNew;
@Override
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
if (isNew) func.compile(target, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, true, true);
}
else {
target.add(Instruction.pushUndefined());
func.compile(target, true);
}
for (var arg : args) arg.compile(target, true);
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type);
if (!pollute) target.add(Instruction.discard());
}
@Override
public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
}

View File

@@ -1,32 +1,31 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement {
public final AssignableStatement value;
public final double addAmount;
public final boolean postfix;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard(loc()));
else if (postfix) {
target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
super(loc);
this.value = value;
this.addAmount = addAmount;
this.postfix = postfix;
}
}
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ChangeStatement extends Statement {
public final AssignableStatement value;
public final double addAmount;
public final boolean postfix;
@Override
public void compile(CompileResult target, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true);
if (!pollute) target.add(Instruction.discard());
else if (postfix) {
target.add(Instruction.pushValue(addAmount));
target.add(Instruction.operation(Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
super(loc);
this.value = value;
this.addAmount = addAmount;
this.postfix = postfix;
}
}

View File

@@ -0,0 +1,47 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ConstantStatement extends Statement {
public final Object value;
public final boolean isNull;
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
if (pollute) {
if (isNull) target.add(Instruction.pushNull());
else if (value instanceof Double) target.add(Instruction.pushValue((Double)value));
else if (value instanceof String) target.add(Instruction.pushValue((String)value));
else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value));
else target.add(Instruction.pushUndefined());
}
}
private ConstantStatement(Location loc, Object val, boolean isNull) {
super(loc);
this.value = val;
this.isNull = isNull;
}
public ConstantStatement(Location loc, boolean val) {
this(loc, val, false);
}
public ConstantStatement(Location loc, String val) {
this(loc, val, false);
}
public ConstantStatement(Location loc, double val) {
this(loc, val, false);
}
public static ConstantStatement ofUndefined(Location loc) {
return new ConstantStatement(loc, null, false);
}
public static ConstantStatement ofNull(Location loc) {
return new ConstantStatement(loc, null, true);
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class DiscardStatement extends Statement {
public final Statement value;
@Override public boolean pure() { return value.pure(); }
@Override
public void compile(CompileResult target, boolean pollute) {
value.compile(target, false);
if (pollute) target.add(Instruction.pushUndefined());
}
public DiscardStatement(Location loc, Statement val) {
super(loc);
this.value = val;
}
}

View File

@@ -0,0 +1,127 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.core.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
public final CompoundStatement body;
public final String varName;
public final String[] args;
public final boolean statement;
public final Location end;
@Override public boolean pure() { return varName == null && statement; }
@Override
public void declare(CompileResult target) {
if (varName != null && statement) target.scope.define(varName);
}
public static void checkBreakAndCont(CompileResult target, int start) {
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) {
if (target.get(i).is(0, "break") ) {
throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop.");
}
if (target.get(i).is(0, "cont")) {
throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop.");
}
}
}
}
private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])) {
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
}
}
}
var subtarget = new CompileResult(target.scope.child());
subtarget.scope.define("this");
var argsVar = subtarget.scope.define("arguments");
if (args.length > 0) {
for (var i = 0; i < args.length; i++) {
subtarget.add(Instruction.loadVar(argsVar));
subtarget.add(Instruction.pushValue(i));
subtarget.add(Instruction.loadMember());
subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i])));
}
}
if (!statement && this.varName != null) {
subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp);
}
body.declare(subtarget);
body.compile(subtarget, false);
subtarget.length = args.length;
subtarget.add(Instruction.ret()).setLocation(end);
checkBreakAndCont(subtarget, 0);
if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures()));
return target.addChild(subtarget);
}
public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement;
var hasName = name != null;
compileBody(target, pollute || hasVar || hasName, bp);
if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup());
target.add(Instruction.pushValue("name"));
target.add(Instruction.pushValue(name));
target.add(Instruction.storeMember());
}
if (hasVar) {
var key = target.scope.getKey(this.varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
target.add(Instruction.storeVar(target.scope.getKey(this.varName), false));
}
}
public void compile(CompileResult target, boolean pollute, String name) {
compile(target, pollute, name, BreakpointType.NONE);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
compile(target, pollute, (String)null, bp);
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, (String)null, BreakpointType.NONE);
}
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc);
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args;
this.body = body;
}
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name);
else stm.compile(target, pollute);
}
public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp);
else stm.compile(target, pollute, bp);
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class GlobalThisStatement extends Statement {
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.loadGlob());
}
public GlobalThisStatement(Location loc) {
super(loc);
}
}

View File

@@ -0,0 +1,45 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class IndexAssignStatement extends Statement {
public final Statement object;
public final Statement index;
public final Statement value;
public final Operation operation;
@Override
public void compile(CompileResult target, boolean pollute) {
if (operation != null) {
object.compile(target, true);
index.compile(target, true);
target.add(Instruction.dup(2));
target.add(Instruction.loadMember());
value.compile(target, true);
target.add(Instruction.operation(operation));
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
}
else {
object.compile(target, true);
index.compile(target, true);
value.compile(target, true);
target.add(Instruction.storeMember(pollute)).setLocationAndDebug(loc(), BreakpointType.STEP_IN);;
}
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
super(loc);
this.object = object;
this.index = index;
this.value = value;
this.operation = operation;
}
}

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class IndexStatement extends AssignableStatement {
public final Statement object;
public final Statement index;
@Override
public Statement toAssign(Statement val, Operation operation) {
return new IndexAssignStatement(loc(), object, index, val, operation);
}
public void compile(CompileResult target, boolean dupObj, boolean pollute) {
object.compile(target, true);
if (dupObj) target.add(Instruction.dup());
index.compile(target, true);
target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard());
}
@Override
public void compile(CompileResult target, boolean pollute) {
compile(target, false, pollute);
}
public IndexStatement(Location loc, Statement object, Statement index) {
super(loc);
this.object = object;
this.index = index;
}
}

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class LazyAndStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileResult target, boolean pollute) {
first.compile(target, true);
if (pollute) target.add(Instruction.dup());
int start = target.temp();
if (pollute) target.add(Instruction.discard());
second.compile(target, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start));
}
public LazyAndStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class LazyOrStatement extends Statement {
public final Statement first, second;
@Override public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(CompileResult target, boolean pollute) {
first.compile(target, true);
if (pollute) target.add(Instruction.dup());
int start = target.temp();
if (pollute) target.add(Instruction.discard());
second.compile(target, pollute);
target.set(start, Instruction.jmpIf(target.size() - start));
}
public LazyOrStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@@ -0,0 +1,61 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList;
import java.util.Map;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class ObjectStatement extends Statement {
public final Map<String, Statement> map;
public final Map<String, FunctionStatement> getters;
public final Map<String, FunctionStatement> setters;
@Override public boolean pure() {
for (var el : map.values()) {
if (!el.pure()) return false;
}
return true;
}
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadObj());
for (var el : map.entrySet()) {
target.add(Instruction.dup());
target.add(Instruction.pushValue(el.getKey()));
var val = el.getValue();
FunctionStatement.compileWithName(val, target, true, el.getKey().toString());
target.add(Instruction.storeMember());
}
var keys = new ArrayList<Object>();
keys.addAll(getters.keySet());
keys.addAll(setters.keySet());
for (var key : keys) {
target.add(Instruction.pushValue((String)key));
if (getters.containsKey(key)) getters.get(key).compile(target, true);
else target.add(Instruction.pushUndefined());
if (setters.containsKey(key)) setters.get(key).compile(target, true);
else target.add(Instruction.pushUndefined());
target.add(Instruction.defProp());
}
if (!pollute) target.add(Instruction.discard());
}
public ObjectStatement(Location loc, Map<String, Statement> map, Map<String, FunctionStatement> getters, Map<String, FunctionStatement> setters) {
super(loc);
this.map = map;
this.getters = getters;
this.setters = setters;
}
}

View File

@@ -1,37 +1,36 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) {
arg.compile(target, scope, true);
}
if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard(loc()));
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
}
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override
public void compile(CompileResult target, boolean pollute) {
for (var arg : args) {
arg.compile(target, true);
}
if (pollute) target.add(Instruction.operation(operation));
else target.add(Instruction.discard());
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
}

View File

@@ -1,26 +1,25 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class RegexStatement extends Statement {
public final String pattern, flags;
// Not really pure, since a function is called, but can be ignored.
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadRegex(loc(), pattern, flags));
if (!pollute) target.add(Instruction.discard(loc()));
}
public RegexStatement(Location loc, String pattern, String flags) {
super(loc);
this.pattern = pattern;
this.flags = flags;
}
}
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class RegexStatement extends Statement {
public final String pattern, flags;
// Not really pure, since a function is called, but can be ignored.
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadRegex(pattern, flags));
if (!pollute) target.add(Instruction.discard());
}
public RegexStatement(Location loc, String pattern, String flags) {
super(loc);
this.pattern = pattern;
this.flags = flags;
}
}

View File

@@ -1,33 +1,32 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class TypeofStatement extends Statement {
public final Statement value;
// Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted
@Override public boolean pure() { return true; }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {
target.add(Instruction.typeof(loc(), (String)i));
return;
}
}
value.compile(target, scope, pollute);
target.add(Instruction.typeof(loc()));
}
public TypeofStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class TypeofStatement extends Statement {
public final Statement value;
// Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
if (value instanceof VariableStatement) {
var i = target.scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {
target.add(Instruction.typeof((String)i));
return;
}
}
value.compile(target, pollute);
target.add(Instruction.typeof());
}
public TypeofStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@@ -0,0 +1,37 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class VariableAssignStatement extends Statement {
public final String name;
public final Statement value;
public final Operation operation;
@Override public boolean pure() { return false; }
@Override
public void compile(CompileResult target, boolean pollute) {
var i = target.scope.getKey(name);
if (operation != null) {
target.add(Instruction.loadVar(i));
FunctionStatement.compileWithName(value, target, true, name);
target.add(Instruction.operation(operation));
target.add(Instruction.storeVar(i, pollute));
}
else {
FunctionStatement.compileWithName(value, target, true, name);
target.add(Instruction.storeVar(i, pollute));
}
}
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
super(loc);
this.name = name;
this.value = val;
this.operation = operation;
}
}

View File

@@ -0,0 +1,22 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class VariableIndexStatement extends Statement {
public final int index;
@Override public boolean pure() { return true; }
@Override
public void compile(CompileResult target, boolean pollute) {
if (pollute) target.add(Instruction.loadVar(index));
}
public VariableIndexStatement(Location loc, int i) {
super(loc);
this.index = i;
}
}

View File

@@ -1,32 +1,31 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement {
public final String name;
@Override public boolean pure() { return false; }
@Override
public Statement toAssign(Statement val, Operation operation) {
return new VariableAssignStatement(loc(), name, val, operation);
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name);
target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard(loc()));
}
public VariableStatement(Location loc, String name) {
super(loc);
this.name = name;
}
}
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
public class VariableStatement extends AssignableStatement {
public final String name;
@Override public boolean pure() { return false; }
@Override
public Statement toAssign(Statement val, Operation operation) {
return new VariableAssignStatement(loc(), name, val, operation);
}
@Override
public void compile(CompileResult target, boolean pollute) {
var i = target.scope.getKey(name);
target.add(Instruction.loadVar(i));
if (!pollute) target.add(Instruction.discard());
}
public VariableStatement(Location loc, String name) {
super(loc);
this.name = name;
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.core;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.scope.ValueVariable;
import me.topchetoeu.jscript.core.values.CodeFunction;
public interface Compiler {
public Key<Compiler> KEY = new Key<>();
public FunctionBody compile(Filename filename, String source);
public static Compiler get(Extensions ext) {
return ext.get(KEY, (filename, src) -> {
throw EngineException.ofError("No compiler attached to engine.");
});
}
public static CodeFunction compile(Environment env, Filename filename, String raw) {
return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]);
}
}

View File

@@ -0,0 +1,109 @@
package me.topchetoeu.jscript.core;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.core.debug.DebugContext;
import me.topchetoeu.jscript.core.values.CodeFunction;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.scope.ValueVariable;
public class Context implements Extensions {
public static final Context NULL = new Context();
public final Context parent;
public final Environment environment;
public final Frame frame;
// public final Engine engine;
public final int stackSize;
@Override public <T> void add(Key<T> key, T obj) {
if (environment != null) environment.add(key, obj);
// else if (engine != null) engine.add(key, obj);
}
@Override public <T> T get(Key<T> key) {
if (environment != null && environment.has(key)) return environment.get(key);
// else if (engine != null && engine.has(key)) return engine.get(key);
return null;
}
@Override public boolean has(Key<?> key) {
return
environment != null && environment.has(key);
// engine != null && engine.has(key);
}
@Override public boolean remove(Key<?> key) {
var res = false;
if (environment != null) res |= environment.remove(key);
// else if (engine != null) res |= engine.remove(key);
return res;
}
@Override public Iterable<Key<?>> keys() {
if (environment == null) return List.of();
else return environment.keys();
// if (engine == null && environment == null) return List.of();
// if (engine == null) return environment.keys();
// if (environment == null) return engine.keys();
// return () -> Stream.concat(
// StreamSupport.stream(engine.keys().spliterator(), false),
// StreamSupport.stream(environment.keys().spliterator(), false)
// ).distinct().iterator();
}
public FunctionValue compile(Filename filename, String raw) {
DebugContext.get(this).onSource(filename, raw);
var result = new CodeFunction(environment, filename.toString(), Compiler.get(this).compile(filename, raw), new ValueVariable[0]);
return result;
}
public Context pushFrame(Frame frame) {
var res = new Context(this, frame.function.environment, frame, stackSize + 1);
return res;
}
public Iterable<Frame> frames() {
var self = this;
return () -> new Iterator<Frame>() {
private Context curr = self;
private void update() {
while (curr != null && curr.frame == null) curr = curr.parent;
}
@Override public boolean hasNext() {
update();
return curr != null;
}
@Override public Frame next() {
update();
var res = curr.frame;
curr = curr.parent;
return res;
}
};
}
private Context(Context parent, Environment environment, Frame frame, int stackSize) {
this.parent = parent;
this.environment = environment;
this.frame = frame;
this.stackSize = stackSize;
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
throw EngineException.ofRange("Stack overflow!");
}
}
public Context() {
this(null, null, null, 0);
}
public Context(Environment env) {
this(null, env, null, 0);
}
}

View File

@@ -0,0 +1,95 @@
package me.topchetoeu.jscript.core;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.core.exceptions.InterruptException;
import me.topchetoeu.jscript.core.values.FunctionValue;
public class Engine implements EventLoop {
private static class Task<T> implements Comparable<Task<?>> {
public final ResultRunnable<?> runnable;
public final DataNotifier<T> notifier = new DataNotifier<>();
public final boolean micro;
public Task(ResultRunnable<T> runnable, boolean micro) {
this.runnable = runnable;
this.micro = micro;
}
@Override
public int compareTo(Task<?> other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private PriorityBlockingQueue<Task<?>> tasks = new PriorityBlockingQueue<>();
private Thread thread;
@Override
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
var msg = new Task<T>(runnable, micro);
tasks.add(msg);
return msg.notifier;
}
@SuppressWarnings("unchecked")
public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) {
try {
var task = tasks.take();
try {
((Task<Object>)task).notifier.next(task.runnable.run());
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
}
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break;
}
}
}
public Thread thread() {
return thread;
}
public Thread start() {
if (thread == null) {
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
thread.start();
}
return thread;
}
public void stop() {
if (thread != null) thread.interrupt();
thread = null;
}
public boolean inLoopThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
return this.thread != null;
}
public DataNotifier<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
return pushMsg(() -> {
return func.call(new Context(env), thisArg, args);
}, micro);
}
public DataNotifier<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(() -> {
var ctx = new Context(env);
return ctx.compile(filename, raw).call(new Context(env), thisArg, args);
}, micro);
}
public Engine() {
}
}

View File

@@ -0,0 +1,92 @@
package me.topchetoeu.jscript.core;
import java.util.HashMap;
import me.topchetoeu.jscript.core.scope.GlobalScope;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.values.NativeFunction;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
@SuppressWarnings("unchecked")
public class Environment implements Extensions {
public static final Key<Compiler> COMPILE_FUNC = new Key<>();
public static final Key<FunctionValue> REGEX_CONSTR = new Key<>();
public static final Key<Integer> MAX_STACK_COUNT = new Key<>();
public static final Key<Boolean> HIDE_STACK = new Key<>();
public static final Key<ObjectValue> OBJECT_PROTO = new Key<>();
public static final Key<ObjectValue> FUNCTION_PROTO = new Key<>();
public static final Key<ObjectValue> ARRAY_PROTO = new Key<>();
public static final Key<ObjectValue> BOOL_PROTO = new Key<>();
public static final Key<ObjectValue> NUMBER_PROTO = new Key<>();
public static final Key<ObjectValue> STRING_PROTO = new Key<>();
public static final Key<ObjectValue> SYMBOL_PROTO = new Key<>();
public static final Key<ObjectValue> ERROR_PROTO = new Key<>();
public static final Key<ObjectValue> SYNTAX_ERR_PROTO = new Key<>();
public static final Key<ObjectValue> TYPE_ERR_PROTO = new Key<>();
public static final Key<ObjectValue> RANGE_ERR_PROTO = new Key<>();
private HashMap<Key<?>, Object> data = new HashMap<>();
public GlobalScope global;
public WrapperProvider wrappers;
@Override public <T> void add(Key<T> key, T obj) {
data.put(key, obj);
}
@Override public <T> T get(Key<T> key) {
return (T)data.get(key);
}
@Override public boolean remove(Key<?> key) {
if (data.containsKey(key)) {
data.remove(key);
return true;
}
return false;
}
@Override public boolean has(Key<?> key) {
return data.containsKey(key);
}
@Override public Iterable<Key<?>> keys() {
return data.keySet();
}
public static FunctionValue regexConstructor(Extensions ext) {
return ext.init(REGEX_CONSTR, new NativeFunction("RegExp", args -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx);
}));
}
public Environment copy() {
var res = new Environment(null, global);
res.wrappers = wrappers.fork(res);
res.global = global;
res.data.putAll(data);
return res;
}
public Environment child() {
var res = copy();
res.global = res.global.globalChild();
return res;
}
public Context context() {
return new Context(this);
}
public Environment(WrapperProvider nativeConverter, GlobalScope global) {
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope();
this.wrappers = nativeConverter;
this.global = global;
}
public Environment() {
this(null, null);
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.core;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public interface EventLoop {
public static final Key<EventLoop> KEY = new Key<>();
public static EventLoop get(Extensions ext) {
if (ext.hasNotNull(KEY)) return ext.get(KEY);
else return new EventLoop() {
@Override public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
throw EngineException.ofError("No event loop attached to environment.");
}
};
}
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro);
public default DataNotifier<Void> pushMsg(Runnable runnable, boolean micro) {
return pushMsg(() -> { runnable.run(); return null; }, micro);
}
}

View File

@@ -0,0 +1,38 @@
package me.topchetoeu.jscript.core;
public interface Extensions {
<T> T get(Key<T> key);
<T> void add(Key<T> key, T obj);
Iterable<Key<?>> keys();
boolean has(Key<?> key);
boolean remove(Key<?> key);
default void add(Key<Void> key) {
add(key, null);
}
default boolean hasNotNull(Key<?> key) {
return has(key) && get(key) != null;
}
default <T> T get(Key<T> key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
default <T> T init(Key<T> key, T val) {
if (has(key)) return get(key);
else {
add(key, val);
return val;
}
}
@SuppressWarnings("unchecked")
default void addAll(Extensions source) {
if (source == null) return;
for (var key : source.keys()) {
add((Key<Object>)key, (Object)source.get(key));
}
}
}

View File

@@ -1,317 +1,329 @@
package me.topchetoeu.jscript.engine.frame;
import java.util.List;
import java.util.Stack;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ScopeValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame {
public static enum TryState {
TRY,
CATCH,
FINALLY,
}
public static class TryCtx {
public final int start, end, catchStart, finallyStart;
public final int restoreStackPtr;
public final TryState state;
public final EngineException error;
public final PendingResult result;
public boolean hasCatch() { return catchStart >= 0; }
public boolean hasFinally() { return finallyStart >= 0; }
public boolean inBounds(int ptr) {
return ptr >= start && ptr < end;
}
public TryCtx _catch(EngineException e) {
if (error != null) e.setCause(error);
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
}
public TryCtx _finally(PendingResult res) {
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
}
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
this.catchStart = catchStart;
this.finallyStart = finallyStart;
this.restoreStackPtr = stackPtr;
this.result = res == null ? PendingResult.ofNone() : res;
this.state = state;
this.start = start;
this.end = end;
this.error = err;
}
}
private static class PendingResult {
public final boolean isReturn, isJump, isThrow;
public final Object value;
public final EngineException error;
public final int ptr;
public final Instruction instruction;
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
this.instruction = instr;
this.isReturn = isReturn;
this.isJump = isJump;
this.isThrow = isThrow;
this.value = value;
this.error = error;
this.ptr = ptr;
}
public static PendingResult ofNone() {
return new PendingResult(null, false, false, false, null, null, 0);
}
public static PendingResult ofReturn(Object value, Instruction instr) {
return new PendingResult(instr, true, false, false, value, null, 0);
}
public static PendingResult ofThrow(EngineException error, Instruction instr) {
return new PendingResult(instr, false, false, true, null, error, 0);
}
public static PendingResult ofJump(int codePtr, Instruction instr) {
return new PendingResult(instr, false, true, false, null, null, codePtr);
}
}
public final LocalScope scope;
public final Object thisArg;
public final Object[] args;
public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function;
public Object[] stack = new Object[32];
public int stackPtr = 0;
public int codePtr = 0;
public boolean jumpFlag = false, popTryFlag = false;
private Location prevLoc = null;
public ObjectValue getLocalScope(Context ctx, boolean props) {
var names = new String[scope.locals.length];
for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
if (i == 0) name = "this";
else if (i == 1) name = "arguments";
else if (i < function.localNames.length) name = function.localNames[i];
names[i] = name;
}
return new ScopeValue(scope.locals, names);
}
public ObjectValue getCaptureScope(Context ctx, boolean props) {
var names = new String[scope.captures.length];
for (int i = 0; i < scope.captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < function.captureNames.length) name = function.captureNames[i];
names[i] = name;
}
return new ScopeValue(scope.captures, names);
}
public ObjectValue getValStackScope(Context ctx) {
return new ObjectValue() {
@Override
protected Object getField(Context ctx, Object key) {
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null;
else return stack[i];
}
@Override
protected boolean hasField(Context ctx, Object key) {
return true;
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < stackPtr; i++) res.add(i);
return res;
}
};
}
public void addTry(int start, int end, int catchStart, int finallyStart) {
var err = tryStack.empty() ? null : tryStack.peek().error;
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
tryStack.add(res);
}
public Object peek() {
return peek(0);
}
public Object peek(int offset) {
if (stackPtr <= offset) return null;
else return stack[stackPtr - 1 - offset];
}
public Object pop() {
if (stackPtr == 0) return null;
return stack[--stackPtr];
}
public Object[] take(int n) {
int srcI = stackPtr - n;
if (srcI < 0) srcI = 0;
int dstI = n + srcI - stackPtr;
int copyN = stackPtr - srcI;
Object[] res = new Object[n];
System.arraycopy(stack, srcI, res, dstI, copyN);
stackPtr -= copyN;
return res;
}
public void push(Context ctx, Object val) {
if (stack.length <= stackPtr) {
var newStack = new Object[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[stackPtr++] = Values.normalize(ctx, val);
}
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value);
Instruction instr = null;
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
if (returnValue == Runners.NO_RETURN && error == null) {
try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (instr == null) returnValue = null;
else {
// System.out.println(instr + "@" + instr.location);
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (instr.location != null) prevLoc = instr.location;
try {
this.jumpFlag = this.popTryFlag = false;
returnValue = Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
error = e.add(ctx, function.name, prevLoc);
}
}
}
catch (EngineException e) { error = e; }
}
while (!tryStack.empty()) {
var tryCtx = tryStack.peek();
TryCtx newCtx = null;
if (error != null) {
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
}
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
}
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
}
else if (!this.popTryFlag) newCtx = tryCtx;
if (newCtx != null) {
if (newCtx != tryCtx) {
switch (newCtx.state) {
case CATCH:
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
codePtr = tryCtx.catchStart;
stackPtr = tryCtx.restoreStackPtr;
break;
case FINALLY:
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
default:
}
tryStack.pop();
tryStack.push(newCtx);
}
error = null;
returnValue = Runners.NO_RETURN;
break;
}
else {
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
tryStack.pop();
tryStack.push(tryCtx._finally(null));
break;
}
else {
tryStack.pop();
codePtr = tryCtx.end;
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
if (tryCtx.result.isJump) {
codePtr = tryCtx.result.ptr;
jumpFlag = true;
}
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (tryCtx.result.isThrow) {
error = tryCtx.result.error;
}
if (error != null) error.setCause(tryCtx.error);
continue;
}
}
}
if (error != null) {
var caught = false;
for (var frame : ctx.frames()) {
for (var tryCtx : frame.tryStack) {
if (tryCtx.state == TryState.TRY) caught = true;
}
}
ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
throw error;
}
if (returnValue != Runners.NO_RETURN) {
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
return returnValue;
}
return Runners.NO_RETURN;
}
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
this.args = args.clone();
this.scope = new LocalScope(func.localsN, func.captures);
this.scope.get(0).set(null, thisArg);
var argsObj = new ArrayValue();
for (var i = 0; i < args.length; i++) {
argsObj.set(ctx, i, args[i]);
}
this.scope.get(1).value = argsObj;
this.thisArg = thisArg;
this.function = func;
}
}
package me.topchetoeu.jscript.core;
import java.util.List;
import java.util.Stack;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.core.debug.DebugContext;
import me.topchetoeu.jscript.core.scope.LocalScope;
import me.topchetoeu.jscript.core.scope.ValueVariable;
import me.topchetoeu.jscript.core.values.ArrayValue;
import me.topchetoeu.jscript.core.values.CodeFunction;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.ScopeValue;
import me.topchetoeu.jscript.core.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.exceptions.InterruptException;
public class Frame {
public static enum TryState {
TRY,
CATCH,
FINALLY,
}
public static class TryCtx {
public final int start, end, catchStart, finallyStart;
public final int restoreStackPtr;
public final TryState state;
public final EngineException error;
public final PendingResult result;
public boolean hasCatch() { return catchStart >= 0; }
public boolean hasFinally() { return finallyStart >= 0; }
public boolean inBounds(int ptr) {
return ptr >= start && ptr < end;
}
public void setCause(EngineException target) {
if (error != null) target.setCause(error);
}
public TryCtx _catch(EngineException e) {
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
}
public TryCtx _finally(PendingResult res) {
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
}
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
this.catchStart = catchStart;
this.finallyStart = finallyStart;
this.restoreStackPtr = stackPtr;
this.result = res == null ? PendingResult.ofNone() : res;
this.state = state;
this.start = start;
this.end = end;
this.error = err;
}
}
private static class PendingResult {
public final boolean isReturn, isJump, isThrow;
public final Object value;
public final EngineException error;
public final int ptr;
public final Instruction instruction;
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
this.instruction = instr;
this.isReturn = isReturn;
this.isJump = isJump;
this.isThrow = isThrow;
this.value = value;
this.error = error;
this.ptr = ptr;
}
public static PendingResult ofNone() {
return new PendingResult(null, false, false, false, null, null, 0);
}
public static PendingResult ofReturn(Object value, Instruction instr) {
return new PendingResult(instr, true, false, false, value, null, 0);
}
public static PendingResult ofThrow(EngineException error, Instruction instr) {
return new PendingResult(instr, false, false, true, null, error, 0);
}
public static PendingResult ofJump(int codePtr, Instruction instr) {
return new PendingResult(instr, false, true, false, null, null, codePtr);
}
}
public final LocalScope scope;
public final Object thisArg;
public final Object[] args;
public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function;
public final Context ctx;
public Object[] stack = new Object[32];
public int stackPtr = 0;
public int codePtr = 0;
public boolean jumpFlag = false, popTryFlag = false;
public void addTry(int start, int end, int catchStart, int finallyStart) {
var err = tryStack.empty() ? null : tryStack.peek().error;
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
tryStack.add(res);
}
public Object peek() {
return peek(0);
}
public Object peek(int offset) {
if (stackPtr <= offset) return null;
else return stack[stackPtr - 1 - offset];
}
public Object pop() {
if (stackPtr == 0) return null;
return stack[--stackPtr];
}
public Object[] take(int n) {
int srcI = stackPtr - n;
if (srcI < 0) srcI = 0;
int dstI = n + srcI - stackPtr;
int copyN = stackPtr - srcI;
Object[] res = new Object[n];
System.arraycopy(stack, srcI, res, dstI, copyN);
stackPtr -= copyN;
return res;
}
public void push(Object val) {
if (stack.length <= stackPtr) {
var newStack = new Object[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[stackPtr++] = Values.normalize(ctx, val);
}
public Object next(Object value, Object returnValue, EngineException error) {
if (value != Values.NO_RETURN) push(value);
Instruction instr = null;
if (codePtr >= 0 && codePtr < function.body.instructions.length) instr = function.body.instructions[codePtr];
if (returnValue == Values.NO_RETURN && error == null) {
try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (instr == null) returnValue = null;
else {
DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.NO_RETURN, null, false);
try {
this.jumpFlag = this.popTryFlag = false;
returnValue = InstructionRunner.exec(ctx, instr, this);
}
catch (EngineException e) {
error = e.add(ctx, function.name, DebugContext.get(ctx).getMapOrEmpty(function).toLocation(codePtr, true));
}
}
}
catch (EngineException e) { error = e; }
}
while (!tryStack.empty()) {
var tryCtx = tryStack.peek();
TryCtx newCtx = null;
if (error != null) {
tryCtx.setCause(error);
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
}
else if (returnValue != Values.NO_RETURN) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
}
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
}
else if (!this.popTryFlag) newCtx = tryCtx;
if (newCtx != null) {
if (newCtx != tryCtx) {
switch (newCtx.state) {
case CATCH:
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
codePtr = tryCtx.catchStart;
stackPtr = tryCtx.restoreStackPtr;
break;
case FINALLY:
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
default:
}
tryStack.pop();
tryStack.push(newCtx);
}
error = null;
returnValue = Values.NO_RETURN;
break;
}
else {
popTryFlag = false;
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
tryStack.pop();
tryStack.push(tryCtx._finally(null));
break;
}
else {
tryStack.pop();
codePtr = tryCtx.end;
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) {
if (tryCtx.result.isJump) {
codePtr = tryCtx.result.ptr;
jumpFlag = true;
}
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (error == null && tryCtx.result.isThrow) {
error = tryCtx.result.error;
}
}
if (error != null) tryCtx.setCause(error);
continue;
}
}
}
if (error != null) {
var caught = false;
for (var frame : ctx.frames()) {
for (var tryCtx : frame.tryStack) {
if (tryCtx.state == TryState.TRY) caught = true;
}
}
DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught);
throw error;
}
if (returnValue != Values.NO_RETURN) {
DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false);
return returnValue;
}
return Values.NO_RETURN;
}
public void onPush() {
DebugContext.get(ctx).onFramePush(ctx, this);
}
public void onPop() {
DebugContext.get(ctx.parent).onFramePop(ctx.parent, this);
}
public ObjectValue getLocalScope() {
var names = new String[scope.locals.length];
var map = DebugContext.get(ctx).getMapOrEmpty(function);
for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
if (i == 0) name = "this";
else if (i == 1) name = "arguments";
else if (i < map.localNames.length) name = map.localNames[i];
names[i] = name;
}
return new ScopeValue(scope.locals, names);
}
public ObjectValue getCaptureScope() {
var names = new String[scope.captures.length];
var map = DebugContext.get(ctx).getMapOrEmpty(function);
for (int i = 0; i < scope.captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < map.captureNames.length) name = map.captureNames[i];
names[i] = name;
}
return new ScopeValue(scope.captures, names);
}
public ObjectValue getValStackScope() {
return new ObjectValue() {
@Override
protected Object getField(Context ctx, Object key) {
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null;
else return stack[i];
}
@Override
protected boolean hasField(Context ctx, Object key) {
return true;
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < stackPtr; i++) res.add(i);
return res;
}
};
}
public Frame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
this.args = args.clone();
this.scope = new LocalScope(func.body.localsN, func.captures);
this.scope.get(0).set(null, thisArg);
var argsObj = new ArrayValue();
for (var i = 0; i < args.length; i++) {
argsObj.set(ctx, i, args[i]);
}
this.scope.get(1).value = argsObj;
this.thisArg = thisArg;
this.function = func;
this.ctx = ctx.pushFrame(this);
}
}

View File

@@ -0,0 +1,329 @@
package me.topchetoeu.jscript.core;
import java.util.Collections;
import me.topchetoeu.jscript.core.scope.ValueVariable;
import me.topchetoeu.jscript.core.values.ArrayValue;
import me.topchetoeu.jscript.core.values.CodeFunction;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.Symbol;
import me.topchetoeu.jscript.core.values.Values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class InstructionRunner {
private static Object execReturn(Context ctx, Instruction instr, Frame frame) {
return frame.pop();
}
private static Object execThrow(Context ctx, Instruction instr, Frame frame) {
throw new EngineException(frame.pop());
}
private static Object execThrowSyntax(Context ctx, Instruction instr, Frame frame) {
throw EngineException.ofSyntax((String)instr.get(0));
}
private static Object execCall(Context ctx, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var func = frame.pop();
var thisArg = frame.pop();
frame.push(Values.call(ctx, func, thisArg, callArgs));
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execCallNew(Context ctx, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
frame.push(Values.callNew(ctx, funcObj, callArgs));
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execMakeVar(Context ctx, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
ctx.environment.global.define(ctx, name);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execDefProp(Context ctx, Instruction instr, Frame frame) {
var setter = frame.pop();
var getter = frame.pop();
var name = frame.pop();
var obj = frame.pop();
if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined.");
if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined.");
if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object.");
((ObjectValue)obj).defineProperty(ctx, name, (FunctionValue)getter, (FunctionValue)setter, false, false);
frame.push(obj);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execKeys(Context ctx, Instruction instr, Frame frame) {
var val = frame.pop();
var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members);
frame.push(null);
for (var el : members) {
if (el instanceof Symbol) continue;
var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el);
frame.push(obj);
}
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execTryStart(Context ctx, Instruction instr, Frame frame) {
int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1);
if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start;
int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execTryEnd(Context ctx, Instruction instr, Frame frame) {
frame.popTryFlag = true;
return Values.NO_RETURN;
}
private static Object execDup(Context ctx, Instruction instr, Frame frame) {
int count = instr.get(0);
for (var i = 0; i < count; i++) {
frame.push(frame.peek(count - 1));
}
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadValue(Context ctx, Instruction instr, Frame frame) {
switch (instr.type) {
case PUSH_UNDEFINED: frame.push(null); break;
case PUSH_NULL: frame.push(Values.NULL); break;
default: frame.push(instr.get(0)); break;
}
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadVar(Context ctx, Instruction instr, Frame frame) {
var i = instr.get(0);
if (i instanceof String) frame.push(ctx.environment.global.get(ctx, (String)i));
else frame.push(frame.scope.get((int)i).get(ctx));
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadObj(Context ctx, Instruction instr, Frame frame) {
frame.push(new ObjectValue());
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadGlob(Context ctx, Instruction instr, Frame frame) {
frame.push(ctx.environment.global.obj);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadArr(Context ctx, Instruction instr, Frame frame) {
var res = new ArrayValue();
res.setSize(instr.get(0));
frame.push(res);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadFunc(Context ctx, Instruction instr, Frame frame) {
int id = instr.get(0);
var captures = new ValueVariable[instr.params.length - 1];
for (var i = 1; i < instr.params.length; i++) {
captures[i - 1] = frame.scope.get(instr.get(i));
}
var func = new CodeFunction(ctx.environment, "", frame.function.body.children[id], captures);
frame.push(func);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadMember(Context ctx, Instruction instr, Frame frame) {
var key = frame.pop();
var obj = frame.pop();
try {
frame.push(Values.getMember(ctx, obj, key));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execLoadRegEx(Context ctx, Instruction instr, Frame frame) {
if (ctx.hasNotNull(Environment.REGEX_CONSTR)) {
frame.push(Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1)));
}
else {
throw EngineException.ofSyntax("Regex is not supported.");
}
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execDiscard(Context ctx, Instruction instr, Frame frame) {
frame.pop();
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execStoreMember(Context ctx, Instruction instr, Frame frame) {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
if ((boolean)instr.get(0)) frame.push(val);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execStoreVar(Context ctx, Instruction instr, Frame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0);
if (i instanceof String) ctx.environment.global.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execStoreSelfFunc(Context ctx, Instruction instr, Frame frame) {
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execJmp(Context ctx, Instruction instr, Frame frame) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
return Values.NO_RETURN;
}
private static Object execJmpIf(Context ctx, Instruction instr, Frame frame) {
if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return Values.NO_RETURN;
}
private static Object execJmpIfNot(Context ctx, Instruction instr, Frame frame) {
if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return Values.NO_RETURN;
}
private static Object execTypeof(Context ctx, Instruction instr, Frame frame) {
String name = instr.get(0);
Object obj;
if (name != null) {
if (ctx.environment.global.has(ctx, name)) {
obj = ctx.environment.global.get(ctx, name);
}
else obj = null;
}
else obj = frame.pop();
frame.push(Values.type(obj));
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execNop(Context ctx, Instruction instr, Frame frame) {
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execDelete(Context ctx, Instruction instr, Frame frame) {
var key = frame.pop();
var val = frame.pop();
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
frame.codePtr++;
return Values.NO_RETURN;
}
private static Object execOperation(Context ctx, Instruction instr, Frame frame) {
Operation op = instr.get(0);
var args = new Object[op.operands];
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
frame.push(Values.operation(ctx, op, args));
frame.codePtr++;
return Values.NO_RETURN;
}
public static Object exec(Context ctx, Instruction instr, Frame frame) {
switch (instr.type) {
case NOP: return execNop(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame);
case THROW: return execThrow(ctx, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
case CALL: return execCall(ctx, instr, frame);
case CALL_NEW: return execCallNew(ctx, instr, frame);
case TRY_START: return execTryStart(ctx, instr, frame);
case TRY_END: return execTryEnd(ctx, instr, frame);
case DUP: return execDup(ctx, instr, frame);
case PUSH_UNDEFINED:
case PUSH_NULL:
case PUSH_STRING:
case PUSH_NUMBER:
case PUSH_BOOL:
return execLoadValue(ctx, instr, frame);
case LOAD_VAR: return execLoadVar(ctx, instr, frame);
case LOAD_OBJ: return execLoadObj(ctx, instr, frame);
case LOAD_ARR: return execLoadArr(ctx, instr, frame);
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame);
case LOAD_MEMBER: return execLoadMember(ctx, instr, frame);
case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
case LOAD_GLOB: return execLoadGlob(ctx, instr, frame);
case DISCARD: return execDiscard(ctx, instr, frame);
case STORE_MEMBER: return execStoreMember(ctx, instr, frame);
case STORE_VAR: return execStoreVar(ctx, instr, frame);
case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame);
case MAKE_VAR: return execMakeVar(ctx, instr, frame);
case KEYS: return execKeys(ctx, instr, frame);
case DEF_PROP: return execDefProp(ctx, instr, frame);
case TYPEOF: return execTypeof(ctx, instr, frame);
case DELETE: return execDelete(ctx, instr, frame);
case JMP: return execJmp(ctx, instr, frame);
case JMP_IF: return execJmpIf(ctx, instr, frame);
case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
case OPERATION: return execOperation(ctx, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
}
}
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.core;
public class Key<T> {
}

View File

@@ -0,0 +1,12 @@
package me.topchetoeu.jscript.core;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.values.ObjectValue;
public interface WrapperProvider {
public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj);
public WrapperProvider fork(Environment env);
}

View File

@@ -0,0 +1,133 @@
package me.topchetoeu.jscript.core.debug;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.Extensions;
import me.topchetoeu.jscript.core.Frame;
import me.topchetoeu.jscript.core.Key;
import me.topchetoeu.jscript.core.values.CodeFunction;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class DebugContext {
public static final Key<DebugContext> KEY = new Key<>();
public static final Key<Void> IGNORE = new Key<>();
private HashMap<Filename, String> sources;
private WeakHashMap<FunctionBody, FunctionMap> maps;
private DebugHandler debugger;
public boolean attachDebugger(DebugHandler debugger) {
if (this.debugger != null) return false;
if (sources != null) {
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger() {
this.debugger = null;
return true;
}
public DebugHandler debugger() {
if (debugger == null) return DebugHandler.empty();
else return debugger;
}
public FunctionMap getMap(FunctionBody func) {
if (maps == null) return null;
return maps.get(func);
}
public FunctionMap getMap(FunctionValue func) {
if (maps == null || !(func instanceof CodeFunction)) return null;
return getMap(((CodeFunction)func).body);
}
public FunctionMap getMapOrEmpty(FunctionBody func) {
if (maps == null) return FunctionMap.EMPTY;
var res = maps.get(func);
if (res == null) return FunctionMap.EMPTY;
else return res;
}
public FunctionMap getMapOrEmpty(FunctionValue func) {
if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY;
return getMapOrEmpty(((CodeFunction)func).body);
}
public void onFramePop(Context ctx, Frame frame) {
if (debugger != null) debugger.onFramePop(ctx, frame);
}
public void onFramePush(Context ctx, Frame frame) {
if (debugger != null) debugger.onFramePush(ctx, frame);
}
public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
public void onSource(Filename filename, String source) {
if (debugger != null) debugger.onSourceLoad(filename, source);
if (sources != null) sources.put(filename, source);
}
public void onFunctionLoad(FunctionBody func, FunctionMap map) {
if (maps != null) maps.put(func, map);
if (debugger != null) debugger.onFunctionLoad(func, map);
}
private DebugContext(boolean enabled) {
if (enabled) {
sources = new HashMap<>();
maps = new WeakHashMap<>();
}
}
public DebugContext() {
this(true);
}
public static boolean enabled(Extensions exts) {
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
}
public static DebugContext get(Extensions exts) {
if (enabled(exts)) return exts.get(KEY);
else return new DebugContext(false);
}
public static List<String> stackTrace(Context ctx) {
var res = new ArrayList<String>();
var dbgCtx = get(ctx);
for (var el : ctx.frames()) {
var name = el.function.name;
var map = dbgCtx.getMapOrEmpty(el.function);
Location loc = null;
if (map != null) {
loc = map.toLocation(el.codePtr, true);
if (loc == null) loc = map.start();
}
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res;
}
}

View File

@@ -0,0 +1,74 @@
package me.topchetoeu.jscript.core.debug;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.Frame;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public interface DebugHandler {
/**
* Called when a script has been loaded
* @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/
void onSourceLoad(Filename filename, String source);
/**
* Called when a function body has been loaded
* @param body The body loaded
* @param map The map of the function
*/
void onFunctionLoad(FunctionBody body, FunctionMap map);
// /**
// * Called when a function body has been loaded
// * @param body The body loaded
// * @param map The map of the function
// */
// void onFunctionUnload(FunctionBody body, FunctionMap map);
/**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart (currently does nothing)
*/
boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
/**
* Called immediatly before a frame has been pushed on the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was pushed
*/
void onFramePush(Context ctx, Frame frame);
/**
* Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Context ctx, Frame frame);
public static DebugHandler empty() {
return new DebugHandler () {
@Override public void onFramePop(Context ctx, Frame frame) { }
@Override public void onFramePush(Context ctx, Frame frame) { }
@Override public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
return false;
}
@Override public void onSourceLoad(Filename filename, String source) { }
@Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { }
};
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.exceptions;
package me.topchetoeu.jscript.core.exceptions;
public class ConvertException extends RuntimeException {
public final String source, target;

View File

@@ -1,117 +1,109 @@
package me.topchetoeu.jscript.exceptions;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException {
public static class StackElement {
public final Location location;
public final String function;
public final Context ctx;
public boolean visible() {
return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
}
public String toString() {
var res = "";
var loc = location;
if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
if (loc != null) res += "at " + loc.toString() + " ";
if (function != null && !function.equals("")) res += "in " + function + " ";
return res.trim();
}
public StackElement(Context ctx, Location location, String function) {
if (function != null) function = function.trim();
if (function.equals("")) function = null;
if (ctx == null) this.ctx = null;
else this.ctx = new Context(ctx.engine).pushEnv(ctx.environment());
this.location = location;
this.function = function;
}
}
public final Object value;
public EngineException cause;
public Environment env = null;
public Engine engine = null;
public final List<StackElement> stackTrace = new ArrayList<>();
public EngineException add(Context ctx, String name, Location location) {
var el = new StackElement(ctx, location, name);
if (el.function == null && el.location == null) return this;
setCtx(ctx.environment(), ctx.engine);
stackTrace.add(el);
return this;
}
public EngineException setCause(EngineException cause) {
this.cause = cause;
return this;
}
public EngineException setCtx(Environment env, Engine engine) {
if (this.env == null) this.env = env;
if (this.engine == null) this.engine = engine;
return this;
}
public String toString(Context ctx) {
var ss = new StringBuilder();
try {
ss.append(Values.toString(ctx, value)).append('\n');
}
catch (EngineException e) {
ss.append("[Error while stringifying]\n");
}
for (var line : stackTrace) {
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
}
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1);
return ss.toString();
}
private static Object err(String name, String msg, PlaceholderProto proto) {
var res = new ObjectValue(proto);
if (name != null) res.defineProperty(null, "name", name);
res.defineProperty(null, "message", msg);
return res;
}
public EngineException(Object error) {
super(error == null ? "null" : error.toString());
this.value = error;
this.cause = null;
}
public static EngineException ofError(String name, String msg) {
return new EngineException(err(name, msg, PlaceholderProto.ERROR));
}
public static EngineException ofError(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
}
public static EngineException ofSyntax(SyntaxException e) {
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
}
public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
}
public static EngineException ofType(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR));
}
public static EngineException ofRange(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
}
}
package me.topchetoeu.jscript.core.exceptions;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.Environment;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.Values;
import me.topchetoeu.jscript.core.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException {
public static class StackElement {
public final Location location;
public final String name;
public final Context ctx;
public boolean visible() {
return ctx == null || !ctx.get(Environment.HIDE_STACK, false);
}
public String toString() {
var res = "";
var loc = location;
if (loc != null) res += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) res += "in " + name + " ";
return res.trim();
}
public StackElement(Context ctx, Location location, String name) {
if (name != null) name = name.trim();
if (name.equals("")) name = null;
if (ctx == null) this.ctx = null;
else this.ctx = new Context(ctx.environment);
this.location = location;
this.name = name;
}
}
public final Object value;
public EngineException cause;
public Environment env = null;
public final List<StackElement> stackTrace = new ArrayList<>();
public EngineException add(Context ctx, String name, Location location) {
var el = new StackElement(ctx, location, name);
if (el.name == null && el.location == null) return this;
setCtx(ctx);
stackTrace.add(el);
return this;
}
public EngineException setCause(EngineException cause) {
this.cause = cause;
return this;
}
public EngineException setCtx(Context ctx) {
if (this.env == null) this.env = ctx.environment;
return this;
}
public String toString(Context ctx) {
var ss = new StringBuilder();
try {
ss.append(Values.toString(ctx, value)).append('\n');
}
catch (EngineException e) {
ss.append("[Error while stringifying]\n");
}
for (var line : stackTrace) {
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
}
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1);
return ss.toString();
}
private static Object err(String name, String msg, PlaceholderProto proto) {
var res = new ObjectValue(proto);
if (name != null) res.defineProperty(null, "name", name);
res.defineProperty(null, "message", msg);
return res;
}
public EngineException(Object error) {
super(error == null ? "null" : error.toString());
this.value = error;
this.cause = null;
}
public static EngineException ofError(String name, String msg) {
return new EngineException(err(name, msg, PlaceholderProto.ERROR));
}
public static EngineException ofError(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
}
public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
}
public static EngineException ofType(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR));
}
public static EngineException ofRange(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
}
}

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.exceptions;
package me.topchetoeu.jscript.core.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }

View File

@@ -1,14 +1,14 @@
package me.topchetoeu.jscript.exceptions;
import me.topchetoeu.jscript.Location;
public class SyntaxException extends RuntimeException {
public final Location loc;
public final String msg;
public SyntaxException(Location loc, String msg) {
super(String.format("Syntax error (at %s): %s", loc, msg));
this.loc = loc;
this.msg = msg;
}
package me.topchetoeu.jscript.core.exceptions;
import me.topchetoeu.jscript.common.Location;
public class SyntaxException extends RuntimeException {
public final Location loc;
public final String msg;
public SyntaxException(Location loc, String msg) {
super(String.format("Syntax error (at %s): %s", loc, msg));
this.loc = loc;
this.msg = msg;
}
}

View File

@@ -0,0 +1,73 @@
package me.topchetoeu.jscript.core.scope;
import java.util.HashSet;
import java.util.Set;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.values.NativeFunction;
import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class GlobalScope {
public final ObjectValue obj;
public boolean has(Context ctx, String name) {
return Values.hasMember(null, obj, name, false);
}
public GlobalScope globalChild() {
var obj = new ObjectValue();
Values.setPrototype(null, obj, this.obj);
return new GlobalScope(obj);
}
public Object define(Context ctx, String name) {
if (Values.hasMember(Context.NULL, obj, name, false)) return name;
obj.defineProperty(Context.NULL, name, null);
return name;
}
public void define(Context ctx, String name, Variable val) {
obj.defineProperty(Context.NULL, name,
new NativeFunction("get " + name, args -> val.get(args.ctx)),
new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }),
true, true
);
}
public void define(Context ctx, String name, boolean readonly, Object val) {
obj.defineProperty(ctx, name, val, readonly, true, true);
}
public void define(Context ctx, String ...names) {
for (var n : names) define(ctx, n);
}
public void define(Context ctx, boolean readonly, FunctionValue val) {
define(null, val.name, readonly, val);
}
public Object get(Context ctx, String name) {
if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return Values.getMember(ctx, obj, name);
}
public void set(Context ctx, String name, Object val) {
if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!Values.setMember(ctx, obj, name, val)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
}
public Set<String> keys() {
var res = new HashSet<String>();
for (var key : keys()) {
if (key instanceof String) res.add((String)key);
}
return res;
}
public GlobalScope() {
this.obj = new ObjectValue();
}
public GlobalScope(ObjectValue val) {
this.obj = val;
}
}

View File

@@ -1,29 +1,29 @@
package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
public class LocalScope {
public final ValueVariable[] captures;
public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) {
if (i >= locals.length) return catchVars.get(i - locals.length);
if (i >= 0) return locals[i];
else return captures[~i];
}
public int size() {
return captures.length + locals.length;
}
public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n];
this.captures = captures;
for (int i = 0; i < n; i++) {
locals[i] = new ValueVariable(false, null);
}
}
}
package me.topchetoeu.jscript.core.scope;
import java.util.ArrayList;
public class LocalScope {
public final ValueVariable[] captures;
public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) {
if (i >= locals.length) return catchVars.get(i - locals.length);
if (i >= 0) return locals[i];
else return captures[~i];
}
public int size() {
return captures.length + locals.length;
}
public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n];
this.captures = captures;
for (int i = 0; i < n; i++) {
locals[i] = new ValueVariable(false, null);
}
}
}

View File

@@ -1,28 +1,28 @@
package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.Values;
public class ValueVariable implements Variable {
public boolean readonly;
public Object value;
@Override
public boolean readonly() { return readonly; }
@Override
public Object get(Context ctx) {
return value;
}
@Override
public void set(Context ctx, Object val) {
if (readonly) return;
this.value = Values.normalize(ctx, val);
}
public ValueVariable(boolean readonly, Object val) {
this.readonly = readonly;
this.value = val;
}
}
package me.topchetoeu.jscript.core.scope;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.values.Values;
public class ValueVariable implements Variable {
public boolean readonly;
public Object value;
@Override
public boolean readonly() { return readonly; }
@Override
public Object get(Context ctx) {
return value;
}
@Override
public void set(Context ctx, Object val) {
if (readonly) return;
this.value = Values.normalize(ctx, val);
}
public ValueVariable(boolean readonly, Object val) {
this.readonly = readonly;
this.value = val;
}
}

View File

@@ -1,9 +1,9 @@
package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context;
public interface Variable {
Object get(Context ctx);
default boolean readonly() { return true; }
default void set(Context ctx, Object val) { }
}
package me.topchetoeu.jscript.core.scope;
import me.topchetoeu.jscript.core.Context;
public interface Variable {
Object get(Context ctx);
default boolean readonly() { return true; }
default void set(Context ctx, Object val) { }
}

View File

@@ -1,220 +1,227 @@
package me.topchetoeu.jscript.engine.values;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
private Object[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Context ctx, int i, Object val) {
if (i < 0) return;
values = alloc(i);
val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(ctx, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override
protected Object getField(Context ctx, Object key) {
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set(ctx, (int)i, val);
return true;
}
}
return super.setField(ctx, key, val);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ctx, key);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Context ctx, Object ...values) {
this();
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
public static ArrayValue of(Context ctx, Collection<?> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new));
}
}
package me.topchetoeu.jscript.core.values;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.core.Context;
// TODO: Make methods generic
public class ArrayValue extends ObjectValue implements Iterable<Object> {
private static final Object UNDEFINED = new Object();
private Object[] values;
private int size;
private Object[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == UNDEFINED) return null;
else return res;
}
public void set(Context ctx, int i, Object val) {
if (i < 0) return;
values = alloc(i);
val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED;
values[i] = val;
if (i >= size) size = i + 1;
}
public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
public void remove(int i) {
if (i < 0 || i >= values.length) return;
values[i] = null;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Object[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) {
values[--size] = null;
}
}
}
public Object[] toArray() {
Object[] res = new Object[size];
copyTo(res, 0, 0, size);
return res;
}
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
else arr[i + sourceStart] = values[i + destStart];
}
}
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
if (arr == this) {
move(sourceStart, destStart, count);
return;
}
// Iterate in reverse to reallocate at most once
if (destStart + count > arr.size) arr.size = destStart + count;
for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(null, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(null, i + destStart, values[i + sourceStart]);
}
}
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
for (var i = 0; i < count; i++) {
set(ctx, i + destStart, arr[i + sourceStart]);
}
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Object> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == UNDEFINED) _a = 1;
if (a == null) _a = 2;
if (b == UNDEFINED) _b = 1;
if (b == null) _b = 2;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override
protected Object getField(Context ctx, Object key) {
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set(ctx, (int)i, val);
return true;
}
}
return super.setField(ctx, key, val);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ctx, key);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
return res;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public Object next() {
if (!hasNext()) return null;
return get(i++);
}
};
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
values = new Object[16];
size = 0;
}
public ArrayValue(int cap) {
super(PlaceholderProto.ARRAY);
values = new Object[cap];
size = 0;
}
public ArrayValue(Context ctx, Object ...values) {
this();
this.values = new Object[values.length];
size = values.length;
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
public static ArrayValue of(Context ctx, Collection<?> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new));
}
}

View File

@@ -0,0 +1,50 @@
package me.topchetoeu.jscript.core.values;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.Environment;
import me.topchetoeu.jscript.core.Frame;
import me.topchetoeu.jscript.core.scope.ValueVariable;
public class CodeFunction extends FunctionValue {
public final FunctionBody body;
public final ValueVariable[] captures;
public Environment environment;
// public Location loc() {
// for (var instr : body.instructions) {
// if (instr.location != null) return instr.location;
// }
// return null;
// }
// public String readable() {
// var loc = loc();
// if (loc == null) return name;
// else if (name.equals("")) return loc.toString();
// else return name + "@" + loc;
// }
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var frame = new Frame(ctx, thisArg, args, this);
frame.onPush();
try {
while (true) {
var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null);
if (res != Values.NO_RETURN) return res;
}
}
finally {
frame.onPop();
}
}
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable[] captures) {
super(name, body.argsN);
this.captures = captures;
this.environment = environment;
this.body = body;
}
}

View File

@@ -1,6 +1,6 @@
package me.topchetoeu.jscript.engine.frame;
public enum ConvertHint {
TOSTRING,
VALUEOF,
package me.topchetoeu.jscript.core.values;
public enum ConvertHint {
TOSTRING,
VALUEOF,
}

Some files were not shown because too many files have changed in this diff Show More