TopchetoEU/revert-ES5 #31
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
*
|
||||
|
||||
!/src
|
||||
!/src/
|
||||
!/src/**/*
|
||||
|
||||
!/doc
|
||||
@ -22,3 +22,5 @@
|
||||
!/gradle
|
||||
!/gradle/wrapper
|
||||
!/gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
!/
|
235
src/compiler/main.ts
Normal file
235
src/compiler/main.ts
Normal file
@ -0,0 +1,235 @@
|
||||
import { parse } from "acorn";
|
||||
import {} from "acorn";
|
||||
import { traverse } from "estraverse";
|
||||
import { Declaration, Identifier, Node, VariableDeclaration } from "estree";
|
||||
|
||||
enum VariableType {
|
||||
Var,
|
||||
Let,
|
||||
Const,
|
||||
}
|
||||
|
||||
class Variable {
|
||||
public constructor(
|
||||
public readonly scope: Scope,
|
||||
public readonly name: string,
|
||||
public readonly type: VariableType,
|
||||
public readonly readers = new Set<Node>(),
|
||||
public readonly writers = new Set<Node>(),
|
||||
) { }
|
||||
|
||||
public child(scope: Scope) {
|
||||
return new Variable(scope, this.name, this.type, this.readers, this.writers);
|
||||
}
|
||||
|
||||
public get writable() {
|
||||
return this.type !== VariableType.Const;
|
||||
}
|
||||
}
|
||||
|
||||
class Scope {
|
||||
private _locals = new Set<Variable>();
|
||||
private _capturables = new Set<Variable>();
|
||||
private _captures = new Set<Variable>();
|
||||
|
||||
private _localNames = new Map<string, Variable>();
|
||||
private _captureNames = new Map<string, Variable>();
|
||||
private _parentToChild = new Map<Variable, Variable>();
|
||||
private _childToParent = new Map<Variable, Variable>();
|
||||
|
||||
public get locals() {
|
||||
return Iterator.from(this._locals.values());
|
||||
}
|
||||
|
||||
private _addCapture(v?: Variable) {
|
||||
if (v != null && this._locals.delete(v)) {
|
||||
this._capturables.add(v);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public capture(name: string) {
|
||||
if (this._localNames.has(name)) return this._addCapture(this._localNames.get(name));
|
||||
if (this._captureNames.has(name)) return this._addCapture(this._captureNames.get(name));
|
||||
|
||||
const parent = this.parent?.capture(name);
|
||||
if (parent == null) return undefined;
|
||||
|
||||
const child = parent.child(this);
|
||||
|
||||
this._parentToChild.set(parent, child);
|
||||
this._childToParent.set(child, parent);
|
||||
this._captures.add(child);
|
||||
this._captureNames.set(child.name, child);
|
||||
}
|
||||
|
||||
public add(name: string, type: VariableType) {
|
||||
let res = this.get(name, false);
|
||||
if (res != null) return res;
|
||||
|
||||
res = new Variable(this, name, type);
|
||||
this._locals.add(res);
|
||||
this._localNames.set(name, res);
|
||||
return res;
|
||||
}
|
||||
public get(name: string, capture = true): Variable | undefined {
|
||||
if (this._localNames.has(name)) return this._localNames.get(name);
|
||||
if (this._captureNames.has(name)) return this._captureNames.get(name);
|
||||
|
||||
if (capture) this.parent?.capture(name);
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
public readonly major: boolean,
|
||||
public readonly node: Node,
|
||||
public readonly parent?: Scope,
|
||||
) { }
|
||||
}
|
||||
|
||||
class BiMap<A, B> implements Iterable<[A, B]> {
|
||||
private _first = new Map<A, B>();
|
||||
private _second = new Map<B, A>();
|
||||
|
||||
public get(val: A): B;
|
||||
public get(val: B): A;
|
||||
public get(val: any) {
|
||||
if (this._first.has(val)) return this._first.get(val);
|
||||
if (this._second.has(val)) return this._second.get(val);
|
||||
if (this._same.has(val)) return val;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public set(a: A, b: B) {
|
||||
this._first.set(a, b);
|
||||
this._second.set(b, a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public has(val: A | B) {
|
||||
return this._first.has(val as any) || this._second.has(val as any);
|
||||
}
|
||||
|
||||
public delete(val: A | B) {
|
||||
if (this._first.has(val as any)) {
|
||||
const second = this._first.get(val as any)!;
|
||||
this._first.delete(val as any);
|
||||
this._second.delete(second);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (this._second.has(val as any)) {
|
||||
const first = this._second.get(val as any)!;
|
||||
this._second.delete(val as any);
|
||||
this._first.delete(first);
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public *[Symbol.iterator]() {
|
||||
yield *this._first;
|
||||
}
|
||||
public *keys() {
|
||||
yield *this._first.keys();
|
||||
}
|
||||
public *values() {
|
||||
yield *this._second.keys();
|
||||
}
|
||||
public *entries() {
|
||||
yield *this._first.entries();
|
||||
}
|
||||
}
|
||||
|
||||
class ResolutionContext {
|
||||
public readonly variableRefs = new Map<Identifier, Scope>();
|
||||
public readonly declarations = new BiMap<Variable, Declaration>();
|
||||
|
||||
public resolveVariables() {
|
||||
for (const el of this.variableRefs) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NodeContext {
|
||||
public node: Node = undefined!;
|
||||
public path: Node[] = [];
|
||||
|
||||
public atPath(i: number) {
|
||||
return this.path[this.path.length - 1 - i];
|
||||
}
|
||||
|
||||
public scope: Scope = undefined!;
|
||||
public declType?: VariableType;
|
||||
}
|
||||
|
||||
interface Collector {
|
||||
enter(ctx: NodeContext, root: ResolutionContext): void;
|
||||
leave(ctx: NodeContext, root: ResolutionContext): void;
|
||||
}
|
||||
|
||||
function collect(node: Node, root: ResolutionContext, ...collectors: Collector[]) {
|
||||
const nodeCtx = new NodeContext();
|
||||
const path: Node[] = [];
|
||||
|
||||
traverse(node, {
|
||||
enter(node) {
|
||||
nodeCtx.node = node;
|
||||
nodeCtx.path.push(node);
|
||||
for (let i = 0; i < collectors.length; i++) {
|
||||
collectors[i].enter(nodeCtx, root);
|
||||
}
|
||||
},
|
||||
leave(node) {
|
||||
nodeCtx.node = node;
|
||||
nodeCtx.path.pop();
|
||||
for (let i = 0; i < collectors.length; i++) {
|
||||
collectors[i].leave(nodeCtx, root);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function assertDefined(val: unknown): asserts val is {} {
|
||||
if (val == null) throw new Error("Undefined or null expression");
|
||||
}
|
||||
|
||||
const scopeCollector: Collector = {
|
||||
enter(ctx, root) {
|
||||
if (ctx.node.type === "BlockStatement") {
|
||||
ctx.scope = new Scope(false, ctx.node, ctx.scope.parent);
|
||||
}
|
||||
else if (ctx.node.type === "VariableDeclaration") {
|
||||
switch (ctx.node.kind) {
|
||||
case "var": ctx.declType = VariableType.Var; break;
|
||||
case "let": ctx.declType = VariableType.Let; break;
|
||||
case "const": ctx.declType = VariableType.Const; break;
|
||||
default: throw new Error(`Unknown variable type '${(ctx.node as any).kind}'`);
|
||||
}
|
||||
}
|
||||
else if (ctx.node.type === "VariableDeclarator") {
|
||||
ctx.scope.
|
||||
}
|
||||
else if (ctx.node.type === "ClassDeclaration") {
|
||||
|
||||
}
|
||||
else if (ctx.node.type === "Identifier") {
|
||||
}
|
||||
},
|
||||
leave(ctx, root) {
|
||||
if (ctx.scope.node === ctx.node) {
|
||||
assertDefined(ctx.scope.parent);
|
||||
ctx.scope = ctx.scope.parent;
|
||||
}
|
||||
else if (ctx.node.type === "VariableDeclaration") {
|
||||
ctx.declType = undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const program = parse("const a = 10;", { ecmaVersion: "latest" }) as Node;
|
||||
collect(program, domain, ...stage1);
|
@ -17,11 +17,10 @@ public class Instruction {
|
||||
TRY_END(0x06),
|
||||
|
||||
CALL(0x10),
|
||||
CALL_MEMBER(0x11),
|
||||
CALL_NEW(0x12),
|
||||
JMP_IF(0x13),
|
||||
JMP_IFN(0x14),
|
||||
JMP(0x15),
|
||||
JMP_IF(0x18),
|
||||
JMP_IFN(0x19),
|
||||
JMP(0x1A),
|
||||
|
||||
PUSH_UNDEFINED(0x20),
|
||||
PUSH_NULL(0x21),
|
||||
@ -38,11 +37,12 @@ public class Instruction {
|
||||
|
||||
LOAD_GLOB(0x38),
|
||||
LOAD_INTRINSICS(0x39),
|
||||
LOAD_ARGS(0x3A),
|
||||
LOAD_REST_ARGS(0x3B),
|
||||
LOAD_CALLEE(0x3C),
|
||||
LOAD_THIS(0x3D),
|
||||
LOAD_ERROR(0x3E),
|
||||
LOAD_ARG(0x3A),
|
||||
LOAD_ARGS_N(0x3B),
|
||||
LOAD_ARGS(0x3C),
|
||||
LOAD_CALLED(0x3D),
|
||||
LOAD_THIS(0x3E),
|
||||
LOAD_ERROR(0x3F),
|
||||
|
||||
LOAD_VAR(0x40),
|
||||
LOAD_MEMBER(0x41),
|
||||
@ -62,12 +62,7 @@ public class Instruction {
|
||||
|
||||
GLOB_GET(0x60),
|
||||
GLOB_SET(0x61),
|
||||
GLOB_DEF(0x62),
|
||||
|
||||
// CAP_INIT(0x70),
|
||||
VAR_INIT(0x71),
|
||||
CAP_FREE(0x72),
|
||||
VAR_FREE(0x73);
|
||||
GLOB_DEF(0x62);
|
||||
|
||||
private static final HashMap<Integer, Type> types = new HashMap<>();
|
||||
public final int numeric;
|
||||
@ -85,8 +80,19 @@ public class Instruction {
|
||||
}
|
||||
}
|
||||
public static enum BreakpointType {
|
||||
/**
|
||||
* A debugger should never stop at such instruction, unless a breakpoint has been set on it
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Debuggers should pause at instructions marked with this breakpoint type
|
||||
* after any step command
|
||||
*/
|
||||
STEP_OVER,
|
||||
/**
|
||||
* Debuggers should pause at instructions marked with this breakpoint type
|
||||
* only after a step-in command
|
||||
*/
|
||||
STEP_IN;
|
||||
|
||||
public boolean shouldStepIn() {
|
||||
@ -105,27 +111,6 @@ public class Instruction {
|
||||
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;
|
||||
@ -250,55 +235,79 @@ public class Instruction {
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Signals the start of a protected context
|
||||
* @param catchStart The point to witch to jump if an error has been caught
|
||||
* @param finallyStart The point to witch to jump after either the try or catch bodies have exited
|
||||
* @param end The point to which to jump after exiting the whole protected context
|
||||
*/
|
||||
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
|
||||
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
|
||||
}
|
||||
/**
|
||||
* Signifies that the current protected section (try, catch or finally) has ended
|
||||
*/
|
||||
public static Instruction tryEnd() {
|
||||
return new Instruction(Type.TRY_END);
|
||||
}
|
||||
/**
|
||||
* Throws the top stack value
|
||||
*/
|
||||
public static Instruction throwInstr() {
|
||||
return new Instruction(Type.THROW);
|
||||
}
|
||||
/**
|
||||
* Converts the given exception to a runtime syntax error and throws it
|
||||
*/
|
||||
public static Instruction throwSyntax(SyntaxException err) {
|
||||
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
|
||||
}
|
||||
/**
|
||||
* Converts the given exception to a runtime syntax error and throws it
|
||||
*/
|
||||
public static Instruction throwSyntax(String err) {
|
||||
return new Instruction(Type.THROW_SYNTAX, err);
|
||||
}
|
||||
/**
|
||||
* Converts the given exception to a runtime syntax error and throws it
|
||||
*/
|
||||
public static Instruction throwSyntax(Location loc, String err) {
|
||||
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
|
||||
}
|
||||
/**
|
||||
* Performs a JS object property deletion.
|
||||
* Operands:
|
||||
* 1. Object to manipulate
|
||||
* 2. Key to delete
|
||||
*/
|
||||
public static Instruction delete() {
|
||||
return new Instruction(Type.DELETE);
|
||||
}
|
||||
/**
|
||||
* Returns the top stack value
|
||||
*/
|
||||
public static Instruction ret() {
|
||||
return new Instruction(Type.RETURN);
|
||||
}
|
||||
/**
|
||||
* A special NOP instruction telling any debugger to pause
|
||||
*/
|
||||
public static Instruction debug() {
|
||||
return new Instruction(Type.NOP, "debug");
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior
|
||||
*/
|
||||
public static Instruction nop(Object ...params) {
|
||||
return new Instruction(Type.NOP, params);
|
||||
}
|
||||
|
||||
public static Instruction call(int argn, String name) {
|
||||
return new Instruction(Type.CALL, argn, name);
|
||||
}
|
||||
public static Instruction call(int argn) {
|
||||
return call(argn, "");
|
||||
}
|
||||
public static Instruction callMember(int argn, String name) {
|
||||
return new Instruction(Type.CALL_MEMBER, argn, name);
|
||||
}
|
||||
public static Instruction callMember(int argn) {
|
||||
return new Instruction(Type.CALL_MEMBER, argn, "");
|
||||
}
|
||||
public static Instruction callNew(int argn, String name) {
|
||||
return new Instruction(Type.CALL_NEW, argn, name);
|
||||
public static Instruction call(int argn, boolean hasSelf) {
|
||||
return new Instruction(Type.CALL, argn, hasSelf);
|
||||
}
|
||||
public static Instruction callNew(int argn) {
|
||||
return new Instruction(Type.CALL_NEW, argn, "");
|
||||
return new Instruction(Type.CALL_NEW, argn);
|
||||
}
|
||||
|
||||
public static Instruction jmp(int offset) {
|
||||
@ -321,7 +330,6 @@ public class Instruction {
|
||||
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
|
||||
}
|
||||
|
||||
|
||||
public static Instruction pushUndefined() {
|
||||
return new Instruction(Type.PUSH_UNDEFINED);
|
||||
}
|
||||
@ -355,14 +363,30 @@ public class Instruction {
|
||||
public static Instruction loadThis() {
|
||||
return new Instruction(Type.LOAD_THIS);
|
||||
}
|
||||
public static Instruction loadArgs(boolean real) {
|
||||
return new Instruction(Type.LOAD_ARGS, real);
|
||||
/**
|
||||
* Loads the given argument
|
||||
* @param i The index of the argument to load. If -1, will get the index from the stack instead
|
||||
*/
|
||||
public static Instruction loadArg(int i) {
|
||||
return new Instruction(Type.LOAD_ARG, i);
|
||||
}
|
||||
public static Instruction loadRestArgs(int offset) {
|
||||
return new Instruction(Type.LOAD_REST_ARGS, offset);
|
||||
/**
|
||||
* Pushes the amount of arguments to the stack
|
||||
*/
|
||||
public static Instruction loadArgsN() {
|
||||
return new Instruction(Type.LOAD_ARGS_N);
|
||||
}
|
||||
public static Instruction loadCallee() {
|
||||
return new Instruction(Type.LOAD_CALLEE);
|
||||
/**
|
||||
* Pushes the arguments object to the stack
|
||||
*/
|
||||
public static Instruction loadArgs() {
|
||||
return new Instruction(Type.LOAD_ARGS);
|
||||
}
|
||||
/**
|
||||
* Loads a reference to the function being called
|
||||
*/
|
||||
public static Instruction loadCalled() {
|
||||
return new Instruction(Type.LOAD_CALLED);
|
||||
}
|
||||
public static Instruction loadGlob() {
|
||||
return new Instruction(Type.LOAD_GLOB);
|
||||
@ -386,16 +410,12 @@ public class Instruction {
|
||||
public static Instruction loadRegex(String pattern, String flags) {
|
||||
return new Instruction(Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, String name, int[] captures) {
|
||||
if (name == null) name = "";
|
||||
|
||||
var args = new Object[5 + captures.length];
|
||||
// TODO: make this capturing a concern of the compiler
|
||||
public static Instruction loadFunc(int id, String name, int[] captures) {
|
||||
var args = new Object[2 + captures.length];
|
||||
args[0] = id;
|
||||
args[1] = name;
|
||||
args[2] = callable;
|
||||
args[3] = constructible;
|
||||
args[4] = captureThis;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 5] = captures[i];
|
||||
for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i];
|
||||
return new Instruction(Type.LOAD_FUNC, args);
|
||||
}
|
||||
public static Instruction loadObj() {
|
||||
@ -465,22 +485,6 @@ public class Instruction {
|
||||
return new Instruction(Type.OPERATION, op);
|
||||
}
|
||||
|
||||
public static Instruction capFree(int i) {
|
||||
return new Instruction(Type.CAP_FREE, i);
|
||||
}
|
||||
public static Instruction varFree(int i) {
|
||||
return new Instruction(Type.VAR_FREE, i);
|
||||
}
|
||||
public static Instruction varInit(int i, boolean force) {
|
||||
return new Instruction(Type.VAR_INIT, i, force);
|
||||
}
|
||||
// public static Instruction stackAlloc(int start, int n) {
|
||||
// return new Instruction(Type.STACK_ALLOC, start, start + n);
|
||||
// }
|
||||
// public static Instruction stackRealloc(int start, int n) {
|
||||
// return new Instruction(Type.STACK_REALLOC, start, start + n);
|
||||
// }
|
||||
|
||||
@Override public String toString() {
|
||||
var res = type.toString();
|
||||
|
||||
|
@ -3,7 +3,6 @@ package me.topchetoeu.jscript.common.environment;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -12,71 +11,13 @@ public class Environment {
|
||||
private final Map<Key<Object>, Object> map = new HashMap<>();
|
||||
private final Set<Key<Object>> hidden = new HashSet<>();
|
||||
|
||||
private final Map<MultiKey<Object>, Set<Object>> multi = new HashMap<>();
|
||||
private final Map<MultiKey<Object>, Set<Object>> multiHidden = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Set<T> getAll(MultiKey<T> key, boolean forceClone) {
|
||||
Set<T> parent = null, child = null;
|
||||
boolean cloned = false;
|
||||
|
||||
if (this.parent != null && !hidden.contains(key)) {
|
||||
parent = this.parent.getAll(key, false);
|
||||
if (parent.size() == 0) parent = null;
|
||||
else if (multiHidden.containsKey(key)) {
|
||||
parent = new HashSet<>(parent);
|
||||
parent.removeAll(multiHidden.get(key));
|
||||
cloned = true;
|
||||
}
|
||||
}
|
||||
if (multi.containsKey(key)) {
|
||||
child = (Set<T>)multi.get(key);
|
||||
if (child.size() == 0) child = null;
|
||||
}
|
||||
|
||||
if (!forceClone) {
|
||||
if (parent == null && child == null) return new HashSet<>();
|
||||
if (parent == null && child != null) return child;
|
||||
if (parent != null && child == null) return parent;
|
||||
}
|
||||
|
||||
if (!cloned) parent = new HashSet<>();
|
||||
parent.addAll(child);
|
||||
return parent;
|
||||
}
|
||||
private <T> T getMulti(MultiKey<T> key) {
|
||||
return key.of(getAll(key, false));
|
||||
}
|
||||
private boolean hasMulti(MultiKey<?> key) {
|
||||
return getAll(key, false).size() > 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private <T> Environment addMulti(MultiKey<T> key, T value) {
|
||||
if (!multi.containsKey(key)) {
|
||||
if (hidden.contains(key)) {
|
||||
multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true));
|
||||
hidden.remove(key);
|
||||
}
|
||||
|
||||
multi.put((MultiKey)key, new HashSet<>());
|
||||
}
|
||||
|
||||
multi.get(key).add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Key<T> key) {
|
||||
if (key instanceof MultiKey) return getMulti((MultiKey<T>)key);
|
||||
|
||||
if (map.containsKey(key)) return (T)map.get(key);
|
||||
else if (!hidden.contains(key) && parent != null) return parent.get(key);
|
||||
else return null;
|
||||
}
|
||||
public boolean has(Key<?> key) {
|
||||
if (key instanceof MultiKey) return hasMulti((MultiKey<?>)key);
|
||||
|
||||
if (map.containsKey(key)) return true;
|
||||
else if (!hidden.contains(key) && parent != null) return parent.has(key);
|
||||
else return false;
|
||||
@ -90,15 +31,13 @@ public class Environment {
|
||||
if (has(key)) return get(key);
|
||||
else return defaultVal;
|
||||
}
|
||||
public <T> T get(Key<T> key, Supplier<T> defaultVal) {
|
||||
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
|
||||
if (has(key)) return get(key);
|
||||
else return defaultVal.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Environment add(Key<T> key, T val) {
|
||||
if (key instanceof MultiKey) return add(key, val);
|
||||
|
||||
map.put((Key<Object>)key, val);
|
||||
hidden.remove(key);
|
||||
return this;
|
||||
@ -108,14 +47,6 @@ public class Environment {
|
||||
}
|
||||
@SuppressWarnings("all")
|
||||
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
|
||||
for (var pair : map.entrySet()) {
|
||||
if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) {
|
||||
for (var val : (Iterable<?>)pair.getValue()) {
|
||||
addMulti((MultiKey<Object>)pair.getKey(), val);
|
||||
}
|
||||
}
|
||||
else add((Key<Object>)pair.getKey(), pair.getValue());
|
||||
}
|
||||
map.putAll((Map)map);
|
||||
hidden.removeAll(map.keySet());
|
||||
return this;
|
||||
@ -127,26 +58,9 @@ public class Environment {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Environment remove(Key<?> key) {
|
||||
map.remove(key);
|
||||
multi.remove(key);
|
||||
multiHidden.remove(key);
|
||||
hidden.add((Key<Object>)key);
|
||||
return this;
|
||||
}
|
||||
@SuppressWarnings("all")
|
||||
public <T> Environment remove(MultiKey<T> key, T val) {
|
||||
if (multi.containsKey(key)) {
|
||||
multi.get(key).remove(val);
|
||||
multiHidden.get(key).add(val);
|
||||
|
||||
if (multi.get(key).size() == 0) {
|
||||
multi.remove(key);
|
||||
multiHidden.remove(key);
|
||||
hidden.add((Key)key);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T init(Key<T> key, T val) {
|
||||
if (!has(key)) this.add(key, val);
|
||||
@ -180,8 +94,4 @@ public class Environment {
|
||||
public static Environment empty() {
|
||||
return new Environment();
|
||||
}
|
||||
|
||||
public static int nextId() {
|
||||
return new Random().nextInt();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.topchetoeu.jscript.common.environment;
|
||||
|
||||
public interface Key<T> {
|
||||
public final class Key<T> {
|
||||
public static <T> Key<T> of() {
|
||||
return new Key<>() { };
|
||||
return new Key<>();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.common.environment;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface MultiKey<T> extends Key<T> {
|
||||
public T of(Set<T> values);
|
||||
}
|
@ -102,7 +102,7 @@ public class JSON {
|
||||
if (filename == null) filename = new Filename("jscript", "json");
|
||||
|
||||
var res = parseValue(new Source(null, filename, raw), 0);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
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);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public class JSONElement {
|
||||
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.");
|
||||
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap");
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
@ -49,23 +49,23 @@ public class JSONElement {
|
||||
public boolean isNull() { return type == Type.NULL; }
|
||||
|
||||
public JSONMap map() {
|
||||
if (!isMap()) throw new IllegalStateException("Element is not a 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.");
|
||||
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.");
|
||||
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.");
|
||||
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.");
|
||||
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean");
|
||||
return (boolean)value;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
||||
|
||||
public JSONMap map(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||
return el.map();
|
||||
}
|
||||
public JSONMap map(String path, JSONMap defaultVal) {
|
||||
@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
||||
|
||||
public JSONList list(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||
return el.list();
|
||||
}
|
||||
public JSONList list(String path, JSONList defaultVal) {
|
||||
@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
||||
|
||||
public String string(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||
return el.string();
|
||||
}
|
||||
public String string(String path, String defaultVal) {
|
||||
@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
||||
|
||||
public double number(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||
return el.number();
|
||||
}
|
||||
public double number(String path, double defaultVal) {
|
||||
@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
|
||||
|
||||
public boolean bool(String path) {
|
||||
var el = get(path);
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
|
||||
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
|
||||
return el.bool();
|
||||
}
|
||||
public boolean bool(String path, boolean defaultVal) {
|
||||
|
@ -15,7 +15,6 @@ import java.util.stream.Collectors;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||
|
||||
public class FunctionMap {
|
||||
public static class FunctionMapBuilder {
|
||||
@ -54,9 +53,6 @@ public class FunctionMap {
|
||||
public FunctionMap build(String[] localNames, String[] captureNames) {
|
||||
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames);
|
||||
}
|
||||
public FunctionMap build(Scope scope) {
|
||||
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||
}
|
||||
public FunctionMap build() {
|
||||
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
|
||||
}
|
||||
|
@ -34,10 +34,15 @@ public class ParseRes<T> {
|
||||
return new ParseRes<>(state, null, null, result, this.n + n);
|
||||
}
|
||||
public <T2> ParseRes<T2> chainError() {
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
|
||||
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed");
|
||||
return new ParseRes<>(state, errorLocation, error, null, 0);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T2> ParseRes<T2> chainError(ParseRes<?> other) {
|
||||
if (!this.isError()) return other.chainError();
|
||||
return (ParseRes<T2>) this;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T2> ParseRes<T2> chainError(Location loc, String error) {
|
||||
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
|
||||
return (ParseRes<T2>) this;
|
||||
|
@ -86,10 +86,10 @@ public class Parsing {
|
||||
var newC = 0;
|
||||
|
||||
for (var j = 0; j < 2; j++) {
|
||||
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence.");
|
||||
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence");
|
||||
|
||||
int val = fromHex(src.at(i + n));
|
||||
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence.");
|
||||
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence");
|
||||
n++;
|
||||
|
||||
newC = (newC << 4) | val;
|
||||
@ -113,6 +113,7 @@ public class Parsing {
|
||||
return ParseRes.res((char)newC, n);
|
||||
}
|
||||
else if (c == '\n') return ParseRes.res(null, n);
|
||||
else n--;
|
||||
}
|
||||
|
||||
return ParseRes.res(src.at(i + n), n + 1);
|
||||
|
@ -1,158 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
|
||||
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
|
||||
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
|
||||
|
||||
public abstract class ClassNode extends FunctionNode {
|
||||
public static final class ClassBody {
|
||||
public final List<Node> staticMembers;
|
||||
public final List<FieldMemberNode> protoFields;
|
||||
public final List<Node> protoMembers;
|
||||
public final Parameters constructorParameters;
|
||||
public final CompoundNode constructorBody;
|
||||
|
||||
public ClassBody(
|
||||
List<Node> staticMembers, List<FieldMemberNode> protoFields, List<Node> protoMembers,
|
||||
Parameters constructorParameters, CompoundNode constructorBody
|
||||
) {
|
||||
this.staticMembers = staticMembers;
|
||||
this.protoFields = protoFields;
|
||||
this.protoMembers = protoMembers;
|
||||
this.constructorParameters = constructorParameters;
|
||||
this.constructorBody = constructorBody;
|
||||
}
|
||||
}
|
||||
|
||||
public final ClassBody body;
|
||||
public final String name;
|
||||
|
||||
@Override public String name() { return name; }
|
||||
|
||||
public void compileStatic(CompileResult target) {
|
||||
for (var member : body.staticMembers) member.compile(target, true);
|
||||
}
|
||||
public void compilePrototype(CompileResult target) {
|
||||
if (body.protoMembers.size() > 0) {
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("prototype"));
|
||||
|
||||
for (var i = 0; i < body.protoMembers.size() - 1; i++) {
|
||||
body.protoMembers.get(i).compile(target, true);
|
||||
}
|
||||
|
||||
body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void compilePreBody(CompileResult target) {
|
||||
for (var member : body.protoFields) {
|
||||
target.add(Instruction.loadThis());
|
||||
member.compile(target, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, false, true, false, name, captures(id, target)));
|
||||
compileStatic(target);
|
||||
compilePrototype(target);
|
||||
}
|
||||
|
||||
public ClassNode(Location loc, Location end, String name, ClassBody body) {
|
||||
super(loc, end, body.constructorParameters, body.constructorBody);
|
||||
|
||||
this.name = name;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static ParseRes<Node> parseMember(Source src, int i) {
|
||||
return ParseRes.first(src, i,
|
||||
PropertyMemberNode::parse,
|
||||
FieldMemberNode::parseClass,
|
||||
MethodMemberNode::parse
|
||||
);
|
||||
}
|
||||
|
||||
public static ParseRes<ClassBody> parseBody(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var fields = new LinkedList<FieldMemberNode>();
|
||||
var members = new LinkedList<Node>();
|
||||
var statics = new LinkedList<Node>();
|
||||
|
||||
var params = new Parameters(new ArrayList<>());
|
||||
var body = new CompoundNode(loc, false);
|
||||
var hasConstr = false;
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
return ParseRes.res(new ClassBody(statics, fields, members, params, body), n);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ParseRes<Node> prop = parseMember(src, i + n);
|
||||
|
||||
if (prop.isSuccess()) {
|
||||
n += prop.n;
|
||||
|
||||
if (prop.result instanceof FieldMemberNode field) fields.add(field);
|
||||
else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) {
|
||||
if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor");
|
||||
|
||||
params = method.params;
|
||||
body = method.body;
|
||||
hasConstr = true;
|
||||
}
|
||||
else members.add(prop.result);
|
||||
}
|
||||
else if (Parsing.isIdentifier(src, i + n, "static")) {
|
||||
n += 6;
|
||||
|
||||
var staticProp = parseMember(src, i + n);
|
||||
if (!staticProp.isSuccess()) {
|
||||
if (prop.isError()) return prop.chainError();
|
||||
else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword");
|
||||
}
|
||||
n += staticProp.n;
|
||||
|
||||
statics.add(staticProp.result);
|
||||
}
|
||||
else {
|
||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||
if (end.isSuccess()) n += end.n;
|
||||
else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon");
|
||||
}
|
||||
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||
}
|
||||
|
||||
return ParseRes.res(new ClassBody(statics, fields, members, params, body), n);
|
||||
}
|
||||
|
||||
// public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||
// super(loc, end, params, body);
|
||||
// this.name = name;
|
||||
// }
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
|
||||
public class ClassStatementNode extends ClassNode {
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
super.compile(target, pollute, name, bp);
|
||||
var i = target.scope.define(DeclarationType.LET, name(), loc());
|
||||
target.add(_i -> i.index().toInit());
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public ClassStatementNode(Location loc, Location end, String name, ClassBody body) {
|
||||
super(loc, end, name, body);
|
||||
}
|
||||
|
||||
public static ParseRes<ClassStatementNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
|
||||
n += 5;
|
||||
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name");
|
||||
n += name.n;
|
||||
|
||||
var body = parseBody(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
|
||||
n += body.n;
|
||||
|
||||
return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n);
|
||||
}
|
||||
}
|
@ -1,40 +1,33 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import me.topchetoeu.jscript.common.FunctionBody;
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.mapping.FunctionMap;
|
||||
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.scope.Scope;
|
||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||
|
||||
public final class CompileResult {
|
||||
public static final class ChildData {
|
||||
public final int id;
|
||||
public final CompileResult result;
|
||||
public static final Key<Void> DEBUG_LOG = new Key<>();
|
||||
|
||||
public ChildData(int id, CompileResult result) {
|
||||
this.result = result;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<IntFunction<Instruction>> instructions;
|
||||
public final List<Instruction> instructions;
|
||||
public final List<CompileResult> children;
|
||||
public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
|
||||
public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
|
||||
public final FunctionMapBuilder map;
|
||||
public final Environment env;
|
||||
public int length;
|
||||
public Runnable buildTask = () -> {
|
||||
throw new IllegalStateException("Compile result is not ready to be built");
|
||||
};
|
||||
public final Scope scope;
|
||||
public final FunctionScope scope;
|
||||
|
||||
public int temp() {
|
||||
instructions.add(null);
|
||||
@ -42,21 +35,14 @@ public final class CompileResult {
|
||||
}
|
||||
|
||||
public CompileResult add(Instruction instr) {
|
||||
instructions.add(i -> instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult add(IntFunction<Instruction> instr) {
|
||||
instructions.add(instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult set(int i, Instruction instr) {
|
||||
instructions.set(i, _i -> instr);
|
||||
return this;
|
||||
}
|
||||
public CompileResult set(int i, IntFunction<Instruction>instr) {
|
||||
instructions.set(i, instr);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int size() { return instructions.size(); }
|
||||
|
||||
public void setDebug(Location loc, BreakpointType type) {
|
||||
@ -78,61 +64,33 @@ public final class CompileResult {
|
||||
setLocationAndDebug(instructions.size() - 1, loc, type);
|
||||
}
|
||||
|
||||
public void beginScope() {
|
||||
// for (var cap : scope.capturables()) {
|
||||
// add(_i -> Instruction.capInit(cap.index().index));
|
||||
// }
|
||||
}
|
||||
public void reallocScope() {
|
||||
for (var cap : scope.capturables()) {
|
||||
add(_i -> cap.index().toGet());
|
||||
add(_i -> Instruction.capFree(cap.index().index));
|
||||
add(_i -> cap.index().toInit());
|
||||
}
|
||||
|
||||
scope.end();
|
||||
}
|
||||
public void endScope() {
|
||||
for (var cap : scope.capturables()) {
|
||||
add(_i -> Instruction.capFree(cap.index().index));
|
||||
}
|
||||
for (var var : scope.locals()) {
|
||||
add(_i -> Instruction.varFree(var.index().index));
|
||||
}
|
||||
|
||||
scope.end();
|
||||
}
|
||||
|
||||
public int addChild(CompileResult res) {
|
||||
public CompileResult addChild(FunctionNode node, CompileResult res) {
|
||||
this.children.add(res);
|
||||
return this.children.size() - 1;
|
||||
}
|
||||
|
||||
public Instruction[] instructions() {
|
||||
var res = new Instruction[instructions.size()];
|
||||
var i = 0;
|
||||
for (var suppl : instructions) {
|
||||
res[i] = suppl.apply(i);
|
||||
i++;
|
||||
}
|
||||
this.childrenMap.put(node, res);
|
||||
this.childrenIndices.put(node, this.children.size() - 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Instruction[] instructions() {
|
||||
return instructions.toArray(new Instruction[0]);
|
||||
}
|
||||
|
||||
public FunctionMap map() {
|
||||
return map.build(scope);
|
||||
return map.build(scope.localNames(), scope.captureNames());
|
||||
}
|
||||
public FunctionBody body() {
|
||||
var builtChildren = new FunctionBody[children.size()];
|
||||
|
||||
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
|
||||
|
||||
var instrRes = new Instruction[instructions.size()];
|
||||
var i = 0;
|
||||
var instrRes = instructions();
|
||||
|
||||
for (var suppl : instructions) {
|
||||
instrRes[i] = suppl.apply(i);
|
||||
// System.out.println(instrRes[i]);
|
||||
i++;
|
||||
if (env.has(DEBUG_LOG)) {
|
||||
System.out.println("================= BODY =================");
|
||||
System.out.println("LOCALS: " + scope.localsCount());
|
||||
System.out.println("CAPTURABLES: " + scope.capturablesCount());
|
||||
System.out.println("CAPTURES: " + scope.capturesCount());
|
||||
|
||||
for (var instr : instrRes) System.out.println(instr);
|
||||
}
|
||||
|
||||
return new FunctionBody(
|
||||
@ -142,23 +100,36 @@ public final class CompileResult {
|
||||
}
|
||||
|
||||
public CompileResult subtarget() {
|
||||
return new CompileResult(new Scope(scope), this);
|
||||
return new CompileResult(env, new FunctionScope(scope), this);
|
||||
}
|
||||
|
||||
public CompileResult(Environment env, Scope scope, int length, Consumer<CompileResult> task) {
|
||||
public CompileResult setEnvironment(Environment env) {
|
||||
return new CompileResult(env, scope, this);
|
||||
}
|
||||
/**
|
||||
* Returns a compile result with a child of the environment that relates to the given key.
|
||||
* In essence, this is used to create a compile result which is back at the root environment of the compilation
|
||||
*/
|
||||
public CompileResult rootEnvironment(Key<Environment> env) {
|
||||
return new CompileResult(this.env.get(env).child(), scope, this);
|
||||
}
|
||||
public CompileResult subEnvironment() {
|
||||
return new CompileResult(env.child(), scope, this);
|
||||
}
|
||||
|
||||
public CompileResult(Environment env, FunctionScope scope, int length) {
|
||||
this.scope = scope;
|
||||
this.instructions = new ArrayList<>();
|
||||
this.children = new LinkedList<>();
|
||||
this.map = FunctionMap.builder();
|
||||
this.env = env;
|
||||
this.length = length;
|
||||
this.buildTask = () -> task.accept(this);
|
||||
}
|
||||
private CompileResult(Scope scope, CompileResult parent) {
|
||||
private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
|
||||
this.scope = scope;
|
||||
this.instructions = parent.instructions;
|
||||
this.children = parent.children;
|
||||
this.map = parent.map;
|
||||
this.env = parent.env;
|
||||
this.env = env;
|
||||
}
|
||||
}
|
||||
|
@ -14,22 +14,21 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
|
||||
public class CompoundNode extends Node {
|
||||
public final Node[] statements;
|
||||
public boolean hasScope;
|
||||
public Location end;
|
||||
|
||||
@Override public void resolve(CompileResult target) {
|
||||
for (var stm : statements) stm.resolve(target);
|
||||
}
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
for (var stm : statements) stm.compileFunctions(target);
|
||||
}
|
||||
|
||||
public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) {
|
||||
public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
List<Node> statements = new ArrayList<Node>();
|
||||
|
||||
var subtarget = hasScope ? target.subtarget() : target;
|
||||
if (hasScope) subtarget.beginScope();
|
||||
|
||||
for (var stm : this.statements) {
|
||||
if (stm instanceof FunctionStatementNode func) {
|
||||
func.compile(subtarget, false);
|
||||
func.compile(target, false);
|
||||
}
|
||||
else statements.add(stm);
|
||||
}
|
||||
@ -39,41 +38,25 @@ public class CompoundNode extends Node {
|
||||
for (var i = 0; i < statements.size(); i++) {
|
||||
var stm = statements.get(i);
|
||||
|
||||
if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
else stm.compile(subtarget, 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 (hasScope) subtarget.endScope();
|
||||
|
||||
if (!polluted && pollute) {
|
||||
target.add(Instruction.pushUndefined());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
compile(target, pollute, true, type);
|
||||
}
|
||||
|
||||
public CompoundNode setEnd(Location loc) {
|
||||
this.end = loc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompoundNode(Location loc, boolean hasScope, Node ...statements) {
|
||||
public CompoundNode(Location loc, Node ...statements) {
|
||||
super(loc);
|
||||
this.hasScope = hasScope;
|
||||
this.statements = statements;
|
||||
}
|
||||
|
||||
public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) {
|
||||
if (node instanceof CompoundNode comp) {
|
||||
comp.compile(target, pollute, false, type);
|
||||
}
|
||||
else {
|
||||
node.compile(target, pollute, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
|
||||
if (precedence > 1) return ParseRes.failed();
|
||||
|
||||
@ -92,9 +75,9 @@ public class CompoundNode extends Node {
|
||||
children.addAll(Arrays.asList(comp.statements));
|
||||
children.add(curr.result);
|
||||
|
||||
return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(new Node[0])), n);
|
||||
return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
|
||||
}
|
||||
else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n);
|
||||
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
|
||||
}
|
||||
public static ParseRes<CompoundNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
@ -124,6 +107,6 @@ public class CompoundNode extends Node {
|
||||
statements.add(res.result);
|
||||
}
|
||||
|
||||
return ParseRes.res(new CompoundNode(loc, true, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
|
||||
return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
|
||||
public class FunctionArrowNode extends FunctionNode {
|
||||
@Override public String name() { return null; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, false, true, null, captures(id, target)));
|
||||
}
|
||||
|
||||
public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) {
|
||||
super(loc, end, params, expToBody(body));
|
||||
}
|
||||
|
||||
private static final CompoundNode expToBody(Node node) {
|
||||
if (node instanceof CompoundNode res) return res;
|
||||
else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node));
|
||||
}
|
||||
|
||||
public static ParseRes<FunctionArrowNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
Parameters params;
|
||||
|
||||
if (src.is(i + n, "(")) {
|
||||
var paramsRes = Parameters.parseParameters(src, i + n);
|
||||
if (!paramsRes.isSuccess()) return paramsRes.chainError();
|
||||
n += paramsRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params = paramsRes.result;
|
||||
}
|
||||
else {
|
||||
var singleParam = Pattern.parse(src, i + n, true);
|
||||
if (!singleParam.isSuccess()) return ParseRes.failed();
|
||||
n += singleParam.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params = new Parameters(Arrays.asList(singleParam.result));
|
||||
}
|
||||
|
||||
if (!src.is(i + n, "=>")) return ParseRes.failed();
|
||||
n += 2;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "{")) {
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
|
||||
n += body.n;
|
||||
|
||||
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
|
||||
}
|
||||
else {
|
||||
var body = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
|
||||
n += body.n;
|
||||
|
||||
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
@ -7,68 +9,56 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public abstract class FunctionNode extends Node {
|
||||
public final CompoundNode body;
|
||||
public final Parameters params;
|
||||
public final List<VariableNode> params;
|
||||
public final Location end;
|
||||
|
||||
public abstract String name();
|
||||
|
||||
protected final int[] captures(int id, CompileResult target) {
|
||||
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
|
||||
public final String name(String fallback) {
|
||||
return this.name() != null ? this.name() : fallback;
|
||||
}
|
||||
protected final int[] captures(CompileResult target) {
|
||||
return target.childrenMap.get(this).scope.getCaptureIndices();
|
||||
}
|
||||
|
||||
protected void compilePreBody(CompileResult target) { }
|
||||
protected final Environment rootEnv(Environment env) {
|
||||
return env.get(JavaScript.COMPILE_ROOT);
|
||||
}
|
||||
|
||||
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
|
||||
var name = this.name() != null ? this.name() : _name;
|
||||
@Override public void resolve(CompileResult target) { }
|
||||
|
||||
env = env.child()
|
||||
.remove(LabelContext.BREAK_CTX)
|
||||
.remove(LabelContext.CONTINUE_CTX);
|
||||
|
||||
return new CompileResult(env, scope, params.params.size(), target -> {
|
||||
compilePreBody(target);
|
||||
|
||||
if (params.params.size() > 0) {
|
||||
target.add(Instruction.loadArgs(true));
|
||||
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0));
|
||||
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) {
|
||||
var target = new CompileResult(env, scope, params.size());
|
||||
var i = 0;
|
||||
|
||||
for (var param : params.params) {
|
||||
target.add(Instruction.loadMember(i++));
|
||||
param.destruct(target, DeclarationType.VAR, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.rest != null) {
|
||||
target.add(Instruction.loadRestArgs(params.params.size()));
|
||||
params.rest.destruct(target, DeclarationType.VAR, true);
|
||||
}
|
||||
|
||||
if (selfName != null && !scope.has(name, false)) {
|
||||
var i = scope.defineSpecial(new Variable(selfName, true), end);
|
||||
|
||||
target.add(Instruction.loadCallee());
|
||||
target.add(_i -> i.index().toInit());
|
||||
}
|
||||
|
||||
body.resolve(target);
|
||||
|
||||
for (var param : params) scope.define(param.name);
|
||||
|
||||
// if (selfName != null && !scope.has(selfName, false)) {
|
||||
// var i = scope.defineSpecial(new Variable(selfName, true), end);
|
||||
|
||||
// t.add(Instruction.loadCalled());
|
||||
// t.add(_i -> i.index().toInit());
|
||||
// }
|
||||
|
||||
body.compileFunctions(target);
|
||||
|
||||
for (var param : params) {
|
||||
target.add(Instruction.loadArg(i++)).setLocation(param.loc());
|
||||
target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc());
|
||||
}
|
||||
|
||||
body.compile(target, lastReturn, BreakpointType.NONE);
|
||||
|
||||
scope.end();
|
||||
|
||||
for (var child : target.children) child.buildTask.run();
|
||||
|
||||
scope.finish();
|
||||
});
|
||||
return target;
|
||||
}
|
||||
public final CompileResult compileBody(CompileResult parent, String name, String selfName) {
|
||||
return compileBody(parent.env, new FunctionScope(parent.scope), false, name, selfName);
|
||||
public final CompileResult compileBody(CompileResult parent, String selfName) {
|
||||
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, selfName);
|
||||
}
|
||||
|
||||
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
|
||||
@ -82,13 +72,12 @@ public abstract class FunctionNode extends Node {
|
||||
compile(target, pollute, (String)null, BreakpointType.NONE);
|
||||
}
|
||||
|
||||
public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) {
|
||||
public FunctionNode(Location loc, Location end, List<VariableNode> params, CompoundNode body) {
|
||||
super(loc);
|
||||
|
||||
this.end = end;
|
||||
this.params = params;
|
||||
this.body = body;
|
||||
this.body.hasScope = false;
|
||||
}
|
||||
|
||||
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
|
||||
@ -112,7 +101,7 @@ public abstract class FunctionNode extends Node {
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
var params = JavaScript.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += params.n;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
@ -12,17 +14,19 @@ public class FunctionStatementNode extends FunctionNode {
|
||||
@Override public String name() { return name; }
|
||||
|
||||
@Override public void resolve(CompileResult target) {
|
||||
target.scope.define(new Variable(name, false), end);
|
||||
target.scope.define(new Variable(name, false));
|
||||
}
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
target.addChild(this, compileBody(target, name()));
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
|
||||
target.add(VariableNode.toInit(target, end, this.name));
|
||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||
target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc());
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||
public FunctionStatementNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
||||
super(loc, end, params, body);
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -1,20 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class FunctionValueNode extends FunctionNode {
|
||||
public final String name;
|
||||
|
||||
@Override public String name() { return name; }
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
target.addChild(this, compileBody(target, name()));
|
||||
}
|
||||
|
||||
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public FunctionValueNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
|
||||
super(loc, end, params, body);
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package me.topchetoeu.jscript.compilation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
@ -18,7 +20,6 @@ import me.topchetoeu.jscript.compilation.control.DeleteNode;
|
||||
import me.topchetoeu.jscript.compilation.control.DoWhileNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ForInNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ForNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ForOfNode;
|
||||
import me.topchetoeu.jscript.compilation.control.IfNode;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||
import me.topchetoeu.jscript.compilation.control.SwitchNode;
|
||||
@ -28,7 +29,7 @@ import me.topchetoeu.jscript.compilation.control.WhileNode;
|
||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ClassValueNode;
|
||||
import me.topchetoeu.jscript.compilation.values.GlobalThisNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.RegexNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
||||
@ -47,24 +48,18 @@ import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
|
||||
|
||||
public final class JavaScript {
|
||||
public static enum DeclarationType {
|
||||
VAR(false, false),
|
||||
CONST(true, true),
|
||||
LET(true, false);
|
||||
|
||||
public final boolean strict, readonly;
|
||||
|
||||
private DeclarationType(boolean strict, boolean readonly) {
|
||||
this.strict = strict;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
@Deprecated
|
||||
VAR;
|
||||
}
|
||||
|
||||
public static final Key<Environment> COMPILE_ROOT = Key.of();
|
||||
|
||||
static final Set<String> reserved = new HashSet<>(Arrays.asList(
|
||||
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
|
||||
"finally", "for", "do", "while", "switch", "case", "default", "new",
|
||||
"function", "var", "return", "throw", "typeof", "delete", "break",
|
||||
"continue", "debugger", "implements", "interface", "package", "private",
|
||||
"protected", "public", "static", "arguments", "class"
|
||||
"protected", "public", "static", "arguments", "class", "extends"
|
||||
));
|
||||
|
||||
public static ParseRes<? extends Node> parseParens(Source src, int i) {
|
||||
@ -89,7 +84,6 @@ public final class JavaScript {
|
||||
return ParseRes.first(src, i,
|
||||
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
|
||||
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
|
||||
(s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j),
|
||||
JavaScript::parseLiteral,
|
||||
StringNode::parse,
|
||||
RegexNode::parse,
|
||||
@ -98,7 +92,6 @@ public final class JavaScript {
|
||||
ChangeNode::parsePrefixIncrease,
|
||||
OperationNode::parsePrefix,
|
||||
ArrayNode::parse,
|
||||
(s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j),
|
||||
JavaScript::parseParens,
|
||||
CallNode::parseNew,
|
||||
TypeofNode::parse,
|
||||
@ -121,11 +114,12 @@ public final class JavaScript {
|
||||
if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n);
|
||||
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
|
||||
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
|
||||
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n);
|
||||
|
||||
return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<? extends Node> parseExpression(Source src, int i, int precedence, boolean statement) {
|
||||
public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
Node prev = null;
|
||||
|
||||
@ -169,11 +163,11 @@ public final class JavaScript {
|
||||
else return ParseRes.res(prev, n);
|
||||
}
|
||||
|
||||
public static ParseRes<? extends Node> parseExpression(Source src, int i, int precedence) {
|
||||
public static ParseRes<Node> parseExpression(Source src, int i, int precedence) {
|
||||
return parseExpression(src, i, precedence, false);
|
||||
}
|
||||
|
||||
public static ParseRes<? extends Node> parseExpressionStatement(Source src, int i) {
|
||||
public static ParseRes<Node> parseExpressionStatement(Source src, int i) {
|
||||
var res = parseExpression(src, i, 0, true);
|
||||
if (!res.isSuccess()) return res.chainError();
|
||||
|
||||
@ -183,14 +177,13 @@ public final class JavaScript {
|
||||
return res.addN(end.n);
|
||||
}
|
||||
|
||||
public static ParseRes<? extends Node> parseStatement(Source src, int i) {
|
||||
public static ParseRes<Node> parseStatement(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1);
|
||||
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
|
||||
|
||||
ParseRes<? extends Node> res = ParseRes.first(src, i + n,
|
||||
ClassStatementNode::parse,
|
||||
ParseRes<Node> res = ParseRes.first(src, i + n,
|
||||
VariableDeclareNode::parse,
|
||||
ReturnNode::parse,
|
||||
ThrowNode::parse,
|
||||
@ -202,7 +195,6 @@ public final class JavaScript {
|
||||
SwitchNode::parse,
|
||||
ForNode::parse,
|
||||
ForInNode::parse,
|
||||
ForOfNode::parse,
|
||||
DoWhileNode::parse,
|
||||
TryNode::parse,
|
||||
CompoundNode::parse,
|
||||
@ -214,7 +206,7 @@ public final class JavaScript {
|
||||
|
||||
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
if (i >= src.size()) return ParseRes.res(true, n + 1);
|
||||
if (i + n >= src.size()) return ParseRes.res(true, n);
|
||||
|
||||
for (var j = i; j < i + n; j++) {
|
||||
if (src.is(j, '\n')) return ParseRes.res(true, n);
|
||||
@ -226,13 +218,11 @@ public final class JavaScript {
|
||||
return ParseRes.failed();
|
||||
}
|
||||
|
||||
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
|
||||
public static ParseRes<Boolean> parseDeclarationType(Source src, int i) {
|
||||
var res = Parsing.parseIdentifier(src, i);
|
||||
if (!res.isSuccess()) return res.chainError();
|
||||
|
||||
if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n);
|
||||
if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n);
|
||||
if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n);
|
||||
if (res.result.equals("var")) return ParseRes.res(true, res.n);
|
||||
|
||||
return ParseRes.failed();
|
||||
}
|
||||
@ -264,9 +254,11 @@ public final class JavaScript {
|
||||
}
|
||||
|
||||
public static CompileResult compile(Environment env, Node ...statements) {
|
||||
var func = new FunctionValueNode(null, null, new Parameters(Arrays.asList()), new CompoundNode(null, true, statements), null);
|
||||
var res = func.compileBody(env, new FunctionScope(true), true, null, null);
|
||||
res.buildTask.run();
|
||||
env = env.child();
|
||||
env.add(COMPILE_ROOT, env);
|
||||
|
||||
var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null);
|
||||
var res = func.compileBody(env, new FunctionScope(true), true, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -291,4 +283,42 @@ public final class JavaScript {
|
||||
|
||||
return ParseRes.res(nameRes.result, n);
|
||||
}
|
||||
|
||||
public static ParseRes<List<VariableNode>> parseParameters(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += openParen.n;
|
||||
|
||||
var params = new ArrayList<VariableNode>();
|
||||
|
||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||
n += closeParen.n;
|
||||
|
||||
if (!closeParen.isSuccess()) {
|
||||
while (true) {
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var param = VariableNode.parse(src, i + n);
|
||||
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||
n += param.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params.add(param.result);
|
||||
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
}
|
||||
|
||||
if (src.is(i + n, ")")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(params, n);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.Stack;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
@ -18,6 +19,8 @@ public class LabelContext {
|
||||
private final LinkedList<IntSupplier> list = new LinkedList<>();
|
||||
private final HashMap<String, IntSupplier> map = new HashMap<>();
|
||||
|
||||
private final Stack<ArrayList<Runnable>> deferredAdders = new Stack<>();
|
||||
|
||||
public IntSupplier get() {
|
||||
return list.peekLast();
|
||||
}
|
||||
@ -25,15 +28,31 @@ public class LabelContext {
|
||||
return map.get(name);
|
||||
}
|
||||
|
||||
public IntFunction<Instruction> getJump() {
|
||||
var res = get();
|
||||
if (res == null) return null;
|
||||
else return i -> Instruction.jmp(res.getAsInt() - i);
|
||||
public void flushAdders() {
|
||||
for (var adder : deferredAdders.peek()) {
|
||||
adder.run();
|
||||
}
|
||||
public IntFunction<Instruction> getJump(String name) {
|
||||
|
||||
deferredAdders.pop();
|
||||
}
|
||||
|
||||
public boolean jump(CompileResult target) {
|
||||
var res = get();
|
||||
if (res != null) {
|
||||
var tmp = target.temp();
|
||||
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
public boolean jump(CompileResult target, String name) {
|
||||
var res = get(name);
|
||||
if (res == null) return null;
|
||||
else return i -> Instruction.jmp(res.getAsInt() - i);
|
||||
if (res != null) {
|
||||
var tmp = target.temp();
|
||||
this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public void push(IntSupplier jumpTarget) {
|
||||
@ -48,6 +67,7 @@ public class LabelContext {
|
||||
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
|
||||
push(jumpTarget);
|
||||
push(loc, name, jumpTarget);
|
||||
deferredAdders.push(new ArrayList<>());
|
||||
}
|
||||
|
||||
public void pop() {
|
||||
@ -61,6 +81,7 @@ public class LabelContext {
|
||||
public void popLoop(String name) {
|
||||
pop();
|
||||
pop(name);
|
||||
flushAdders();
|
||||
}
|
||||
|
||||
public static LabelContext getBreak(Environment env) {
|
||||
|
@ -17,6 +17,8 @@ public abstract class Node {
|
||||
compile(target, pollute, BreakpointType.NONE);
|
||||
}
|
||||
|
||||
public abstract void compileFunctions(CompileResult target);
|
||||
|
||||
public Location loc() { return loc; }
|
||||
public void setLoc(Location loc) { this.loc = loc; }
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||
|
||||
public final class Parameters {
|
||||
public final int length;
|
||||
public final List<Pattern> params;
|
||||
public final Pattern rest;
|
||||
|
||||
public Parameters(List<Pattern> params, Pattern rest) {
|
||||
var len = params.size();
|
||||
|
||||
for (var i = params.size() - 1; i >= 0; i--) {
|
||||
if (!(params.get(i) instanceof AssignNode)) break;
|
||||
len--;
|
||||
}
|
||||
|
||||
this.params = params;
|
||||
this.length = len;
|
||||
this.rest = rest;
|
||||
}
|
||||
public Parameters(List<Pattern> params) {
|
||||
this(params, null);
|
||||
}
|
||||
|
||||
public static ParseRes<Parameters> parseParameters(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var openParen = Parsing.parseOperator(src, i + n, "(");
|
||||
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
|
||||
n += openParen.n;
|
||||
|
||||
var params = new ArrayList<Pattern>();
|
||||
|
||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||
n += closeParen.n;
|
||||
|
||||
if (!closeParen.isSuccess()) {
|
||||
while (true) {
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "...")) {
|
||||
n += 3;
|
||||
|
||||
var rest = Pattern.parse(src, i + n, true);
|
||||
if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
||||
n += rest.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
||||
n++;
|
||||
|
||||
return ParseRes.res(new Parameters(params, rest.result), n);
|
||||
}
|
||||
|
||||
var param = Pattern.parse(src, i + n, true);
|
||||
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||
n += param.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
params.add(param.result);
|
||||
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
}
|
||||
|
||||
if (src.is(i + n, ")")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseRes.res(new Parameters(params), n);
|
||||
}
|
||||
}
|
@ -3,58 +3,45 @@ package me.topchetoeu.jscript.compilation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class VariableDeclareNode extends Node {
|
||||
public static class Pair {
|
||||
public final Pattern destructor;
|
||||
public final Node value;
|
||||
public final Location location;
|
||||
|
||||
public Pair(Pattern destr, Node value, Location location) {
|
||||
this.destructor = destr;
|
||||
this.value = value;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
@Desugar
|
||||
public static record Pair(VariableNode var, Node value) { }
|
||||
|
||||
public final List<Pair> values;
|
||||
public final DeclarationType declType;
|
||||
|
||||
@Override public void resolve(CompileResult target) {
|
||||
if (!declType.strict) {
|
||||
for (var entry : values) {
|
||||
entry.destructor.destructDeclResolve(target);
|
||||
target.scope.define(entry.var.name);
|
||||
}
|
||||
}
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
for (var pair : values) {
|
||||
if (pair.value != null) pair.value.compileFunctions(target);
|
||||
}
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.value == null) {
|
||||
if (declType == DeclarationType.VAR) entry.destructor.declare(target, null, false);
|
||||
else entry.destructor.declare(target, declType, false);
|
||||
}
|
||||
else {
|
||||
if (entry.value != null) {
|
||||
entry.value.compile(target, true);
|
||||
|
||||
if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true);
|
||||
else entry.destructor.destruct(target, declType, true);
|
||||
target.add(VariableNode.toSet(target, loc(), entry.var.name, false, true)).setLocation(loc());
|
||||
}
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public VariableDeclareNode(Location loc, DeclarationType declType, List<Pair> values) {
|
||||
public VariableDeclareNode(Location loc, List<Pair> values) {
|
||||
super(loc);
|
||||
this.values = values;
|
||||
this.declType = declType;
|
||||
}
|
||||
|
||||
public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
|
||||
@ -70,14 +57,14 @@ public class VariableDeclareNode extends Node {
|
||||
var end = JavaScript.parseStatementEnd(src, i + n);
|
||||
if (end.isSuccess()) {
|
||||
n += end.n;
|
||||
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
|
||||
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var nameLoc = src.loc(i + n);
|
||||
|
||||
var name = Pattern.parse(src, i + n, false);
|
||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor");
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
||||
n += name.n;
|
||||
|
||||
Node val = null;
|
||||
@ -96,7 +83,7 @@ public class VariableDeclareNode extends Node {
|
||||
val = valRes.result;
|
||||
}
|
||||
|
||||
res.add(new Pair(name.result, val, nameLoc));
|
||||
res.add(new Pair(new VariableNode(nameLoc, name.result), val));
|
||||
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
@ -107,7 +94,7 @@ public class VariableDeclareNode extends Node {
|
||||
|
||||
if (end.isSuccess()) {
|
||||
n += end.n + endN - n;
|
||||
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n);
|
||||
return ParseRes.res(new VariableDeclareNode(loc, res), n);
|
||||
}
|
||||
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class BreakNode extends Node {
|
||||
public final String label;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
var res = LabelContext.getBreak(target.env).getJump();
|
||||
if (res == null) {
|
||||
if (!LabelContext.getBreak(target.env).jump(target)) {
|
||||
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||
else throw new SyntaxException(loc(), "Illegal break statement");
|
||||
}
|
||||
target.add(res);
|
||||
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ContinueNode extends Node {
|
||||
public final String label;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
var res = LabelContext.getCont(target.env).getJump();
|
||||
if (res == null) {
|
||||
if (!LabelContext.getCont(target.env).jump(target)) {
|
||||
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
|
||||
else throw new SyntaxException(loc(), "Illegal continue statement");
|
||||
}
|
||||
target.add(res);
|
||||
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
public class DebugNode extends Node {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.debug());
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
|
@ -16,6 +16,10 @@ public class DeleteNode extends Node {
|
||||
public final Node key;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
key.compileFunctions(target);
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
value.compile(target, true);
|
||||
key.compile(target, true);
|
||||
|
@ -16,6 +16,10 @@ public class DoWhileNode extends Node {
|
||||
public final Node condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
condition.compileFunctions(target);
|
||||
body.compileFunctions(target);
|
||||
}
|
||||
@Override public void resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
}
|
||||
@ -27,13 +31,14 @@ public class DoWhileNode extends Node {
|
||||
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
mid.set(target.size());
|
||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||
int endI = target.size();
|
||||
end.set(endI + 1);
|
||||
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
target.add(Instruction.jmpIf(start - endI));
|
||||
}
|
||||
|
||||
@ -57,6 +62,7 @@ public class DoWhileNode extends Node {
|
||||
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
|
||||
n += bodyRes.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
|
||||
n += 5;
|
||||
|
@ -7,15 +7,15 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Binding;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
public class ForInNode extends Node {
|
||||
public final Binding binding;
|
||||
public final boolean isDecl;
|
||||
public final VariableNode binding;
|
||||
public final Node object, body;
|
||||
public final String label;
|
||||
|
||||
@ -24,9 +24,11 @@ public class ForInNode extends Node {
|
||||
binding.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
object.compileFunctions(target);
|
||||
body.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
binding.declareLateInit(target);
|
||||
|
||||
object.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.keys(false, true));
|
||||
|
||||
@ -35,27 +37,32 @@ public class ForInNode extends Node {
|
||||
int mid = target.temp();
|
||||
|
||||
target.add(Instruction.loadMember("value")).setLocation(binding.loc());
|
||||
binding.assign(target, false);
|
||||
target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc());
|
||||
|
||||
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
|
||||
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int endI = target.size();
|
||||
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||
|
||||
end.set(endI);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public ForInNode(Location loc, String label, Binding binding, Node object, Node body) {
|
||||
public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.binding = binding;
|
||||
this.isDecl = isDecl;
|
||||
this.object = object;
|
||||
this.body = body;
|
||||
}
|
||||
@ -76,9 +83,15 @@ public class ForInNode extends Node {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var binding = Binding.parse(src, i + n);
|
||||
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-in loop");
|
||||
n += binding.n;
|
||||
var varKw = JavaScript.parseDeclarationType(src, i + n);
|
||||
n += varKw.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var bindingLoc = src.loc(i + n);
|
||||
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name");
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
|
||||
@ -96,6 +109,6 @@ public class ForInNode extends Node {
|
||||
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
|
||||
n += bodyRes.n;
|
||||
|
||||
return ParseRes.res(new ForInNode(loc, label.result, binding.result, obj.result, bodyRes.result), n);
|
||||
return ParseRes.res(new ForInNode(loc, label.result, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||
@ -23,35 +22,36 @@ public class ForNode extends Node {
|
||||
declaration.resolve(target);
|
||||
body.resolve(target);
|
||||
}
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
if (declaration != null) declaration.compileFunctions(target);
|
||||
if (assignment != null) assignment.compileFunctions(target);
|
||||
if (condition != null) condition.compileFunctions(target);
|
||||
body.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.scope.singleEntry = false;
|
||||
subtarget.beginScope();
|
||||
if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
declaration.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
|
||||
int start = subtarget.size();
|
||||
CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER);
|
||||
int mid = subtarget.temp();
|
||||
int start = target.size();
|
||||
int mid = -1;
|
||||
if (condition != null) {
|
||||
condition.compile(target, true, BreakpointType.STEP_OVER);
|
||||
mid = target.temp();
|
||||
}
|
||||
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.pushLoop(subtarget.env, loc(), label, end, start);
|
||||
CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER);
|
||||
LabelContext.popLoop(subtarget.env, label);
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
subtarget.reallocScope();
|
||||
|
||||
CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER);
|
||||
int endI = subtarget.size();
|
||||
if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER);
|
||||
int endI = target.size();
|
||||
|
||||
end.set(endI);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
subtarget.add(Instruction.jmp(start - endI));
|
||||
subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||
if (pollute) subtarget.add(Instruction.pushUndefined());
|
||||
|
||||
subtarget.endScope();
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
if (mid >= 0) target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
|
||||
|
@ -1,110 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Binding;
|
||||
|
||||
public class ForOfNode extends Node {
|
||||
public final Binding binding;
|
||||
public final Node iterable, body;
|
||||
public final String label;
|
||||
|
||||
@Override public void resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
binding.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
binding.declareLateInit(target);
|
||||
|
||||
iterable.compile(target, true, BreakpointType.STEP_OVER);
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadIntrinsics("it_key"));
|
||||
target.add(Instruction.loadMember()).setLocation(iterable.loc());
|
||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("next")).setLocation(iterable.loc());
|
||||
target.add(Instruction.call(0)).setLocation(iterable.loc());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("done")).setLocation(iterable.loc());
|
||||
int mid = target.temp();
|
||||
|
||||
target.add(Instruction.loadMember("value")).setLocation(binding.loc);
|
||||
binding.assign(target, false);
|
||||
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
int endI = target.size();
|
||||
end.set(endI);
|
||||
|
||||
target.add(Instruction.jmp(start - endI));
|
||||
target.add(Instruction.discard());
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(endI - mid + 1));
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
}
|
||||
|
||||
public ForOfNode(Location loc, String label, Binding binding, Node object, Node body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.binding = binding;
|
||||
this.iterable = object;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static ParseRes<ForOfNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var label = JavaScript.parseLabel(src, i + n);
|
||||
n += label.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
|
||||
n += 3;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var binding = Binding.parse(src, i + n);
|
||||
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-of loop");
|
||||
n += binding.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration");
|
||||
n += 2;
|
||||
|
||||
var obj = JavaScript.parseExpression(src, i + n, 0);
|
||||
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
|
||||
n += obj.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
|
||||
n++;
|
||||
|
||||
var bodyRes = JavaScript.parseStatement(src, i + n);
|
||||
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body");
|
||||
n += bodyRes.n;
|
||||
|
||||
return ParseRes.res(new ForOfNode(loc, label.result, binding.result, obj.result, bodyRes.result), n);
|
||||
}
|
||||
}
|
@ -20,7 +20,11 @@ public class IfNode extends Node {
|
||||
body.resolve(target);
|
||||
if (elseBody != null) elseBody.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
condition.compileFunctions(target);
|
||||
body.compileFunctions(target);
|
||||
if (elseBody != null) elseBody.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
|
||||
condition.compile(target, true, breakpoint);
|
||||
|
||||
@ -29,7 +33,7 @@ public class IfNode extends Node {
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||
LabelContext.getBreak(target.env).pop(label);
|
||||
|
||||
int endI = target.size();
|
||||
@ -42,11 +46,11 @@ public class IfNode extends Node {
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
body.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||
|
||||
int mid = target.temp();
|
||||
|
||||
elseBody.compile(target, false, BreakpointType.STEP_OVER);
|
||||
elseBody.compile(target, pollute, BreakpointType.STEP_OVER);
|
||||
LabelContext.getBreak(target.env).pop(label);
|
||||
|
||||
int endI = target.size();
|
||||
@ -80,7 +84,7 @@ public class IfNode extends Node {
|
||||
var a = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
|
||||
n += a.n;
|
||||
n += Parsing.skipEmpty(src, i);
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||
n++;
|
||||
|
@ -12,6 +12,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ReturnNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
if (value != null) value.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (value == null) target.add(Instruction.pushUndefined());
|
||||
else value.compile(target, true);
|
||||
|
@ -36,48 +36,52 @@ public class SwitchNode extends Node {
|
||||
@Override public void resolve(CompileResult target) {
|
||||
for (var stm : body) stm.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
value.compileFunctions(target);
|
||||
for (var _case : cases) {
|
||||
_case.value.compileFunctions(target);
|
||||
}
|
||||
for (var stm : body) {
|
||||
stm.compileFunctions(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);
|
||||
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.beginScope();
|
||||
|
||||
// TODO: create a jump map
|
||||
for (var ccase : cases) {
|
||||
subtarget.add(Instruction.dup());
|
||||
ccase.value.compile(subtarget, true);
|
||||
subtarget.add(Instruction.operation(Operation.EQUALS));
|
||||
caseToStatement.put(subtarget.temp(), ccase.statementI);
|
||||
target.add(Instruction.dup());
|
||||
ccase.value.compile(target, true);
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
caseToStatement.put(target.temp(), ccase.statementI);
|
||||
}
|
||||
|
||||
int start = subtarget.temp();
|
||||
int start = target.temp();
|
||||
var end = new DeferredIntSupplier();
|
||||
|
||||
LabelContext.getBreak(target.env).push(loc(), label, end);
|
||||
LabelContext.getBreak(target.env).pushLoop(loc(), label, end);
|
||||
for (var stm : body) {
|
||||
statementToIndex.put(statementToIndex.size(), subtarget.size());
|
||||
stm.compile(subtarget, false, BreakpointType.STEP_OVER);
|
||||
statementToIndex.put(statementToIndex.size(), target.size());
|
||||
stm.compile(target, false, BreakpointType.STEP_OVER);
|
||||
}
|
||||
LabelContext.getBreak(target.env).pop(label);
|
||||
|
||||
subtarget.endScope();
|
||||
|
||||
int endI = subtarget.size();
|
||||
int endI = target.size();
|
||||
end.set(endI);
|
||||
subtarget.add(Instruction.discard());
|
||||
if (pollute) subtarget.add(Instruction.pushUndefined());
|
||||
LabelContext.getBreak(target.env).popLoop(label);
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start));
|
||||
else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
||||
target.add(Instruction.discard());
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start));
|
||||
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
|
||||
|
||||
for (var el : caseToStatement.entrySet()) {
|
||||
var i = statementToIndex.get(el.getValue());
|
||||
if (i == null) i = endI;
|
||||
subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -100,6 +104,7 @@ public class SwitchNode extends Node {
|
||||
var val = JavaScript.parseExpression(src, i + n, 0);
|
||||
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
|
||||
n += val.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
|
||||
n++;
|
||||
@ -118,7 +123,6 @@ public class SwitchNode extends Node {
|
||||
|
||||
return ParseRes.res(null, n);
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
public static ParseRes<SwitchNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
@ -12,6 +12,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ThrowNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
value.compile(target, true);
|
||||
target.add(Instruction.throwInstr()).setLocation(loc());
|
||||
|
@ -12,7 +12,6 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
|
||||
public class TryNode extends Node {
|
||||
public final CompoundNode tryBody;
|
||||
@ -26,7 +25,11 @@ public class TryNode extends Node {
|
||||
if (catchBody != null) catchBody.resolve(target);
|
||||
if (finallyBody != null) finallyBody.resolve(target);
|
||||
}
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
tryBody.compileFunctions(target);
|
||||
if (catchBody != null) catchBody.compileFunctions(target);
|
||||
if (finallyBody != null) finallyBody.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
|
||||
int replace = target.temp();
|
||||
var endSuppl = new DeferredIntSupplier();
|
||||
@ -42,17 +45,11 @@ public class TryNode extends Node {
|
||||
catchStart = target.size() - start;
|
||||
|
||||
if (captureName != null) {
|
||||
var subtarget = target.subtarget();
|
||||
subtarget.beginScope();
|
||||
subtarget.scope.singleEntry = true;
|
||||
|
||||
var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc());
|
||||
subtarget.add(Instruction.loadError());
|
||||
subtarget.add(catchVar.index().toInit());
|
||||
catchBody.compile(subtarget, false);
|
||||
subtarget.endScope();
|
||||
|
||||
subtarget.scope.end();
|
||||
var catchVar = target.scope.defineCatch(captureName);
|
||||
target.add(Instruction.loadError()).setLocation(catchBody.loc());
|
||||
target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc());
|
||||
catchBody.compile(target, false);
|
||||
target.scope.undefineCatch();
|
||||
}
|
||||
else catchBody.compile(target, false);
|
||||
|
||||
|
@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.LabelContext;
|
||||
@ -20,6 +19,10 @@ public class WhileNode extends Node {
|
||||
@Override public void resolve(CompileResult target) {
|
||||
body.resolve(target);
|
||||
}
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
condition.compileFunctions(target);
|
||||
body.compileFunctions(target);
|
||||
}
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
int start = target.size();
|
||||
condition.compile(target, true);
|
||||
@ -29,11 +32,11 @@ public class WhileNode extends Node {
|
||||
|
||||
|
||||
LabelContext.pushLoop(target.env, loc(), label, end, start);
|
||||
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
body.compile(target, false, BreakpointType.STEP_OVER);
|
||||
|
||||
var endI = target.size();
|
||||
end.set(endI + 1);
|
||||
LabelContext.popLoop(target.env, label);
|
||||
|
||||
target.add(Instruction.jmp(start - end.getAsInt()));
|
||||
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1));
|
||||
|
@ -1,54 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.members;
|
||||
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||
|
||||
public class AssignShorthandNode extends Node {
|
||||
public final Node key;
|
||||
public final AssignTarget target;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context");
|
||||
}
|
||||
|
||||
public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public AssignTarget target() {
|
||||
return new AssignNode(loc(), target, value);
|
||||
}
|
||||
|
||||
public static ParseRes<AssignShorthandNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var var = VariableNode.parse(src, i + n);
|
||||
if (!var.isSuccess()) return var.chainError();
|
||||
n += var.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n);
|
||||
}
|
||||
}
|
@ -9,13 +9,19 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class FieldMemberNode extends Node {
|
||||
public class FieldMemberNode implements Member {
|
||||
public final Location loc;
|
||||
public final Node key;
|
||||
public final Node value;
|
||||
|
||||
@Override public Location loc() { return loc; }
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
key.compileFunctions(target);
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
key.compile(target, true);
|
||||
@ -27,12 +33,12 @@ public class FieldMemberNode extends Node {
|
||||
}
|
||||
|
||||
public FieldMemberNode(Location loc, Node key, Node value) {
|
||||
super(loc);
|
||||
this.loc = loc;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseObject(Source src, int i) {
|
||||
public static ParseRes<FieldMemberNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
@ -50,40 +56,4 @@ public class FieldMemberNode extends Node {
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseShorthand(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var var = VariableNode.parse(src, i + n);
|
||||
if (!var.isSuccess()) return var.chainError();
|
||||
n += var.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<FieldMemberNode> parseClass(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var name = ObjectNode.parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError();
|
||||
n += name.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) {
|
||||
var end = JavaScript.parseStatement(src, i + n);
|
||||
if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer");
|
||||
n += end.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, null), n);
|
||||
}
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package me.topchetoeu.jscript.compilation.members;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
|
||||
public interface Member {
|
||||
Location loc();
|
||||
|
||||
void compileFunctions(CompileResult target);
|
||||
void compile(CompileResult target, boolean pollute);
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.members;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.Parameters;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class MethodMemberNode extends FunctionNode {
|
||||
public final Node key;
|
||||
|
||||
@Override public String name() {
|
||||
if (key instanceof StringNode str) return str.value;
|
||||
else return null;
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
key.compile(target, true);
|
||||
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||
|
||||
target.add(Instruction.defField());
|
||||
}
|
||||
|
||||
public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) {
|
||||
super(loc, end, params, body);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public static ParseRes<MethodMemberNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var name = ObjectNode.parsePropName(src, i + n);
|
||||
if (!name.isSuccess()) return name.chainError();
|
||||
n += name.n;
|
||||
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||
n += params.n;
|
||||
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor.");
|
||||
n += body.n;
|
||||
|
||||
var end = src.loc(i + n - 1);
|
||||
|
||||
return ParseRes.res(new MethodMemberNode(
|
||||
loc, end, name.result, params.result, body.result
|
||||
), n);
|
||||
}
|
||||
}
|
@ -11,15 +11,15 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||
import me.topchetoeu.jscript.compilation.FunctionNode;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.Parameters;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class PropertyMemberNode extends FunctionNode {
|
||||
public final class PropertyMemberNode extends FunctionNode implements Member {
|
||||
public final Node key;
|
||||
public final Pattern argument;
|
||||
public final VariableNode argument;
|
||||
|
||||
@Override public String name() {
|
||||
if (key instanceof StringNode str) {
|
||||
@ -32,18 +32,21 @@ public class PropertyMemberNode extends FunctionNode {
|
||||
public boolean isGetter() { return argument == null; }
|
||||
public boolean isSetter() { return argument != null; }
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
key.compileFunctions(target);
|
||||
target.addChild(this, compileBody(target, null));
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
key.compile(target, true);
|
||||
|
||||
var id = target.addChild(compileBody(target, name, null));
|
||||
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||
|
||||
target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
|
||||
target.add(Instruction.defProp(isSetter()));
|
||||
}
|
||||
|
||||
public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) {
|
||||
super(loc, end, argument == null ? new Parameters(Arrays.asList()) : new Parameters(Arrays.asList(argument)), body);
|
||||
public PropertyMemberNode(Location loc, Location end, Node key, VariableNode argument, CompoundNode body) {
|
||||
super(loc, end, argument == null ? Arrays.asList() : Arrays.asList(argument), body);
|
||||
this.key = key;
|
||||
this.argument = argument;
|
||||
}
|
||||
@ -61,11 +64,10 @@ public class PropertyMemberNode extends FunctionNode {
|
||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||
n += name.n;
|
||||
|
||||
var params = Parameters.parseParameters(src, i + n);
|
||||
var params = JavaScript.parseParameters(src, i + n);
|
||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||
if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
||||
if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
||||
if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments");
|
||||
if (access.result.equals("get") && params.result.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
||||
if (access.result.equals("set") && params.result.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
||||
n += params.n;
|
||||
|
||||
var body = CompoundNode.parse(src, i + n);
|
||||
@ -75,7 +77,7 @@ public class PropertyMemberNode extends FunctionNode {
|
||||
var end = src.loc(i + n - 1);
|
||||
|
||||
return ParseRes.res(new PropertyMemberNode(
|
||||
loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result
|
||||
loc, end, name.result, access.result.equals("get") ? null : params.result.get(0), body.result
|
||||
), n);
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
|
||||
public class AssignPattern implements Pattern {
|
||||
public final Location loc;
|
||||
public final AssignTarget assignable;
|
||||
public final Node value;
|
||||
|
||||
@Override public Location loc() { return loc; }
|
||||
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
p.destructDeclResolve(target);
|
||||
}
|
||||
|
||||
private void common(CompileResult target) {
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.pushUndefined());
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
var start = target.temp();
|
||||
target.add(Instruction.discard());
|
||||
|
||||
value.compile(target, true);
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
|
||||
if (lateInitializer) {
|
||||
if (assignable instanceof Pattern p) p.declare(target, decl, lateInitializer);
|
||||
else throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
}
|
||||
else throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration");
|
||||
}
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
common(target);
|
||||
p.destruct(target, decl, shouldDeclare);
|
||||
}
|
||||
|
||||
@Override public void beforeAssign(CompileResult target) {
|
||||
assignable.beforeAssign(target);
|
||||
}
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
common(target);
|
||||
assignable.afterAssign(target, false);
|
||||
}
|
||||
|
||||
public AssignPattern(Location loc, Pattern assignable, Node value) {
|
||||
this.loc = loc;
|
||||
this.assignable = assignable;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ParseRes<AssignPattern> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var pattern = Pattern.parse(src, i + n, false);
|
||||
if (!pattern.isSuccess()) return pattern.chainError();
|
||||
n += pattern.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n, "=")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value");
|
||||
n += value.n;
|
||||
|
||||
return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
|
||||
public class Binding implements Pattern {
|
||||
public final Location loc;
|
||||
public final DeclarationType type;
|
||||
public final AssignTarget assignable;
|
||||
|
||||
@Override public Location loc() { return loc; }
|
||||
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
if (type != null && !type.strict) {
|
||||
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
p.destructDeclResolve(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
p.destruct(target, decl, shouldDeclare);
|
||||
}
|
||||
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
|
||||
if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context");
|
||||
p.declare(target, decl, lateInitializer);
|
||||
}
|
||||
|
||||
public void resolve(CompileResult target) {
|
||||
if (type != null) destructDeclResolve(target);
|
||||
}
|
||||
|
||||
public void declare(CompileResult target, boolean hasInit) {
|
||||
if (type != null) destructVar(target, type, hasInit);
|
||||
}
|
||||
public void declareLateInit(CompileResult target) {
|
||||
if (type != null) declare(target, type, true);
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
assignable.assign(target, pollute);
|
||||
}
|
||||
|
||||
public Binding(Location loc, DeclarationType type, AssignTarget assignable) {
|
||||
this.loc = loc;
|
||||
this.type = type;
|
||||
this.assignable = assignable;
|
||||
}
|
||||
|
||||
public static ParseRes<Binding> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
var declType = JavaScript.parseDeclarationType(src, i + n);
|
||||
if (!declType.isSuccess()) {
|
||||
var res = JavaScript.parseExpression(src, i + n, 13);
|
||||
if (res.isSuccess() && res.result instanceof AssignTargetLike target) {
|
||||
n += res.n;
|
||||
return ParseRes.res(new Binding(loc, null, target.toAssignTarget()), n);
|
||||
}
|
||||
else return ParseRes.failed();
|
||||
}
|
||||
else {
|
||||
n += declType.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var res = Pattern.parse(src, i + n, false);
|
||||
if (!res.isSuccess()) return ParseRes.failed();
|
||||
n += res.n;
|
||||
|
||||
return ParseRes.res(new Binding(loc, declType.result, res.result), n);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||
|
||||
public class ObjectPattern extends Node implements Pattern {
|
||||
public static final class Member {
|
||||
public final Node key;
|
||||
public final AssignTarget consumable;
|
||||
|
||||
public Member(Node key, AssignTarget consumer) {
|
||||
this.key = key;
|
||||
this.consumable = consumer;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Member> members;
|
||||
|
||||
public void compile(CompileResult target, Consumer<AssignTarget> consumer, boolean pollute) {
|
||||
for (var el : members) {
|
||||
target.add(Instruction.dup());
|
||||
IndexNode.indexLoad(target, el.key, true);
|
||||
consumer.accept(el.consumable);
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
for (var t : members) {
|
||||
if (t.consumable instanceof Pattern p) p.destructDeclResolve(target);
|
||||
else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
compile(target, t -> {
|
||||
if (t instanceof Pattern p) p.destruct(target, decl, shouldDeclare);
|
||||
else throw new SyntaxException(t.loc(), "Unexpected non-pattern in destruct context");
|
||||
}, false);
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
compile(target, t -> t.assign(target, false), pollute);
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
|
||||
if (lateInitializer) {
|
||||
for (var t : members) {
|
||||
if (t.consumable instanceof Pattern p) p.declare(target, decl, lateInitializer);
|
||||
else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context");
|
||||
}
|
||||
}
|
||||
else throw new SyntaxException(loc(), "Object pattern must be initialized");
|
||||
}
|
||||
|
||||
public ObjectPattern(Location loc, List<Member> members) {
|
||||
super(loc);
|
||||
this.members = members;
|
||||
}
|
||||
|
||||
private static ParseRes<Member> parseShorthand(Source src, int i) {
|
||||
ParseRes<Pattern> res = ParseRes.first(src, i,
|
||||
AssignPattern::parse,
|
||||
VariableNode::parse
|
||||
);
|
||||
|
||||
if (res.isSuccess()) {
|
||||
if (res.result instanceof AssignPattern assign) {
|
||||
if (assign.assignable instanceof VariableNode var) {
|
||||
return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||
}
|
||||
}
|
||||
else if (res.result instanceof VariableNode var) {
|
||||
return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||
}
|
||||
}
|
||||
|
||||
return res.chainError();
|
||||
}
|
||||
private static ParseRes<Member> parseKeyed(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
var key = ObjectNode.parsePropName(src, i + n);
|
||||
if (!key.isSuccess()) return key.chainError();
|
||||
n += key.n;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (!src.is(i + n , ":")) return ParseRes.failed();
|
||||
n++;
|
||||
|
||||
ParseRes<Pattern> res = Pattern.parse(src, i + n, true);
|
||||
if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon");
|
||||
n += res.n;
|
||||
|
||||
return ParseRes.res(new Member(key.result, res.result), n);
|
||||
}
|
||||
|
||||
public static ParseRes<ObjectPattern> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var members = new LinkedList<Member>();
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ParseRes<Member> prop = ParseRes.first(src, i + n,
|
||||
ObjectPattern::parseKeyed,
|
||||
ObjectPattern::parseShorthand
|
||||
);
|
||||
|
||||
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern");
|
||||
n += prop.n;
|
||||
|
||||
members.add(prop.result);
|
||||
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
if (src.is(i + n, ",")) {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||
}
|
||||
|
||||
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.patterns;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
|
||||
/**
|
||||
* Represents all nodes that can be a destructors (note that all destructors are assign targets, too)
|
||||
*/
|
||||
public interface Pattern extends AssignTarget {
|
||||
Location loc();
|
||||
|
||||
/**
|
||||
* Called when the destructor has to declare
|
||||
* @param target
|
||||
*/
|
||||
void destructDeclResolve(CompileResult target);
|
||||
|
||||
/**
|
||||
* Called when a declaration-like is being destructed
|
||||
* @param decl The variable type the destructor must declare, if it is a named pne
|
||||
*/
|
||||
void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare);
|
||||
|
||||
/**
|
||||
* Run when destructing a declaration without an initializer
|
||||
*/
|
||||
void declare(CompileResult target, DeclarationType decl, boolean lateInitializer);
|
||||
|
||||
public default void destructArg(CompileResult target, DeclarationType decl) {
|
||||
destruct(target, decl, false);
|
||||
}
|
||||
public default void destructVar(CompileResult target, DeclarationType decl, boolean hasInitializer) {
|
||||
if (hasInitializer) {
|
||||
if (decl == null || !decl.strict) destruct(target, null, true);
|
||||
else destruct(target, decl, true);
|
||||
}
|
||||
else {
|
||||
if (decl == null || !decl.strict) declare(target, null, false);
|
||||
else declare(target, decl, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseRes<Pattern> parse(Source src, int i, boolean withDefault) {
|
||||
return withDefault ?
|
||||
ParseRes.first(src, i,
|
||||
AssignPattern::parse,
|
||||
ObjectPattern::parse,
|
||||
VariableNode::parse
|
||||
) :
|
||||
ParseRes.first(src, i,
|
||||
ObjectPattern::parse,
|
||||
VariableNode::parse
|
||||
);
|
||||
}
|
||||
}
|
@ -1,98 +1,161 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public class FunctionScope extends Scope {
|
||||
public final class FunctionScope {
|
||||
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS);
|
||||
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.locals);
|
||||
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
|
||||
|
||||
private final HashMap<String, Variable> specialVarMap = new HashMap<>();
|
||||
private final HashMap<String, Variable> functionVarMap = new HashMap<>();
|
||||
private final HashMap<String, Variable> localsMap = new HashMap<>();
|
||||
private final HashMap<String, Variable> capturesMap = new HashMap<>();
|
||||
private final HashSet<String> blacklistNames = new HashSet<>();
|
||||
private final ArrayList<Variable> catchesMap = new ArrayList<>();
|
||||
|
||||
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
|
||||
private final HashMap<Variable, Variable> parentToChild = new HashMap<>();
|
||||
|
||||
private final Scope captureParent;
|
||||
public final FunctionScope parent;
|
||||
public final boolean passthrough;
|
||||
|
||||
public final boolean passtrough;
|
||||
|
||||
@Override public boolean hasNonStrict(String name) {
|
||||
if (functionVarMap.containsKey(name)) return true;
|
||||
if (blacklistNames.contains(name)) return true;
|
||||
|
||||
return false;
|
||||
private Variable addCaptured(Variable var, boolean captured) {
|
||||
if (captured && !this.capturables.has(var)) this.capturables.add(var);
|
||||
return var;
|
||||
}
|
||||
|
||||
@Override public Variable define(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
private Variable getCatchVar(String name) {
|
||||
for (var el : catchesMap) {
|
||||
if (el.name.equals(name)) return el;
|
||||
}
|
||||
|
||||
if (passtrough) {
|
||||
blacklistNames.add(var.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns If a variable with the same name exists, the old variable. Otherwise, the given variable
|
||||
*/
|
||||
public Variable define(Variable var) {
|
||||
if (passthrough) return null;
|
||||
else {
|
||||
functionVarMap.put(var.name, var);
|
||||
var catchVar = getCatchVar(var.name);
|
||||
if (catchVar != null) return catchVar;
|
||||
if (localsMap.containsKey(var.name)) return localsMap.get(var.name);
|
||||
if (capturesMap.containsKey(var.name)) throw new RuntimeException("HEY!");
|
||||
|
||||
localsMap.put(var.name, var);
|
||||
return locals.add(var);
|
||||
}
|
||||
}
|
||||
public Variable defineSpecial(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
|
||||
specialVarMap.put(var.name, var);
|
||||
return locals.add(var);
|
||||
/**
|
||||
* @returns A variable with the given name, or null if a global variable
|
||||
*/
|
||||
public Variable define(String name) {
|
||||
return define(new Variable(name, false));
|
||||
}
|
||||
|
||||
@Override public Variable get(String name, boolean capture) {
|
||||
var superRes = super.get(name, capture);
|
||||
if (superRes != null) return superRes;
|
||||
/**
|
||||
* Creates a catch variable and returns it
|
||||
*/
|
||||
public Variable defineCatch(String name) {
|
||||
var var = new Variable(name, false);
|
||||
this.locals.add(var);
|
||||
this.catchesMap.add(var);
|
||||
return var;
|
||||
}
|
||||
/**
|
||||
* Removes the last catch variable.
|
||||
* NOTE: the variable is still in the internal list. It just won't be findable by its name
|
||||
*/
|
||||
public void undefineCatch() {
|
||||
this.catchesMap.remove(this.catchesMap.size() - 1);
|
||||
}
|
||||
|
||||
if (specialVarMap.containsKey(name)) return addCaptured(specialVarMap.get(name), capture);
|
||||
if (functionVarMap.containsKey(name)) return addCaptured(functionVarMap.get(name), capture);
|
||||
/**
|
||||
* Gets the index supplier of the given variable name, or null if it is a global
|
||||
*
|
||||
* @param capture If true, the variable is being captured by a function
|
||||
*/
|
||||
public Variable get(String name, boolean capture) {
|
||||
var catchVar = getCatchVar(name);
|
||||
|
||||
if (catchVar != null) return addCaptured(catchVar, capture);
|
||||
if (localsMap.containsKey(name)) return addCaptured(localsMap.get(name), capture);
|
||||
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
|
||||
|
||||
if (captureParent == null) return null;
|
||||
if (parent == null) return null;
|
||||
|
||||
var parentVar = captureParent.get(name, true);
|
||||
var parentVar = parent.get(name, true);
|
||||
if (parentVar == null) return null;
|
||||
|
||||
var childVar = captures.add(parentVar.clone());
|
||||
var childVar = captures.add(parentVar.clone().setIndexSupplier(null));
|
||||
capturesMap.put(childVar.name, childVar);
|
||||
childToParent.put(childVar, parentVar);
|
||||
parentToChild.put(parentVar, childVar);
|
||||
|
||||
return childVar;
|
||||
}
|
||||
|
||||
@Override public boolean has(String name, boolean capture) {
|
||||
if (functionVarMap.containsKey(name)) return true;
|
||||
if (specialVarMap.containsKey(name)) return true;
|
||||
/**
|
||||
* If the variable given is contained in this function, just returns the variable itself.
|
||||
* However, this function is important to handle cases in which you might want to access
|
||||
* a captured variable. In such cases, this function will return a capture to the given variable.
|
||||
*
|
||||
* @param capture Whether or not to execute this capturing logic
|
||||
*/
|
||||
public Variable get(Variable var, boolean capture) {
|
||||
if (captures.has(var)) return addCaptured(var, capture);
|
||||
if (locals.has(var)) return addCaptured(var, capture);
|
||||
|
||||
if (capture) {
|
||||
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
|
||||
|
||||
if (parent == null) return null;
|
||||
|
||||
var parentVar = parent.get(var, true);
|
||||
if (parentVar == null) return null;
|
||||
|
||||
var childVar = captures.add(parentVar.clone());
|
||||
childToParent.put(childVar, parentVar);
|
||||
parentToChild.put(parentVar, childVar);
|
||||
|
||||
return childVar;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given variable name is accessible
|
||||
*
|
||||
* @param capture If true, will check beyond this function's scope
|
||||
*/
|
||||
public boolean has(String name, boolean capture) {
|
||||
if (localsMap.containsKey(name)) return true;
|
||||
// if (specialVarMap.containsKey(name)) return true;
|
||||
|
||||
if (capture) {
|
||||
if (capturesMap.containsKey(name)) return true;
|
||||
if (captureParent != null) return captureParent.has(name, true);
|
||||
if (parent != null) return parent.has(name, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override protected void onFinish() {
|
||||
captures.freeze();
|
||||
super.onFinish();
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
|
||||
@Override public int capturesCount() {
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
public int capturablesCount() {
|
||||
return capturables.size();
|
||||
}
|
||||
|
||||
public int[] getCaptureIndices() {
|
||||
var res = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var el : captures.all()) {
|
||||
for (var el : captures) {
|
||||
assert childToParent.containsKey(el);
|
||||
res[i] = childToParent.get(el).index().toCaptureIndex();
|
||||
i++;
|
||||
@ -101,17 +164,43 @@ public class FunctionScope extends Scope {
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionScope(Scope parent) {
|
||||
super();
|
||||
if (parent.finished()) throw new RuntimeException("Parent is finished");
|
||||
this.captureParent = parent;
|
||||
this.passtrough = false;
|
||||
this.singleEntry = false;
|
||||
public Iterable<Variable> capturables() {
|
||||
return capturables;
|
||||
}
|
||||
public FunctionScope(boolean passtrough) {
|
||||
super();
|
||||
this.captureParent = null;
|
||||
this.passtrough = passtrough;
|
||||
this.singleEntry = false;
|
||||
public Iterable<Variable> locals() {
|
||||
return locals;
|
||||
}
|
||||
|
||||
public String[] captureNames() {
|
||||
var res = new String[this.captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var el : this.captures) {
|
||||
res[i++] = el.name;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
public String[] localNames() {
|
||||
var res = new String[this.locals.size() + this.capturables.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var el : this.locals) {
|
||||
res[i++] = el.name;
|
||||
}
|
||||
for (var el : this.capturables) {
|
||||
res[i++] = el.name;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionScope(FunctionScope parent) {
|
||||
this.parent = parent;
|
||||
this.passthrough = false;
|
||||
}
|
||||
public FunctionScope(boolean passthrough) {
|
||||
this.parent = null;
|
||||
this.passthrough = passthrough;
|
||||
}
|
||||
}
|
||||
|
@ -1,237 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
|
||||
public class Scope {
|
||||
protected final HashMap<String, Variable> strictVarMap = new HashMap<>();
|
||||
|
||||
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset);
|
||||
protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset);
|
||||
|
||||
private boolean ended = false;
|
||||
private boolean finished = false;
|
||||
private Scope child;
|
||||
private LinkedList<Scope> children = new LinkedList<>();
|
||||
|
||||
public final Scope parent;
|
||||
|
||||
/**
|
||||
* Wether or not the scope is going to be entered multiple times.
|
||||
* If set to true, captured variables will be kept as allocations, otherwise will be converted to locals
|
||||
*/
|
||||
public boolean singleEntry = true;
|
||||
|
||||
|
||||
protected void transferCaptured(Variable var) {
|
||||
if (!singleEntry) {
|
||||
this.capturables.add(var);
|
||||
}
|
||||
else if (parent != null) {
|
||||
parent.transferCaptured(var);
|
||||
}
|
||||
else throw new IllegalStateException("Couldn't transfer captured variable");
|
||||
}
|
||||
|
||||
protected final Variable addCaptured(Variable var, boolean captured) {
|
||||
if (captured) transferCaptured(var);
|
||||
return var;
|
||||
}
|
||||
|
||||
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
||||
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if the scope is ended
|
||||
*/
|
||||
protected final void checkNotEnded() {
|
||||
if (ended) throw new IllegalStateException("Cannot define in an ended scope");
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a nameless variable for holding intermediate temporary values
|
||||
*
|
||||
* @throws RuntimeException If the scope is finalized or has an active child
|
||||
*/
|
||||
public Variable defineTemp() {
|
||||
checkNotEnded();
|
||||
return this.locals.add(new Variable("<temp>", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an ES5-style variable
|
||||
*
|
||||
* @returns The index supplier of the variable if it is a local, or null if it is a global
|
||||
* @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope
|
||||
* @throws RuntimeException If the scope is finalized or has an active child
|
||||
*/
|
||||
public Variable define(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
if (parent != null) return parent.define(var, loc);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this scope's function parent has a non-strict variable of the given name
|
||||
*/
|
||||
public boolean hasNonStrict(String name) { return false; }
|
||||
|
||||
/**
|
||||
* Defines an ES2015-style variable
|
||||
* @param readonly True if const, false if let
|
||||
* @return The index supplier of the variable
|
||||
* @throws SyntaxException If any variable with the same name exists in the current scope
|
||||
* @throws RuntimeException If the scope is finalized or has an active child
|
||||
*/
|
||||
public Variable defineStrict(Variable var, Location loc) {
|
||||
checkNotEnded();
|
||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name);
|
||||
|
||||
strictVarMap.put(var.name, var);
|
||||
return locals.add(var);
|
||||
}
|
||||
/**
|
||||
* Gets the index supplier of the given variable name, or null if it is a global
|
||||
*
|
||||
* @param capture If true, the variable is being captured by a function
|
||||
*/
|
||||
public Variable get(String name, boolean capture) {
|
||||
var res = strictVarMap.get(name);
|
||||
|
||||
if (res != null) return addCaptured(res, capture);
|
||||
if (parent != null) return parent.get(name, capture);
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Checks if the given variable name is accessible
|
||||
*
|
||||
* @param capture If true, will check beyond this function's scope
|
||||
*/
|
||||
public boolean has(String name, boolean capture) {
|
||||
if (strictVarMap.containsKey(name)) return true;
|
||||
if (parent != null) return parent.has(name, capture);
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Gets the index offset from this scope to its children
|
||||
*/
|
||||
public final int variableOffset() {
|
||||
var res = 0;
|
||||
|
||||
for (var curr = parent; curr != null; curr = curr.parent) {
|
||||
res += parent.locals.size();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
public final int capturablesOffset() {
|
||||
var res = 0;
|
||||
|
||||
for (var curr = this; curr != null; curr = curr.parent) {
|
||||
if (curr != this) res += parent.capturables.size();
|
||||
if (curr.parent == null) res += curr.localsCount();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public final Variable define(DeclarationType type, String name, Location loc) {
|
||||
if (type.strict) return defineStrict(new Variable(name, type.readonly), loc);
|
||||
else return define(new Variable(name, type.readonly), loc);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
var res = 0;
|
||||
for (var child : children) {
|
||||
var childN = child.localsCount();
|
||||
if (res < childN) res = childN;
|
||||
}
|
||||
|
||||
return res + locals.size();
|
||||
}
|
||||
public int capturesCount() { return 0; }
|
||||
public int allocCount() {
|
||||
var res = capturables.size();
|
||||
return res;
|
||||
}
|
||||
public int capturablesCount() {
|
||||
var res = capturables.size();
|
||||
for (var child : children) res += child.capturablesCount();
|
||||
return res;
|
||||
}
|
||||
|
||||
public Iterable<Variable> capturables() {
|
||||
return capturables.all();
|
||||
}
|
||||
public Iterable<Variable> locals() {
|
||||
return locals.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends this scope. This will make it possible for another child to take its place
|
||||
*/
|
||||
public boolean end() {
|
||||
if (ended) return false;
|
||||
|
||||
this.ended = true;
|
||||
|
||||
if (this.parent != null) {
|
||||
assert this.parent.child == this;
|
||||
this.parent.child = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onFinish() {
|
||||
this.locals.freeze();
|
||||
this.capturables.freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes this scope. The scope will become immutable after this call
|
||||
* @return
|
||||
*/
|
||||
public final boolean finish() {
|
||||
if (finished) return false;
|
||||
|
||||
if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished");
|
||||
this.onFinish();
|
||||
|
||||
for (var child : children) child.finish();
|
||||
|
||||
this.finished = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean ended() { return ended; }
|
||||
public final boolean finished() { return finished; }
|
||||
public final Scope child() { return child; }
|
||||
|
||||
public Scope() {
|
||||
this(null);
|
||||
}
|
||||
public Scope(Scope parent) {
|
||||
if (parent != null) {
|
||||
if (parent.ended) throw new RuntimeException("Parent is not active");
|
||||
if (parent.finished) throw new RuntimeException("Parent is finished");
|
||||
if (parent.child != null) throw new RuntimeException("Parent has an active child");
|
||||
|
||||
this.parent = parent;
|
||||
this.parent.child = this;
|
||||
this.parent.children.add(this);
|
||||
}
|
||||
else this.parent = null;
|
||||
}
|
||||
}
|
@ -4,20 +4,14 @@ import java.util.function.Supplier;
|
||||
|
||||
public final class Variable {
|
||||
private Supplier<VariableIndex> indexSupplier;
|
||||
private boolean frozen;
|
||||
|
||||
public final boolean readonly;
|
||||
public final String name;
|
||||
|
||||
public final VariableIndex index() {
|
||||
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
|
||||
return indexSupplier.get();
|
||||
}
|
||||
|
||||
public final void freeze() {
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
|
||||
this.indexSupplier = index;
|
||||
return this;
|
||||
|
@ -4,12 +4,21 @@ import me.topchetoeu.jscript.common.Instruction;
|
||||
|
||||
public final class VariableIndex {
|
||||
public static enum IndexType {
|
||||
/**
|
||||
* A simple variable that is only ever used within the function
|
||||
*/
|
||||
LOCALS,
|
||||
/**
|
||||
* A variable that has the ability to be captured by children functions
|
||||
*/
|
||||
CAPTURABLES,
|
||||
/**
|
||||
* A variable that has been captured from the parent function
|
||||
*/
|
||||
CAPTURES,
|
||||
}
|
||||
|
||||
public final VariableIndex.IndexType type;
|
||||
public final IndexType type;
|
||||
public final int index;
|
||||
|
||||
public final int toCaptureIndex() {
|
||||
@ -36,22 +45,6 @@ public final class VariableIndex {
|
||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||
}
|
||||
}
|
||||
public final Instruction toInit() {
|
||||
switch (type) {
|
||||
case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||
case CAPTURABLES: return Instruction.storeVar(index, false, true);
|
||||
case LOCALS: return Instruction.storeVar(index, false, true);
|
||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||
}
|
||||
}
|
||||
public final Instruction toUndefinedInit(boolean force) {
|
||||
switch (type) {
|
||||
case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||
case CAPTURABLES: return Instruction.varInit(index, force);
|
||||
case LOCALS: return Instruction.varInit(index, force);
|
||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public VariableIndex(VariableIndex.IndexType type, int index) {
|
||||
this.type = type;
|
||||
|
@ -1,48 +1,36 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class VariableList {
|
||||
import me.topchetoeu.jscript.compilation.scope.VariableIndex.IndexType;
|
||||
|
||||
public final class VariableList implements Iterable<Variable> {
|
||||
private final class VariableNode implements Supplier<VariableIndex> {
|
||||
public Variable var;
|
||||
public VariableNode next;
|
||||
public VariableNode prev;
|
||||
public boolean frozen;
|
||||
public int index;
|
||||
public int indexIteration = -1;
|
||||
|
||||
public VariableList list() { return VariableList.this; }
|
||||
|
||||
private int getIndex() {
|
||||
if (this.indexIteration != VariableList.this.indexIteration) {
|
||||
this.indexIteration = VariableList.this.indexIteration;
|
||||
|
||||
if (prev == null) this.index = 0;
|
||||
else this.index = prev.getIndex() + 1;
|
||||
}
|
||||
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override public VariableIndex get() {
|
||||
if (frozen) {
|
||||
if (offset == null) return new VariableIndex(indexType, index);
|
||||
else new VariableIndex(indexType, index + offset.getAsInt());
|
||||
}
|
||||
|
||||
var res = 0;
|
||||
if (offset != null) res = offset.getAsInt();
|
||||
|
||||
for (var it = prev; it != null; it = it.prev) {
|
||||
res++;
|
||||
}
|
||||
|
||||
return new VariableIndex(indexType, res);
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
if (frozen) return;
|
||||
this.frozen = true;
|
||||
this.next = null;
|
||||
this.var.freeze();
|
||||
if (prev == null) return;
|
||||
|
||||
this.index = prev.index + 1;
|
||||
this.next = null;
|
||||
|
||||
return;
|
||||
if (offset == null) return new VariableIndex(indexType, this.getIndex());
|
||||
else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex());
|
||||
}
|
||||
|
||||
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
|
||||
@ -54,32 +42,25 @@ public final class VariableList {
|
||||
|
||||
private VariableNode first, last;
|
||||
|
||||
private ArrayList<VariableNode> frozenList = null;
|
||||
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
|
||||
|
||||
private final IntSupplier offset;
|
||||
/**
|
||||
* Increased when indices need recalculation. VariableNode will check if
|
||||
* its internal indexIteration is up to date with this, and if not, will
|
||||
* recalculate its index
|
||||
*/
|
||||
private int indexIteration = 0;
|
||||
|
||||
public final VariableIndex.IndexType indexType;
|
||||
|
||||
public boolean frozen() {
|
||||
if (frozenList != null) {
|
||||
assert frozenList != null;
|
||||
assert varMap == null;
|
||||
assert first == null;
|
||||
assert last == null;
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
assert frozenList == null;
|
||||
assert varMap != null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given variable to this list. If it already exists, does nothing
|
||||
* @return val
|
||||
*/
|
||||
public Variable add(Variable val) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
if (this.varMap.containsKey(val)) return val;
|
||||
this.indexIteration++;
|
||||
|
||||
if (val.indexSupplier() instanceof VariableNode prevNode) {
|
||||
prevNode.list().remove(val);
|
||||
@ -105,14 +86,18 @@ public final class VariableList {
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the variable is not in the list, does nothing. Otherwise, removes the variable from the list
|
||||
* @return null if nothing was done, else the deleted variable (should be var)
|
||||
*/
|
||||
public Variable remove(Variable var) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
|
||||
if (var == null) return null;
|
||||
|
||||
var node = varMap.get(var);
|
||||
if (node == null) return null;
|
||||
|
||||
this.indexIteration++;
|
||||
|
||||
if (node.prev != null) {
|
||||
assert node != first;
|
||||
node.prev.next = node.next;
|
||||
@ -135,39 +120,31 @@ public final class VariableList {
|
||||
node.prev = null;
|
||||
|
||||
varMap.remove(node.var);
|
||||
node.var.setIndexSupplier(null);
|
||||
|
||||
return node.var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the list has the given variable
|
||||
*/
|
||||
public boolean has(Variable var) {
|
||||
return varMap.containsKey(var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an indexer for the given variable
|
||||
*/
|
||||
public Supplier<VariableIndex> indexer(Variable var) {
|
||||
return varMap.get(var);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
if (frozen()) return frozenList.size();
|
||||
else return varMap.size();
|
||||
return varMap.size();
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
if (frozen()) return;
|
||||
|
||||
frozenList = new ArrayList<>();
|
||||
|
||||
for (var node = first; node != null; ) {
|
||||
frozenList.add(node);
|
||||
|
||||
var tmp = node;
|
||||
node = node.next;
|
||||
tmp.freeze();
|
||||
}
|
||||
|
||||
first = last = null;
|
||||
varMap = null;
|
||||
}
|
||||
|
||||
public Iterable<Variable> all() {
|
||||
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
|
||||
else return () -> new Iterator<Variable>() {
|
||||
public Iterator<Variable> iterator() {
|
||||
return new Iterator<Variable>() {
|
||||
private VariableNode curr = first;
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
@ -183,19 +160,31 @@ public final class VariableList {
|
||||
};
|
||||
}
|
||||
|
||||
public VariableList(VariableIndex.IndexType type, IntSupplier offset) {
|
||||
/**
|
||||
* @param offset Will offset the indices by the given amount from the supplier
|
||||
*/
|
||||
public VariableList(IndexType type, IntSupplier offset) {
|
||||
this.indexType = type;
|
||||
this.offset = offset;
|
||||
}
|
||||
public VariableList(VariableIndex.IndexType type, int offset) {
|
||||
/**
|
||||
* @param offset Will offset the indices by the given amount
|
||||
*/
|
||||
public VariableList(IndexType type, int offset) {
|
||||
this.indexType = type;
|
||||
this.offset = () -> offset;
|
||||
}
|
||||
public VariableList(VariableIndex.IndexType type, VariableList prev) {
|
||||
/**
|
||||
* @param offset Will offset the indices by the size of the given list
|
||||
*/
|
||||
public VariableList(IndexType type, VariableList prev) {
|
||||
this.indexType = type;
|
||||
this.offset = prev::size;
|
||||
this.offset = () -> {
|
||||
if (prev.offset != null) return prev.offset.getAsInt() + prev.size();
|
||||
else return prev.size();
|
||||
};
|
||||
}
|
||||
public VariableList(VariableIndex.IndexType type) {
|
||||
public VariableList(IndexType type) {
|
||||
this.indexType = type;
|
||||
this.offset = null;
|
||||
}
|
||||
|
@ -7,8 +7,11 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class ArgumentsNode extends Node {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadArgs(false));
|
||||
if (pollute) target.add(Instruction.loadArgs());
|
||||
}
|
||||
|
||||
public ArgumentsNode(Location loc) {
|
||||
|
@ -15,6 +15,12 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class ArrayNode extends Node {
|
||||
public final Node[] statements;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
for (var stm : statements) {
|
||||
if (stm != null) stm.compileFunctions(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadArr(statements.length));
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.ClassNode;
|
||||
|
||||
public class ClassValueNode extends ClassNode {
|
||||
public ClassValueNode(Location loc, Location end, String name, ClassBody body) {
|
||||
super(loc, end, name, body);
|
||||
}
|
||||
|
||||
public static ParseRes<ClassValueNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
var loc = src.loc(i + n);
|
||||
|
||||
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
|
||||
n += 5;
|
||||
|
||||
var name = Parsing.parseIdentifier(src, i + n);
|
||||
n += name.n;
|
||||
|
||||
var body = parseBody(src, i + n);
|
||||
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
|
||||
n += body.n;
|
||||
|
||||
return ParseRes.res(new ClassValueNode(loc, src.loc(i + n), name.result, body.result), n);
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class GlobalThisNode extends Node {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob());
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
@ -12,77 +11,25 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.members.AssignShorthandNode;
|
||||
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
|
||||
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
|
||||
import me.topchetoeu.jscript.compilation.members.Member;
|
||||
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ObjectPattern;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class ObjectNode extends Node implements AssignTargetLike {
|
||||
public final List<Node> members;
|
||||
public class ObjectNode extends Node {
|
||||
public final List<Member> members;
|
||||
|
||||
// TODO: Implement spreading into object
|
||||
|
||||
// private void compileRestObjBuilder(CompileResult target, int srcDupN) {
|
||||
// var subtarget = target.subtarget();
|
||||
// var src = subtarget.scope.defineTemp();
|
||||
// var dst = subtarget.scope.defineTemp();
|
||||
|
||||
// target.add(Instruction.loadObj());
|
||||
// target.add(_i -> src.index().toSet(true));
|
||||
// target.add(_i -> dst.index().toSet(destructors.size() > 0));
|
||||
|
||||
// target.add(Instruction.keys(true, true));
|
||||
// var start = target.size();
|
||||
|
||||
// target.add(Instruction.dup());
|
||||
// var mid = target.temp();
|
||||
|
||||
// target.add(_i -> src.index().toGet());
|
||||
// target.add(Instruction.dup(1, 1));
|
||||
// target.add(Instruction.loadMember());
|
||||
|
||||
// target.add(_i -> dst.index().toGet());
|
||||
// target.add(Instruction.dup(1, 1));
|
||||
// target.add(Instruction.storeMember());
|
||||
|
||||
// target.add(Instruction.discard());
|
||||
// var end = target.size();
|
||||
// target.add(Instruction.jmp(start - end));
|
||||
// target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||
|
||||
// target.add(Instruction.discard());
|
||||
|
||||
// target.add(Instruction.dup(srcDupN, 1));
|
||||
|
||||
// target.scope.end();
|
||||
// }
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
for (var member : members) member.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadObj());
|
||||
for (var el : members) el.compile(target, true);
|
||||
}
|
||||
|
||||
@Override public AssignTarget toAssignTarget() {
|
||||
var newMembers = new LinkedList<ObjectPattern.Member>();
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof FieldMemberNode field) {
|
||||
if (field.value instanceof AssignTargetLike target) newMembers.add(new ObjectPattern.Member(field.key, target.toAssignTarget()));
|
||||
else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor");
|
||||
}
|
||||
else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new ObjectPattern.Member(shorthand.key, shorthand.target()));
|
||||
else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor");
|
||||
}
|
||||
|
||||
return new ObjectPattern(loc(), newMembers);
|
||||
}
|
||||
|
||||
public ObjectNode(Location loc, List<Node> map) {
|
||||
public ObjectNode(Location loc, List<Member> map) {
|
||||
super(loc);
|
||||
this.members = map;
|
||||
}
|
||||
@ -126,7 +73,7 @@ public class ObjectNode extends Node implements AssignTargetLike {
|
||||
n++;
|
||||
n += Parsing.skipEmpty(src, i + n);
|
||||
|
||||
var members = new LinkedList<Node>();
|
||||
var members = new LinkedList<Member>();
|
||||
|
||||
if (src.is(i + n, "}")) {
|
||||
n++;
|
||||
@ -134,12 +81,9 @@ public class ObjectNode extends Node implements AssignTargetLike {
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ParseRes<Node> prop = ParseRes.first(src, i + n,
|
||||
MethodMemberNode::parse,
|
||||
ParseRes<Member> prop = ParseRes.first(src, i + n,
|
||||
PropertyMemberNode::parse,
|
||||
FieldMemberNode::parseObject,
|
||||
AssignShorthandNode::parse,
|
||||
FieldMemberNode::parseShorthand
|
||||
FieldMemberNode::parse
|
||||
);
|
||||
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
|
||||
n += prop.n;
|
||||
|
@ -11,12 +11,14 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class RegexNode extends Node {
|
||||
public final String pattern, flags;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(pattern, flags));
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
|
||||
public static ParseRes<RegexNode> parse(Source src, int i) {
|
||||
var n = Parsing.skipEmpty(src, i);
|
||||
|
||||
@ -29,28 +31,35 @@ public class RegexNode extends Node {
|
||||
|
||||
var inBrackets = false;
|
||||
|
||||
while (true) {
|
||||
if (src.is(i + n, '[')) {
|
||||
n++;
|
||||
loop: while (true) {
|
||||
switch (src.at(i + n)) {
|
||||
case '[':
|
||||
inBrackets = true;
|
||||
source.append(src.at(i + n));
|
||||
continue;
|
||||
}
|
||||
else if (src.is(i + n, ']')) {
|
||||
source.append('[');
|
||||
n++;
|
||||
continue;
|
||||
case ']':
|
||||
inBrackets = false;
|
||||
source.append(src.at(i + n));
|
||||
source.append(']');
|
||||
n++;
|
||||
continue;
|
||||
case '/':
|
||||
n++;
|
||||
if (inBrackets) {
|
||||
source.append('/');
|
||||
continue;
|
||||
}
|
||||
else if (src.is(i + n, '/') && !inBrackets) {
|
||||
else break loop;
|
||||
case '\\':
|
||||
source.append('\\');
|
||||
source.append(src.at(i + n + 1));
|
||||
n += 2;
|
||||
break;
|
||||
default:
|
||||
source.append(src.at(i + n));
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
|
||||
var charRes = Parsing.parseChar(src, i + n);
|
||||
if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed");
|
||||
source.append(charRes.result);
|
||||
n++;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
@ -7,6 +7,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
|
||||
public class ThisNode extends Node {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadThis());
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
@ -11,98 +8,46 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||
|
||||
public class VariableNode extends Node implements Pattern, ChangeTarget {
|
||||
public class VariableNode extends Node implements ChangeTarget {
|
||||
public final String name;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
public String assignName() { return name; }
|
||||
|
||||
@Override public void beforeChange(CompileResult target) {
|
||||
target.add(VariableNode.toGet(target, loc(), name));
|
||||
}
|
||||
|
||||
@Override public void destructDeclResolve(CompileResult target) {
|
||||
var i = target.scope.define(new Variable(name, false), loc());
|
||||
if (i != null) target.add(_i -> i.index().toUndefinedInit(false));
|
||||
}
|
||||
|
||||
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute));
|
||||
}
|
||||
|
||||
@Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) {
|
||||
if (decl != null) {
|
||||
var i = target.scope.define(decl, name, loc());
|
||||
target.add(_i -> i.index().toUndefinedInit(decl.strict));
|
||||
}
|
||||
else target.add(_i -> {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (i == null) return Instruction.globDef(name);
|
||||
else return Instruction.nop();
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||
if (!shouldDeclare || decl == null) {
|
||||
if (shouldDeclare) target.add(VariableNode.toInit(target, loc(), name));
|
||||
else target.add(VariableNode.toInit(target, loc(), name));
|
||||
}
|
||||
else {
|
||||
if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed");
|
||||
var v = target.scope.define(decl, name, loc());
|
||||
target.add(_i -> v.index().toInit());
|
||||
}
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
target.add(toGet(target, loc(), name, true, false));
|
||||
target.add(toGet(target, loc(), name, true, false)).setLocation(loc());
|
||||
}
|
||||
|
||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
|
||||
var oldI = target.scope.get(name, false);
|
||||
public static Instruction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (oldI != null) {
|
||||
if (keep) return _i -> oldI.index().toGet();
|
||||
else return _i -> Instruction.nop();
|
||||
}
|
||||
else return _i -> {
|
||||
var newI = target.scope.get(name, false);
|
||||
|
||||
if (newI == null) return Instruction.globGet(name, forceGet);
|
||||
else if (keep) return newI.index().toGet();
|
||||
if (i != null) {
|
||||
if (keep) return i.index().toGet();
|
||||
else return Instruction.nop();
|
||||
};
|
||||
}
|
||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) {
|
||||
else return Instruction.globGet(name, forceGet);
|
||||
}
|
||||
public static Instruction toGet(CompileResult target, Location loc, String name) {
|
||||
return toGet(target, loc, name, true, false);
|
||||
}
|
||||
|
||||
public static IntFunction<Instruction> toInit(CompileResult target, Location loc, String name) {
|
||||
var oldI = target.scope.get(name, false);
|
||||
|
||||
if (oldI != null) return _i -> oldI.index().toInit();
|
||||
else return _i -> {
|
||||
public static Instruction toSet(CompileResult target, Location loc, String name, boolean keep, boolean init) {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (i == null) return Instruction.globSet(name, false, true);
|
||||
else return i.index().toInit();
|
||||
};
|
||||
}
|
||||
public static IntFunction<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep) {
|
||||
var oldI = target.scope.get(name, false);
|
||||
|
||||
if (oldI != null) return _i -> oldI.index().toSet(keep);
|
||||
else return _i -> {
|
||||
var i = target.scope.get(name, false);
|
||||
|
||||
if (i == null) return Instruction.globSet(name, keep, false);
|
||||
else return i.index().toSet(keep);
|
||||
};
|
||||
if (i != null) return i.index().toSet(keep);
|
||||
else return Instruction.globSet(name, keep, init);
|
||||
}
|
||||
|
||||
public VariableNode(Location loc, String name) {
|
||||
|
@ -8,6 +8,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class BoolNode extends Node {
|
||||
public final boolean value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
|
||||
public class NullNode extends Node {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushNull());
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class NumberNode extends Node {
|
||||
public final double value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class StringNode extends Node {
|
||||
public final String value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.pushValue(value));
|
||||
}
|
||||
|
@ -12,6 +12,11 @@ public class AssignNode extends Node implements AssignTarget {
|
||||
public final AssignTarget assignable;
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
((Node)assignable).compileFunctions(target);
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
|
||||
|
||||
|
@ -4,8 +4,6 @@ import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
import me.topchetoeu.jscript.common.json.JSONElement;
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
@ -13,104 +11,38 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||
import me.topchetoeu.jscript.compilation.Node;
|
||||
import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ArrayNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||
import me.topchetoeu.jscript.compilation.values.ThisNode;
|
||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||
|
||||
public class CallNode extends Node {
|
||||
public static boolean ATTACH_NAME = true;
|
||||
|
||||
public final Node func;
|
||||
public final Node[] args;
|
||||
public final boolean isNew;
|
||||
|
||||
private String generateName(Node func, Node index) {
|
||||
String res = "(intermediate value)";
|
||||
boolean shouldParen = false;
|
||||
|
||||
if (func instanceof ObjectNode) {
|
||||
var obj = (ObjectNode)func;
|
||||
|
||||
shouldParen = true;
|
||||
|
||||
if (obj.members.size() > 0) res = "{}";
|
||||
else res = "{(intermediate value)}";
|
||||
}
|
||||
else if (func instanceof StringNode) {
|
||||
res = JSON.stringify(JSONElement.string(((StringNode)func).value));
|
||||
}
|
||||
else if (func instanceof NumberNode) {
|
||||
res = JSON.stringify(JSONElement.number(((NumberNode)func).value));
|
||||
}
|
||||
else if (func instanceof BoolNode) {
|
||||
res = ((BoolNode)func).value ? "true" : "false";
|
||||
}
|
||||
else if (func instanceof VariableNode) {
|
||||
res = ((VariableNode)func).name;
|
||||
}
|
||||
else if (func instanceof ThisNode) {
|
||||
res = "this";
|
||||
}
|
||||
else if (func instanceof ArgumentsNode) {
|
||||
res = "arguments";
|
||||
}
|
||||
else if (func instanceof ArrayNode) {
|
||||
var els = new ArrayList<String>();
|
||||
|
||||
for (var el : ((ArrayNode)func).statements) {
|
||||
if (el != null) els.add(generateName(el, null));
|
||||
else els.add("(intermediate value)");
|
||||
}
|
||||
|
||||
res = "[" + String.join(",", els) + "]";
|
||||
}
|
||||
|
||||
if (index == null) return res;
|
||||
|
||||
if (shouldParen) res = "(" + res + ")";
|
||||
|
||||
if (index instanceof StringNode) {
|
||||
var val = ((StringNode)index).value;
|
||||
var bracket = JSON.stringify(JSONElement.string(val));
|
||||
|
||||
if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]";
|
||||
if (Parsing.parseIdentifier(new Source(val), 0).n != val.length()) return res + "[" + bracket + "]";
|
||||
|
||||
return res + "." + val;
|
||||
}
|
||||
|
||||
return res + "[" + generateName(index, null) + "]";
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
func.compileFunctions(target);
|
||||
for (var arg : args) arg.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
|
||||
if (!isNew && func instanceof IndexNode) {
|
||||
var obj = ((IndexNode)func).object;
|
||||
var index = ((IndexNode)func).index;
|
||||
String name = "";
|
||||
if (!isNew && func instanceof IndexNode indexNode) {
|
||||
var obj = indexNode.object;
|
||||
var index = indexNode.index;
|
||||
|
||||
obj.compile(target, true);
|
||||
index.compile(target, true);
|
||||
target.add(Instruction.dup());
|
||||
IndexNode.indexLoad(target, index, true);
|
||||
|
||||
for (var arg : args) arg.compile(target, true);
|
||||
|
||||
if (ATTACH_NAME) name = generateName(obj, index);
|
||||
target.add(Instruction.call(args.length, true));
|
||||
|
||||
target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type);
|
||||
target.setLocationAndDebug(loc(), type);
|
||||
}
|
||||
else {
|
||||
String name = "";
|
||||
|
||||
func.compile(target, true);
|
||||
for (var arg : args) arg.compile(target, true);
|
||||
|
||||
if (ATTACH_NAME) name = generateName(func, null);
|
||||
|
||||
if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type);
|
||||
else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type);
|
||||
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
|
||||
else target.add(Instruction.call(args.length, false)).setLocationAndDebug(loc(), type);
|
||||
}
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
@ -147,7 +79,8 @@ public class CallNode extends Node {
|
||||
prevArg = true;
|
||||
}
|
||||
else if (argRes.isError()) return argRes.chainError();
|
||||
else if (prevArg && src.is(i + n, ",")) {
|
||||
|
||||
if (prevArg && src.is(i + n, ",")) {
|
||||
prevArg = false;
|
||||
n++;
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ public class ChangeNode extends Node {
|
||||
public final Node value;
|
||||
public final Operation op;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
((Node)changable).compileFunctions(target);
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
changable.beforeChange(target);
|
||||
value.compile(target, true);
|
||||
|
@ -13,6 +13,10 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class DiscardNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
if (value != null) value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (value != null) value.compile(target, false);
|
||||
if (pollute) target.add(Instruction.pushUndefined());
|
||||
|
@ -17,9 +17,13 @@ public class IndexNode extends Node implements ChangeTarget {
|
||||
public final Node object;
|
||||
public final Node index;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
object.compileFunctions(target);
|
||||
index.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void beforeAssign(CompileResult target) {
|
||||
object.compile(target, true);
|
||||
|
||||
indexStorePushKey(target, index);
|
||||
}
|
||||
@Override public void beforeChange(CompileResult target) {
|
||||
|
@ -12,6 +12,11 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class LazyAndNode extends Node {
|
||||
public final Node first, second;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
first.compileFunctions(target);
|
||||
second.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
first.compile(target, true);
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
|
@ -13,6 +13,11 @@ import me.topchetoeu.jscript.compilation.Node;
|
||||
public class LazyOrNode extends Node {
|
||||
public final Node first, second;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
first.compileFunctions(target);
|
||||
second.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
first.compile(target, true);
|
||||
if (pollute) target.add(Instruction.dup());
|
||||
|
@ -105,13 +105,17 @@ public class OperationNode extends Node {
|
||||
public final Node[] args;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
for (var arg : args) arg.compileFunctions(target);
|
||||
}
|
||||
|
||||
@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());
|
||||
target.add(Instruction.operation(operation));
|
||||
if (!pollute) target.add(Instruction.discard());
|
||||
}
|
||||
|
||||
public OperationNode(Location loc, Operation operation, Node ...args) {
|
||||
|
@ -12,6 +12,10 @@ import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||
|
||||
public class PostfixNode extends ChangeNode {
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
((Node)changable).compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
super.compile(target, pollute);
|
||||
|
||||
|
@ -14,6 +14,10 @@ import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||
public class TypeofNode extends Node {
|
||||
public final Node value;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (value instanceof VariableNode varNode) {
|
||||
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true));
|
||||
|
@ -13,16 +13,20 @@ public class VariableAssignNode extends Node {
|
||||
public final Node value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override public void compileFunctions(CompileResult target) {
|
||||
value.compileFunctions(target);
|
||||
}
|
||||
|
||||
@Override public void compile(CompileResult target, boolean pollute) {
|
||||
if (operation != null) {
|
||||
target.add(VariableNode.toGet(target, loc(), name));
|
||||
FunctionNode.compileWithName(value, target, true, name);
|
||||
target.add(Instruction.operation(operation));
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute));
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||
}
|
||||
else {
|
||||
FunctionNode.compileWithName(value, target, true, name);
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute));
|
||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ public interface Compiler {
|
||||
|
||||
public static Compiler get(Environment ext) {
|
||||
return ext.get(KEY, (env, filename, src) -> {
|
||||
throw EngineException.ofError("No compiler attached to engine.");
|
||||
throw EngineException.ofError("No compiler attached to engine");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,9 @@ public interface EventLoop {
|
||||
}
|
||||
|
||||
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
|
||||
return pushMsg(() -> func.invoke(env, thisArg, args), micro);
|
||||
return pushMsg(() -> func.apply(env, thisArg, args), micro);
|
||||
}
|
||||
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
|
||||
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).invoke(env, thisArg, args), micro);
|
||||
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).apply(env, thisArg, args), micro);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package me.topchetoeu.jscript.runtime;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
@ -12,9 +13,15 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.IntValue;
|
||||
|
||||
public final class Frame {
|
||||
public static final Key<Frame> KEY = Key.of();
|
||||
public static final EngineException STACK_OVERFLOW;
|
||||
static {
|
||||
STACK_OVERFLOW = EngineException.ofRange("Stack overflow!");
|
||||
STACK_OVERFLOW.value.freeze();
|
||||
}
|
||||
|
||||
public static enum TryState {
|
||||
TRY,
|
||||
@ -90,16 +97,25 @@ public final class Frame {
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of one-element arrays of values. This is so that we can pass captures to other functions
|
||||
* An array of captures from the parent function
|
||||
*/
|
||||
public final Value[][] captures;
|
||||
/**
|
||||
* An array of non-capture variables
|
||||
*/
|
||||
public final Value[] locals;
|
||||
/**
|
||||
* An array of children-captured variables
|
||||
*/
|
||||
public final Value[][] capturables;
|
||||
public final Value argsVal;
|
||||
public Value self;
|
||||
public Value fakeArgs;
|
||||
|
||||
public final Value self;
|
||||
public final Value[] args;
|
||||
public final Value argsVal;
|
||||
public final Value argsLen;
|
||||
|
||||
public final boolean isNew;
|
||||
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
public final Environment env;
|
||||
@ -193,6 +209,7 @@ public final class Frame {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError e) { throw STACK_OVERFLOW; }
|
||||
catch (EngineException e) { error = e; }
|
||||
catch (RuntimeException e) {
|
||||
System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true));
|
||||
@ -268,6 +285,10 @@ public final class Frame {
|
||||
}
|
||||
}
|
||||
|
||||
if (returnValue != null) {
|
||||
dbg.onInstruction(env, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
if (error != null) {
|
||||
var caught = false;
|
||||
|
||||
@ -280,10 +301,6 @@ public final class Frame {
|
||||
dbg.onInstruction(env, this, instr, null, error, caught);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != null) {
|
||||
dbg.onInstruction(env, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -349,18 +366,21 @@ public final class Frame {
|
||||
};
|
||||
}
|
||||
|
||||
public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) {
|
||||
public Frame(Environment env, boolean isNew, Value self, Value[] args, CodeFunction func) {
|
||||
this.env = env;
|
||||
this.dbg = DebugContext.get(env);
|
||||
this.function = func;
|
||||
this.isNew = isNew;
|
||||
|
||||
this.self = thisArg;
|
||||
this.self = self;
|
||||
this.args = args;
|
||||
this.argsVal = new ArgumentsValue(this, args);
|
||||
this.argsLen = new IntValue(args.length);
|
||||
this.captures = func.captures;
|
||||
|
||||
this.locals = new Value[func.body.localsN];
|
||||
Arrays.fill(this.locals, Value.UNDEFINED);
|
||||
this.capturables = new Value[func.body.capturablesN][1];
|
||||
for (var i = 0; i < this.capturables.length; i++) this.capturables[i][0] = Value.UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import me.topchetoeu.jscript.common.Instruction;
|
||||
import me.topchetoeu.jscript.common.Operation;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||
@ -31,18 +32,9 @@ public class InstructionRunner {
|
||||
private static Value execCall(Environment env, Instruction instr, Frame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var func = frame.pop();
|
||||
var self = (boolean)instr.get(1) ? frame.pop() : Value.UNDEFINED;
|
||||
|
||||
frame.push(func.call(env, false, instr.get(1), Value.UNDEFINED, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execCallMember(Environment env, Instruction instr, Frame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var index = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs));
|
||||
frame.push(func.apply(env, self, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -51,7 +43,7 @@ public class InstructionRunner {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
frame.push(funcObj.construct(env, instr.get(1), callArgs));
|
||||
frame.push(funcObj.construct(env, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -66,7 +58,7 @@ public class InstructionRunner {
|
||||
|
||||
if (val == Value.UNDEFINED) accessor = null;
|
||||
else if (val instanceof FunctionValue func) accessor = func;
|
||||
else throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
else throw EngineException.ofType("Getter must be a function or undefined");
|
||||
|
||||
if ((boolean)instr.get(0)) obj.defineOwnMember(env, key, new PropertyMember(obj, null, accessor, true, true));
|
||||
else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true));
|
||||
@ -79,7 +71,7 @@ public class InstructionRunner {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
obj.defineOwnMember(env, key, val);
|
||||
obj.defineOwnMember(env, key, FieldMember.of(obj, val, true, true, true));
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -147,9 +139,7 @@ public class InstructionRunner {
|
||||
private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
|
||||
int i = instr.get(0);
|
||||
|
||||
var res = frame.getVar(i);
|
||||
if (res == null) throw EngineException.ofSyntax("Uninitialized variable");
|
||||
frame.push(res);
|
||||
frame.push(frame.getVar(i));
|
||||
frame.codePtr++;
|
||||
|
||||
return null;
|
||||
@ -181,23 +171,15 @@ public class InstructionRunner {
|
||||
private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
|
||||
int id = instr.get(0);
|
||||
String name = instr.get(1);
|
||||
boolean callable = instr.get(2);
|
||||
boolean constructible = instr.get(3);
|
||||
boolean captureThis = instr.get(4);
|
||||
|
||||
var captures = new Value[instr.params.length - 5][];
|
||||
var captures = new Value[instr.params.length - 2][];
|
||||
|
||||
for (var i = 5; i < instr.params.length; i++) {
|
||||
captures[i - 5] = frame.captureVar(instr.get(i));
|
||||
for (var i = 2; i < instr.params.length; i++) {
|
||||
captures[i - 2] = frame.captureVar(instr.get(i));
|
||||
}
|
||||
|
||||
var func = new CodeFunction(env, name, frame.function.body.children[id], captures);
|
||||
if (!callable) func.enableCall = false;
|
||||
if (!constructible) func.enableNew = false;
|
||||
if (captureThis) {
|
||||
func.self = frame.self;
|
||||
func.argsVal = frame.argsVal;
|
||||
}
|
||||
|
||||
frame.push(func);
|
||||
|
||||
frame.codePtr++;
|
||||
@ -243,7 +225,7 @@ public class InstructionRunner {
|
||||
frame.push(env.get(Value.REGEX_CONSTR).construct(env, instr.get(0), instr.get(1)));
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofSyntax("Regex is not supported.");
|
||||
throw EngineException.ofSyntax("Regex is not supported");
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
@ -260,7 +242,7 @@ public class InstructionRunner {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'.");
|
||||
if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'");
|
||||
if ((boolean)instr.get(0)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -269,7 +251,7 @@ public class InstructionRunner {
|
||||
var val = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'.");
|
||||
if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'");
|
||||
if ((boolean)instr.get(1)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -278,7 +260,7 @@ public class InstructionRunner {
|
||||
var val = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'.");
|
||||
if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'");
|
||||
if ((boolean)instr.get(1)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -287,7 +269,6 @@ public class InstructionRunner {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
int i = instr.get(0);
|
||||
|
||||
if (!(boolean)instr.get(2) && frame.getVar(i) == null) throw EngineException.ofSyntax("Uninitialized variable");
|
||||
frame.setVar(i, val);
|
||||
frame.codePtr++;
|
||||
|
||||
@ -337,7 +318,7 @@ public class InstructionRunner {
|
||||
var key = frame.pop();
|
||||
var val = frame.pop();
|
||||
|
||||
if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'.");
|
||||
if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'");
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
@ -445,7 +426,7 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) {
|
||||
private static Value execGlobDef(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
|
||||
if (!Value.global(env).hasMember(env, name, false)) {
|
||||
@ -455,7 +436,7 @@ public class InstructionRunner {
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) {
|
||||
private static Value execGlobGet(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
if ((boolean)instr.get(1)) {
|
||||
frame.push(Value.global(env).getMember(env, name));
|
||||
@ -470,7 +451,7 @@ public class InstructionRunner {
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) {
|
||||
private static Value execGlobSet(Environment env, Instruction instr, Frame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
var keep = (boolean)instr.get(1);
|
||||
var define = (boolean)instr.get(2);
|
||||
@ -487,19 +468,20 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) {
|
||||
if ((boolean)instr.get(0) || frame.fakeArgs == null) frame.push(frame.argsVal);
|
||||
else frame.push(frame.fakeArgs);
|
||||
private static Value execLoadArg(Environment env, Instruction instr, Frame frame) {
|
||||
int i = instr.get(0);
|
||||
if (i >= frame.args.length) frame.push(Value.UNDEFINED);
|
||||
else frame.push(frame.args[i]);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadRestArgs(Environment env, Instruction instr, Frame frame) {
|
||||
int offset = instr.get(0);
|
||||
var res = new ArrayValue();
|
||||
|
||||
if (offset < frame.args.length) res.copyFrom(frame.args, instr.get(0), 0, frame.args.length - offset);
|
||||
|
||||
frame.push(res);
|
||||
private static Value execLoadArgsN(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(frame.argsLen);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) {
|
||||
frame.push(frame.argsVal);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
@ -509,6 +491,7 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
private static Value execLoadThis(Environment env, Instruction instr, Frame frame) {
|
||||
if (frame.self == null) throw EngineException.ofError("Super constructor must be called before 'this' is accessed");
|
||||
frame.push(frame.self);
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
@ -519,25 +502,6 @@ public class InstructionRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Value execVarInit(Environment env, Instruction instr, Frame frame) {
|
||||
if ((boolean)instr.get(1) || frame.getVar(instr.get(0)) == null) {
|
||||
frame.setVar(instr.get(0), Value.UNDEFINED);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execVarFree(Environment env, Instruction instr, Frame frame) {
|
||||
frame.locals[(int)instr.get(0)] = null;
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
private static Value execCapFree(Environment env, Instruction instr, Frame frame) {
|
||||
frame.capturables[(int)instr.get(0) - frame.locals.length] = new Value[1];
|
||||
frame.codePtr++;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Value exec(Environment env, Instruction instr, Frame frame) {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(env, instr, frame);
|
||||
@ -546,7 +510,6 @@ public class InstructionRunner {
|
||||
case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
|
||||
case CALL: return execCall(env, instr, frame);
|
||||
case CALL_NEW: return execCallNew(env, instr, frame);
|
||||
case CALL_MEMBER: return execCallMember(env, instr, frame);
|
||||
case TRY_START: return execTryStart(env, instr, frame);
|
||||
case TRY_END: return execTryEnd(env, instr, frame);
|
||||
|
||||
@ -567,12 +530,14 @@ public class InstructionRunner {
|
||||
case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
|
||||
case LOAD_GLOB: return execLoadGlob(env, instr, frame);
|
||||
case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame);
|
||||
case LOAD_ARGS: return execLoadArgs(env, instr, frame);
|
||||
case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame);
|
||||
case LOAD_CALLEE: return execLoadCallee(env, instr, frame);
|
||||
case LOAD_THIS: return execLoadThis(env, instr, frame);
|
||||
case LOAD_ERROR: return execLoadError(env, instr, frame);
|
||||
|
||||
case LOAD_THIS: return execLoadThis(env, instr, frame);
|
||||
case LOAD_ARG: return execLoadArg(env, instr, frame);
|
||||
case LOAD_ARGS: return execLoadArgs(env, instr, frame);
|
||||
case LOAD_ARGS_N: return execLoadArgsN(env, instr, frame);
|
||||
case LOAD_CALLED: return execLoadCallee(env, instr, frame);
|
||||
|
||||
case DISCARD: return execDiscard(env, instr, frame);
|
||||
case STORE_MEMBER: return execStoreMember(env, instr, frame);
|
||||
case STORE_MEMBER_STR: return execStoreMemberStr(env, instr, frame);
|
||||
@ -591,15 +556,11 @@ public class InstructionRunner {
|
||||
|
||||
case OPERATION: return execOperation(env, instr, frame);
|
||||
|
||||
case GLOB_DEF: return exexGlobDef(env, instr, frame);
|
||||
case GLOB_GET: return exexGlobGet(env, instr, frame);
|
||||
case GLOB_SET: return exexGlobSet(env, instr, frame);
|
||||
case GLOB_DEF: return execGlobDef(env, instr, frame);
|
||||
case GLOB_GET: return execGlobGet(env, instr, frame);
|
||||
case GLOB_SET: return execGlobSet(env, instr, frame);
|
||||
|
||||
case VAR_INIT: return execVarInit(env, instr, frame);
|
||||
case VAR_FREE: return execVarFree(env, instr, frame);
|
||||
case CAP_FREE: return execCapFree(env, instr, frame);
|
||||
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ import me.topchetoeu.jscript.common.Metadata;
|
||||
import me.topchetoeu.jscript.common.Reading;
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
@ -212,12 +214,12 @@ public class SimpleRepl {
|
||||
|
||||
res.defineOwnMember(env, "setCallable", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
func.enableCall = args.get(1).toBoolean();
|
||||
func.enableApply = args.get(1).toBoolean();
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
func.enableNew = args.get(1).toBoolean();
|
||||
func.enableConstruct = args.get(1).toBoolean();
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
res.defineOwnMember(env, "invokeType", new NativeFunction(args -> {
|
||||
@ -229,16 +231,14 @@ public class SimpleRepl {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var self = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
var name = args.get(3).toString(env);
|
||||
|
||||
return func.invoke(env, name, self, funcArgs.toArray());
|
||||
return func.apply(env, self, funcArgs.toArray());
|
||||
}));
|
||||
res.defineOwnMember(env, "construct", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var funcArgs = (ArrayValue)args.get(1);
|
||||
var name = args.get(2).toString(env);
|
||||
|
||||
return func.construct(env, name, funcArgs.toArray());
|
||||
return func.construct(env, funcArgs.toArray());
|
||||
}));
|
||||
|
||||
return res;
|
||||
@ -254,21 +254,17 @@ public class SimpleRepl {
|
||||
res.defineOwnMember(env, "parse", new NativeFunction(args -> {
|
||||
return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env)));
|
||||
}));
|
||||
res.defineOwnMember(env, "invokeType", new NativeFunction(args -> {
|
||||
if (((ArgumentsValue)args.get(0)).frame.isNew) return StringValue.of("new");
|
||||
else return StringValue.of("call");
|
||||
}));
|
||||
res.defineOwnMember(env, "invoke", new NativeFunction(args -> {
|
||||
var func = (FunctionValue)args.get(0);
|
||||
var self = args.get(1);
|
||||
var funcArgs = (ArrayValue)args.get(2);
|
||||
|
||||
return func.invoke(env, self, funcArgs.toArray());
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void setProto(Environment env, Environment target, Key<ObjectValue> key, ObjectValue repo, String name) {
|
||||
var val = repo.getMember(env, name);
|
||||
if (val instanceof ObjectValue obj) {
|
||||
target.add(key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectValue primordials(Environment env) {
|
||||
var res = new ObjectValue();
|
||||
res.setPrototype(null, null);
|
||||
@ -282,45 +278,20 @@ public class SimpleRepl {
|
||||
|
||||
int[] i = new int[1];
|
||||
|
||||
res.defineOwnMember(env, "setGlobalPrototype", new NativeFunction(args -> {
|
||||
var type = args.get(0).toString(env);
|
||||
var obj = (ObjectValue)args.get(1);
|
||||
res.defineOwnMember(env, "setGlobalPrototypes", new NativeFunction(args -> {
|
||||
var obj = (ObjectValue)args.get(0);
|
||||
|
||||
switch (type) {
|
||||
case "object":
|
||||
args.env.add(Value.OBJECT_PROTO, obj);
|
||||
break;
|
||||
case "function":
|
||||
args.env.add(Value.FUNCTION_PROTO, obj);
|
||||
break;
|
||||
case "array":
|
||||
args.env.add(Value.ARRAY_PROTO, obj);
|
||||
break;
|
||||
case "boolean":
|
||||
args.env.add(Value.BOOL_PROTO, obj);
|
||||
break;
|
||||
case "number":
|
||||
args.env.add(Value.NUMBER_PROTO, obj);
|
||||
break;
|
||||
case "string":
|
||||
args.env.add(Value.STRING_PROTO, obj);
|
||||
break;
|
||||
case "symbol":
|
||||
args.env.add(Value.SYMBOL_PROTO, obj);
|
||||
break;
|
||||
case "error":
|
||||
args.env.add(Value.ERROR_PROTO, obj);
|
||||
break;
|
||||
case "syntax":
|
||||
args.env.add(Value.SYNTAX_ERR_PROTO, obj);
|
||||
break;
|
||||
case "type":
|
||||
args.env.add(Value.TYPE_ERR_PROTO, obj);
|
||||
break;
|
||||
case "range":
|
||||
args.env.add(Value.RANGE_ERR_PROTO, obj);
|
||||
break;
|
||||
}
|
||||
setProto(args.env, env, Value.OBJECT_PROTO, obj, "object");
|
||||
setProto(args.env, env, Value.FUNCTION_PROTO, obj, "function");
|
||||
setProto(args.env, env, Value.ARRAY_PROTO, obj, "array");
|
||||
setProto(args.env, env, Value.BOOL_PROTO, obj, "boolean");
|
||||
setProto(args.env, env, Value.NUMBER_PROTO, obj, "number");
|
||||
setProto(args.env, env, Value.STRING_PROTO, obj, "string");
|
||||
setProto(args.env, env, Value.SYMBOL_PROTO, obj, "symbol");
|
||||
setProto(args.env, env, Value.ERROR_PROTO, obj, "error");
|
||||
setProto(args.env, env, Value.SYNTAX_ERR_PROTO, obj, "syntax");
|
||||
setProto(args.env, env, Value.TYPE_ERR_PROTO, obj, "type");
|
||||
setProto(args.env, env, Value.RANGE_ERR_PROTO, obj, "range");
|
||||
|
||||
return Value.UNDEFINED;
|
||||
}));
|
||||
@ -343,6 +314,7 @@ public class SimpleRepl {
|
||||
environment.add(EventLoop.KEY, engine);
|
||||
environment.add(DebugContext.KEY, new DebugContext());
|
||||
environment.add(Compiler.KEY, Compiler.DEFAULT);
|
||||
// environment.add(CompileResult.DEBUG_LOG);
|
||||
|
||||
var glob = Value.global(environment);
|
||||
|
||||
@ -362,7 +334,7 @@ public class SimpleRepl {
|
||||
glob.defineOwnMember(null, "measure", new NativeFunction("measure", args -> {
|
||||
var start = System.nanoTime();
|
||||
|
||||
((FunctionValue)args.get(0)).invoke(args.env, Value.UNDEFINED);
|
||||
((FunctionValue)args.get(0)).apply(args.env, Value.UNDEFINED);
|
||||
|
||||
System.out.println(String.format("Finished in %sns", System.nanoTime() - start));
|
||||
|
||||
|
@ -14,12 +14,12 @@ public interface Member {
|
||||
public boolean enumerable;
|
||||
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
if (getter != null) return getter.call(env, false, "", self);
|
||||
if (getter != null) return getter.apply(env, self);
|
||||
else return Value.UNDEFINED;
|
||||
}
|
||||
@Override public boolean set(Environment env, Value val, Value self) {
|
||||
if (setter == null) return false;
|
||||
setter.call(env, false, "", self, val);
|
||||
setter.apply(env, self, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3,27 +3,25 @@ package me.topchetoeu.jscript.runtime.values;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.SyntaxException;
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.environment.Key;
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
import me.topchetoeu.jscript.common.json.JSONElement;
|
||||
import me.topchetoeu.jscript.runtime.EventLoop;
|
||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
@ -84,35 +82,25 @@ public abstract class Value {
|
||||
return this == NumberValue.NAN || this instanceof NumberValue num && Double.isNaN(num.getDouble());
|
||||
}
|
||||
|
||||
public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) {
|
||||
if (name == null || name.equals("")) name = "(intermediate value)";
|
||||
|
||||
if (isNew) throw EngineException.ofType(name + " is not a constructor");
|
||||
else throw EngineException.ofType(name + " is not a function");
|
||||
public Value apply(Environment env, Value self, Value ...args) {
|
||||
throw EngineException.ofType("Value is not a function");
|
||||
}
|
||||
public Value construct(Environment env, Value self, Value ...args) {
|
||||
throw EngineException.ofType("Value is not a constructor");
|
||||
}
|
||||
|
||||
public final Value invoke(Environment env, String name, Value self, Value ...args) {
|
||||
return call(env, false, name, self, args);
|
||||
}
|
||||
public final Value construct(Environment env, String name, Value ...args) {
|
||||
public final Value construct(Environment env, Value ...args) {
|
||||
var res = new ObjectValue();
|
||||
var proto = getMember(env, StringValue.of("prototype"));
|
||||
|
||||
if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto);
|
||||
else res.setPrototype(env, null);
|
||||
|
||||
var ret = this.call(env, true, name, res, args);
|
||||
var ret = this.construct(env, res, args);
|
||||
|
||||
if (!ret.isPrimitive()) return ret;
|
||||
return res;
|
||||
if (ret == Value.UNDEFINED || ret.isPrimitive()) return res;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public final Value invoke(Environment env, Value self, Value ...args) {
|
||||
return invoke(env, "", self, args);
|
||||
}
|
||||
public final Value construct(Environment env, Value ...args) {
|
||||
return construct(env, "", args);
|
||||
}
|
||||
|
||||
public abstract Value toPrimitive(Environment env);
|
||||
public abstract NumberValue toNumber(Environment env);
|
||||
@ -120,7 +108,7 @@ public abstract class Value {
|
||||
public abstract boolean toBoolean();
|
||||
|
||||
public final boolean isInstanceOf(Environment env, Value proto) {
|
||||
for (var val = getPrototype(env); val != null; val = getPrototype(env)) {
|
||||
for (var val = getPrototype(env); val != null; val = val.getPrototype(env)) {
|
||||
if (val.equals(proto)) return true;
|
||||
}
|
||||
|
||||
@ -392,7 +380,7 @@ public abstract class Value {
|
||||
private void loadNext() {
|
||||
if (supplier == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = supplier.invoke(env, Value.UNDEFINED);
|
||||
var curr = supplier.apply(env, Value.UNDEFINED);
|
||||
|
||||
if (curr == null) { supplier = null; value = null; }
|
||||
if (curr.getMember(env, StringValue.of("done")).toBoolean()) { supplier = null; value = null; }
|
||||
@ -420,114 +408,22 @@ public abstract class Value {
|
||||
|
||||
public void callWith(Environment env, Iterable<? extends Value> it) {
|
||||
for (var el : it) {
|
||||
this.invoke(env, Value.UNDEFINED, el);
|
||||
this.apply(env, Value.UNDEFINED, el);
|
||||
}
|
||||
}
|
||||
public void callWithAsync(Environment env, Iterable<? extends Value> it, boolean async) {
|
||||
for (var el : it) {
|
||||
env.get(EventLoop.KEY).pushMsg(() -> this.invoke(env, Value.UNDEFINED, el), true);
|
||||
env.get(EventLoop.KEY).pushMsg(() -> this.apply(env, Value.UNDEFINED, el), true);
|
||||
}
|
||||
}
|
||||
|
||||
private final String toReadable(Environment env, HashSet<Object> passed, int tab) {
|
||||
if (passed.contains(this)) return "[circular]";
|
||||
|
||||
if (this instanceof ObjectValue obj) {
|
||||
var res = new StringBuilder();
|
||||
var dbg = DebugContext.get(env);
|
||||
var printed = true;
|
||||
var keys = this.getMembers(env, true, false);
|
||||
|
||||
if (this instanceof FunctionValue func) {
|
||||
res.append(this.toString());
|
||||
var loc = dbg.getMapOrEmpty(func).start();
|
||||
|
||||
if (loc != null) res.append(" @ " + loc);
|
||||
|
||||
if (
|
||||
func.prototype instanceof ObjectValue objProto &&
|
||||
objProto.getMember(env, "constructor") == func &&
|
||||
objProto.getOwnMembers(env, true).size() + objProto.getOwnSymbolMembers(env, true).size() == 1
|
||||
) { keys.remove("constructor"); }
|
||||
}
|
||||
else if (this instanceof ArrayValue) {
|
||||
res.append("[");
|
||||
var arr = (ArrayValue)this;
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (i != 0) res.append(", ");
|
||||
else res.append(" ");
|
||||
|
||||
if (arr.hasMember(env, i, true)) {
|
||||
res.append(arr.getMember(env, i).toReadable(env, passed, tab));
|
||||
keys.remove(i + "");
|
||||
}
|
||||
else res.append("<empty>");
|
||||
}
|
||||
|
||||
res.append(" ] ");
|
||||
}
|
||||
else printed = false;
|
||||
|
||||
|
||||
passed.add(this);
|
||||
|
||||
if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) {
|
||||
if (!printed) res.append("{}");
|
||||
}
|
||||
else if (!printed) {
|
||||
if (tab > 3) return "{...}";
|
||||
res.append("{\n");
|
||||
|
||||
for (var entry : obj.getOwnSymbolMembers(env, true)) {
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append("[" + entry.value + "]" + ": ");
|
||||
|
||||
var member = obj.getOwnMember(env, entry);
|
||||
if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else if (member instanceof PropertyMember prop) {
|
||||
if (prop.getter == null && prop.setter == null) res.append("[No accessors]");
|
||||
else if (prop.getter == null) res.append("[Setter]");
|
||||
else if (prop.setter == null) res.append("[Getter]");
|
||||
else res.append("[Getter/Setter]");
|
||||
}
|
||||
else res.append("[???]");
|
||||
|
||||
res.append(",\n");
|
||||
}
|
||||
for (var entry : obj.getOwnMembers(env, true)) {
|
||||
for (int i = 0; i < tab + 1; i++) res.append(" ");
|
||||
res.append(entry + ": ");
|
||||
|
||||
var member = obj.getOwnMember(env, entry);
|
||||
if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1));
|
||||
else if (member instanceof PropertyMember prop) {
|
||||
if (prop.getter == null && prop.setter == null) res.append("[No accessors]");
|
||||
else if (prop.getter == null) res.append("[Setter]");
|
||||
else if (prop.setter == null) res.append("[Getter]");
|
||||
else res.append("[Getter/Setter]");
|
||||
}
|
||||
else res.append("[???]");
|
||||
|
||||
res.append(",\n");
|
||||
}
|
||||
|
||||
for (int i = 0; i < tab; i++) res.append(" ");
|
||||
res.append("}");
|
||||
}
|
||||
|
||||
passed.remove(this);
|
||||
return res.toString();
|
||||
}
|
||||
else if (this instanceof VoidValue) return ((VoidValue)this).name;
|
||||
else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value));
|
||||
else if (this instanceof SymbolValue) return this.toString();
|
||||
else if (this instanceof NumberValue num && num.isLong()) return num.getLong() + "i";
|
||||
else return this.toString(env);
|
||||
/** @internal */
|
||||
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(toString(env));
|
||||
}
|
||||
|
||||
public final String toReadable(Environment ext) {
|
||||
return toReadable(ext, new HashSet<>(), 0);
|
||||
return String.join("\n", toReadableLines(ext, new HashSet<>()));
|
||||
}
|
||||
|
||||
public static final ObjectValue global(Environment env) {
|
||||
@ -638,8 +534,8 @@ public abstract class Value {
|
||||
var na = a.toNumber(env);
|
||||
var nb = b.toNumber(env);
|
||||
|
||||
if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() - nb.getInt());
|
||||
else return NumberValue.of(na.getDouble() - nb.getDouble());
|
||||
if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() * nb.getInt());
|
||||
else return NumberValue.of(na.getDouble() * nb.getDouble());
|
||||
}
|
||||
public static final NumberValue divide(Environment env, Value a, Value b) {
|
||||
var na = a.toNumber(env);
|
||||
|
@ -3,6 +3,7 @@ package me.topchetoeu.jscript.runtime.values.functions;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.UserValue;
|
||||
|
||||
public class Arguments {
|
||||
public final Value self;
|
||||
@ -21,6 +22,11 @@ public class Arguments {
|
||||
public Value self() {
|
||||
return get(-1);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T self(Class<T> clazz) {
|
||||
if (self instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
|
||||
else return null;
|
||||
}
|
||||
public Value get(int i) {
|
||||
if (i >= args.length || i < -1) return Value.UNDEFINED;
|
||||
else if (i == -1) return self;
|
||||
|
@ -8,8 +8,6 @@ import me.topchetoeu.jscript.runtime.values.Value;
|
||||
public final class CodeFunction extends FunctionValue {
|
||||
public final FunctionBody body;
|
||||
public final Value[][] captures;
|
||||
public Value self;
|
||||
public Value argsVal;
|
||||
public Environment env;
|
||||
|
||||
private Value onCall(Frame frame) {
|
||||
@ -26,12 +24,13 @@ public final class CodeFunction extends FunctionValue {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) {
|
||||
var frame = new Frame(env, isNew, thisArg, args, this);
|
||||
if (argsVal != null) frame.fakeArgs = argsVal;
|
||||
if (self != null) frame.self = self;
|
||||
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
||||
var frame = new Frame(env, isNew, self, args, this);
|
||||
|
||||
return onCall(frame);
|
||||
var res = onCall(frame);
|
||||
|
||||
if (isNew) return frame.self;
|
||||
else return res;
|
||||
}
|
||||
|
||||
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
|
||||
|
@ -1,6 +1,12 @@
|
||||
package me.topchetoeu.jscript.runtime.values.functions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
@ -15,8 +21,8 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
public int length;
|
||||
public Value prototype = new ObjectValue();
|
||||
|
||||
public boolean enableCall = true;
|
||||
public boolean enableNew = true;
|
||||
public boolean enableApply = true;
|
||||
public boolean enableConstruct = true;
|
||||
|
||||
private final FieldMember nameField = new FieldMember(this, true, false, false) {
|
||||
@Override public Value get(Environment env, Value self) {
|
||||
@ -46,17 +52,16 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
}
|
||||
};
|
||||
|
||||
protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args);
|
||||
protected abstract Value onCall(Environment ext, boolean isNew, Value thisArg, Value ...args);
|
||||
|
||||
@Override public String toString() { return String.format("function %s(...)", name); }
|
||||
@Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) {
|
||||
if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args);
|
||||
if (!isNew && !enableCall) {
|
||||
if (name == null || name.equals("")) name = "(intermediate value)";
|
||||
throw EngineException.ofType(name + " is not invokable");
|
||||
@Override public Value apply(Environment env, Value self, Value... args) {
|
||||
if (!enableApply) throw EngineException.ofType("Function cannot be applied");
|
||||
return onCall(env, false, self, args);
|
||||
}
|
||||
|
||||
return onCall(ext, isNew, name, thisArg, args);
|
||||
@Override public Value construct(Environment env, Value self, Value... args) {
|
||||
if (!enableConstruct) throw EngineException.ofType("Function cannot be constructed");
|
||||
return onCall(env, true, self, args);
|
||||
}
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
@ -83,6 +88,20 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
|
||||
@Override public StringValue type() { return StringValue.of("function"); }
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
var dbg = DebugContext.get(env);
|
||||
var res = new StringBuilder(this.toString());
|
||||
var loc = dbg.getMapOrEmpty(this).start();
|
||||
|
||||
if (loc != null) res.append(" @ " + loc);
|
||||
|
||||
var lines = new LinkedList<String>(super.toReadableLines(env, passed));
|
||||
if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString());
|
||||
lines.set(0, res.toString() + " " + lines.getFirst());
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setName(String val) {
|
||||
if (this.name == null || this.name.equals("")) this.name = val;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ public final class NativeFunction extends FunctionValue {
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) {
|
||||
@Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) {
|
||||
return action.run(new Arguments(env, isNew, self, args));
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
package me.topchetoeu.jscript.runtime.values.objects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
@ -104,4 +108,90 @@ public abstract class ArrayLikeValue extends ObjectValue {
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private LinkedList<String> toReadableBase(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
||||
var stringified = new LinkedList<LinkedList<String>>();
|
||||
|
||||
passed.add(this);
|
||||
|
||||
var emptyN = 0;
|
||||
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (has(i)) {
|
||||
String emptyStr = null;
|
||||
|
||||
if (emptyN == 1) emptyStr = "<empty>";
|
||||
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
||||
|
||||
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ",")));
|
||||
emptyN = 0;
|
||||
|
||||
stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed)));
|
||||
ignoredKeys.add(i + "");
|
||||
|
||||
var entry = stringified.getLast();
|
||||
entry.set(entry.size() - 1, entry.getLast() + ",");
|
||||
}
|
||||
else {
|
||||
emptyN++;
|
||||
}
|
||||
}
|
||||
|
||||
String emptyStr = null;
|
||||
|
||||
if (emptyN == 1) emptyStr = "<empty>";
|
||||
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
|
||||
|
||||
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr)));
|
||||
else if (stringified.size() > 0) {
|
||||
var lastEntry = stringified.getLast();
|
||||
lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1));
|
||||
}
|
||||
|
||||
|
||||
passed.remove(this);
|
||||
|
||||
if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]"));
|
||||
var concat = new StringBuilder();
|
||||
for (var entry : stringified) {
|
||||
// We make a one-liner only when all members are one-liners
|
||||
if (entry.size() != 1) {
|
||||
concat = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (concat.length() != 0) concat.append(" ");
|
||||
concat.append(entry.get(0));
|
||||
}
|
||||
|
||||
// We don't want too long one-liners
|
||||
if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]"));
|
||||
|
||||
var res = new LinkedList<String>();
|
||||
|
||||
res.add("[");
|
||||
|
||||
for (var entry : stringified) {
|
||||
for (var line : entry) {
|
||||
res.add(" " + line);
|
||||
}
|
||||
}
|
||||
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
||||
res.add("]");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
var ignored = new HashSet<String>();
|
||||
var lines = toReadableBase(env, passed, ignored);
|
||||
|
||||
var superLines = new LinkedList<String>(super.toReadableLines(env, passed, ignored));
|
||||
if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines;
|
||||
|
||||
lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst());
|
||||
lines.addAll(superLines.subList(1, superLines.size()));
|
||||
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package me.topchetoeu.jscript.runtime.values.objects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
@ -10,6 +14,7 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
|
||||
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
||||
@ -41,13 +46,13 @@ public class ObjectValue extends Value {
|
||||
var valueOf = getMember(env, "valueOf");
|
||||
|
||||
if (valueOf instanceof FunctionValue) {
|
||||
var res = valueOf.invoke(env, this);
|
||||
var res = valueOf.apply(env, this);
|
||||
if (res.isPrimitive()) return res;
|
||||
}
|
||||
|
||||
var toString = getMember(env, "toString");
|
||||
if (toString instanceof FunctionValue) {
|
||||
var res = toString.invoke(env, this);
|
||||
var res = toString.apply(env, this);
|
||||
if (res.isPrimitive()) return res;
|
||||
}
|
||||
}
|
||||
@ -134,6 +139,81 @@ public class ObjectValue extends Value {
|
||||
return setPrototype(_env -> val);
|
||||
}
|
||||
|
||||
private final LinkedList<String> memberToReadable(Environment env, String key, Member member, HashSet<ObjectValue> passed) {
|
||||
if (member instanceof PropertyMember prop) {
|
||||
if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]"));
|
||||
else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]"));
|
||||
else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]"));
|
||||
else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]"));
|
||||
}
|
||||
else {
|
||||
var res = new LinkedList<String>();
|
||||
var first = true;
|
||||
|
||||
for (var line : member.get(env, this).toReadableLines(env, passed)) {
|
||||
if (first) res.add(key + ": " + line);
|
||||
else res.add(line);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
|
||||
passed.add(this);
|
||||
|
||||
var stringified = new LinkedList<LinkedList<String>>();
|
||||
|
||||
for (var entry : getOwnSymbolMembers(env, true)) {
|
||||
var member = getOwnMember(env, entry);
|
||||
stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed));
|
||||
}
|
||||
for (var entry : getOwnMembers(env, true)) {
|
||||
if (ignoredKeys.contains(entry)) continue;
|
||||
|
||||
var member = getOwnMember(env, entry);
|
||||
stringified.add(memberToReadable(env, entry, member, passed));
|
||||
}
|
||||
|
||||
passed.remove(this);
|
||||
|
||||
if (stringified.size() == 0) return Arrays.asList("{}");
|
||||
var concat = new StringBuilder();
|
||||
for (var entry : stringified) {
|
||||
// We make a one-liner only when all members are one-liners
|
||||
if (entry.size() != 1) {
|
||||
concat = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (concat.length() != 0) concat.append(", ");
|
||||
concat.append(entry.get(0));
|
||||
}
|
||||
|
||||
// We don't want too long one-liners
|
||||
if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }");
|
||||
|
||||
var res = new LinkedList<String>();
|
||||
|
||||
res.add("{");
|
||||
|
||||
for (var entry : stringified) {
|
||||
for (var line : entry) {
|
||||
res.add(" " + line);
|
||||
}
|
||||
|
||||
res.set(res.size() - 1, res.getLast() + ",");
|
||||
}
|
||||
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
|
||||
res.add("}");
|
||||
|
||||
return res;
|
||||
}
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return toReadableLines(env, passed, new HashSet<>());
|
||||
}
|
||||
|
||||
public final boolean setPrototype(PrototypeProvider val) {
|
||||
if (!getState().extendable) return false;
|
||||
prototype = val;
|
||||
|
@ -20,6 +20,9 @@ public final class BoolValue extends PrimitiveValue {
|
||||
return env.get(BOOL_PROTO);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Boolean.hashCode(value);
|
||||
}
|
||||
@Override public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
else if (other instanceof BoolValue bool) return value == bool.value;
|
||||
|
@ -1,10 +1,15 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.common.json.JSON;
|
||||
import me.topchetoeu.jscript.common.json.JSONElement;
|
||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.common.parsing.Source;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
@ -31,6 +36,9 @@ public final class StringValue extends PrimitiveValue {
|
||||
}
|
||||
@Override public String toString(Environment ext) { return value; }
|
||||
|
||||
@Override public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
else if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value);
|
||||
@ -67,6 +75,10 @@ public final class StringValue extends PrimitiveValue {
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(JSON.stringify(JSONElement.string(value)));
|
||||
}
|
||||
|
||||
private StringValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
@ -34,6 +37,11 @@ public final class SymbolValue extends PrimitiveValue {
|
||||
else return "Symbol(" + value + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(toString());
|
||||
}
|
||||
|
||||
public SymbolValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
import me.topchetoeu.jscript.runtime.values.Member;
|
||||
import me.topchetoeu.jscript.runtime.values.Value;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||
|
||||
public final class UserValue<T> extends Value {
|
||||
public final T value;
|
||||
public final ObjectValue prototype;
|
||||
|
||||
@Override public StringValue type() { return StringValue.of("object"); }
|
||||
|
||||
@Override public boolean toBoolean() { return true; }
|
||||
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
|
||||
@Override public String toString(Environment ext) { return "[user value]"; }
|
||||
|
||||
@Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; }
|
||||
@Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; }
|
||||
@Override public final boolean isPrimitive() { return false; }
|
||||
@Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; }
|
||||
|
||||
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
|
||||
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
|
||||
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
|
||||
|
||||
@Override public State getState() { return State.FROZEN; }
|
||||
|
||||
@Override public void preventExtensions() {}
|
||||
@Override public void seal() {}
|
||||
@Override public void freeze() {}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
else if (other instanceof UserValue val) return Objects.equals(value, val.value);
|
||||
else return false;
|
||||
}
|
||||
|
||||
@Override public ObjectValue getPrototype(Environment env) { return prototype; }
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(value.toString());
|
||||
}
|
||||
|
||||
private UserValue(T value, ObjectValue prototype) {
|
||||
this.value = value;
|
||||
this.prototype = prototype;
|
||||
}
|
||||
|
||||
public static <T> UserValue<T> of(T value) {
|
||||
return new UserValue<T>(value, null);
|
||||
}
|
||||
public static <T> UserValue<T> of(T value, ObjectValue prototype) {
|
||||
return new UserValue<T>(value, prototype);
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||
@ -19,7 +23,12 @@ public final class VoidValue extends PrimitiveValue {
|
||||
@Override public ObjectValue getPrototype(Environment env) { return null; }
|
||||
|
||||
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||
throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env)));
|
||||
if (key.isSymbol()) throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toSymbol().toString()));
|
||||
else throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env)));
|
||||
}
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(name);
|
||||
}
|
||||
|
||||
public VoidValue(String name, String type) {
|
||||
|
@ -24,6 +24,9 @@ public final class DoubleValue extends NumberValue {
|
||||
|
||||
@Override public String toString() { return JSON.stringify(JSONElement.number(value)); }
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Double.hashCode(value);
|
||||
}
|
||||
@Override public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
else if (other instanceof NumberValue val) return value == val.getDouble();
|
||||
|
@ -1,5 +1,12 @@
|
||||
package me.topchetoeu.jscript.runtime.values.primitives.numbers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.common.environment.Environment;
|
||||
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||
|
||||
public final class IntValue extends NumberValue {
|
||||
public final long value;
|
||||
|
||||
@ -19,6 +26,9 @@ public final class IntValue extends NumberValue {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Long.hashCode(value);
|
||||
}
|
||||
@Override public String toString() { return value + ""; }
|
||||
@Override public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
@ -26,6 +36,10 @@ public final class IntValue extends NumberValue {
|
||||
else return false;
|
||||
}
|
||||
|
||||
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
|
||||
return Arrays.asList(value + "i");
|
||||
}
|
||||
|
||||
public IntValue(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -1,251 +1,251 @@
|
||||
// return;
|
||||
|
||||
const target = arguments[0];
|
||||
const primordials = arguments[1];
|
||||
|
||||
const symbol = primordials.symbol || (() => {
|
||||
const repo = {};
|
||||
(function main() {
|
||||
function extend(derived, base) {
|
||||
if (base == null) {
|
||||
object.setPrototype(derived.prototype, null);
|
||||
}
|
||||
else {
|
||||
object.setPrototype(derived, base);
|
||||
object.setPrototype(derived.prototype, base.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
var target = arguments[0];
|
||||
var primordials = arguments[1];
|
||||
var symbol = primordials.symbol || (function () {
|
||||
var repo = {};
|
||||
return {
|
||||
makeSymbol: (name) => { name },
|
||||
getSymbol(name) {
|
||||
makeSymbol: function (name) { return { name: name }; },
|
||||
getSymbol: function (name) {
|
||||
if (name in repo) return repo[name];
|
||||
else return repo[name] = { name };
|
||||
else return repo[name] = { name: name };
|
||||
},
|
||||
getSymbolKey(symbol) {
|
||||
getSymbolKey: function (symbol) {
|
||||
if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name;
|
||||
else return undefined;
|
||||
},
|
||||
getSymbolDescription: ({ name }) => name,
|
||||
getSymbolDescription: function (symbol) {
|
||||
return symbol.name;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const number = primordials.number || (() => {
|
||||
return {
|
||||
parseInt() { throw new Error("parseInt not supported"); },
|
||||
parseFloat() { throw new Error("parseFloat not supported"); },
|
||||
isNaN: (val) => val !== val,
|
||||
var number = primordials.number || {
|
||||
parseInt: function () { throw new Error("parseInt not supported"); },
|
||||
parseFloat: function () { throw new Error("parseFloat not supported"); },
|
||||
isNaN: function (val) { return val !== val; },
|
||||
NaN: 0 / 0,
|
||||
Infinity: 1 / 0,
|
||||
};
|
||||
});
|
||||
|
||||
const fromCharCode = primordials.string.fromCharCode;
|
||||
const fromCodePoint = primordials.string.fromCodePoint;
|
||||
const stringBuild = primordials.string.stringBuild;
|
||||
|
||||
const defineProperty = primordials.object.defineProperty;
|
||||
const defineField = primordials.object.defineField;
|
||||
const getOwnMember = primordials.object.getMember;
|
||||
const getOwnSymbolMember = primordials.object.getOwnSymbolMember;
|
||||
const getOwnMembers = primordials.object.getOwnMembers;
|
||||
const getOwnSymbolMembers = primordials.object.getOwnSymbolMembers;
|
||||
const getPrototype = primordials.object.getPrototype;
|
||||
const setPrototype = primordials.object.setPrototype;
|
||||
|
||||
const invokeType = primordials.function.invokeType;
|
||||
const setConstructable = primordials.function.setConstructable;
|
||||
const setCallable = primordials.function.setCallable;
|
||||
const invoke = primordials.function.invoke;
|
||||
const construct = primordials.function.construct;
|
||||
|
||||
const json = primordials.json;
|
||||
|
||||
const setGlobalPrototype = primordials.setGlobalPrototype;
|
||||
const compile = primordials.compile;
|
||||
const setIntrinsic = primordials.setIntrinsic;
|
||||
|
||||
const valueKey = symbol.makeSymbol("Primitive.value");
|
||||
const undefined = ({}).definitelyDefined;
|
||||
var string = primordials.string;
|
||||
var object = primordials.object || {
|
||||
defineProperty: function () { throw new Error("Define property not polyfillable"); },
|
||||
defineField: function (obj, key, a, b, c, value) { obj[key] = value; },
|
||||
getOwnMember: function () { throw new Error("Get own member not polyfillable"); },
|
||||
getOwnSymbolMember: function () { throw new Error("Get own symbol member not polyfillable"); },
|
||||
getOwnMembers: function () { throw new Error("Get own members not polyfillable"); },
|
||||
getOwnSymbolMembers: function () { throw new Error("Get own symbol members not polyfillable"); },
|
||||
getPrototype: function () { throw new Error("Get prototype not polyfillable"); },
|
||||
setPrototype: function () { throw new Error("Set prototype not polyfillable"); },
|
||||
};
|
||||
var func = primordials.function || {
|
||||
invokeType: function (args, self) {
|
||||
if (typeof self === "object") return "new";
|
||||
else return "call";
|
||||
},
|
||||
setConstructable: function () { throw new Error("Set constructable not polyfillable"); },
|
||||
setCallable: function () { throw new Error("Set callable not polyfillable"); },
|
||||
invoke: function () { throw new Error("Invoke not polyfillable"); },
|
||||
construct: function () { throw new Error("Construct not polyfillable"); },
|
||||
};
|
||||
var json = primordials.json || {
|
||||
stringify: function (val) { throw new Error("JSON stringify not polyfillable"); },
|
||||
parse: function (val) { throw new Error("JSON parse not polyfillable"); },
|
||||
}
|
||||
|
||||
var setGlobalPrototypes = primordials.setGlobalPrototypes;
|
||||
var compile = primordials.compile;
|
||||
var valueKey = symbol.makeSymbol("Primitive.value");
|
||||
var undefined = void 0;
|
||||
target.undefined = undefined;
|
||||
|
||||
const unwrapThis = (self, type, constr, name, arg = "this", defaultVal) => {
|
||||
if (typeof self === type) return self;
|
||||
if (self instanceof constr && valueKey in self) self = self[valueKey];
|
||||
if (typeof self === type) return self;
|
||||
if (defaultVal !== undefined) return defaultVal;
|
||||
|
||||
function unwrapThis(self, type, constr, name, arg, defaultVal) {
|
||||
if (arg === void 0) { arg = "this"; }
|
||||
if (typeof self === type)
|
||||
return self;
|
||||
if (self instanceof constr && valueKey in self)
|
||||
self = self[valueKey];
|
||||
if (typeof self === type)
|
||||
return self;
|
||||
if (defaultVal !== undefined)
|
||||
return defaultVal;
|
||||
throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name);
|
||||
}
|
||||
|
||||
const wrapIndex = (i, len) => {};
|
||||
|
||||
class Symbol {
|
||||
get description() {
|
||||
return symbol.getSymbolDescriptor(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description"));
|
||||
}
|
||||
toString() {
|
||||
return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")";
|
||||
}
|
||||
valueOf() {
|
||||
return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf");
|
||||
}
|
||||
|
||||
constructor(name = "") {
|
||||
function wrapIndex(i, len) { }
|
||||
function Symbol(name) {
|
||||
if (name === undefined) name = "";
|
||||
return symbol.makeSymbol(name);
|
||||
}
|
||||
|
||||
static for(name) {
|
||||
Symbol.prototype.toString = function () {
|
||||
return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")";
|
||||
};
|
||||
Symbol.prototype.valueOf = function () {
|
||||
return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf");
|
||||
};
|
||||
Symbol.for = function (name) {
|
||||
return symbol.getSymbol(name + "");
|
||||
}
|
||||
static keyFor(value) {
|
||||
};
|
||||
Symbol.keyFor = function (value) {
|
||||
return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor"));
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(Symbol, true);
|
||||
setConstructable(Symbol, false);
|
||||
|
||||
defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator"));
|
||||
defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator"));
|
||||
defineField(Symbol, "match", false, false, false, Symbol("Symbol.match"));
|
||||
defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll"));
|
||||
defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace"));
|
||||
defineField(Symbol, "search", false, false, false, Symbol("Symbol.search"));
|
||||
defineField(Symbol, "split", false, false, false, Symbol("Symbol.split"));
|
||||
defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag"));
|
||||
|
||||
Symbol();
|
||||
};
|
||||
object.defineProperty(Symbol.prototype, "desc", false, true, function () {
|
||||
return symbol.getSymbolDescriptor(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description"));
|
||||
});
|
||||
object.defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator"));
|
||||
object.defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator"));
|
||||
object.defineField(Symbol, "match", false, false, false, Symbol("Symbol.match"));
|
||||
object.defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll"));
|
||||
object.defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace"));
|
||||
object.defineField(Symbol, "search", false, false, false, Symbol("Symbol.search"));
|
||||
object.defineField(Symbol, "split", false, false, false, Symbol("Symbol.split"));
|
||||
object.defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag"));
|
||||
func.setConstructable(Symbol, false);
|
||||
target.Symbol = Symbol;
|
||||
|
||||
class Number {
|
||||
toString() {
|
||||
return "" + unwrapThis(this, "number", Number, "Number.prototype.toString");
|
||||
}
|
||||
valueOf() {
|
||||
return unwrapThis(this, "number", Number, "Number.prototype.toString");
|
||||
}
|
||||
|
||||
constructor(value) {
|
||||
if (invokeType(arguments) === "call") {
|
||||
function Number(value) {
|
||||
if (func.invokeType(arguments, this) === "call") {
|
||||
if (arguments.length === 0) return 0;
|
||||
else return +value;
|
||||
}
|
||||
|
||||
this[valueKey] = target.Number(value);
|
||||
}
|
||||
|
||||
static isFinite(value) {
|
||||
Number.prototype.toString = function () {
|
||||
return "" + unwrapThis(this, "number", Number, "Number.prototype.toString");
|
||||
};
|
||||
Number.prototype.valueOf = function () {
|
||||
return unwrapThis(this, "number", Number, "Number.prototype.toString");
|
||||
};
|
||||
Number.isFinite = function (value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined);
|
||||
|
||||
if (value === undefined || value !== value) return false;
|
||||
if (value === Infinity || value === -Infinity) return false;
|
||||
|
||||
if (value === undefined || value !== value)
|
||||
return false;
|
||||
if (value === Infinity || value === -Infinity)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
static isInteger(value) {
|
||||
};
|
||||
Number.isInteger = function (value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined);
|
||||
if (value === undefined) return false;
|
||||
if (value === undefined)
|
||||
return false;
|
||||
return number.parseInt(value) === value;
|
||||
}
|
||||
static isNaN(value) {
|
||||
};
|
||||
Number.isNaN = function (value) {
|
||||
return number.isNaN(value);
|
||||
}
|
||||
static isSafeInteger(value) {
|
||||
};
|
||||
Number.isSafeInteger = function (value) {
|
||||
value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined);
|
||||
if (value === undefined || number.parseInt(value) !== value) return false;
|
||||
if (value === undefined || number.parseInt(value) !== value)
|
||||
return false;
|
||||
return value >= -9007199254740991 && value <= 9007199254740991;
|
||||
}
|
||||
static parseFloat(value) {
|
||||
};
|
||||
Number.parseFloat = function (value) {
|
||||
value = 0 + value;
|
||||
return number.parseFloat(value);
|
||||
}
|
||||
static parseInt(value, radix) {
|
||||
};
|
||||
Number.parseInt = function (value, radix) {
|
||||
value = 0 + value;
|
||||
radix = +radix;
|
||||
if (number.isNaN(radix)) radix = 10;
|
||||
|
||||
if (number.isNaN(radix))
|
||||
radix = 10;
|
||||
return number.parseInt(value, radix);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16);
|
||||
defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991);
|
||||
defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991);
|
||||
defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity);
|
||||
defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity);
|
||||
defineField(Number, "NaN", false, false, false, number.NaN);
|
||||
defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308);
|
||||
defineField(Number, "MIN_VALUE", false, false, false, 5e-324);
|
||||
|
||||
setCallable(Number, true);
|
||||
object.defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16);
|
||||
object.defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991);
|
||||
object.defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991);
|
||||
object.defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity);
|
||||
object.defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity);
|
||||
object.defineField(Number, "NaN", false, false, false, number.NaN);
|
||||
object.defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308);
|
||||
object.defineField(Number, "MIN_VALUE", false, false, false, 5e-324);
|
||||
func.setCallable(Number, true);
|
||||
target.Number = Number;
|
||||
target.parseInt = Number.parseInt;
|
||||
target.parseFloat = Number.parseFloat;
|
||||
target.NaN = Number.NaN;
|
||||
target.Infinity = Number.POSITIVE_INFINITY;
|
||||
|
||||
class String {
|
||||
at(index) {
|
||||
throw "Not implemented :/";
|
||||
return unwrapThis(this, "string", String, "String.prototype.at")[index];
|
||||
function String(value) {
|
||||
if (func.invokeType(arguments, this) === "call") {
|
||||
if (arguments.length === 0)
|
||||
return "";
|
||||
else
|
||||
return value + "";
|
||||
}
|
||||
toString() {
|
||||
return unwrapThis(this, "string", String, "String.prototype.toString");
|
||||
}
|
||||
valueOf() {
|
||||
return unwrapThis(this, "string", String, "String.prototype.valueOf");
|
||||
}
|
||||
|
||||
constructor(value) {
|
||||
if (invokeType(arguments) === "call") {
|
||||
if (arguments.length === 0) return "";
|
||||
else return value + "";
|
||||
}
|
||||
|
||||
this[valueKey] = String(value);
|
||||
}
|
||||
|
||||
static fromCharCode() {
|
||||
const res = [];
|
||||
String.prototype.at = function (index) {
|
||||
throw "Not implemented :/";
|
||||
return unwrapThis(this, "string", String, "String.prototype.at")[index];
|
||||
};
|
||||
String.prototype.toString = function () {
|
||||
return unwrapThis(this, "string", String, "String.prototype.toString");
|
||||
};
|
||||
String.prototype.valueOf = function () {
|
||||
return unwrapThis(this, "string", String, "String.prototype.valueOf");
|
||||
};
|
||||
String.fromCharCode = function () {
|
||||
var res = [];
|
||||
res[arguments.length] = 0;
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
res[i] = string.fromCharCode(+arguments[i]);
|
||||
}
|
||||
return string.stringBuild(res);
|
||||
};
|
||||
String.fromCodePoint = function () {
|
||||
var res = [];
|
||||
res[arguments.length] = 0;
|
||||
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
res[i] = fromCharCode(+arguments[i]);
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
res[i] = string.fromCodePoint(+arguments[i]);
|
||||
}
|
||||
|
||||
return stringBuild(res);
|
||||
}
|
||||
static fromCodePoint() {
|
||||
const res = [];
|
||||
res[arguments.length] = 0;
|
||||
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
res[i] = fromCodePoint(+arguments[i]);
|
||||
}
|
||||
|
||||
return stringBuild(res);
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(String, true);
|
||||
return string.stringBuild(res);
|
||||
};
|
||||
func.setCallable(String, true);
|
||||
target.String = String;
|
||||
|
||||
class Boolean {
|
||||
toString() {
|
||||
return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString");
|
||||
}
|
||||
valueOf() {
|
||||
return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf");
|
||||
}
|
||||
|
||||
constructor(value) {
|
||||
if (invokeType(arguments) === "call") {
|
||||
function Boolean(value) {
|
||||
if (func.invokeType(arguments, this) === "call") {
|
||||
if (arguments.length === 0) return false;
|
||||
else return !!value;
|
||||
}
|
||||
|
||||
this[valueKey] = Boolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(Boolean, true);
|
||||
Boolean.prototype.toString = function () {
|
||||
return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString");
|
||||
};
|
||||
Boolean.prototype.valueOf = function () {
|
||||
return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf");
|
||||
};
|
||||
func.setCallable(Boolean, true);
|
||||
target.Boolean = Boolean;
|
||||
|
||||
class Object {
|
||||
toString() {
|
||||
print("2");
|
||||
function Object(value) {
|
||||
if (typeof value === 'object' && value !== null) return value;
|
||||
if (typeof value === 'string') return new String(value);
|
||||
if (typeof value === 'number') return new Number(value);
|
||||
if (typeof value === 'boolean') return new Boolean(value);
|
||||
if (typeof value === 'symbol') {
|
||||
var res = {};
|
||||
setPrototype(res, Symbol.prototype);
|
||||
res[valueKey] = value;
|
||||
return res;
|
||||
}
|
||||
|
||||
return {};
|
||||
// // TODO: use new.target.prototype as proto
|
||||
// if (target == null || typeof target !== 'object') target = {};
|
||||
// return target;
|
||||
}
|
||||
Object.prototype.toString = function () {
|
||||
if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]";
|
||||
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
|
||||
else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]";
|
||||
@ -253,203 +253,156 @@ class Object {
|
||||
else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]";
|
||||
else if (typeof this === "function") return "[object Function]";
|
||||
else return "[object Object]";
|
||||
}
|
||||
valueOf() {
|
||||
print("1");
|
||||
};
|
||||
Object.prototype.valueOf = function () {
|
||||
return this;
|
||||
}
|
||||
|
||||
constructor(value) {
|
||||
if (typeof value === 'object' && value !== null) return value;
|
||||
|
||||
if (typeof value === 'string') return new String(value);
|
||||
if (typeof value === 'number') return new Number(value);
|
||||
if (typeof value === 'boolean') return new Boolean(value);
|
||||
if (typeof value === 'symbol') {
|
||||
const res = {};
|
||||
setPrototype(res, Symbol.prototype);
|
||||
res[valueKey] = value;
|
||||
return res;
|
||||
}
|
||||
|
||||
const target = this;
|
||||
// TODO: use new.target.prototype as proto
|
||||
if (target == null || typeof target !== 'object') target = {};
|
||||
|
||||
this[valueKey] = Object(value);
|
||||
}
|
||||
|
||||
static defineProperty(obj, key, desc) {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
print(obj);
|
||||
print(typeof obj);
|
||||
throw new TypeError("Object.defineProperty called on non-object");
|
||||
}
|
||||
};
|
||||
Object.defineProperty = function (obj, key, desc) {
|
||||
if (typeof obj !== "object" || obj === null) throw new TypeError("Object.defineProperty called on non-object");
|
||||
if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc);
|
||||
|
||||
if ("get" in desc || "set" in desc) {
|
||||
let get = desc.get, set = desc.set;
|
||||
|
||||
print(typeof get);
|
||||
var get = desc.get, set = desc.set;
|
||||
|
||||
if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get);
|
||||
if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set);
|
||||
|
||||
if ("value" in desc || "writable" in desc) {
|
||||
throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");
|
||||
}
|
||||
|
||||
if (!defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) {
|
||||
if (!object.defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) {
|
||||
throw new TypeError("Cannot redefine property: " + key);
|
||||
}
|
||||
}
|
||||
else if (!defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) {
|
||||
else if (!object.defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) {
|
||||
throw new TypeError("Cannot redefine property: " + key);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(Object, true);
|
||||
setPrototype(Object.prototype, null);
|
||||
};
|
||||
func.setCallable(Object, true);
|
||||
extend(Object, null);
|
||||
object.setPrototype(Object.prototype, null);
|
||||
target.Object = Object;
|
||||
|
||||
class Function {
|
||||
toString() {
|
||||
if (this.name !== "") return "function " + this.name + "(...) { ... }";
|
||||
else return "function (...) { ... }";
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const parts = ["return function annonymous("];
|
||||
|
||||
for (let i = 0; i < arguments.length - 1; i++) {
|
||||
if (i > 0) parts[parts.length] = ",";
|
||||
function Function() {
|
||||
var parts = ["(function annonymous("];
|
||||
for (var i = 0; i < arguments.length - 1; i++) {
|
||||
if (i > 0)
|
||||
parts[parts.length] = ",";
|
||||
parts[parts.length] = arguments[i];
|
||||
}
|
||||
parts[parts.length] = "){\n";
|
||||
parts[parts.length] = String(arguments[arguments.length - 1]);
|
||||
parts[parts.length] = "\n}";
|
||||
|
||||
const res = compile(stringBuild(parts))();
|
||||
parts[parts.length] = "\n})";
|
||||
var res = compile(string.stringBuild(parts))();
|
||||
return res;
|
||||
}
|
||||
Function.prototype.toString = function () {
|
||||
if (this.name !== "")
|
||||
return "function " + this.name + "(...) { ... }";
|
||||
else
|
||||
return "function (...) { ... }";
|
||||
};
|
||||
Function.compile = function (src, opts) {
|
||||
if (src === void 0) src = "";
|
||||
if (opts === void 0) opts = {};
|
||||
if (opts.globals === void 0) opts.globals = [];
|
||||
if (opts.wrap === void 0) opts.wrap = false;
|
||||
|
||||
static compile(src = "", { globals = [], wrap = false } = {}) {
|
||||
const parts = [];
|
||||
var globals = opts.globals;
|
||||
var wrap = opts.wrap;
|
||||
var parts = [];
|
||||
|
||||
if (wrap) parts[parts.length] = "return (function() {\n";
|
||||
if (globals.length > 0) {
|
||||
parts[parts.length] = "var ";
|
||||
|
||||
for (let i = 0; i < globals.length; i++) {
|
||||
parts[parts.length] = "let {";
|
||||
for (var i = 0; i < globals.length; i++) {
|
||||
if (i > 0) parts[parts.length] = ",";
|
||||
parts[parts.length] = globals[i];
|
||||
}
|
||||
|
||||
parts[parts.length] = ";((g=arguments[0])=>{";
|
||||
|
||||
for (let i = 0; i < globals.length; i++) {
|
||||
const name = globals[i];
|
||||
parts[parts.length] = name + "=g[" + json.stringify(name) + "];";
|
||||
parts[parts.length] = "} = arguments[0];";
|
||||
}
|
||||
|
||||
parts[parts.length] = "})()\n";
|
||||
}
|
||||
|
||||
parts[parts.length] = src;
|
||||
if (wrap) parts[parts.length] = "\n})(arguments[0])";
|
||||
|
||||
const res = compile(stringBuild(parts));
|
||||
var res = compile(string.stringBuild(parts));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(Function, true);
|
||||
};
|
||||
func.setCallable(Function, true);
|
||||
target.Function = Function;
|
||||
|
||||
class Array {
|
||||
constructor(len) {
|
||||
function Array(len) {
|
||||
if (arguments.length === 1 && typeof len === "number") {
|
||||
const res = [];
|
||||
var res = [];
|
||||
res.length = len;
|
||||
return res;
|
||||
}
|
||||
// TODO: Implement spreading
|
||||
else throw new Error("Spreading not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
setCallable(Array, true);
|
||||
func.setCallable(Array, true);
|
||||
target.Array = Array;
|
||||
|
||||
class Error {
|
||||
toString() {
|
||||
let res = this.name || "Error";
|
||||
|
||||
const msg = this.message;
|
||||
if (msg) res += ": " + msg;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
constructor (msg = "") {
|
||||
if (invokeType(arguments) === "call") return new Error(msg);
|
||||
function Error(msg) {
|
||||
if (msg === void 0) { msg = ""; }
|
||||
if (func.invokeType(arguments, this) === "call")
|
||||
return new Error(msg);
|
||||
this.message = msg + "";
|
||||
}
|
||||
}
|
||||
|
||||
defineField(Error.prototype, "name", true, false, true, "Error");
|
||||
defineField(Error.prototype, "message", true, false, true, "");
|
||||
setCallable(Error, true);
|
||||
Error.prototype.toString = function () {
|
||||
var res = this.name || "Error";
|
||||
var msg = this.message;
|
||||
if (msg)
|
||||
res += ": " + msg;
|
||||
return res;
|
||||
};
|
||||
object.defineField(Error.prototype, "name", true, false, true, "Error");
|
||||
object.defineField(Error.prototype, "message", true, false, true, "");
|
||||
func.setCallable(Error, true);
|
||||
target.Error = Error;
|
||||
|
||||
class SyntaxError {
|
||||
constructor (msg = "") {
|
||||
if (invokeType(arguments) === "call") return new SyntaxError(msg);
|
||||
this.message = msg + "";
|
||||
extend(SyntaxError, Error);
|
||||
function SyntaxError(msg) {
|
||||
if (func.invokeType(arguments, this) === "call")
|
||||
return new SyntaxError(msg);
|
||||
return _super.call(this, msg) || this;
|
||||
}
|
||||
}
|
||||
|
||||
defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError");
|
||||
setPrototype(SyntaxError, Error);
|
||||
setPrototype(SyntaxError.prototype, Error.prototype);
|
||||
setCallable(SyntaxError, true);
|
||||
object.defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError");
|
||||
func.setCallable(SyntaxError, true);
|
||||
target.SyntaxError = SyntaxError;
|
||||
|
||||
class TypeError {
|
||||
constructor (msg = "") {
|
||||
if (invokeType(arguments) === "call") return new TypeError(msg);
|
||||
this.message = msg + "";
|
||||
extend(TypeError, Error);
|
||||
function TypeError(msg) {
|
||||
if (func.invokeType(arguments, this) === "call")
|
||||
return new TypeError(msg);
|
||||
return _super.call(this, msg) || this;
|
||||
}
|
||||
}
|
||||
|
||||
defineField(TypeError.prototype, "name", true, false, true, "TypeError");
|
||||
setPrototype(TypeError, Error);
|
||||
setPrototype(TypeError.prototype, Error.prototype);
|
||||
setCallable(TypeError, true);
|
||||
object.defineField(TypeError.prototype, "name", true, false, true, "TypeError");
|
||||
func.setCallable(TypeError, true);
|
||||
target.TypeError = TypeError;
|
||||
|
||||
class RangeError {
|
||||
constructor (msg = "") {
|
||||
if (invokeType(arguments) === "call") return new RangeError(msg);
|
||||
this.message = msg + "";
|
||||
extend(RangeError, Error);
|
||||
function RangeError(msg) {
|
||||
if (func.invokeType(arguments, this) === "call")
|
||||
return new RangeError(msg);
|
||||
return _super.call(this, msg) || this;
|
||||
}
|
||||
}
|
||||
|
||||
defineField(RangeError.prototype, "name", true, false, true, "RangeError");
|
||||
setPrototype(RangeError, Error);
|
||||
setPrototype(RangeError.prototype, Error.prototype);
|
||||
setCallable(RangeError, true);
|
||||
object.defineField(RangeError.prototype, "name", true, false, true, "RangeError");
|
||||
func.setCallable(RangeError, true);
|
||||
target.RangeError = RangeError;
|
||||
|
||||
setGlobalPrototype("string", String.prototype);
|
||||
setGlobalPrototype("number", Number.prototype);
|
||||
setGlobalPrototype("boolean", Boolean.prototype);
|
||||
setGlobalPrototype("symbol", Symbol.prototype);
|
||||
setGlobalPrototype("object", Object.prototype);
|
||||
setGlobalPrototype("array", Array.prototype);
|
||||
setGlobalPrototype("function", Function.prototype);
|
||||
setGlobalPrototype("error", Error.prototype);
|
||||
setGlobalPrototype("syntax", SyntaxError.prototype);
|
||||
target.uint8 = primordials.uint8;
|
||||
|
||||
setGlobalPrototypes({
|
||||
string: String.prototype,
|
||||
number: Number.prototype,
|
||||
boolean: Boolean.prototype,
|
||||
symbol: Symbol.prototype,
|
||||
object: Object.prototype,
|
||||
array: Array.prototype,
|
||||
function: Function.prototype,
|
||||
error: Error.prototype,
|
||||
syntax: SyntaxError.prototype,
|
||||
range: RangeError.prototype,
|
||||
type: TypeError.prototype,
|
||||
});
|
||||
})(arguments[0], arguments[1]);
|
||||
|
Loading…
Reference in New Issue
Block a user