Add support for source mappings #10

Merged
TopchetoEU merged 22 commits from TopchetoEU/mapping into master 2023-12-18 20:42:38 +00:00
81 changed files with 4481 additions and 4173 deletions

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,8 +1,8 @@
(function (_arguments) { (function (ts, env, libs) {
var ts = _arguments[0];
var src = '', version = 0; var src = '', version = 0;
var lib = _arguments[2].concat([ var lib = libs.concat([
'declare const exit: never; declare const go: any;', 'declare function exit(): never;',
'declare function go(): any;',
'declare function getTsDeclarations(): string[];' 'declare function getTsDeclarations(): string[];'
]).join(''); ]).join('');
var libSnapshot = ts.ScriptSnapshot.fromString(lib); var libSnapshot = ts.ScriptSnapshot.fromString(lib);
@ -21,6 +21,7 @@
forceConsistentCasingInFileNames: true, forceConsistentCasingInFileNames: true,
experimentalDecorators: true, experimentalDecorators: true,
strict: true, strict: true,
sourceMap: true,
}; };
var reg = ts.createDocumentRegistry(); var reg = ts.createDocumentRegistry();
@ -55,6 +56,8 @@
service.getEmitOutput("/lib.d.ts"); service.getEmitOutput("/lib.d.ts");
log("Loaded libraries!"); log("Loaded libraries!");
var oldCompile = env.compile;
function compile(code, filename, env) { function compile(code, filename, env) {
src = code; src = code;
version++; version++;
@ -82,21 +85,20 @@
throw new SyntaxError(diagnostics.join("\n")); throw new SyntaxError(diagnostics.join("\n"));
} }
var result = emit.outputFiles[0].text; var map = JSON.parse(emit.outputFiles[0].text);
var declaration = emit.outputFiles[1].text; var result = emit.outputFiles[1].text;
var declaration = emit.outputFiles[2].text;
var compiled = oldCompile(result, filename, env);
return { return {
source: result, function: function () {
runner: function(func) { var val = compiled.function.apply(this, arguments);
return function() { if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
var val = func.apply(this, arguments); return val;
if (declaration !== '') { },
declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); breakpoints: compiled.breakpoints,
} mapChain: compiled.mapChain.concat(map.mappings),
return val;
}
}
}; };
} }
@ -107,5 +109,5 @@
} }
} }
apply(_arguments[1]); apply(env);
})(arguments); })(arguments[0], arguments[1], arguments[2]);

View File

@ -19,12 +19,7 @@ type Extract<T, U> = T extends U ? T : never;
type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT } type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT }
type ReplaceFunc = (match: string, ...args: any[]) => string; type ReplaceFunc = (match: string, ...args: any[]) => string;
type PromiseFulfillFunc<T> = (val: T) => void; type PromiseResult<T> = { type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
type PromiseThenFunc<T, NextT> = (val: T) => NextT;
type PromiseRejectFunc = (err: unknown) => void;
type PromiseFunc<T> = (resolve: PromiseFulfillFunc<T>, reject: PromiseRejectFunc) => void;
type PromiseResult<T> ={ type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
// wippidy-wine, this code is now mine :D // wippidy-wine, this code is now mine :D
type Awaited<T> = type Awaited<T> =
@ -46,8 +41,7 @@ type IteratorReturnResult<TReturn> =
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>; type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Thenable<T> { interface Thenable<T> {
then<NextT>(onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>; then<NextT = void>(onFulfilled?: (val: T) => NextT, onRejected?: (err: any) => NextT): Promise<Awaited<NextT>>;
then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>;
} }
interface RegExpResultIndices extends Array<[number, number]> { interface RegExpResultIndices extends Array<[number, number]> {
@ -464,14 +458,19 @@ interface SymbolConstructor {
readonly asyncIterator: unique symbol; readonly asyncIterator: unique symbol;
} }
interface Promise<T> extends Thenable<T> { interface Promise<T> extends Thenable<T> {
catch(func: PromiseRejectFunc): Promise<T>; catch<ResT = void>(func: (err: unknown) => ResT): Promise<ResT>;
finally(func: () => void): Promise<T>; finally(func: () => void): Promise<T>;
constructor: PromiseConstructor;
} }
interface PromiseConstructor { interface PromiseConstructorLike {
new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Thenable<Awaited<T>>;
}
interface PromiseConstructor extends PromiseConstructorLike {
prototype: Promise<any>; prototype: Promise<any>;
new <T>(func: PromiseFunc<T>): Promise<Awaited<T>>; new <T>(func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Promise<Awaited<T>>;
resolve<T>(val: T): Promise<Awaited<T>>; resolve<T>(val: T): Promise<Awaited<T>>;
reject(val: any): Promise<never>; reject(val: any): Promise<never>;
@ -544,6 +543,7 @@ declare var Error: ErrorConstructor;
declare var RangeError: RangeErrorConstructor; declare var RangeError: RangeErrorConstructor;
declare var TypeError: TypeErrorConstructor; declare var TypeError: TypeErrorConstructor;
declare var SyntaxError: SyntaxErrorConstructor; declare var SyntaxError: SyntaxErrorConstructor;
declare var self: typeof globalThis;
declare class Map<KeyT, ValueT> { declare class Map<KeyT, ValueT> {
public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>; public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>;

View File

@ -1,4 +1,4 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript;
public class Buffer { public class Buffer {
private byte[] data; private byte[] data;
@ -24,6 +24,10 @@ public class Buffer {
return n; return n;
} }
public void append(byte b) {
write(length, new byte[] { b });
}
public byte[] data() { public byte[] data() {
var res = new byte[length]; var res = new byte[length];
System.arraycopy(this.data, 0, res, 0, length); System.arraycopy(this.data, 0, res, 0, length);
@ -38,4 +42,12 @@ public class Buffer {
this.length = data.length; this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length); System.arraycopy(data, 0, this.data, 0, data.length);
} }
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
} }

View File

@ -71,4 +71,23 @@ public class Location implements Comparable<Location> {
this.start = start; this.start = start;
this.filename = filename; this.filename = filename;
} }
public static Location parse(String raw) {
int i0 = -1, i1 = -1;
for (var i = raw.length() - 1; i >= 0; i--) {
if (raw.charAt(i) == ':') {
if (i1 == -1) i1 = i;
else if (i0 == -1) {
i0 = i;
break;
}
}
}
return new Location(
Integer.parseInt(raw.substring(i0 + 1, i1)),
Integer.parseInt(raw.substring(i1 + 1)),
Filename.parse(raw.substring(0, i0))
);
}
} }

View File

@ -11,6 +11,8 @@ import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.debug.DebugServer; import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger; import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
@ -101,11 +103,11 @@ public class Main {
private static void initEnv() { private static void initEnv() {
environment = Internals.apply(environment); environment = Internals.apply(environment);
environment.global.define("exit", _ctx -> { environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
exited = true; exited = true;
throw new InterruptException(); throw new InterruptException();
}); }));
environment.global.define("go", _ctx -> { environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
try { try {
var f = Path.of("do.js"); var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
@ -114,7 +116,7 @@ public class Main {
catch (IOException e) { catch (IOException e) {
throw new EngineException("Couldn't open do.js"); throw new EngineException("Couldn't open do.js");
} }
}); }));
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
@ -127,7 +129,10 @@ public class Main {
private static void initTypescript() { private static void initTypescript() {
try { try {
var tsEnv = Internals.apply(new Environment(null, null, null)); var tsEnv = Internals.apply(new Environment(null, null, null));
tsEnv.stackVisible = false;
tsEnv.global.define(null, "module", false, new ObjectValue());
var bsEnv = Internals.apply(new Environment(null, null, null)); var bsEnv = Internals.apply(new Environment(null, null, null));
bsEnv.stackVisible = false;
engine.pushMsg( engine.pushMsg(
false, new Context(engine, tsEnv), false, new Context(engine, tsEnv),
@ -140,7 +145,7 @@ public class Main {
engine.pushMsg( engine.pushMsg(
false, ctx, false, ctx,
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await(); ).await();
} }

View File

@ -15,22 +15,13 @@ public class Reading {
} }
public static String streamToString(InputStream in) { public static String streamToString(InputStream in) {
try { try { return new String(in.readAllBytes()); }
StringBuilder out = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
for(var line = br.readLine(); line != null; line = br.readLine()) {
out.append(line).append('\n');
}
br.close();
return out.toString();
}
catch (Throwable e) { throw new UncheckedException(e); } catch (Throwable e) { throw new UncheckedException(e); }
} }
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/assets/" + name);
}
public static String resourceToString(String name) { public static String resourceToString(String name) {
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); return streamToString(resourceToStream(name));
if (str == null) return null;
return streamToString(str);
} }
} }

View File

@ -0,0 +1,21 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.engine.values.Values;
public final class CalculateResult {
public final boolean exists;
public final Object value;
public final boolean isTruthy() {
return exists && Values.toBoolean(value);
}
public CalculateResult(Object value) {
this.exists = true;
this.value = value;
}
public CalculateResult() {
this.exists = false;
this.value = null;
}
}

View File

@ -1,15 +1,20 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.Vector; import java.util.Vector;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.CodeFunction;
public class CompileTarget { public class CompileTarget {
public final Vector<Instruction> target = new Vector<>(); public final Vector<Instruction> target = new Vector<>();
public final Map<Long, FunctionBody> functions; public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints; public final TreeSet<Location> breakpoints;
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
public Instruction add(Instruction instr) { public Instruction add(Instruction instr) {
target.add(instr); target.add(instr);
@ -18,19 +23,42 @@ public class CompileTarget {
public Instruction set(int i, Instruction instr) { public Instruction set(int i, Instruction instr) {
return target.set(i, instr); return target.set(i, instr);
} }
public void setDebug(int i) { public void setDebug(int i, BreakpointType type) {
breakpoints.add(target.get(i).location); var instr = target.get(i);
instr.breakpoint = type;
if (type == BreakpointType.NONE) {
breakpoints.remove(target.get(i).location);
bpToInstr.remove(instr.location, instr);
}
else {
breakpoints.add(target.get(i).location);
var old = bpToInstr.put(instr.location, instr);
if (old != null) old.breakpoint = BreakpointType.NONE;
}
} }
public void setDebug() { public void setDebug(BreakpointType type) {
setDebug(target.size() - 1); setDebug(target.size() - 1, type);
} }
public Instruction get(int i) { public Instruction get(int i) {
return target.get(i); return target.get(i);
} }
public int size() { return target.size(); } public int size() { return target.size(); }
public Location lastLoc(Location fallback) {
if (target.size() == 0) return fallback;
else return target.get(target.size() - 1).location;
}
public Instruction[] array() { return target.toArray(Instruction[]::new); } public Instruction[] array() { return target.toArray(Instruction[]::new); }
public FunctionBody body() {
return functions.get(0l);
}
public CodeFunction func(Environment env) {
return new CodeFunction(env, "", body());
}
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) { public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions; this.functions = functions;
this.breakpoints = breakpoints; this.breakpoints = breakpoints;

View File

@ -1,66 +1,54 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.Vector; import java.util.Vector;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.control.ContinueStatement; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement { public class CompoundStatement extends Statement {
public final Statement[] statements; public final Statement[] statements;
public final boolean separateFuncs;
public Location end; public Location end;
@Override public boolean pure() {
for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override @Override
public void declare(ScopeRecord varsScope) { public void declare(ScopeRecord varsScope) {
for (var stm : statements) { for (var stm : statements) stm.declare(varsScope);
stm.declare(varsScope);
}
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
for (var stm : statements) { List<Statement> statements = new Vector<Statement>();
if (stm instanceof FunctionStatement) { if (separateFuncs) for (var stm : this.statements) {
((FunctionStatement)stm).compile(target, scope, null, true); if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
target.add(Instruction.discard()); stm.compile(target, scope, false);
} }
else statements.add(stm);
}
else statements = List.of(this.statements);
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
} }
for (var i = 0; i < statements.length; i++) { if (!polluted && pollute) {
var stm = statements[i]; target.add(Instruction.loadValue(loc(), null));
if (stm instanceof FunctionStatement) continue;
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
else stm.compileWithDebug(target, scope, pollute);
} }
if (end != null) {
target.add(Instruction.nop().locate(end));
target.setDebug();
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(statements.length);
for (var i = 0; i < statements.length; i++) {
var stm = statements[i].optimize();
if (i < statements.length - 1 && stm.pure()) continue;
res.add(stm);
if (
stm instanceof ContinueStatement ||
stm instanceof ReturnStatement ||
stm instanceof ThrowStatement ||
stm instanceof ContinueStatement
) break;
}
if (res.size() == 1) return res.get(0);
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
} }
public CompoundStatement setEnd(Location loc) { public CompoundStatement setEnd(Location loc) {
@ -68,8 +56,9 @@ public class CompoundStatement extends Statement {
return this; return this;
} }
public CompoundStatement(Location loc, Statement ...statements) { public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
super(loc); super(loc);
this.separateFuncs = separateFuncs;
this.statements = statements; this.statements = statements;
} }
} }

View File

@ -3,13 +3,25 @@ package me.topchetoeu.jscript.compilation;
public class FunctionBody { public class FunctionBody {
public final Instruction[] instructions; public final Instruction[] instructions;
public final String[] captureNames, localNames; public final String[] captureNames, localNames;
public final int localsN, argsN;
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) { public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions; this.instructions = instructions;
this.captureNames = captureNames; this.captureNames = captureNames;
this.localNames = localNames; this.localNames = localNames;
} }
public FunctionBody(Instruction[] instructions) { public FunctionBody(int localsN, int argsN, Instruction[] instructions) {
this.argsN = argsN;
this.localsN = localsN;
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
public FunctionBody(Instruction... instructions) {
this.argsN = 0;
this.localsN = 2;
this.instructions = instructions; this.instructions = instructions;
this.captureNames = new String[0]; this.captureNames = new String[0];
this.localNames = new String[0]; this.localNames = new String[0];

View File

@ -10,7 +10,8 @@ public class Instruction {
THROW, THROW,
THROW_SYNTAX, THROW_SYNTAX,
DELETE, DELETE,
TRY, TRY_START,
TRY_END,
NOP, NOP,
CALL, CALL,
@ -33,7 +34,6 @@ public class Instruction {
LOAD_REGEX, LOAD_REGEX,
DUP, DUP,
MOVE,
STORE_VAR, STORE_VAR,
STORE_MEMBER, STORE_MEMBER,
@ -45,51 +45,30 @@ public class Instruction {
TYPEOF, TYPEOF,
OPERATION; OPERATION;
// TYPEOF, }
// INSTANCEOF(true), public static enum BreakpointType {
// IN(true), NONE,
STEP_OVER,
STEP_IN;
// MULTIPLY(true), public boolean shouldStepIn() {
// DIVIDE(true), return this != NONE;
// MODULO(true), }
// ADD(true), public boolean shouldStepOver() {
// SUBTRACT(true), return this == STEP_OVER;
}
// USHIFT_RIGHT(true),
// SHIFT_RIGHT(true),
// SHIFT_LEFT(true),
// GREATER(true),
// LESS(true),
// GREATER_EQUALS(true),
// LESS_EQUALS(true),
// LOOSE_EQUALS(true),
// LOOSE_NOT_EQUALS(true),
// EQUALS(true),
// NOT_EQUALS(true),
// AND(true),
// OR(true),
// XOR(true),
// NEG(true),
// POS(true),
// NOT(true),
// INVERSE(true);
// final boolean isOperation;
// private Type(boolean isOperation) {
// this.isOperation = isOperation;
// }
// private Type() {
// this(false);
// }
} }
public final Type type; public final Type type;
public final Object[] params; public final Object[] params;
public Location location; public Location location;
public BreakpointType breakpoint = BreakpointType.NONE;
public Instruction setDbgData(Instruction other) {
this.location = other.location;
this.breakpoint = other.breakpoint;
return this;
}
public Instruction locate(Location loc) { public Instruction locate(Location loc) {
this.location = loc; this.location = loc;
@ -129,26 +108,32 @@ public class Instruction {
this.params = params; this.params = params;
} }
public static Instruction tryInstr(int n, int catchN, int finallyN) { public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
return new Instruction(null, Type.TRY, n, catchN, finallyN); return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
} }
public static Instruction throwInstr() { public static Instruction tryEnd(Location loc) {
return new Instruction(null, Type.THROW); return new Instruction(loc, Type.TRY_END);
} }
public static Instruction throwSyntax(SyntaxException err) { public static Instruction throwInstr(Location loc) {
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage()); return new Instruction(loc, Type.THROW);
} }
public static Instruction delete() { public static Instruction throwSyntax(Location loc, SyntaxException err) {
return new Instruction(null, Type.DELETE); return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
} }
public static Instruction ret() { public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(null, Type.RETURN); return new Instruction(loc, Type.THROW_SYNTAX, err);
} }
public static Instruction debug() { public static Instruction delete(Location loc) {
return new Instruction(null, Type.NOP, "debug"); return new Instruction(loc, Type.DELETE);
}
public static Instruction ret(Location loc) {
return new Instruction(loc, Type.RETURN);
}
public static Instruction debug(Location loc) {
return new Instruction(loc, Type.NOP, "debug");
} }
public static Instruction nop(Object ...params) { public static Instruction nop(Location loc, Object ...params) {
for (var param : params) { for (var param : params) {
if (param instanceof String) continue; if (param instanceof String) continue;
if (param instanceof Boolean) continue; if (param instanceof Boolean) continue;
@ -158,109 +143,104 @@ public class Instruction {
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls."); throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
} }
return new Instruction(null, Type.NOP, params); return new Instruction(loc, Type.NOP, params);
} }
public static Instruction call(int argn) { public static Instruction call(Location loc, int argn) {
return new Instruction(null, Type.CALL, argn); return new Instruction(loc, Type.CALL, argn);
} }
public static Instruction callNew(int argn) { public static Instruction callNew(Location loc, int argn) {
return new Instruction(null, Type.CALL_NEW, argn); return new Instruction(loc, Type.CALL_NEW, argn);
} }
public static Instruction jmp(int offset) { public static Instruction jmp(Location loc, int offset) {
return new Instruction(null, Type.JMP, offset); return new Instruction(loc, Type.JMP, offset);
} }
public static Instruction jmpIf(int offset) { public static Instruction jmpIf(Location loc, int offset) {
return new Instruction(null, Type.JMP_IF, offset); return new Instruction(loc, Type.JMP_IF, offset);
} }
public static Instruction jmpIfNot(int offset) { public static Instruction jmpIfNot(Location loc, int offset) {
return new Instruction(null, Type.JMP_IFN, offset); return new Instruction(loc, Type.JMP_IFN, offset);
} }
public static Instruction loadValue(Object val) { public static Instruction loadValue(Location loc, Object val) {
return new Instruction(null, Type.LOAD_VALUE, val); return new Instruction(loc, Type.LOAD_VALUE, val);
} }
public static Instruction makeVar(String name) { public static Instruction makeVar(Location loc, String name) {
return new Instruction(null, Type.MAKE_VAR, name); return new Instruction(loc, Type.MAKE_VAR, name);
} }
public static Instruction loadVar(Object i) { public static Instruction loadVar(Location loc, Object i) {
return new Instruction(null, Type.LOAD_VAR, i); return new Instruction(loc, Type.LOAD_VAR, i);
} }
public static Instruction loadGlob() { public static Instruction loadGlob(Location loc) {
return new Instruction(null, Type.LOAD_GLOB); return new Instruction(loc, Type.LOAD_GLOB);
} }
public static Instruction loadMember() { public static Instruction loadMember(Location loc) {
return new Instruction(null, Type.LOAD_MEMBER); return new Instruction(loc, Type.LOAD_MEMBER);
} }
public static Instruction loadMember(Object key) { public static Instruction loadMember(Location loc, Object key) {
if (key instanceof Number) key = ((Number)key).doubleValue(); if (key instanceof Number) key = ((Number)key).doubleValue();
return new Instruction(null, Type.LOAD_VAL_MEMBER, key); return new Instruction(loc, Type.LOAD_VAL_MEMBER, key);
} }
public static Instruction loadRegex(String pattern, String flags) { public static Instruction loadRegex(Location loc, String pattern, String flags) {
return new Instruction(null, Type.LOAD_REGEX, pattern, flags); return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
} }
public static Instruction loadFunc(long id, int varN, int len, int[] captures) { public static Instruction loadFunc(Location loc, long id, int[] captures) {
var args = new Object[3 + captures.length]; var args = new Object[1 + captures.length];
args[0] = id; args[0] = id;
args[1] = varN; for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
args[2] = len; return new Instruction(loc, Type.LOAD_FUNC, args);
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
return new Instruction(null, Type.LOAD_FUNC, args);
} }
public static Instruction loadObj() { public static Instruction loadObj(Location loc) {
return new Instruction(null, Type.LOAD_OBJ); return new Instruction(loc, Type.LOAD_OBJ);
} }
public static Instruction loadArr(int count) { public static Instruction loadArr(Location loc, int count) {
return new Instruction(null, Type.LOAD_ARR, count); return new Instruction(loc, Type.LOAD_ARR, count);
} }
public static Instruction dup() { public static Instruction dup(Location loc) {
return new Instruction(null, Type.DUP, 0, 1); return new Instruction(loc, Type.DUP, 1);
} }
public static Instruction dup(int count, int offset) { public static Instruction dup(Location loc, int count) {
return new Instruction(null, Type.DUP, offset, count); return new Instruction(loc, Type.DUP, count);
}
public static Instruction move(int count, int offset) {
return new Instruction(null, Type.MOVE, offset, count);
} }
public static Instruction storeSelfFunc(int i) { public static Instruction storeSelfFunc(Location loc, int i) {
return new Instruction(null, Type.STORE_SELF_FUNC, i); return new Instruction(loc, Type.STORE_SELF_FUNC, i);
} }
public static Instruction storeVar(Object i) { public static Instruction storeVar(Location loc, Object i) {
return new Instruction(null, Type.STORE_VAR, i, false); return new Instruction(loc, Type.STORE_VAR, i, false);
} }
public static Instruction storeVar(Object i, boolean keep) { public static Instruction storeVar(Location loc, Object i, boolean keep) {
return new Instruction(null, Type.STORE_VAR, i, keep); return new Instruction(loc, Type.STORE_VAR, i, keep);
} }
public static Instruction storeMember() { public static Instruction storeMember(Location loc) {
return new Instruction(null, Type.STORE_MEMBER, false); return new Instruction(loc, Type.STORE_MEMBER, false);
} }
public static Instruction storeMember(boolean keep) { public static Instruction storeMember(Location loc, boolean keep) {
return new Instruction(null, Type.STORE_MEMBER, keep); return new Instruction(loc, Type.STORE_MEMBER, keep);
} }
public static Instruction discard() { public static Instruction discard(Location loc) {
return new Instruction(null, Type.DISCARD); return new Instruction(loc, Type.DISCARD);
} }
public static Instruction typeof() { public static Instruction typeof(Location loc) {
return new Instruction(null, Type.TYPEOF); return new Instruction(loc, Type.TYPEOF);
} }
public static Instruction typeof(Object varName) { public static Instruction typeof(Location loc, Object varName) {
return new Instruction(null, Type.TYPEOF, varName); return new Instruction(loc, Type.TYPEOF, varName);
} }
public static Instruction keys(boolean forInFormat) { public static Instruction keys(Location loc, boolean forInFormat) {
return new Instruction(null, Type.KEYS, forInFormat); return new Instruction(loc, Type.KEYS, forInFormat);
} }
public static Instruction defProp() { public static Instruction defProp(Location loc) {
return new Instruction(null, Type.DEF_PROP); return new Instruction(loc, Type.DEF_PROP);
} }
public static Instruction operation(Operation op) { public static Instruction operation(Location loc, Operation op) {
return new Instruction(null, Type.OPERATION, op); return new Instruction(loc, Type.OPERATION, op);
} }
@Override @Override

View File

@ -1,20 +1,26 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public abstract class Statement { public abstract class Statement {
private Location _loc; private Location _loc;
public boolean pure() { return false; } public boolean pure() { return false; }
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
public void declare(ScopeRecord varsScope) { } public void declare(ScopeRecord varsScope) { }
public Statement optimize() { return this; }
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
int start = target.size(); int start = target.size();
compile(target, scope, pollute); compile(target, scope, pollute);
if (target.size() != start) target.setDebug(start);
if (target.size() != start) {
target.get(start).locate(loc());
target.setDebug(start, type);
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.NONE);
} }
public Location loc() { return _loc; } public Location loc() { return _loc; }

View File

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

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -32,23 +33,16 @@ public class VariableDeclareStatement extends Statement {
for (var entry : values) { for (var entry : values) {
if (entry.name == null) continue; if (entry.name == null) continue;
var key = scope.getKey(entry.name); var key = scope.getKey(entry.name);
int start = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location)); if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
if (entry.value instanceof FunctionStatement) { if (entry.value != null) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false); FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
target.add(Instruction.storeVar(key).locate(entry.location)); target.add(Instruction.storeVar(entry.location, key));
} }
else if (entry.value != null) {
entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(entry.location));
}
if (target.size() != start) target.setDebug(start);
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public VariableDeclareStatement(Location loc, List<Pair> values) { public VariableDeclareStatement(Location loc, List<Pair> values) {

View File

@ -11,8 +11,8 @@ public class BreakStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop("break", label).locate(loc())); target.add(Instruction.nop(loc(), "break", label));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public BreakStatement(Location loc, String label) { public BreakStatement(Location loc, String label) {

View File

@ -11,8 +11,8 @@ public class ContinueStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop("cont", label).locate(loc())); target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public ContinueStatement(Location loc, String label) { public ContinueStatement(Location loc, String label) {

View File

@ -9,8 +9,8 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DebugStatement extends Statement { public class DebugStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.debug().locate(loc())); target.add(Instruction.debug(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public DebugStatement(Location loc) { public DebugStatement(Location loc) {

View File

@ -15,8 +15,8 @@ public class DeleteStatement extends Statement {
value.compile(target, scope, true); value.compile(target, scope, true);
key.compile(target, scope, true); key.compile(target, scope, true);
target.add(Instruction.delete().locate(loc())); target.add(Instruction.delete(loc()));
if (!pollute) target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), true));
} }
public DeleteStatement(Location loc, Statement key, Statement value) { public DeleteStatement(Location loc, Statement key, Statement value) {

View File

@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class DoWhileStatement extends Statement { public class DoWhileStatement extends Statement {
public final Statement condition, body; public final Statement condition, body;
@ -20,54 +18,14 @@ public class DoWhileStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (condition instanceof ConstantStatement) {
int start = target.size();
body.compile(target, scope, false);
int end = target.size();
if (Values.toBoolean(((ConstantStatement)condition).value)) {
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
}
else {
target.add(Instruction.jmp(start - end).locate(loc()));
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
}
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
return;
}
int start = target.size(); int start = target.size();
body.compileWithDebug(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1); WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end).locate(loc())); target.add(Instruction.jmpIf(loc(), start - end));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof CompoundStatement) {
var comp = (CompoundStatement)b;
if (comp.statements.length > 0) {
var last = comp.statements[comp.statements.length - 1];
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
if (last instanceof BreakStatement) {
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
return new CompoundStatement(loc());
}
}
}
else if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
else return new DoWhileStatement(loc(), label, cond, b);
} }
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {

View File

@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -25,39 +26,38 @@ public class ForInStatement extends Statement {
var key = scope.getKey(varName); var key = scope.getKey(varName);
int first = target.size(); int first = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key)); if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
if (varValue != null) { if (varValue != null) {
varValue.compile(target, scope, true); varValue.compile(target, scope, true);
target.add(Instruction.storeVar(scope.getKey(varName))); target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
} }
object.compileWithDebug(target, scope, true); object.compile(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(true)); target.add(Instruction.keys(loc(), true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup()); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(null)); target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.operation(Operation.EQUALS)); target.add(Instruction.operation(loc(), Operation.EQUALS));
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(loc()));
target.add(Instruction.loadMember("value").locate(varLocation)); target.add(Instruction.loadMember(varLocation, "value"));
target.setDebug(); target.add(Instruction.storeVar(object.loc(), key));
target.add(Instruction.storeVar(key)); target.setDebug(BreakpointType.STEP_OVER);
body.compileWithDebug(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end)); target.add(Instruction.jmp(loc(), start - end));
target.add(Instruction.discard()); target.add(Instruction.discard(loc()));
target.set(mid, Instruction.jmpIf(end - mid + 1)); target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
target.get(first).locate(loc()); target.get(first).locate(loc());
target.setDebug(first);
} }
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {

View File

@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class ForStatement extends Statement { public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body; public final Statement declaration, assignment, condition, body;
@ -20,58 +18,22 @@ public class ForStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false); declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compile(target, scope, false);
int mid = target.size();
assignment.compileWithDebug(target, scope, false);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}
return;
}
int start = target.size(); int start = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size(); int beforeAssign = target.size();
assignment.compileWithDebug(target, scope, false); assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@Override
public Statement optimize() {
var decl = declaration.optimize();
var asgn = assignment.optimize();
var cond = condition.optimize();
var b = body.optimize();
if (asgn.pure()) {
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
else return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, b)
).optimize();
}
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
);
else if (b instanceof BreakStatement) return decl;
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
else return new ForStatement(loc(), label, decl, cond, asgn, b);
} }
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
@ -82,14 +44,4 @@ public class ForStatement extends Statement {
this.assignment = assignment; this.assignment = assignment;
this.body = body; this.body = body;
} }
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
return new CompoundStatement(loc,
declaration,
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
body,
increment
))
);
}
} }

View File

@ -2,13 +2,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement; import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class IfStatement extends Statement { public class IfStatement extends Statement {
public final Statement condition, body, elseBody; public final Statement condition, body, elseBody;
@ -19,53 +16,31 @@ public class IfStatement extends Statement {
if (elseBody != null) elseBody.declare(globScope); if (elseBody != null) elseBody.declare(globScope);
} }
@Override @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { condition.compile(target, scope, true, breakpoint);
if (condition instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)condition).value)) {
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);
}
else {
body.compileWithDebug(target, scope, pollute);
}
return;
}
condition.compile(target, scope, true);
if (elseBody == null) { if (elseBody == null) {
int i = target.size(); int i = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compile(target, scope, pollute, breakpoint);
int endI = target.size(); int endI = target.size();
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc())); target.set(i, Instruction.jmpIfNot(loc(), endI - i));
} }
else { else {
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compile(target, scope, pollute, breakpoint);
target.add(Instruction.nop()); target.add(Instruction.nop(null));
int mid = target.size(); int mid = target.size();
elseBody.compileWithDebug(target, scope, pollute); elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size(); int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc())); target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc())); target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
} }
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@Override compile(target, scope, pollute, BreakpointType.STEP_IN);
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
var e = elseBody == null ? null : elseBody.optimize();
if (b.pure()) b = new CompoundStatement(null);
if (e != null && e.pure()) e = null;
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
else return new IfStatement(loc(), cond, b, e);
} }
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {

View File

@ -11,9 +11,9 @@ public class ReturnStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value == null) target.add(Instruction.loadValue(null).locate(loc())); if (value == null) target.add(Instruction.loadValue(loc(), null));
else value.compile(target, scope, true); else value.compile(target, scope, true);
target.add(Instruction.ret().locate(loc())); target.add(Instruction.ret(loc()));
} }
public ReturnStatement(Location loc, Statement value) { public ReturnStatement(Location loc, Statement value) {

View File

@ -6,6 +6,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -36,44 +37,42 @@ public class SwitchStatement extends Statement {
var caseToStatement = new HashMap<Integer, Integer>(); var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>(); var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, scope, true); value.compile(target, scope, true, BreakpointType.STEP_OVER);
for (var ccase : cases) { for (var ccase : cases) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
ccase.value.compile(target, scope, true); ccase.value.compile(target, scope, true);
target.add(Instruction.operation(Operation.EQUALS).locate(loc())); target.add(Instruction.operation(loc(), Operation.EQUALS));
caseToStatement.put(target.size(), ccase.statementI); caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop().locate(ccase.value.loc())); target.add(Instruction.nop(null));
} }
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
for (var stm : body) { for (var stm : body) {
statementToIndex.put(statementToIndex.size(), target.size()); statementToIndex.put(statementToIndex.size(), target.size());
stm.compileWithDebug(target, scope, false); stm.compile(target, scope, false, BreakpointType.STEP_OVER);
} }
int end = target.size(); int end = target.size();
target.add(Instruction.discard().locate(loc())); target.add(Instruction.discard(loc()));
if (pollute) target.add(Instruction.loadValue(null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start).locate(loc())); if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)).locate(loc()); else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
var instr = target.get(i); var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
target.set(i, Instruction.jmp(end - i).locate(instr.location)); target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
} }
} }
for (var el : caseToStatement.entrySet()) { for (var el : caseToStatement.entrySet()) {
var loc = target.get(el.getKey()).location;
var i = statementToIndex.get(el.getValue()); var i = statementToIndex.get(el.getValue());
if (i == null) i = end; if (i == null) i = end;
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc)); target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
target.setDebug(el.getKey());
} }
} }

View File

@ -12,7 +12,7 @@ public class ThrowStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.throwInstr().locate(loc())); target.add(Instruction.throwInstr(loc()));
} }
public ThrowStatement(Location loc, Statement value) { public ThrowStatement(Location loc, Statement value) {

View File

@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord; import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -22,31 +23,32 @@ public class TryStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
target.add(Instruction.nop()); target.add(Instruction.nop(null));
int start = target.size(), tryN, catchN = -1, finN = -1; int start = target.size(), catchStart = -1, finallyStart = -1;
tryBody.compile(target, scope, false); tryBody.compile(target, scope, false);
tryN = target.size() - start; target.add(Instruction.tryEnd(loc()));
if (catchBody != null) { if (catchBody != null) {
int tmp = target.size(); catchStart = target.size() - start;
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope; var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
local.define(name, true); local.define(name, true);
catchBody.compile(target, scope, false); catchBody.compile(target, scope, false);
local.undefine(); local.undefine();
catchN = target.size() - tmp; target.add(Instruction.tryEnd(loc()));
} }
if (finallyBody != null) { if (finallyBody != null) {
int tmp = target.size(); finallyStart = target.size() - start;
finallyBody.compile(target, scope, false); finallyBody.compile(target, scope, false);
finN = target.size() - tmp; target.add(Instruction.tryEnd(loc()));
} }
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc())); target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); target.setDebug(start - 1, BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {

View File

@ -3,13 +3,10 @@ package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class WhileStatement extends Statement { public class WhileStatement extends Statement {
public final Statement condition, body; public final Statement condition, body;
@ -21,43 +18,19 @@ public class WhileStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compile(target, scope, false);
int end = target.size();
replaceBreaks(target, label, start, end, start, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
return;
}
}
int start = target.size(); int start = target.size();
condition.compile(target, scope, true); condition.compile(target, scope, true);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
replaceBreaks(target, label, mid + 1, end, start, end + 1); replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
else return new WhileStatement(loc(), label, cond, b);
} }
public WhileStatement(Location loc, String label, Statement condition, Statement body) { public WhileStatement(Location loc, String label, Statement condition, Statement body) {
@ -71,23 +44,11 @@ public class WhileStatement extends Statement {
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
var instr = target.get(i); var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(continuePoint - i)); target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
target.get(i).location = instr.location;
} }
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(breakPoint - i)); target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
target.get(i).location = instr.location;
} }
} }
} }
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
// return new CompoundStatement(loc,
// declaration,
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
// body,
// increment
// ))
// );
// }
} }

View File

@ -1,4 +1,4 @@
package me.topchetoeu.jscript.compilation.control; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
@ -9,23 +9,29 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ArrayStatement extends Statement { public class ArrayStatement extends Statement {
public final Statement[] statements; public final Statement[] statements;
@Override @Override public boolean pure() {
public boolean pure() { return true; } for (var stm : statements) {
if (!stm.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(statements.length).locate(loc())); target.add(Instruction.loadArr(loc(), statements.length));
var i = 0;
for (var el : statements) { for (var i = 0; i < statements.length; i++) {
var el = statements[i];
if (el != null) { if (el != null) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(i).locate(loc())); target.add(Instruction.loadValue(loc(), i));
el.compile(target, scope, true); el.compile(target, scope, true);
target.add(Instruction.storeMember().locate(loc())); target.add(Instruction.storeMember(loc()));
} }
i++;
} }
if (!pollute) target.add(Instruction.discard().locate(loc()));
if (!pollute) target.add(Instruction.discard(loc()));
} }
public ArrayStatement(Location loc, Statement[] statements) { public ArrayStatement(Location loc, Statement[] statements) {

View File

@ -4,36 +4,47 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CallStatement extends Statement { public class CallStatement extends Statement {
public final Statement func; public final Statement func;
public final Statement[] args; public final Statement[] args;
public final boolean isNew;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
if (func instanceof IndexStatement) { if (isNew) func.compile(target, scope, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true, true); ((IndexStatement)func).compile(target, scope, true, true);
} }
else { else {
target.add(Instruction.loadValue(null).locate(loc())); target.add(Instruction.loadValue(loc(), null));
func.compile(target, scope, true); func.compile(target, scope, true);
} }
for (var arg : args) arg.compile(target, scope, true); for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.call(args.length).locate(loc())); if (isNew) target.add(Instruction.callNew(loc(), args.length));
target.setDebug(); else target.add(Instruction.call(loc(), args.length));
if (!pollute) target.add(Instruction.discard().locate(loc())); target.setDebug(type);
if (!pollute) target.add(Instruction.discard(loc()));
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.STEP_IN);
} }
public CallStatement(Location loc, Statement func, Statement ...args) { public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc); super(loc);
this.isNew = isNew;
this.func = func; this.func = func;
this.args = args; this.args = args;
} }
public CallStatement(Location loc, Statement obj, Object key, Statement ...args) { public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) {
super(loc); super(loc);
this.isNew = isNew;
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key)); this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
this.args = args; this.args = args;
} }

View File

@ -16,10 +16,10 @@ public class ChangeStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true); value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
else if (postfix) { else if (postfix) {
target.add(Instruction.loadValue(addAmount)); target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(Operation.SUBTRACT)); target.add(Instruction.operation(loc(), Operation.SUBTRACT));
} }
} }

View File

@ -1,38 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement[] values;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) {
values[i].compileWithDebug(target, scope, i == values.length - 1 && pollute);
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(values.length);
for (var i = 0; i < values.length; i++) {
var stm = values[i].optimize();
if (i < values.length - 1 && stm.pure()) continue;
res.add(stm);
}
if (res.size() == 1) return res.get(0);
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
}
public CommaStatement(Location loc, Statement ...args) {
super(loc);
this.values = args;
}
}

View File

@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ConstantStatement extends Statement { public class ConstantStatement extends Statement {
public final Object value; public final Object value;
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadValue(value).locate(loc())); if (pollute) target.add(Instruction.loadValue(loc(), value));
} }
public ConstantStatement(Location loc, Object val) { public ConstantStatement(Location loc, Object val) {

View File

@ -1,23 +1,20 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.values.ConstantStatement; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DiscardStatement extends Statement { public class DiscardStatement extends Statement {
public final Statement value; public final Statement value;
@Override public boolean pure() { return value.pure(); }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false); value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new DiscardStatement(loc(), val);
} }
public DiscardStatement(Location loc, Statement val) { public DiscardStatement(Location loc, Statement val) {

View File

@ -8,23 +8,25 @@ import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
public class FunctionStatement extends Statement { public class FunctionStatement extends Statement {
public final CompoundStatement body; public final CompoundStatement body;
public final String name; public final String varName;
public final String[] args; public final String[] args;
public final boolean statement;
public final Location end;
private static Random rand = new Random(); private static Random rand = new Random();
@Override @Override public boolean pure() { return varName == null && statement; }
public boolean pure() { return name == null; }
@Override @Override
public void declare(ScopeRecord scope) { public void declare(ScopeRecord scope) {
if (name != null) scope.define(name); if (varName != null && statement) scope.define(varName);
} }
public static void checkBreakAndCont(CompileTarget target, int start) { public static void checkBreakAndCont(CompileTarget target, int start) {
@ -40,73 +42,98 @@ public class FunctionStatement extends Statement {
} }
} }
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) { private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) { for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])){ if (args[i].equals(args[j])) {
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."))); throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
return;
} }
} }
} }
var subscope = scope.child();
int start = target.size(); var id = rand.nextLong();
var funcTarget = new CompileTarget(target.functions, target.breakpoints); var subscope = scope.child();
var subtarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this"); subscope.define("this");
var argsVar = subscope.define("arguments"); var argsVar = subscope.define("arguments");
if (args.length > 0) { if (args.length > 0) {
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
funcTarget.add(Instruction.loadVar(argsVar).locate(loc())); subtarget.add(Instruction.loadVar(loc(), argsVar));
funcTarget.add(Instruction.loadMember(i).locate(loc())); subtarget.add(Instruction.loadMember(loc(), i));
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc())); subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
} }
} }
if (!isStatement && this.name != null) { if (!statement && this.varName != null) {
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name))); subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
subtarget.setDebug(bp);
} }
body.declare(subscope); body.declare(subscope);
body.compile(funcTarget, subscope, false); body.compile(subtarget, subscope, false);
funcTarget.add(Instruction.ret().locate(loc())); subtarget.add(Instruction.ret(end));
checkBreakAndCont(funcTarget, start); checkBreakAndCont(subtarget, 0);
var id = rand.nextLong(); if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
target.functions.put(id, new FunctionBody(
subscope.localsCount(), args.length,
subtarget.array(), subscope.captures(), subscope.locals()
));
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); return id;
target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
if (name == null) name = this.name;
if (name != null) {
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue("name").locate(loc()));
target.add(Instruction.loadValue(name).locate(loc()));
target.add(Instruction.storeMember().locate(loc()));
}
if (this.name != null && isStatement) {
var key = scope.getKey(this.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
}
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, null, false);
if (!pollute) target.add(Instruction.discard().locate(loc()));
} }
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement;
var hasName = name != null;
compileBody(target, scope, pollute || hasVar || hasName, bp);
if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), "name"));
target.add(Instruction.loadValue(loc(), name));
target.add(Instruction.storeMember(loc()));
}
if (hasVar) {
var key = scope.getKey(this.varName);
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
compile(target, scope, pollute, name, BreakpointType.NONE);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) {
compile(target, scope, pollute, (String)null, bp);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, (String)null, BreakpointType.NONE);
}
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc); super(loc);
this.name = name;
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args; this.args = args;
this.body = body; this.body = body;
} }
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
else stm.compile(target, scope, pollute);
}
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
else stm.compile(target, scope, pollute, bp);
}
} }

View File

@ -7,12 +7,11 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class GlobalThisStatement extends Statement { public class GlobalThisStatement extends Statement {
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadGlob().locate(loc())); if (pollute) target.add(Instruction.loadGlob(loc()));
} }
public GlobalThisStatement(Location loc) { public GlobalThisStatement(Location loc) {

View File

@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -18,22 +19,22 @@ public class IndexAssignStatement extends Statement {
if (operation != null) { if (operation != null) {
object.compile(target, scope, true); object.compile(target, scope, true);
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.dup(2, 0).locate(loc())); target.add(Instruction.dup(loc(), 2));
target.add(Instruction.loadMember().locate(loc())); target.add(Instruction.loadMember(loc()));
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeMember(pollute).locate(loc())); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(); target.setDebug(BreakpointType.STEP_IN);
} }
else { else {
object.compile(target, scope, true); object.compile(target, scope, true);
index.compile(target, scope, true); index.compile(target, scope, true);
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.storeMember(pollute).locate(loc())); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(); target.setDebug(BreakpointType.STEP_IN);
} }
} }

View File

@ -5,6 +5,7 @@ import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@ -12,26 +13,23 @@ public class IndexStatement extends AssignableStatement {
public final Statement object; public final Statement object;
public final Statement index; public final Statement index;
@Override
public boolean pure() { return true; }
@Override @Override
public Statement toAssign(Statement val, Operation operation) { public Statement toAssign(Statement val, Operation operation) {
return new IndexAssignStatement(loc(), object, index, val, operation); return new IndexAssignStatement(loc(), object, index, val, operation);
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
object.compile(target, scope, true); object.compile(target, scope, true);
if (dupObj) target.add(Instruction.dup().locate(loc())); if (dupObj) target.add(Instruction.dup(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
target.setDebug(); target.setDebug(BreakpointType.STEP_IN);
return; return;
} }
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc())); target.add(Instruction.loadMember(loc()));
target.setDebug(); target.setDebug(BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {

View File

@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
public class LazyAndStatement extends Statement { public class LazyAndStatement extends Statement {
public final Statement first, second; public final Statement first, second;
@Override @Override public boolean pure() { return first.pure() && second.pure(); }
public boolean pure() {
return first.pure() && second.pure();
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@ -26,12 +23,12 @@ public class LazyAndStatement extends Statement {
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
} }
public LazyAndStatement(Location loc, Statement first, Statement second) { public LazyAndStatement(Location loc, Statement first, Statement second) {

View File

@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values;
public class LazyOrStatement extends Statement { public class LazyOrStatement extends Statement {
public final Statement first, second; public final Statement first, second;
@Override @Override public boolean pure() { return first.pure() && second.pure(); }
public boolean pure() {
return first.pure() && second.pure();
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
@ -26,12 +23,12 @@ public class LazyOrStatement extends Statement {
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIf(loc(), target.size() - start));
} }
public LazyOrStatement(Location loc, Statement first, Statement second) { public LazyOrStatement(Location loc, Statement first, Statement second) {

View File

@ -1,28 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class NewStatement extends Statement {
public final Statement func;
public final Statement[] args;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
func.compile(target, scope, true);
for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.callNew(args.length).locate(loc()));
target.setDebug();
}
public NewStatement(Location loc, Statement func, Statement ...args) {
super(loc);
this.func = func;
this.args = args;
}
}

View File

@ -14,17 +14,24 @@ public class ObjectStatement extends Statement {
public final Map<Object, FunctionStatement> getters; public final Map<Object, FunctionStatement> getters;
public final Map<Object, FunctionStatement> setters; public final Map<Object, FunctionStatement> setters;
@Override public boolean pure() {
for (var el : map.values()) {
if (!el.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadObj().locate(loc())); target.add(Instruction.loadObj(loc()));
for (var el : map.entrySet()) { for (var el : map.entrySet()) {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(el.getKey()).locate(loc())); target.add(Instruction.loadValue(loc(), el.getKey()));
var val = el.getValue(); var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false); FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
else val.compile(target, scope, true); target.add(Instruction.storeMember(loc()));
target.add(Instruction.storeMember().locate(loc()));
} }
var keys = new ArrayList<Object>(); var keys = new ArrayList<Object>();
@ -32,19 +39,19 @@ public class ObjectStatement extends Statement {
keys.addAll(setters.keySet()); keys.addAll(setters.keySet());
for (var key : keys) { for (var key : keys) {
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc())); if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
else target.add(Instruction.loadValue((Double)key).locate(loc())); else target.add(Instruction.loadValue(loc(), (Double)key));
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true); if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(null).locate(loc())); else target.add(Instruction.loadValue(loc(), null));
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true); if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(null).locate(loc())); else target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.defProp().locate(loc())); target.add(Instruction.defProp(loc()));
} }
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) { public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {

View File

@ -4,57 +4,29 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class OperationStatement extends Statement { public class OperationStatement extends Statement {
public final Statement[] args; public final Statement[] args;
public final Operation operation; public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) { for (var arg : args) {
arg.compile(target, scope, true); arg.compile(target, scope, true);
} }
if (pollute) target.add(Instruction.operation(operation).locate(loc())); if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard().locate(loc())); else target.add(Instruction.discard(loc()));
}
@Override
public boolean pure() {
for (var arg : args) {
if (!arg.pure()) return false;
}
return true;
}
@Override
public Statement optimize() {
var args = new Statement[this.args.length];
var allConst = true;
for (var i = 0; i < this.args.length; i++) {
args[i] = this.args[i].optimize();
if (!(args[i] instanceof ConstantStatement)) allConst = false;
}
if (allConst) {
var vals = new Object[this.args.length];
for (var i = 0; i < args.length; i++) {
vals[i] = ((ConstantStatement)args[i]).value;
}
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
}
return new OperationStatement(loc(), operation, args);
} }
public OperationStatement(Location loc, Operation operation, Statement ...args) { public OperationStatement(Location loc, Operation operation, Statement ...args) {

View File

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

View File

@ -4,44 +4,26 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.control.ArrayStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class TypeofStatement extends Statement { public class TypeofStatement extends Statement {
public final Statement value; public final Statement value;
@Override // Not really pure, since a variable from the global scope could be accessed,
public boolean pure() { return true; } // which could lead to code execution, that would get omitted
@Override public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value instanceof VariableStatement) { if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name); var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) { if (i instanceof String) {
target.add(Instruction.typeof((String)i).locate(loc())); target.add(Instruction.typeof(loc(), (String)i));
return; return;
} }
} }
value.compile(target, scope, pollute); value.compile(target, scope, pollute);
target.add(Instruction.typeof().locate(loc())); target.add(Instruction.typeof(loc()));
}
@Override
public Statement optimize() {
var val = value.optimize();
if (val instanceof ConstantStatement) {
return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value));
}
else if (
val instanceof ObjectStatement ||
val instanceof ArrayStatement ||
val instanceof GlobalThisStatement
) return new ConstantStatement(loc(), "object");
else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function");
return new TypeofStatement(loc(), val);
} }
public TypeofStatement(Location loc, Statement value) { public TypeofStatement(Location loc, Statement value) {

View File

@ -12,20 +12,20 @@ public class VariableAssignStatement extends Statement {
public final Statement value; public final Statement value;
public final Operation operation; public final Operation operation;
@Override public boolean pure() { return false; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name); var i = scope.getKey(name);
if (operation != null) { if (operation != null) {
target.add(Instruction.loadVar(i).locate(loc())); target.add(Instruction.loadVar(loc(), i));
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); FunctionStatement.compileWithName(value, target, scope, true, name);
else value.compile(target, scope, true); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.storeVar(loc(), i, pollute));
target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
else { else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); FunctionStatement.compileWithName(value, target, scope, true, name);
else value.compile(target, scope, true); target.add(Instruction.storeVar(loc(), i, pollute));
target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
} }

View File

@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableIndexStatement extends Statement { public class VariableIndexStatement extends Statement {
public final int index; public final int index;
@Override @Override public boolean pure() { return true; }
public boolean pure() { return true; }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadVar(index).locate(loc())); if (pollute) target.add(Instruction.loadVar(loc(), index));
} }
public VariableIndexStatement(Location loc, int i) { public VariableIndexStatement(Location loc, int i) {

View File

@ -11,8 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement { public class VariableStatement extends AssignableStatement {
public final String name; public final String name;
@Override @Override public boolean pure() { return false; }
public boolean pure() { return true; }
@Override @Override
public Statement toAssign(Statement val, Operation operation) { public Statement toAssign(Statement val, Operation operation) {
@ -22,8 +21,8 @@ public class VariableStatement extends AssignableStatement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var i = scope.getKey(name); var i = scope.getKey(name);
target.add(Instruction.loadVar(i).locate(loc())); target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public VariableStatement(Location loc, String name) { public VariableStatement(Location loc, String name) {

View File

@ -1,30 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
public class VoidStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value != null) value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new VoidStatement(loc(), val);
}
public VoidStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@ -1,19 +1,21 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.mapping.SourceMap;
public class Context { public class Context {
private final Stack<Environment> env = new Stack<>(); private final Stack<Environment> env = new Stack<>();
@ -34,24 +36,35 @@ public class Context {
public FunctionValue compile(Filename filename, String raw) { public FunctionValue compile(Filename filename, String raw) {
var env = environment(); var env = environment();
var transpiled = env.compile.call(this, null, raw, filename.toString(), env); var result = env.compile.call(this, null, raw, filename.toString(), env);
String source = null;
FunctionValue runner = null;
if (transpiled instanceof ObjectValue) { var function = (FunctionValue)Values.getMember(this, result, "function");
source = Values.toString(this, Values.getMember(this, transpiled, "source")); if (!engine.debugging) return function;
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner; var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
var breakpoints = new TreeSet<>(
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
.map(v -> Location.parse(Values.toString(this, v)))
.collect(Collectors.toList())
);
var maps = new SourceMap[rawMapChain.length];
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
var map = SourceMap.chain(maps);
if (map != null) {
var newBreakpoints = new TreeSet<Location>();
for (var bp : breakpoints) {
bp = map.toCompiled(bp);
if (bp != null) newBreakpoints.add(bp);
}
breakpoints = newBreakpoints;
} }
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>(); engine.onSource(filename, raw, breakpoints, map);
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source);
engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res); return function;
return res;
} }
@ -59,6 +72,7 @@ public class Context {
frames.add(frame); frames.add(frame);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
pushEnv(frame.function.environment); pushEnv(frame.function.environment);
engine.onFramePush(this, frame);
} }
public boolean popFrame(CodeFrame frame) { public boolean popFrame(CodeFrame frame) {
if (frames.size() == 0) return false; if (frames.size() == 0) return false;

View File

@ -15,6 +15,7 @@ import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Engine implements DebugController { public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue { private class UncompiledFunction extends FunctionValue {
@ -66,22 +67,36 @@ public class Engine implements DebugController {
private final HashMap<Filename, String> sources = new HashMap<>(); private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>(); private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
public Location mapToCompiled(Location location) {
var map = maps.get(location.filename());
if (map == null) return location;
return map.toCompiled(location);
}
public Location mapToOriginal(Location location) {
var map = maps.get(location.filename());
if (map == null) return location;
return map.toOriginal(location);
}
private DebugController debugger; private DebugController debugger;
private Thread thread; private Thread thread;
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>(); private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
public boolean attachDebugger(DebugController debugger) { public synchronized boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false; if (!debugging || this.debugger != null) return false;
for (var source : sources.entrySet()) { for (var source : sources.entrySet()) debugger.onSource(
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey())); source.getKey(), source.getValue(),
} bpts.get(source.getKey()),
maps.get(source.getKey())
);
this.debugger = debugger; this.debugger = debugger;
return true; return true;
} }
public boolean detachDebugger() { public synchronized boolean detachDebugger() {
if (!debugging || this.debugger == null) return false; if (!debugging || this.debugger == null) return false;
this.debugger = null; this.debugger = null;
return true; return true;
@ -122,7 +137,7 @@ public class Engine implements DebugController {
public boolean inExecThread() { public boolean inExecThread() {
return Thread.currentThread() == thread; return Thread.currentThread() == thread;
} }
public boolean isRunning() { public synchronized boolean isRunning() {
return this.thread != null; return this.thread != null;
} }
@ -135,6 +150,10 @@ public class Engine implements DebugController {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
} }
@Override
public void onFramePush(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
}
@Override public void onFramePop(Context ctx, CodeFrame frame) { @Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePop(ctx, frame); if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
} }
@ -142,11 +161,12 @@ public class Engine implements DebugController {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false; else return false;
} }
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) { @Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
if (!debugging) return; if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints); if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
sources.put(filename, source); sources.put(filename, source);
bpts.put(filename, breakpoints); bpts.put(filename, breakpoints);
maps.put(filename, map);
} }
public Engine(boolean debugging) { public Engine(boolean debugging) {

View File

@ -1,18 +1,24 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.RootFilesystem; import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.permissions.Permission; import me.topchetoeu.jscript.permissions.Permission;
import me.topchetoeu.jscript.permissions.PermissionsProvider; import me.topchetoeu.jscript.permissions.PermissionsProvider;
@ -29,9 +35,31 @@ public class Environment implements PermissionsProvider {
private static int nextId = 0; private static int nextId = 0;
@Native public boolean stackVisible = true;
@Native public int id = ++nextId; @Native public int id = ++nextId;
@Native public FunctionValue compile; @Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
var source = Values.toString(ctx, args[0]);
var filename = Values.toString(ctx, args[1]);
var isDebug = Values.toBoolean(args[2]);
var env = Values.wrapper(args[2], Environment.class);
var res = new ObjectValue();
var target = Parsing.compile(env, Filename.parse(filename), source);
Engine.functions.putAll(target.functions);
Engine.functions.remove(0l);
res.defineProperty(ctx, "function", target.func(env));
res.defineProperty(ctx, "mapChain", new ArrayValue());
if (isDebug) {
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
}
return res;
});
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
}); });
@ -49,12 +77,7 @@ public class Environment implements PermissionsProvider {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name); return getSymbol(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
} }
@NativeGetter("global") public ObjectValue getGlobal() { @NativeGetter("global") public ObjectValue getGlobal() {
@ -88,13 +111,21 @@ public class Environment implements PermissionsProvider {
return new Context(engine).pushEnv(this); return new Context(engine).pushEnv(this);
} }
public static Symbol getSymbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
}
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]); if (compile != null) this.compile = compile;
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope(); if (global == null) global = new GlobalScope();
this.wrappers = nativeConverter; this.wrappers = nativeConverter;
this.compile = compile;
this.global = global; this.global = global;
} }
} }

View File

@ -8,13 +8,17 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.mapping.SourceMap;
public interface DebugController { public interface DebugController {
/** /**
* Called when a script has been loaded * Called when a script has been loaded
* @param breakpoints * @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/ */
void onSource(Filename filename, String source, TreeSet<Location> breakpoints); void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
/** /**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
@ -30,6 +34,13 @@ public interface DebugController {
*/ */
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
/**
* Called immediatly before a frame has been pushed on the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was pushed
*/
void onFramePush(Context ctx, CodeFrame frame);
/** /**
* Called immediatly after a frame has been popped out of the frame stack. * Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands. * This function might pause in order to await debugging commands.

View File

@ -4,7 +4,6 @@ public interface DebugHandler {
void enable(V8Message msg); void enable(V8Message msg);
void disable(V8Message msg); void disable(V8Message msg);
void setBreakpoint(V8Message msg);
void setBreakpointByUrl(V8Message msg); void setBreakpointByUrl(V8Message msg);
void removeBreakpoint(V8Message msg); void removeBreakpoint(V8Message msg);
void continueToLocation(V8Message msg); void continueToLocation(V8Message msg);

View File

@ -9,6 +9,7 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.Metadata;
import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
@ -74,7 +75,6 @@ public class DebugServer {
debugger.enable(msg); continue; debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue; case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
@ -232,10 +232,9 @@ public class DebugServer {
public DebugServer() { public DebugServer() {
try { try {
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); this.index = Reading.resourceToString("debugger/index.html")
this.index = index
.replace("${NAME}", Metadata.name()) .replace("${NAME}", Metadata.name())
.replace("${VERSION}", Metadata.version()) .replace("${VERSION}", Metadata.version())
.replace("${AUTHOR}", Metadata.author()) .replace("${AUTHOR}", Metadata.author())

View File

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -30,16 +30,24 @@ import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
import me.topchetoeu.jscript.mapping.SourceMap;
import me.topchetoeu.jscript.parsing.Parsing;
// very simple indeed // very simple indeed
public class SimpleDebugger implements Debugger { public class SimpleDebugger implements Debugger {
public static final Set<String> VSCODE_EMPTY = Set.of(
"function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}",
"function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}",
"function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}",
"function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}"
);
public static final Set<String> VSCODE_SELF = Set.of(
"function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}"
);
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}"; public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}";
public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}";
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}"; public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
public static final String VSCODE_AUTOCOMPLETE = "function(t,e,r){let n=r?\"variable\":\"property\",i=(l,p,f)=>{if(p!==\"function\")return n;if(l===\"constructor\")return\"class\";let m=String(f);return m.startsWith(\"class \")||m.includes(\"[native code]\")&&/^[A-Z]/.test(l)?\"class\":r?\"function\":\"method\"\n},o=l=>{switch(typeof l){case\"number\":case\"boolean\":return`${l}`;case\"object\":return l===null?\"null\":l.constructor.name||\"object\";case\"function\":return`fn(${new Array(l.length).fill(\"?\").join(\", \")})`;default:return typeof l}},s=[],a=new Set,u=\"~\",c=t===void 0?this:t;for(;c!=null;c=c.__proto__){u+=\"~\";let l=Object.getOwnPropertyNames(c).filter(p=>p.startsWith(e)&&!p.match(/^\\d+$/));for(let p of l){if(a.has(p))continue;a.add(p);let f=Object.getOwnPropertyDescriptor(c,p),m=n,h;try{let H=c[p];m=i(p,typeof f?.value,H),h=o(H)}catch{}s.push({label:p,sortText:u+p.replace(/^_+/,H=>\"{\".repeat(H.length)),type:m,detail:h})}r=!1}return{result:s,isArray:this instanceof Array}}";
private static enum State { private static enum State {
RESUMED, RESUMED,
@ -104,7 +112,7 @@ public class SimpleDebugger implements Debugger {
public boolean debugData = false; public boolean debugData = false;
public void updateLoc(Location loc) { public void updateLoc(Location loc) {
serialized.set("location", serializeLocation(loc)); if (loc == null) return;
this.location = loc; this.location = loc;
} }
@ -112,7 +120,6 @@ public class SimpleDebugger implements Debugger {
this.frame = frame; this.frame = frame;
this.func = frame.function; this.func = frame.function;
this.id = id; this.id = id;
this.location = frame.function.loc();
this.global = frame.function.environment.global.obj; this.global = frame.function.environment.global.obj;
this.local = frame.getLocalScope(ctx, true); this.local = frame.getLocalScope(ctx, true);
@ -120,20 +127,32 @@ public class SimpleDebugger implements Debugger {
this.local.setPrototype(ctx, capture); this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global); this.capture.setPrototype(ctx, global);
this.valstack = frame.getValStackScope(ctx); this.valstack = frame.getValStackScope(ctx);
debugData = true;
if (location != null) { this.serialized = new JSONMap()
debugData = true; .set("callFrameId", id + "")
this.serialized = new JSONMap() .set("functionName", func.name)
.set("callFrameId", id + "") .set("scopeChain", new JSONList()
.set("functionName", func.name) .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
.set("location", serializeLocation(location)) .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
.set("scopeChain", new JSONList() .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) .add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture))) );
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) }
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack))) }
); private class ObjRef {
} public final ObjectValue obj;
public final Context ctx;
public final HashSet<String> heldGroups = new HashSet<>();
public boolean held = true;
public boolean shouldRelease() {
return !held && heldGroups.size() == 0;
}
public ObjRef(Context ctx, ObjectValue obj) {
this.ctx = ctx;
this.obj = obj;
} }
} }
@ -150,7 +169,7 @@ public class SimpleDebugger implements Debugger {
} }
public boolean enabled = true; public boolean enabled = true;
public CatchType execptionType = CatchType.ALL; public CatchType execptionType = CatchType.NONE;
public State state = State.RESUMED; public State state = State.RESUMED;
public final WebSocket ws; public final WebSocket ws;
@ -171,22 +190,49 @@ public class SimpleDebugger implements Debugger {
private HashMap<Integer, Frame> idToFrame = new HashMap<>(); private HashMap<Integer, Frame> idToFrame = new HashMap<>();
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>(); private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>(); private HashMap<Integer, ObjRef> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>(); private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>(); private HashMap<String, ArrayList<ObjRef>> objectGroups = new HashMap<>();
private HashMap<String, ArrayList<ObjectValue>> objectGroups = new HashMap<>();
private Notifier updateNotifier = new Notifier(); private Notifier updateNotifier = new Notifier();
private boolean pendingPause = false;
private int nextId = new Random().nextInt() & 0x7FFFFFFF; private int nextId = 0;
private Location prevLocation = null;
private Frame stepOutFrame = null, currFrame = null; private Frame stepOutFrame = null, currFrame = null;
private int stepOutPtr = 0;
private int nextId() { private boolean compare(String src, String target) {
return nextId++ ^ 1630022591 /* big prime */; if (src.length() != target.length()) return false;
var diff = 0;
var all = 0;
for (var i = 0; i < src.length(); i++) {
var a = src.charAt(i);
var b = target.charAt(i);
var letter = Parsing.isLetter(a) && Parsing.isLetter(b);
if (a != b) {
if (letter) diff++;
else return false;
}
if (letter) all++;
}
return diff / (float)all < .5f;
}
private boolean compare(String src, Set<String> target) {
for (var el : target) {
if (compare(src, el)) return true;
}
return false;
} }
private void updateFrames(Context ctx) { private int nextId() {
return nextId++;
}
private synchronized void updateFrames(Context ctx) {
var frame = ctx.peekFrame(); var frame = ctx.peekFrame();
if (frame == null) return; if (frame == null) return;
@ -205,7 +251,10 @@ public class SimpleDebugger implements Debugger {
var frames = ctx.frames(); var frames = ctx.frames();
for (var i = frames.size() - 1; i >= 0; i--) { for (var i = frames.size() - 1; i >= 0; i--) {
res.add(codeFrameToFrame.get(frames.get(i)).serialized); var frame = codeFrameToFrame.get(frames.get(i));
if (frame.location == null) continue;
frame.serialized.set("location", serializeLocation(frame.location));
if (frame.location != null) res.add(frame.serialized);
} }
return res; return res;
@ -245,13 +294,13 @@ public class SimpleDebugger implements Debugger {
if (objectToId.containsKey(obj)) return objectToId.get(obj); if (objectToId.containsKey(obj)) return objectToId.get(obj);
else { else {
int id = nextId(); int id = nextId();
var ref = new ObjRef(ctx, obj);
objectToId.put(obj, id); objectToId.put(obj, id);
if (ctx != null) objectToCtx.put(obj, ctx); idToObject.put(id, ref);
idToObject.put(id, obj);
return id; return id;
} }
} }
private JSONMap serializeObj(Context ctx, Object val) { private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
val = Values.normalize(null, val); val = Values.normalize(null, val);
if (val == Values.NULL) { if (val == Values.NULL) {
@ -272,7 +321,7 @@ public class SimpleDebugger implements Debugger {
if (obj instanceof FunctionValue) type = "function"; if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array"; if (obj instanceof ArrayValue) subtype = "array";
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); } try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); }
catch (Exception e) { } catch (Exception e) { }
var res = new JSONMap() var res = new JSONMap()
@ -282,9 +331,28 @@ public class SimpleDebugger implements Debugger {
if (subtype != null) res.set("subtype", subtype); if (subtype != null) res.set("subtype", subtype);
if (className != null) { if (className != null) {
res.set("className", className); res.set("className", className);
res.set("description", "{ " + className + " }");
} }
if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")");
else if (obj instanceof FunctionValue) res.set("description", obj.toString());
else {
var defaultToString = false;
try {
defaultToString =
Values.getMember(ctx, obj, "toString") ==
Values.getMember(ctx, ctx.environment().proto("object"), "toString");
}
catch (Exception e) { }
try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, obj) + " }")); }
catch (EngineException e) { res.set("description", className); }
}
if (byValue) try { res.put("value", JSON.fromJs(ctx, obj)); }
catch (Exception e) { }
return res; return res;
} }
@ -308,33 +376,44 @@ public class SimpleDebugger implements Debugger {
throw new IllegalArgumentException("Unexpected JS object."); throw new IllegalArgumentException("Unexpected JS object.");
} }
private void setObjectGroup(String name, Object val) { private JSONMap serializeObj(Context ctx, Object val) {
return serializeObj(ctx, val, false);
}
private void addObjectGroup(String name, Object val) {
if (val instanceof ObjectValue) { if (val instanceof ObjectValue) {
var obj = (ObjectValue)val; var obj = (ObjectValue)val;
var id = objectToId.getOrDefault(obj, -1);
if (id < 0) return;
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj); var ref = idToObject.get(id);
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
if (objectGroups.containsKey(name)) objectGroups.get(name).add(ref);
else objectGroups.put(name, new ArrayList<>(List.of(ref)));
ref.heldGroups.add(name);
} }
} }
private void releaseGroup(String name) { private void releaseGroup(String name) {
var objs = objectGroups.remove(name); var objs = objectGroups.remove(name);
if (objs != null) { if (objs != null) for (var obj : objs) {
for (var obj : objs) { if (obj.heldGroups.remove(name) && obj.shouldRelease()) {
var id = objectToId.remove(obj); var id = objectToId.remove(obj.obj);
if (id != null) idToObject.remove(id); if (id != null) idToObject.remove(id);
} }
} }
} }
private Object deserializeArgument(JSONMap val) { private Object deserializeArgument(JSONMap val) {
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))); if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))).obj;
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
case "NaN": return Double.NaN; case "NaN": return Double.NaN;
case "-Infinity": return Double.NEGATIVE_INFINITY; case "-Infinity": return Double.NEGATIVE_INFINITY;
case "Infinity": return Double.POSITIVE_INFINITY; case "Infinity": return Double.POSITIVE_INFINITY;
case "-0": return -0.; case "-0": return -0.;
} }
return JSON.toJs(val.get("value")); var res = val.get("value");
if (res == null) return null;
else return JSON.toJs(res);
} }
private JSONMap serializeException(Context ctx, EngineException err) { private JSONMap serializeException(Context ctx, EngineException err) {
@ -350,7 +429,6 @@ public class SimpleDebugger implements Debugger {
var res = new JSONMap() var res = new JSONMap()
.set("exceptionId", nextId()) .set("exceptionId", nextId())
.set("exception", serializeObj(ctx, err.value)) .set("exception", serializeObj(ctx, err.value))
.set("exception", serializeObj(ctx, err.value))
.set("text", text); .set("text", text);
return res; return res;
@ -398,16 +476,11 @@ public class SimpleDebugger implements Debugger {
} }
private RunResult run(Frame codeFrame, String code) { private RunResult run(Frame codeFrame, String code) {
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
var engine = new Engine(false); var engine = new Engine(false);
var env = codeFrame.func.environment.fork(); var env = codeFrame.func.environment.fork();
ObjectValue global = env.global.obj, env.global = new GlobalScope(codeFrame.local);
capture = codeFrame.frame.getCaptureScope(null, enabled),
local = codeFrame.frame.getLocalScope(null, enabled);
capture.setPrototype(null, global);
local.setPrototype(null, capture);
env.global = new GlobalScope(local);
var ctx = new Context(engine).pushEnv(env); var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
@ -418,7 +491,80 @@ public class SimpleDebugger implements Debugger {
catch (EngineException e) { return new RunResult(ctx, null, e); } catch (EngineException e) { return new RunResult(ctx, null, e); }
} }
@Override public void enable(V8Message msg) { private ObjectValue vscodeAutoSuggest(Context ctx, Object target, String query, boolean variable) {
var res = new ArrayValue();
var passed = new HashSet<String>();
var tildas = "~";
if (target == null) target = ctx.environment().getGlobal();
for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) {
for (var el : Values.getMembers(ctx, proto, true, true)) {
var strKey = Values.toString(ctx, el);
if (passed.contains(strKey)) continue;
passed.add(strKey);
var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, proto, el), "value");
var desc = new ObjectValue();
var sortText = "";
if (strKey.startsWith(query)) sortText += "0@";
else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@";
else if (strKey.contains(query)) sortText += "2@";
else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@";
else sortText += "4@";
sortText += tildas + strKey;
desc.defineProperty(ctx, "label", strKey);
desc.defineProperty(ctx, "sortText", sortText);
if (val instanceof FunctionValue) {
if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name");
else desc.defineProperty(ctx, "type", variable ? "function" : "method");
}
else desc.defineProperty(ctx, "type", variable ? "variable" : "property");
switch (Values.type(val)) {
case "number":
case "boolean":
desc.defineProperty(ctx, "detail", Values.toString(ctx, val));
break;
case "object":
if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null");
else try {
desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name"));
}
catch (IllegalArgumentException e) {
desc.defineProperty(ctx, "detail", "object");
}
break;
case "function": {
var type = "fn(";
for (var i = 0; i < ((FunctionValue)val).length; i++) {
if (i != 0) type += ",";
type += "?";
}
type += ")";
desc.defineProperty(ctx, "detail", type);
break;
}
default:
desc.defineProperty(ctx, "type", Values.type(val));
break;
}
res.set(ctx, res.size(), desc);
}
tildas += "~";
variable = true;
}
var resObj = new ObjectValue();
resObj.defineProperty(ctx, "result", res);
resObj.defineProperty(ctx, "isArray", target instanceof ArrayValue);
return resObj;
}
@Override public synchronized void enable(V8Message msg) {
enabled = true; enabled = true;
ws.send(msg.respond()); ws.send(msg.respond());
@ -427,17 +573,17 @@ public class SimpleDebugger implements Debugger {
updateNotifier.next(); updateNotifier.next();
} }
@Override public void disable(V8Message msg) { @Override public synchronized void disable(V8Message msg) {
enabled = false; enabled = false;
ws.send(msg.respond()); ws.send(msg.respond());
updateNotifier.next(); updateNotifier.next();
} }
@Override public void getScriptSource(V8Message msg) { @Override public synchronized void getScriptSource(V8Message msg) {
int id = Integer.parseInt(msg.params.string("scriptId")); int id = Integer.parseInt(msg.params.string("scriptId"));
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
} }
@Override public void getPossibleBreakpoints(V8Message msg) { @Override public synchronized void getPossibleBreakpoints(V8Message msg) {
var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId")));
var start = deserializeLocation(msg.params.get("start"), false); var start = deserializeLocation(msg.params.get("start"), false);
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
@ -452,26 +598,16 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond(new JSONMap().set("locations", res))); ws.send(msg.respond(new JSONMap().set("locations", res)));
} }
@Override public void pause(V8Message msg) { @Override public synchronized void pause(V8Message msg) {
pendingPause = true;
ws.send(msg.respond());
} }
@Override public synchronized void resume(V8Message msg) {
@Override public void resume(V8Message msg) {
resume(State.RESUMED); resume(State.RESUMED);
ws.send(msg.respond(new JSONMap())); ws.send(msg.respond(new JSONMap()));
} }
@Override public void setBreakpoint(V8Message msg) { @Override public synchronized void setBreakpointByUrl(V8Message msg) {
// int id = nextId();
// var loc = deserializeLocation(msg.params.get("location"), true);
// var bpt = new Breakpoint(id, loc, null);
// breakpoints.put(loc, bpt);
// idToBrpt.put(id, bpt);
// ws.send(msg.respond(new JSONMap()
// .set("breakpointId", id)
// .set("actualLocation", serializeLocation(loc))
// ));
}
@Override public void setBreakpointByUrl(V8Message msg) {
var line = (int)msg.params.number("lineNumber") + 1; var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 1; var col = (int)msg.params.number("columnNumber", 0) + 1;
var cond = msg.params.string("condition", "").trim(); var cond = msg.params.string("condition", "").trim();
@ -506,7 +642,7 @@ public class SimpleDebugger implements Debugger {
.set("locations", locs) .set("locations", locs)
)); ));
} }
@Override public void removeBreakpoint(V8Message msg) { @Override public synchronized void removeBreakpoint(V8Message msg) {
var id = Integer.parseInt(msg.params.string("breakpointId")); var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) { if (idToBptCand.containsKey(id)) {
@ -523,7 +659,7 @@ public class SimpleDebugger implements Debugger {
} }
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void continueToLocation(V8Message msg) { @Override public synchronized void continueToLocation(V8Message msg) {
var loc = deserializeLocation(msg.params.get("location"), true); var loc = deserializeLocation(msg.params.get("location"), true);
tmpBreakpts.add(loc); tmpBreakpts.add(loc);
@ -532,39 +668,48 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void setPauseOnExceptions(V8Message msg) { @Override public synchronized void setPauseOnExceptions(V8Message msg) {
ws.send(new V8Error("i dont wanna to")); switch (msg.params.string("state")) {
case "none": execptionType = CatchType.NONE; break;
case "all": execptionType = CatchType.ALL; break;
case "uncaught": execptionType = CatchType.UNCAUGHT; break;
default:
ws.send(new V8Error("Invalid exception pause type."));
return;
}
ws.send(msg.respond());
} }
@Override public void stepInto(V8Message msg) { @Override public synchronized void stepInto(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_IN); resume(State.STEPPING_IN);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void stepOut(V8Message msg) { @Override public synchronized void stepOut(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OUT); resume(State.STEPPING_OUT);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void stepOver(V8Message msg) { @Override public synchronized void stepOver(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else { else {
prevLocation = currFrame.location;
stepOutFrame = currFrame; stepOutFrame = currFrame;
stepOutPtr = currFrame.frame.codePtr;
resume(State.STEPPING_OVER); resume(State.STEPPING_OVER);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
@Override public void evaluateOnCallFrame(V8Message msg) { @Override public synchronized void evaluateOnCallFrame(V8Message msg) {
var cfId = Integer.parseInt(msg.params.string("callFrameId")); var cfId = Integer.parseInt(msg.params.string("callFrameId"));
var expr = msg.params.string("expression"); var expr = msg.params.string("expression");
var group = msg.params.string("objectGroup", null); var group = msg.params.string("objectGroup", null);
@ -572,32 +717,37 @@ public class SimpleDebugger implements Debugger {
var cf = idToFrame.get(cfId); var cf = idToFrame.get(cfId);
var res = run(cf, expr); var res = run(cf, expr);
if (group != null) setObjectGroup(group, res.result); if (group != null) addObjectGroup(group, res.result);
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result)))); if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(res.ctx, res.error))));
else ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
} }
@Override public void releaseObjectGroup(V8Message msg) { @Override public synchronized void releaseObjectGroup(V8Message msg) {
var group = msg.params.string("objectGroup"); var group = msg.params.string("objectGroup");
releaseGroup(group); releaseGroup(group);
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override @Override public synchronized void releaseObject(V8Message msg) {
public void releaseObject(V8Message msg) {
var id = Integer.parseInt(msg.params.string("objectId")); var id = Integer.parseInt(msg.params.string("objectId"));
var obj = idToObject.remove(id); var ref = idToObject.get(id);
if (obj != null) objectToId.remove(obj); ref.held = false;
if (ref.shouldRelease()) {
objectToId.remove(ref.obj);
idToObject.remove(id);
}
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void getProperties(V8Message msg) { @Override public synchronized void getProperties(V8Message msg) {
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var obj = ref.obj;
var res = new JSONList(); var res = new JSONList();
var ctx = objectToCtx.get(obj); var ctx = ref.ctx;
if (obj != emptyObject) { if (obj != emptyObject && obj != null) {
for (var key : obj.keys(true)) { for (var key : obj.keys(true)) {
var propDesc = new JSONMap(); var propDesc = new JSONMap();
@ -637,7 +787,7 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond(new JSONMap().set("result", res))); ws.send(msg.respond(new JSONMap().set("result", res)));
} }
@Override public void callFunctionOn(V8Message msg) { @Override public synchronized void callFunctionOn(V8Message msg) {
var src = msg.params.string("functionDeclaration"); var src = msg.params.string("functionDeclaration");
var args = msg.params var args = msg.params
.list("arguments", new JSONList()) .list("arguments", new JSONList())
@ -645,9 +795,11 @@ public class SimpleDebugger implements Debugger {
.map(v -> v.map()) .map(v -> v.map())
.map(this::deserializeArgument) .map(this::deserializeArgument)
.collect(Collectors.toList()); .collect(Collectors.toList());
var byValue = msg.params.bool("returnByValue", false);
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var ctx = objectToCtx.get(thisArg); var thisArg = thisArgRef.obj;
var ctx = thisArgRef.ctx;
while (true) { while (true) {
var start = src.lastIndexOf("//# sourceURL="); var start = src.lastIndexOf("//# sourceURL=");
@ -658,47 +810,36 @@ public class SimpleDebugger implements Debugger {
} }
try { try {
switch (src) { Object res = null;
case CHROME_GET_PROP_FUNC: { if (compare(src, VSCODE_EMPTY)) res = emptyObject;
var path = JSON.parse(null, (String)args.get(0)).list(); else if (compare(src, VSCODE_SELF)) res = thisArg;
Object res = thisArg; else if (compare(src, CHROME_GET_PROP_FUNC)) {
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); res = thisArg;
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el));
return;
}
case VSCODE_STRINGIFY_VAL:
case VSCODE_STRINGIFY_PROPS:
case VSCODE_SHALLOW_COPY:
case VSCODE_SYMBOL_REQUEST:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
break;
case VSCODE_FLATTEN_ARRAY:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
break;
case VSCODE_CALL: {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
break;
}
default:
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
break;
// default: {
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
// }
} }
else if (compare(src, VSCODE_CALL)) {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
}
else if (compare(src, VSCODE_AUTOCOMPLETE)) {
var target = args.get(0);
if (target == null) target = thisArg;
res = vscodeAutoSuggest(ctx, target, Values.toString(ctx, args.get(1)), Values.toBoolean(args.get(2)));
}
else {
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
return;
}
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res, byValue))));
} }
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); } catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
} }
@Override @Override public synchronized void runtimeEnable(V8Message msg) {
public void runtimeEnable(V8Message msg) {
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) { @Override public void onSource(Filename filename, String source, TreeSet<Location> locations, SourceMap map) {
int id = nextId(); int id = nextId();
var src = new Source(id, filename, source, locations); var src = new Source(id, filename, source, locations);
@ -720,87 +861,103 @@ public class SimpleDebugger implements Debugger {
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false; if (!enabled) return false;
updateFrames(ctx); boolean isBreakpointable;
var frame = codeFrameToFrame.get(cf); Location loc;
Frame frame;
if (!frame.debugData) return false; synchronized (this) {
frame = codeFrameToFrame.get(cf);
if (instruction.location != null) frame.updateLoc(instruction.location); if (!frame.debugData) return false;
var loc = frame.location;
var isBreakpointable = loc != null && (
idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) ||
returnVal != Runners.NO_RETURN
);
// TODO: FIXXXX if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location));
// if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; loc = frame.location;
isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn());
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(ctx); pauseException(ctx);
}
else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Runners.NO_RETURN && stepOutFrame == frame) {
pauseDebug(ctx, null);
}
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
var bp = locToBreakpoint.get(loc);
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
}
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
else if (isBreakpointable && pendingPause) {
pauseDebug(ctx, null);
pendingPause = false;
}
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
} }
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
var bp = locToBreakpoint.get(loc);
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
}
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
while (enabled) { while (enabled) {
switch (state) { synchronized (this) {
case PAUSED_EXCEPTION: switch (state) {
case PAUSED_NORMAL: break; case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT: case STEPPING_OUT:
case RESUMED: return false; case RESUMED: return false;
case STEPPING_IN:
if (!prevLocation.equals(loc)) { case STEPPING_IN:
if (isBreakpointable) pauseDebug(ctx, null); case STEPPING_OVER:
else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null); if (stepOutFrame.frame == frame.frame) {
else return false; if (returnVal != Runners.NO_RETURN || error != null) {
} state = State.STEPPING_OUT;
else return false; continue;
break; }
case STEPPING_OVER: else if (stepOutPtr != frame.frame.codePtr) {
if (stepOutFrame.frame == frame.frame) { if (state == State.STEPPING_IN && instruction.breakpoint.shouldStepIn()) {
if (isBreakpointable && ( pauseDebug(ctx, null);
!loc.filename().equals(prevLocation.filename()) || break;
loc.line() != prevLocation.line() }
)) pauseDebug(ctx, null); else if (state == State.STEPPING_OVER && instruction.breakpoint.shouldStepOver()) {
else return false; pauseDebug(ctx, null);
} break;
else return false; }
break; }
}
return false;
}
} }
updateNotifier.await(); updateNotifier.await();
} }
return false; return false;
} }
@Override public void onFramePush(Context ctx, CodeFrame frame) {
var prevFrame = currFrame;
updateFrames(ctx);
if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) {
stepOutFrame = currFrame;
}
}
@Override public void onFramePop(Context ctx, CodeFrame frame) { @Override public void onFramePop(Context ctx, CodeFrame frame) {
updateFrames(ctx); updateFrames(ctx);
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { } catch (NullPointerException e) { }
if (ctx.frames().size() == 0) resume(State.RESUMED); if (ctx.frames().size() == 0) {
else if (stepOutFrame != null && stepOutFrame.frame == frame && if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
(state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) }
) { else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN; state = State.STEPPING_IN;
// else { stepOutFrame = currFrame;
pauseDebug(ctx, null);
updateNotifier.await();
// }
} }
} }
@Override public void connect() { @Override public synchronized void connect() {
if (!target.attachDebugger(this)) { if (!target.attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine.")); ws.send(new V8Error("A debugger is already attached to this engine."));
} }
} }
@Override public void disconnect() { @Override public synchronized void disconnect() {
target.detachDebugger(); target.detachDebugger();
enabled = false; enabled = false;
updateNotifier.next(); updateNotifier.next();

View File

@ -17,32 +17,74 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame { public class CodeFrame {
public static enum TryState {
TRY,
CATCH,
FINALLY,
}
public static class TryCtx { public static class TryCtx {
public static final int STATE_TRY = 0; public final int start, end, catchStart, finallyStart;
public static final int STATE_CATCH = 1; public final int restoreStackPtr;
public static final int STATE_FINALLY_THREW = 2; public final TryState state;
public static final int STATE_FINALLY_RETURNED = 3; public final EngineException error;
public static final int STATE_FINALLY_JUMPED = 4; public final PendingResult result;
public final boolean hasCatch, hasFinally; public boolean hasCatch() { return catchStart >= 0; }
public final int tryStart, catchStart, finallyStart, end; public boolean hasFinally() { return finallyStart >= 0; }
public int state;
public Object retVal;
public EngineException err;
public int jumpPtr;
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { public boolean inBounds(int ptr) {
hasCatch = catchN >= 0; return ptr >= start && ptr < end;
hasFinally = finallyN >= 0; }
if (catchN < 0) catchN = 0; public TryCtx _catch(EngineException e) {
if (finallyN < 0) finallyN = 0; if (error != null) e.setCause(error);
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
}
public TryCtx _finally(PendingResult res) {
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
}
this.tryStart = tryStart; public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
this.catchStart = tryStart + tryN; this.catchStart = catchStart;
this.finallyStart = catchStart + catchN; this.finallyStart = finallyStart;
this.end = finallyStart + finallyN; this.restoreStackPtr = stackPtr;
this.jumpPtr = end; this.result = res == null ? PendingResult.ofNone() : res;
this.state = state;
this.start = start;
this.end = end;
this.error = err;
}
}
private static class PendingResult {
public final boolean isReturn, isJump, isThrow;
public final Object value;
public final EngineException error;
public final int ptr;
public final Instruction instruction;
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
this.instruction = instr;
this.isReturn = isReturn;
this.isJump = isJump;
this.isThrow = isThrow;
this.value = value;
this.error = error;
this.ptr = ptr;
}
public static PendingResult ofNone() {
return new PendingResult(null, false, false, false, null, null, 0);
}
public static PendingResult ofReturn(Object value, Instruction instr) {
return new PendingResult(instr, true, false, false, value, null, 0);
}
public static PendingResult ofThrow(EngineException error, Instruction instr) {
return new PendingResult(instr, false, false, true, null, error, 0);
}
public static PendingResult ofJump(int codePtr, Instruction instr) {
return new PendingResult(instr, false, true, false, null, null, codePtr);
} }
} }
@ -51,11 +93,10 @@ public class CodeFrame {
public final Object[] args; public final Object[] args;
public final Stack<TryCtx> tryStack = new Stack<>(); public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function; public final CodeFunction function;
public Object[] stack = new Object[32]; public Object[] stack = new Object[32];
public int stackPtr = 0; public int stackPtr = 0;
public int codePtr = 0; public int codePtr = 0;
public boolean jumpFlag = false; public boolean jumpFlag = false, popTryFlag = false;
private Location prevLoc = null; private Location prevLoc = null;
public ObjectValue getLocalScope(Context ctx, boolean props) { public ObjectValue getLocalScope(Context ctx, boolean props) {
@ -105,9 +146,9 @@ public class CodeFrame {
}; };
} }
public void addTry(int n, int catchN, int finallyN) { public void addTry(int start, int end, int catchStart, int finallyStart) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN); var err = tryStack.empty() ? null : tryStack.peek().error;
if (!tryStack.empty()) res.err = tryStack.peek().err; var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
tryStack.add(res); tryStack.add(res);
} }
@ -145,10 +186,6 @@ public class CodeFrame {
stack[stackPtr++] = Values.normalize(ctx, val); stack[stackPtr++] = Values.normalize(ctx, val);
} }
private void setCause(Context ctx, EngineException err, EngineException cause) {
err.setCause(cause);
}
public Object next(Context ctx, Object value, Object returnValue, EngineException error) { public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value); if (value != Runners.NO_RETURN) push(ctx, value);
@ -161,16 +198,17 @@ public class CodeFrame {
if (instr == null) returnValue = null; if (instr == null) returnValue = null;
else { else {
// System.out.println(instr + "@" + instr.location);
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (instr.location != null) prevLoc = instr.location; if (instr.location != null) prevLoc = instr.location;
try { try {
this.jumpFlag = false; this.jumpFlag = this.popTryFlag = false;
returnValue = Runners.exec(ctx, instr, this); returnValue = Runners.exec(ctx, instr, this);
} }
catch (EngineException e) { catch (EngineException e) {
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine); error = e.add(ctx, function.name, prevLoc);
} }
} }
} }
@ -179,109 +217,80 @@ public class CodeFrame {
while (!tryStack.empty()) { while (!tryStack.empty()) {
var tryCtx = tryStack.peek(); var tryCtx = tryStack.peek();
var newState = -1; TryCtx newCtx = null;
switch (tryCtx.state) { if (error != null) {
case TryCtx.STATE_TRY: if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
if (error != null) { else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
if (tryCtx.hasCatch) {
tryCtx.err = error;
newState = TryCtx.STATE_CATCH;
}
else if (tryCtx.hasFinally) {
tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW;
}
break;
}
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) {
tryCtx.retVal = returnValue;
newState = TryCtx.STATE_FINALLY_RETURNED;
}
break;
}
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN;
if (tryCtx.hasFinally) {
if (jumpFlag) tryCtx.jumpPtr = codePtr;
else tryCtx.jumpPtr = tryCtx.end;
newState = TryCtx.STATE_FINALLY_JUMPED;
}
else codePtr = tryCtx.end;
break;
case TryCtx.STATE_CATCH:
if (error != null) {
setCause(ctx, error, tryCtx.err);
if (tryCtx.hasFinally) {
tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW;
}
break;
}
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) {
tryCtx.retVal = returnValue;
newState = TryCtx.STATE_FINALLY_RETURNED;
}
break;
}
else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN;
if (tryCtx.hasFinally) {
if (jumpFlag) tryCtx.jumpPtr = codePtr;
else tryCtx.jumpPtr = tryCtx.end;
newState = TryCtx.STATE_FINALLY_JUMPED;
}
else codePtr = tryCtx.end;
break;
case TryCtx.STATE_FINALLY_THREW:
if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break;
case TryCtx.STATE_FINALLY_RETURNED:
if (error != null) setCause(ctx, error, tryCtx.err);
if (returnValue == Runners.NO_RETURN) {
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
else return Runners.NO_RETURN;
}
break;
case TryCtx.STATE_FINALLY_JUMPED:
if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
else codePtr = tryCtx.end;
}
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break;
} }
else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
if (newState == -1) {
var err = tryCtx.err;
tryStack.pop();
if (!tryStack.isEmpty()) tryStack.peek().err = err;
continue;
} }
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
}
else if (!this.popTryFlag) newCtx = tryCtx;
tryCtx.state = newState; if (newCtx != null) {
switch (newState) { if (newCtx != tryCtx) {
case TryCtx.STATE_CATCH: switch (newCtx.state) {
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); case CATCH:
codePtr = tryCtx.catchStart; if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true); codePtr = tryCtx.catchStart;
break; stackPtr = tryCtx.restoreStackPtr;
default: break;
case FINALLY:
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
default:
}
tryStack.pop();
tryStack.push(newCtx);
}
error = null;
returnValue = Runners.NO_RETURN;
break;
}
else {
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
codePtr = tryCtx.finallyStart; codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
tryStack.pop();
tryStack.push(tryCtx._finally(null));
break;
}
else {
tryStack.pop();
codePtr = tryCtx.end;
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
if (tryCtx.result.isJump) {
codePtr = tryCtx.result.ptr;
jumpFlag = true;
}
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (tryCtx.result.isThrow) {
error = tryCtx.result.error;
}
if (error != null) error.setCause(tryCtx.error);
continue;
}
} }
return Runners.NO_RETURN;
} }
if (error != null) { if (error != null) {
ctx.engine.onInstruction(ctx, this, instr, null, error, false); var caught = false;
for (var frame : ctx.frames()) {
for (var tryCtx : frame.tryStack) {
if (tryCtx.state == TryState.TRY) caught = true;
}
}
ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
throw error; throw error;
} }
if (returnValue != Runners.NO_RETURN) { if (returnValue != Runners.NO_RETURN) {

View File

@ -97,50 +97,37 @@ public class Runners {
obj.defineProperty(ctx, "value", el); obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj); frame.push(ctx, obj);
} }
// var arr = new ObjectValue();
// var members = Values.getMembers(ctx, val, false, false);
// Collections.reverse(members);
// for (var el : members) {
// if (el instanceof Symbol) continue;
// arr.defineProperty(ctx, i++, el);
// }
// arr.defineProperty(ctx, "length", i);
// frame.push(ctx, arr);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) { public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
frame.addTry(instr.get(0), instr.get(1), instr.get(2)); int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1);
if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start;
int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
frame.popTryFlag = true;
return NO_RETURN;
}
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) { public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
int offset = instr.get(0), count = instr.get(1); int count = instr.get(0);
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
frame.push(ctx, frame.peek(offset + count - 1)); frame.push(ctx, frame.peek(count - 1));
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) {
int offset = instr.get(0), count = instr.get(1);
var tmp = frame.take(offset);
var res = frame.take(count);
for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]);
for (var i = 0; i < count; i++) frame.push(ctx, res[i]);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, null); frame.push(ctx, null);
frame.codePtr++; frame.codePtr++;
@ -179,16 +166,13 @@ public class Runners {
} }
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
long id = (Long)instr.get(0); long id = (Long)instr.get(0);
int localsN = (Integer)instr.get(1); var captures = new ValueVariable[instr.params.length - 1];
int len = (Integer)instr.get(2);
var captures = new ValueVariable[instr.params.length - 3];
for (var i = 3; i < instr.params.length; i++) { for (var i = 1; i < instr.params.length; i++) {
captures[i - 3] = frame.scope.get(instr.get(i)); captures[i - 1] = frame.scope.get(instr.get(i));
} }
var body = Engine.functions.get(id); var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
frame.push(ctx, func); frame.push(ctx, func);
@ -306,7 +290,6 @@ public class Runners {
var val = frame.pop(); var val = frame.pop();
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
frame.push(ctx, true);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -330,10 +313,10 @@ public class Runners {
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame); case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
case CALL: return execCall(ctx, instr, frame); case CALL: return execCall(ctx, instr, frame);
case CALL_NEW: return execCallNew(ctx, instr, frame); case CALL_NEW: return execCallNew(ctx, instr, frame);
case TRY: return execTry(ctx, instr, frame); case TRY_START: return execTryStart(ctx, instr, frame);
case TRY_END: return execTryEnd(ctx, instr, frame);
case DUP: return execDup(ctx, instr, frame); case DUP: return execDup(ctx, instr, frame);
case MOVE: return execMove(ctx, instr, frame);
case LOAD_VALUE: return execLoadValue(ctx, instr, frame); case LOAD_VALUE: return execLoadValue(ctx, instr, frame);
case LOAD_VAR: return execLoadVar(ctx, instr, frame); case LOAD_VAR: return execLoadVar(ctx, instr, frame);
case LOAD_OBJ: return execLoadObj(ctx, instr, frame); case LOAD_OBJ: return execLoadObj(ctx, instr, frame);

View File

@ -12,9 +12,6 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord { public class GlobalScope implements ScopeRecord {
public final ObjectValue obj; public final ObjectValue obj;
@Override
public GlobalScope parent() { return null; }
public boolean has(Context ctx, String name) { public boolean has(Context ctx, String name) {
return obj.hasMember(ctx, name, false); return obj.hasMember(ctx, name, false);
} }
@ -28,7 +25,7 @@ public class GlobalScope implements ScopeRecord {
return new GlobalScope(obj); return new GlobalScope(obj);
} }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this); return new LocalScopeRecord();
} }
public Object define(String name) { public Object define(String name) {

View File

@ -2,11 +2,8 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
import me.topchetoeu.jscript.engine.Context;
public class LocalScopeRecord implements ScopeRecord { public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent; public final LocalScopeRecord parent;
public final GlobalScope global;
private final ArrayList<String> captures = new ArrayList<>(); private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>(); private final ArrayList<String> locals = new ArrayList<>();
@ -18,11 +15,8 @@ public class LocalScopeRecord implements ScopeRecord {
return locals.toArray(String[]::new); return locals.toArray(String[]::new);
} }
@Override
public LocalScopeRecord parent() { return parent; }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this, global); return new LocalScopeRecord(this);
} }
public int localsCount() { public int localsCount() {
@ -62,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord {
return name; return name;
} }
public boolean has(Context ctx, String name) {
return
global.has(ctx, name) ||
locals.contains(name) ||
parent != null && parent.has(ctx, name);
}
public Object define(String name, boolean force) { public Object define(String name, boolean force) {
if (!force && locals.contains(name)) return locals.indexOf(name); if (!force && locals.contains(name)) return locals.indexOf(name);
locals.add(name); locals.add(name);
@ -80,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord {
locals.remove(locals.size() - 1); locals.remove(locals.size() - 1);
} }
public LocalScopeRecord(GlobalScope global) { public LocalScopeRecord() {
this.parent = null; this.parent = null;
this.global = global;
} }
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) { public LocalScopeRecord(LocalScopeRecord parent) {
this.parent = parent; this.parent = parent;
this.global = global;
} }
} }

View File

@ -3,6 +3,5 @@ package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord { public interface ScopeRecord {
public Object getKey(String name); public Object getKey(String name);
public Object define(String name); public Object define(String name);
public ScopeRecord parent();
public LocalScopeRecord child(); public LocalScopeRecord child();
} }

View File

@ -11,7 +11,6 @@ import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
public final int localsN; public final int localsN;
public final int length;
public final Instruction[] body; public final Instruction[] body;
public final String[] captureNames, localNames; public final String[] captureNames, localNames;
public final ValueVariable[] captures; public final ValueVariable[] captures;
@ -46,14 +45,13 @@ public class CodeFunction extends FunctionValue {
} }
} }
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) { public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
super(name, length); super(name, body.argsN);
this.captures = captures; this.captures = captures;
this.captureNames = body.captureNames; this.captureNames = body.captureNames;
this.localNames = body.localNames; this.localNames = body.localNames;
this.environment = environment; this.environment = environment;
this.localsN = localsN; this.localsN = body.localsN;
this.length = length;
this.body = body.instructions; this.body = body.instructions;
} }
} }

View File

@ -22,6 +22,9 @@ public class ScopeValue extends ObjectValue {
return true; return true;
} }
var proto = getPrototype(ctx);
if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true;
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override

View File

@ -70,8 +70,7 @@ public class Values {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T wrapper(Object val, Class<T> clazz) { public static <T> T wrapper(Object val, Class<T> clazz) {
if (!isWrapper(val)) return null; if (!isWrapper(val)) val = new NativeWrapper(val);
var res = (NativeWrapper)val; var res = (NativeWrapper)val;
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped; if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
else return null; else return null;

View File

@ -12,19 +12,48 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException { public class EngineException extends RuntimeException {
public static class StackElement {
public final Location location;
public final String function;
public final Context ctx;
public boolean visible() {
return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
}
public String toString() {
var res = "";
var loc = location;
if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
if (loc != null) res += "at " + loc.toString() + " ";
if (function != null && !function.equals("")) res += "in " + function + " ";
return res.trim();
}
public StackElement(Context ctx, Location location, String function) {
if (function != null) function = function.trim();
if (function.equals("")) function = null;
if (ctx == null) this.ctx = null;
else this.ctx = new Context(ctx.engine).pushEnv(ctx.environment());
this.location = location;
this.function = function;
}
}
public final Object value; public final Object value;
public EngineException cause; public EngineException cause;
public Environment env = null; public Environment env = null;
public Engine engine = null; public Engine engine = null;
public final List<String> stackTrace = new ArrayList<>(); public final List<StackElement> stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) { public EngineException add(Context ctx, String name, Location location) {
var res = ""; var el = new StackElement(ctx, location, name);
if (el.function == null && el.location == null) return this;
if (location != null) res += "at " + location.toString() + " "; setCtx(ctx.environment(), ctx.engine);
if (name != null && !name.equals("")) res += "in " + name + " "; stackTrace.add(el);
this.stackTrace.add(res.trim());
return this; return this;
} }
public EngineException setCause(EngineException cause) { public EngineException setCause(EngineException cause) {
@ -46,7 +75,7 @@ public class EngineException extends RuntimeException {
ss.append("[Error while stringifying]\n"); ss.append("[Error while stringifying]\n");
} }
for (var line : stackTrace) { for (var line : stackTrace) {
ss.append(" ").append(line).append('\n'); if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
} }
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1); ss.deleteCharAt(ss.length() - 1);
@ -74,7 +103,7 @@ public class EngineException extends RuntimeException {
return new EngineException(err(null, msg, PlaceholderProto.ERROR)); return new EngineException(err(null, msg, PlaceholderProto.ERROR));
} }
public static EngineException ofSyntax(SyntaxException e) { public static EngineException ofSyntax(SyntaxException e) {
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc);
} }
public static EngineException ofSyntax(String msg) { public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));

View File

@ -1,5 +1,7 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import me.topchetoeu.jscript.Buffer;
public interface File { public interface File {
int read(byte[] buff); int read(byte[] buff);
void write(byte[] buff); void write(byte[] buff);
@ -21,7 +23,7 @@ public interface File {
return new String(res); return new String(res);
} }
default String readLine() { default String readLine() {
var res = new Buffer(new byte[0]); var res = new Buffer();
var buff = new byte[1]; var buff = new byte[1];
while (true) { while (true) {

View File

@ -1,5 +1,6 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File { public class MemoryFile implements File {

View File

@ -4,6 +4,7 @@ import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem { public class MemoryFilesystem implements Filesystem {
@ -24,7 +25,7 @@ public class MemoryFilesystem implements Filesystem {
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer(new byte[0])); files.put(_path, new Buffer());
break; break;
case FOLDER: case FOLDER:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);

View File

@ -165,7 +165,7 @@ public class NativeWrapperProvider implements WrappersProvider {
} }
if (((OverloadFunction)func).overloads.size() == 0) { if (((OverloadFunction)func).overloads.size() == 0) {
func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
} }
applyMethods(ctx, false, func, clazz); applyMethods(ctx, false, func, clazz);

View File

@ -83,11 +83,11 @@ public class OverloadFunction extends FunctionValue {
catch (InvocationTargetException e) { catch (InvocationTargetException e) {
var loc = Location.INTERNAL; var loc = Location.INTERNAL;
if (e.getTargetException() instanceof EngineException) { if (e.getTargetException() instanceof EngineException) {
throw ((EngineException)e.getTargetException()).add(name, loc); throw ((EngineException)e.getTargetException()).add(ctx, name, loc);
} }
else if (e.getTargetException() instanceof NullPointerException) { else if (e.getTargetException() instanceof NullPointerException) {
e.printStackTrace(); e.printStackTrace();
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc);
} }
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) { else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
throw new InterruptException(); throw new InterruptException();
@ -100,11 +100,11 @@ public class OverloadFunction extends FunctionValue {
err.defineProperty(ctx, "message", target.getMessage()); err.defineProperty(ctx, "message", target.getMessage());
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass)); err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
throw new EngineException(err).add(name, loc); throw new EngineException(err).add(ctx, name, loc);
} }
} }
catch (ReflectiveOperationException e) { catch (ReflectiveOperationException e) {
throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL); throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
} }
} }

View File

@ -37,21 +37,6 @@ public class JSON {
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val); if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL; if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) { if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val); prev.add(val);
@ -67,6 +52,21 @@ public class JSON {
prev.remove(val); prev.remove(val);
return JSONElement.of(res); return JSONElement.of(res);
} }
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null; if (val == null) return null;
return null; return null;
} }

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.Reading; import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.DataKey;
@ -10,8 +11,10 @@ import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.parsing.Parsing;
public class Internals { public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>(); private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
@ -112,6 +115,60 @@ public class Internals {
@NativeGetter public static double Infinity(Context ctx) { @NativeGetter public static double Infinity(Context ctx) {
return Double.POSITIVE_INFINITY; return Double.POSITIVE_INFINITY;
} }
private static final String HEX = "0123456789ABCDEF";
private static String encodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var bytes = str.getBytes();
var sb = new StringBuilder(bytes.length);
for (byte c : bytes) {
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
else {
sb.append('%');
sb.append(HEX.charAt(c / 16));
sb.append(HEX.charAt(c % 16));
}
}
return sb.toString();
}
private static String decodeUriAny(String str, String keepAlphabet) {
if (str == null) str = "undefined";
var res = new Buffer();
var bytes = str.getBytes();
for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
if (c == '%') {
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
if (!Parsing.isAny((char)b, keepAlphabet)) {
i += 2;
res.append((byte)b);
continue;
}
}
res.append(c);
}
return new String(res.data());
}
@Native public static String encodeURIComponent(String str) {
return encodeUriAny(str, ".-_!~*'()");
}
@Native public static String decodeURIComponent(String str) {
return decodeUriAny(str, "");
}
@Native public static String encodeURI(String str) {
return encodeUriAny(str, ";,/?:@&=+$#.-_!~*'()");
}
@Native public static String decodeURI(String str) {
return decodeUriAny(str, ",/?:@&=+$#.");
}
public static Environment apply(Environment env) { public static Environment apply(Environment env) {
var wp = env.wrappers; var wp = env.wrappers;
@ -122,25 +179,25 @@ public class Internals {
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(null, "Date", false, wp.getConstr(DateLib.class)); glob.define(false, wp.getConstr(DateLib.class));
glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); glob.define(false, wp.getConstr(ObjectLib.class));
glob.define(null, "Function", false, wp.getConstr(FunctionLib.class)); glob.define(false, wp.getConstr(FunctionLib.class));
glob.define(null, "Array", false, wp.getConstr(ArrayLib.class)); glob.define(false, wp.getConstr(ArrayLib.class));
glob.define(null, "Boolean", false, wp.getConstr(BooleanLib.class)); glob.define(false, wp.getConstr(BooleanLib.class));
glob.define(null, "Number", false, wp.getConstr(NumberLib.class)); glob.define(false, wp.getConstr(NumberLib.class));
glob.define(null, "String", false, wp.getConstr(StringLib.class)); glob.define(false, wp.getConstr(StringLib.class));
glob.define(null, "Symbol", false, wp.getConstr(SymbolLib.class)); glob.define(false, wp.getConstr(SymbolLib.class));
glob.define(null, "Promise", false, wp.getConstr(PromiseLib.class)); glob.define(false, wp.getConstr(PromiseLib.class));
glob.define(null, "RegExp", false, wp.getConstr(RegExpLib.class)); glob.define(false, wp.getConstr(RegExpLib.class));
glob.define(null, "Map", false, wp.getConstr(MapLib.class)); glob.define(false, wp.getConstr(MapLib.class));
glob.define(null, "Set", false, wp.getConstr(SetLib.class)); glob.define(false, wp.getConstr(SetLib.class));
glob.define(null, "Error", false, wp.getConstr(ErrorLib.class)); glob.define(false, wp.getConstr(ErrorLib.class));
glob.define(null, "SyntaxError", false, wp.getConstr(SyntaxErrorLib.class)); glob.define(false, wp.getConstr(SyntaxErrorLib.class));
glob.define(null, "TypeError", false, wp.getConstr(TypeErrorLib.class)); glob.define(false, wp.getConstr(TypeErrorLib.class));
glob.define(null, "RangeError", false, wp.getConstr(RangeErrorLib.class)); glob.define(false, wp.getConstr(RangeErrorLib.class));
env.setProto("object", wp.getProto(ObjectLib.class)); env.setProto("object", wp.getProto(ObjectLib.class));
env.setProto("function", wp.getProto(FunctionLib.class)); env.setProto("function", wp.getProto(FunctionLib.class));

View File

@ -32,18 +32,18 @@ import me.topchetoeu.jscript.interop.NativeGetter;
} }
@Native public ObjectValue entries(Context ctx) { @Native public ObjectValue entries(Context ctx) {
var res = map.entrySet().stream().map(v -> { return ArrayValue.of(ctx, map
return new ArrayValue(ctx, v.getKey(), v.getValue()); .entrySet()
}).collect(Collectors.toList()); .stream()
return Values.toJSIterator(ctx, res.iterator()); .map(v -> new ArrayValue(ctx, v.getKey(), v.getValue()))
.collect(Collectors.toList())
);
} }
@Native public ObjectValue keys(Context ctx) { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(map.keySet()); return ArrayValue.of(ctx, map.keySet());
return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(map.values()); return ArrayValue.of(ctx, map.values());
return Values.toJSIterator(ctx, res.iterator());
} }
@Native public Object get(Object key) { @Native public Object get(Object key) {

View File

@ -154,20 +154,20 @@ import me.topchetoeu.jscript.interop.Native;
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) { @Native(thisArg=true) public static Object then(Context ctx, Object _thisArg, Object _onFulfill, Object _onReject) {
var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null;
var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null;
var res = new PromiseLib(); var res = new PromiseLib();
var fulfill = onFulfill == null ? new NativeFunction((_ctx, _thisArg, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill; var fulfill = onFulfill == null ? new NativeFunction((_ctx, _0, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill;
var reject = onReject == null ? new NativeFunction((_ctx, _thisArg, _args) -> { var reject = onReject == null ? new NativeFunction((_ctx, _0, _args) -> {
throw new EngineException(_args.length > 0 ? _args[0] : null); throw new EngineException(_args.length > 0 ? _args[0] : null);
}) : (FunctionValue)onReject; }) : (FunctionValue)onReject;
if (thisArg instanceof NativeWrapper && ((NativeWrapper)thisArg).wrapped instanceof PromiseLib) { var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ?
thisArg = ((NativeWrapper)thisArg).wrapped; ((NativeWrapper)_thisArg).wrapped :
} _thisArg;
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); } try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); }
@ -177,6 +177,7 @@ import me.topchetoeu.jscript.interop.Native;
var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> { var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> {
try { res.fulfill(ctx, reject.call(ctx, null, a[0])); } try { res.fulfill(ctx, reject.call(ctx, null, a[0])); }
catch (EngineException err) { res.reject(ctx, err.value); } catch (EngineException err) { res.reject(ctx, err.value); }
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true;
return null; return null;
}); });

View File

@ -21,16 +21,13 @@ import me.topchetoeu.jscript.interop.NativeGetter;
} }
@Native public ObjectValue entries(Context ctx) { @Native public ObjectValue entries(Context ctx) {
var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); return ArrayValue.of(ctx, set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()));
return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue keys(Context ctx) { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(set); return ArrayValue.of(ctx, set);
return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(set); return ArrayValue.of(ctx, set);
return Values.toJSIterator(ctx, res.iterator());
} }
@Native public Object add(Object key) { @Native public Object add(Object key) {

View File

@ -0,0 +1,108 @@
package me.topchetoeu.jscript.mapping;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.json.JSON;
public class SourceMap {
private final TreeMap<Long, Long> origToComp = new TreeMap<>();
private final TreeMap<Long, Long> compToOrig = new TreeMap<>();
public Location toCompiled(Location loc) { return convert(loc, origToComp); }
public Location toOriginal(Location loc) { return convert(loc, compToOrig); }
private void add(long orig, long comp) {
var a = origToComp.remove(orig);
var b = compToOrig.remove(comp);
if (b != null) origToComp.remove(b);
if (a != null) compToOrig.remove(a);
origToComp.put(orig, comp);
compToOrig.put(comp, orig);
}
public SourceMap apply(SourceMap map) {
var res = new SourceMap();
for (var el : new ArrayList<>(origToComp.entrySet())) {
var mapped = convert(el.getValue(), map.origToComp);
add(el.getKey(), mapped);
}
for (var el : new ArrayList<>(compToOrig.entrySet())) {
var mapped = convert(el.getKey(), map.compToOrig);
add(el.getValue(), mapped);
}
return res;
}
public SourceMap clone() {
var res = new SourceMap();
res.origToComp.putAll(this.origToComp);
res.compToOrig.putAll(this.compToOrig);
return res;
}
public static SourceMap parse(String raw) {
var mapping = VLQ.decodeMapping(raw);
var res = new SourceMap();
var compRow = 0l;
var compCol = 0l;
for (var origRow = 0; origRow < mapping.length; origRow++) {
var origCol = 0;
for (var rawSeg : mapping[origRow]) {
if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files.");
origCol += rawSeg.length > 0 ? rawSeg[0] : 0;
compRow += rawSeg.length > 2 ? rawSeg[2] : 0;
compCol += rawSeg.length > 3 ? rawSeg[3] : 0;
var compPacked = ((long)compRow << 32) | compCol;
var origPacked = ((long)origRow << 32) | origCol;
res.add(origPacked, compPacked);
}
}
return res;
}
public static List<String> getSources(String raw) {
var json = JSON.parse(null, raw).map();
return json
.list("sourcesContent")
.stream()
.map(v -> v.string())
.collect(Collectors.toList());
}
public static SourceMap chain(SourceMap ...maps) {
if (maps.length == 0) return null;
var res = maps[0];
for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]);
return res;
}
private static Long convert(long packed, TreeMap<Long, Long> map) {
if (map.containsKey(packed)) return map.get(packed);
var key = map.floorKey(packed);
if (key == null) return null;
else return map.get(key);
}
private static Location convert(Location loc, TreeMap<Long, Long> map) {
var packed = ((loc.line() - 1l) << 32) | (loc.start() - 1);
var resPacked = convert(packed, map);
if (resPacked == null) return null;
else return new Location((int)(resPacked >> 32) + 1, (int)(resPacked & 0xFFFF) + 1, loc.filename());
}
}

View File

@ -0,0 +1,95 @@
package me.topchetoeu.jscript.mapping;
import java.util.ArrayList;
import java.util.List;
public class VLQ {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static long[] toArray(List<Long> list) {
var arr = new long[list.size()];
for (var i = 0; i < list.size(); i++) arr[i] = list.get(i);
return arr;
}
public static String encode(long... arr) {
var raw = new StringBuilder();
for (var data : arr) {
var b = data < 0 ? 1 : 0;
data = Math.abs(data);
b |= (int)(data & 0b1111) << 1;
data >>= 4;
b |= data > 0 ? 0x20 : 0;;
raw.append(ALPHABET.charAt(b));
while (data > 0) {
b = (int)(data & 0b11111);
data >>= 5;
b |= data > 0 ? 0x20 : 0;
raw.append(ALPHABET.charAt(b));
}
}
return raw.toString();
}
public static long[] decode(String val) {
if (val.length() == 0) return new long[0];
var list = new ArrayList<Long>();
for (var i = 0; i < val.length();) {
var sign = 1;
var curr = ALPHABET.indexOf(val.charAt(i++));
var cont = (curr & 0x20) == 0x20;
if ((curr & 1) == 1) sign = -1;
long res = (curr & 0b11110) >> 1;
var n = 4;
for (; i < val.length() && cont;) {
curr = ALPHABET.indexOf(val.charAt(i++));
cont = (curr & 0x20) == 0x20;
res |= (curr & 0b11111) << n;
n += 5;
if (!cont) break;
}
list.add(res * sign);
}
return toArray(list);
}
public static String encodeMapping(long[][][] arr) {
var res = new StringBuilder();
var semicolon = false;
for (var line : arr) {
var comma = false;
if (semicolon) res.append(";");
semicolon = true;
for (var el : line) {
if (comma) res.append(",");
comma = true;
res.append(encode(el));
}
}
return res.toString();
}
public static long[][][] decodeMapping(String val) {
var lines = new ArrayList<long[][]>();
for (var line : val.split(";", -1)) {
var elements = new ArrayList<long[]>();
for (var el : line.split(",", -1)) {
elements.add(decode(el));
}
lines.add(elements.toArray(long[][]::new));
}
return lines.toArray(long[][][]::new);
}
}

View File

@ -17,8 +17,7 @@ import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.values.*; import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.ParseRes.State; import me.topchetoeu.jscript.parsing.ParseRes.State;
@ -90,7 +89,6 @@ public class Parsing {
// We allow yield and await, because they're part of the custom async and generator functions // We allow yield and await, because they're part of the custom async and generator functions
} }
public static boolean isDigit(char c) { public static boolean isDigit(char c) {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }
@ -396,7 +394,7 @@ public class Parsing {
return tokens; return tokens;
} }
private static int fromHex(char c) { public static int fromHex(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0'; if (c >= '0' && c <= '9') return c - '0';
@ -807,9 +805,11 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res);
n += res.n; n += res.n;
var end = getLoc(filename, tokens, i + n - 1);
return ParseRes.res(new ObjProp( return ParseRes.res(new ObjProp(
name, access, name, access,
new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result) new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result)
), n); ), n);
} }
public static ParseRes<ObjectStatement> parseObject(Filename filename, List<Token> tokens, int i) { public static ParseRes<ObjectStatement> parseObject(Filename filename, List<Token> tokens, int i) {
@ -869,7 +869,7 @@ public class Parsing {
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
} }
public static ParseRes<NewStatement> parseNew(Filename filename, List<Token> tokens, int i) { public static ParseRes<CallStatement> parseNew(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
@ -880,10 +880,10 @@ public class Parsing {
var callRes = parseCall(filename, tokens, i + n, valRes.result, 0); var callRes = parseCall(filename, tokens, i + n, valRes.result, 0);
n += callRes.n; n += callRes.n;
if (callRes.isError()) return callRes.transform(); if (callRes.isError()) return callRes.transform();
else if (callRes.isFailed()) return ParseRes.res(new NewStatement(loc, valRes.result), n); else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n);
var call = (CallStatement)callRes.result; var call = (CallStatement)callRes.result;
return ParseRes.res(new NewStatement(loc, call.func, call.args), n); return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n);
} }
public static ParseRes<TypeofStatement> parseTypeof(Filename filename, List<Token> tokens, int i) { public static ParseRes<TypeofStatement> parseTypeof(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
@ -896,7 +896,7 @@ public class Parsing {
return ParseRes.res(new TypeofStatement(loc, valRes.result), n); return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
} }
public static ParseRes<VoidStatement> parseVoid(Filename filename, List<Token> tokens, int i) { public static ParseRes<DiscardStatement> parseVoid(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
@ -905,7 +905,7 @@ public class Parsing {
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes);
n += valRes.n; n += valRes.n;
return ParseRes.res(new VoidStatement(loc, valRes.result), n); return ParseRes.res(new DiscardStatement(loc, valRes.result), n);
} }
public static ParseRes<? extends Statement> parseDelete(Filename filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseDelete(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
@ -966,8 +966,9 @@ public class Parsing {
var res = parseCompound(filename, tokens, i + n); var res = parseCompound(filename, tokens, i + n);
n += res.n; n += res.n;
var end = getLoc(filename, tokens, i + n - 1);
if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, name, args.toArray(String[]::new), res.result), n); if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n);
else return ParseRes.error(loc, "Expected a compound statement for function.", res); else return ParseRes.error(loc, "Expected a compound statement for function.", res);
} }
@ -1187,7 +1188,7 @@ public class Parsing {
else return ParseRes.failed(); else return ParseRes.failed();
} }
return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n); return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
} }
public static ParseRes<ChangeStatement> parsePostfixChange(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<ChangeStatement> parsePostfixChange(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
@ -1233,7 +1234,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
} }
public static ParseRes<CommaStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<CompoundStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1244,7 +1245,7 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
n += res.n; n += res.n;
return ParseRes.res(new CommaStatement(loc, prev, res.result), n); return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n);
} }
public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
@ -1510,7 +1511,7 @@ public class Parsing {
statements.add(res.result); statements.add(res.result);
} }
return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n);
} }
public static ParseRes<String> parseLabel(List<Token> tokens, int i) { public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
int n = 0; int n = 0;
@ -1691,7 +1692,7 @@ public class Parsing {
if (isOperator(tokens, i + n, Operator.SEMICOLON)) { if (isOperator(tokens, i + n, Operator.SEMICOLON)) {
n++; n++;
decl = new CompoundStatement(loc); decl = new CompoundStatement(loc, false);
} }
else { else {
var declRes = ParseRes.any( var declRes = ParseRes.any(
@ -1717,7 +1718,7 @@ public class Parsing {
if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) {
n++; n++;
inc = new CompoundStatement(loc); inc = new CompoundStatement(loc, false);
} }
else { else {
var incRes = parseValue(filename, tokens, i + n, 0); var incRes = parseValue(filename, tokens, i + n, 0);
@ -1830,7 +1831,7 @@ public class Parsing {
} }
public static ParseRes<? extends Statement> parseStatement(Filename filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseStatement(Filename filename, List<Token> tokens, int i) {
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1); if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1);
if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed.");
return ParseRes.any( return ParseRes.any(
parseVariableDeclare(filename, tokens, i), parseVariableDeclare(filename, tokens, i),
@ -1873,38 +1874,30 @@ public class Parsing {
return list.toArray(Statement[]::new); return list.toArray(Statement[]::new);
} }
public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> breakpoints, Environment environment, Statement ...statements) { public static CompileTarget compile(Environment environment, Statement ...statements) {
var target = environment.global.globalChild(); var subscope = new LocalScopeRecord();
var subscope = target.child(); var target = new CompileTarget(new HashMap<>(), new TreeSet<>());
var res = new CompileTarget(funcs, breakpoints); var stm = new CompoundStatement(null, true, statements);
var body = new CompoundStatement(null, statements);
if (body instanceof CompoundStatement) body = (CompoundStatement)body;
else body = new CompoundStatement(null, new Statement[] { body });
subscope.define("this"); subscope.define("this");
subscope.define("arguments"); subscope.define("arguments");
body.declare(target);
try { try {
body.compile(res, subscope, true); stm.compile(target, subscope, true);
FunctionStatement.checkBreakAndCont(res, 0); FunctionStatement.checkBreakAndCont(target, 0);
} }
catch (SyntaxException e) { catch (SyntaxException e) {
res.target.clear(); target.target.clear();
res.add(Instruction.throwSyntax(e)); target.add(Instruction.throwSyntax(e.loc, e));
} }
res.add(Instruction.ret()); target.add(Instruction.ret(stm.loc()));
target.functions.put(0l, new FunctionBody(subscope.localsCount(), 0, target.array(), subscope.captures(), subscope.locals()));
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); return target;
} }
public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> breakpoints, Environment environment, Filename filename, String raw) { public static CompileTarget compile(Environment environment, Filename filename, String raw) {
try { try { return compile(environment, parse(filename, raw)); }
return compile(funcs, breakpoints, environment, parse(filename, raw)); catch (SyntaxException e) { return compile(environment, new ThrowSyntaxStatement(e)); }
}
catch (SyntaxException e) {
return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new FunctionBody(new Instruction[] { Instruction.throwSyntax(e).locate(e.loc) }));
}
} }
} }