cant be fucked to split this one up

This commit is contained in:
TopchetoEU 2023-12-14 12:39:01 +02:00
parent fe86123f0f
commit 34434965d2
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
64 changed files with 174267 additions and 4052 deletions

View File

@ -1,8 +1,8 @@
(function (ts, env, libs) { (function (ts, env, libs) {
var src = '', version = 0; var src = '', version = 0;
var lib = libs.concat([ var lib = libs.concat([
'declare const exit: never;', 'declare function exit(): never;',
'declare const go: any;', '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);
@ -64,6 +64,7 @@
if (!environments[env.id]) environments[env.id] = [] if (!environments[env.id]) environments[env.id] = []
declSnapshots = environments[env.id]; declSnapshots = environments[env.id];
debugger;
var emit = service.getEmitOutput("/src.ts"); var emit = service.getEmitOutput("/src.ts");
var diagnostics = [] var diagnostics = []
@ -97,11 +98,8 @@
if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
return val; return val;
}, },
mapChain: compiled.mapChain.concat(JSON.stringify({ breakpoints: compiled.breakpoints,
file: filename, mapChain: compiled.mapChain.concat(map.mappings),
sources: [filename],
mappings: map.mappings,
}))
}; };
} }

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]> {
@ -465,13 +459,13 @@ interface SymbolConstructor {
} }
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>;
} }
interface PromiseConstructor { interface PromiseConstructor {
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 +538,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]>;

File diff suppressed because one or more lines are too long

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;

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,6 +129,7 @@ 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.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));
engine.pushMsg( engine.pushMsg(
@ -140,7 +143,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

@ -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,28 +1,44 @@
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<>();
private BreakpointType queueType = BreakpointType.NONE;
private Location queueLoc = null;
public Instruction add(Instruction instr) { public Instruction add(Instruction instr) {
target.add(instr); target.add(instr);
if (queueType != BreakpointType.NONE) setDebug(queueType);
if (queueLoc != null) instr.locate(queueLoc);
queueType = BreakpointType.NONE;
queueLoc = null;
return instr; return instr;
} }
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) {
var instr = target.get(i);
instr.breakpoint = type;
breakpoints.add(target.get(i).location); 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);
@ -33,8 +49,22 @@ public class CompileTarget {
else return target.get(target.size() - 1).location; else return target.get(target.size() - 1).location;
} }
public void queueDebug(BreakpointType type) {
queueType = type;
}
public void queueDebug(BreakpointType type, Location loc) {
queueType = type;
}
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,63 +1,53 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
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 @Override public boolean pure() {
public void declare(ScopeRecord varsScope) {
for (var stm : statements) { for (var stm : statements) {
stm.declare(varsScope); if (!stm.pure()) return false;
} }
return true;
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void declare(ScopeRecord varsScope) {
for (var stm : statements) { for (var stm : statements) stm.declare(varsScope);
if (stm instanceof FunctionStatement) stm.compile(target, scope, false); }
@Override
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
if (separateFuncs) for (var stm : statements) {
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
stm.compile(target, scope, false);
}
} }
var polluted = false;
for (var i = 0; i < statements.length; i++) { for (var i = 0; i < statements.length; i++) {
var stm = statements[i]; var stm = statements[i];
if (stm instanceof FunctionStatement) continue; if (separateFuncs && stm instanceof FunctionStatement) continue;
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); if (i != statements.length - 1) stm.compileWithDebug(target, scope, false, BreakpointType.STEP_OVER);
else stm.compileWithDebug(target, scope, pollute); else stm.compileWithDebug(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
} }
if (end != null) { if (!polluted && pollute) {
target.add(Instruction.nop(end)); target.add(Instruction.loadValue(loc(), null));
target.setDebug();
} }
} }
@Override @Override
public Statement optimize() { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var res = new Vector<Statement>(statements.length); compileWithDebug(target, scope, pollute, BreakpointType.STEP_IN);
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) {
@ -65,8 +55,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

@ -1,27 +0,0 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DiscardStatement extends Statement {
public final Statement value;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false);
}
@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) {
super(loc);
this.value = val;
}
}

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,
@ -86,10 +87,29 @@ public class Instruction {
// this(false); // this(false);
// } // }
} }
public static enum BreakpointType {
NONE,
STEP_OVER,
STEP_IN;
public boolean shouldStepIn() {
return this != NONE;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type; public final 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,8 +149,11 @@ public class Instruction {
this.params = params; this.params = params;
} }
public static Instruction tryInstr(Location loc, int n, int catchN, int finallyN) { public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
return new Instruction(loc, Type.TRY, n, catchN, finallyN); return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
}
public static Instruction tryEnd(Location loc) {
return new Instruction(loc, Type.TRY_END);
} }
public static Instruction throwInstr(Location loc) { public static Instruction throwInstr(Location loc) {
return new Instruction(loc, Type.THROW); return new Instruction(loc, Type.THROW);

View File

@ -1,6 +1,7 @@
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 {
@ -9,12 +10,15 @@ public abstract class Statement {
public boolean pure() { return false; } public boolean pure() { return false; }
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute); 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 compileWithDebug(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 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,16 +33,13 @@ 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(entry.location, (String)key)); if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
if (entry.value != null) { target.queueDebug(BreakpointType.STEP_OVER, entry.location);
FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name); if (entry.value != null) FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name);
target.add(Instruction.storeVar(entry.location, key)); else target.add(Instruction.loadValue(entry.location, null));
} target.add(Instruction.storeVar(entry.location, key));
if (target.size() != start) target.setDebug(start);
} }
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));

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,56 +18,16 @@ 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(loc(), start - end));
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
}
if (pollute) target.add(Instruction.loadValue(loc(), null));
return;
}
int start = target.size(); int start = target.size();
body.compileWithDebug(target, scope, false); body.compileWithDebug(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
condition.compile(target, scope, true); condition.compileWithDebug(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(loc(), start - end)); 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) {
super(loc); super(loc);
this.label = label; this.label = label;

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;
@ -32,7 +33,7 @@ public class ForInStatement extends Statement {
target.add(Instruction.storeVar(loc(), scope.getKey(varName))); target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
} }
object.compileWithDebug(target, scope, true); object.compileWithDebug(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(loc(), true)); target.add(Instruction.keys(loc(), true));
int start = target.size(); int start = target.size();
@ -43,10 +44,11 @@ public class ForInStatement extends Statement {
target.add(Instruction.nop(loc())); target.add(Instruction.nop(loc()));
target.add(Instruction.loadMember(varLocation, "value")); target.add(Instruction.loadMember(varLocation, "value"));
target.queueDebug(BreakpointType.STEP_OVER, object.loc());
target.add(Instruction.storeVar(varLocation, key)); target.add(Instruction.storeVar(varLocation, key));
target.setDebug();
body.compileWithDebug(target, scope, false); target.queueDebug(BreakpointType.STEP_OVER, body.loc());
body.compile(target, scope, false);
int end = target.size(); int end = target.size();
@ -57,7 +59,6 @@ public class ForInStatement extends Statement {
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1)); target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), 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,29 +18,15 @@ 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.compileWithDebug(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(loc(), start - target.size()));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
return;
}
int start = target.size(); int start = target.size();
condition.compile(target, scope, true); condition.compileWithDebug(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compileWithDebug(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size(); int beforeAssign = target.size();
assignment.compileWithDebug(target, scope, false); assignment.compileWithDebug(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);
@ -51,28 +35,6 @@ public class ForStatement extends Statement {
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1)); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null)); 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) {
super(loc); super(loc);
@ -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;
@ -20,52 +17,32 @@ public class IfStatement extends Statement {
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
if (condition instanceof ConstantStatement) { condition.compileWithDebug(target, scope, true, breakpoint);
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(null)); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compileWithDebug(target, scope, pollute, breakpoint);
int endI = target.size(); int endI = target.size();
target.set(i, Instruction.jmpIfNot(loc(), endI - i)); target.set(i, Instruction.jmpIfNot(loc(), endI - i));
} }
else { else {
int start = target.size(); int start = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
body.compileWithDebug(target, scope, pollute); body.compileWithDebug(target, scope, pollute, breakpoint);
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
int mid = target.size(); int mid = target.size();
elseBody.compileWithDebug(target, scope, pollute); elseBody.compileWithDebug(target, scope, pollute, breakpoint);
int end = target.size(); int end = target.size();
target.set(start, Instruction.jmpIfNot(loc(), mid - start)); target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1)); target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
} }
} }
@Override @Override
public Statement optimize() { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var cond = condition.optimize(); compileWithDebug(target, scope, pollute, BreakpointType.STEP_IN);
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

@ -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,7 +37,7 @@ 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.compileWithDebug(target, scope, true, BreakpointType.STEP_OVER);
for (var ccase : cases) { for (var ccase : cases) {
target.add(Instruction.dup(loc())); target.add(Instruction.dup(loc()));
@ -52,7 +53,7 @@ public class SwitchStatement extends Statement {
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.compileWithDebug(target, scope, false, BreakpointType.STEP_OVER);
} }
int end = target.size(); int end = target.size();

View File

@ -25,27 +25,28 @@ public class TryStatement extends Statement {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(null)); 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(loc(), tryN, catchN, finN));
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }

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,22 +18,11 @@ 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(loc(), start - target.size()));
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(null)); target.add(Instruction.nop(null));
body.compile(target, scope, false); body.compileWithDebug(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size(); int end = target.size();
@ -46,19 +32,6 @@ public class WhileStatement extends Statement {
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1)); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null)); 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) {
super(loc); super(loc);
@ -71,21 +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(instr.location, continuePoint - i)); target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
} }
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(instr.location, breakPoint - i)); target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
} }
} }
} }
// 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

@ -9,8 +9,13 @@ 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) {

View File

@ -4,15 +4,18 @@ 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 compileWithDebug(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 {
@ -22,18 +25,26 @@ public class CallStatement extends Statement {
for (var arg : args) arg.compile(target, scope, true); for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.call(loc(), args.length)); target.queueDebug(type);
target.setDebug(); if (isNew) target.add(Instruction.callNew(loc(), args.length));
else target.add(Instruction.call(loc(), args.length));
if (!pollute) target.add(Instruction.discard(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compileWithDebug(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

@ -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,8 +9,7 @@ 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) {

View File

@ -1,30 +1,24 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; 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.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VoidStatement 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) {
if (value != null) value.compile(target, scope, false); value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
@Override public DiscardStatement(Location loc, Statement val) {
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); super(loc);
this.value = value; this.value = val;
} }
} }

View File

@ -17,15 +17,15 @@ public class FunctionStatement extends Statement {
public final String varName; public final String varName;
public final String[] args; public final String[] args;
public final boolean statement; 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 varName == null; }
@Override @Override
public void declare(ScopeRecord scope) { public void declare(ScopeRecord scope) {
if (varName != null) scope.define(varName); if (varName != null && statement) scope.define(varName);
} }
public static void checkBreakAndCont(CompileTarget target, int start) { public static void checkBreakAndCont(CompileTarget target, int start) {
@ -41,7 +41,7 @@ public class FunctionStatement extends Statement {
} }
} }
protected long compileBody(CompileTarget target, ScopeRecord scope, boolean polute) { private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute) {
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])) {
@ -71,11 +71,14 @@ public class FunctionStatement extends Statement {
body.declare(subscope); body.declare(subscope);
body.compile(subtarget, subscope, false); body.compile(subtarget, subscope, false);
subtarget.add(Instruction.ret(subtarget.lastLoc(loc()))); subtarget.add(Instruction.ret(end));
checkBreakAndCont(subtarget, 0); checkBreakAndCont(subtarget, 0);
if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures())); 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.functions.put(id, new FunctionBody(
subscope.localsCount(), args.length,
subtarget.array(), subscope.captures(), subscope.locals()
));
return id; return id;
} }
@ -106,9 +109,10 @@ public class FunctionStatement extends Statement {
compile(target, scope, pollute, null); compile(target, scope, pollute, null);
} }
public FunctionStatement(Location loc, String varName, String[] args, boolean statement, CompoundStatement body) { public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc); super(loc);
this.end = end;
this.varName = varName; this.varName = varName;
this.statement = statement; this.statement = statement;

View File

@ -7,8 +7,7 @@ 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) {

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,7 +26,7 @@ public class IndexAssignStatement extends Statement {
target.add(Instruction.operation(loc(), operation)); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeMember(loc(), pollute)); 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);
@ -33,7 +34,7 @@ public class IndexAssignStatement extends Statement {
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.storeMember(loc(), pollute)); 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,9 +13,6 @@ 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);
@ -24,13 +22,13 @@ public class IndexStatement extends AssignableStatement {
if (dupObj) target.add(Instruction.dup(loc())); if (dupObj) target.add(Instruction.dup(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value)); 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(loc())); target.add(Instruction.loadMember(loc()));
target.setDebug(); target.setDebug(BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
@Override @Override

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) {

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) {

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(loc(), args.length));
target.setDebug();
}
public NewStatement(Location loc, Statement func, Statement ...args) {
super(loc);
this.func = func;
this.args = args;
}
}

View File

@ -14,6 +14,14 @@ 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(loc())); target.add(Instruction.loadObj(loc()));

View File

@ -4,16 +4,21 @@ 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) {
@ -24,39 +29,6 @@ public class OperationStatement extends Statement {
else target.add(Instruction.discard(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) {
super(loc); super(loc);
this.operation = operation; this.operation = operation;

View File

@ -9,8 +9,8 @@ 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) {

View File

@ -5,13 +5,13 @@ 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.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) {
@ -26,23 +26,6 @@ public class TypeofStatement extends Statement {
target.add(Instruction.typeof(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) {
super(loc); super(loc);
this.value = value; this.value = value;

View File

@ -12,6 +12,8 @@ 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);

View File

@ -9,8 +9,7 @@ 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) {

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) {

View File

@ -1,10 +1,12 @@
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;
@ -37,12 +39,30 @@ public class Context {
var result = env.compile.call(this, null, raw, filename.toString(), env); var result = env.compile.call(this, null, raw, filename.toString(), env);
var function = (FunctionValue)Values.getMember(this, result, "function"); var function = (FunctionValue)Values.getMember(this, result, "function");
if (!engine.debugging) return function;
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray(); 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]; var maps = new SourceMap[rawMapChain.length];
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse((String)rawMapChain[i]);
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
var map = SourceMap.chain(maps); var map = SourceMap.chain(maps);
engine.onSource(filename, raw, new TreeSet<>(), map); if (map != null) {
var newBreakpoints = new TreeSet<Location>();
for (var bp : breakpoints) {
bp = map.toCompiled(bp);
if (bp != null) newBreakpoints.add(bp);
}
breakpoints = newBreakpoints;
}
engine.onSource(filename, raw, breakpoints, map);
return function; return function;
} }
@ -52,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,25 @@ 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<>();
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 +126,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 +139,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 +150,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,9 +1,10 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
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.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
@ -39,12 +40,25 @@ public class Environment implements PermissionsProvider {
@Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> { @Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
var source = Values.toString(ctx, args[0]); var source = Values.toString(ctx, args[0]);
var filename = Values.toString(ctx, args[1]); var filename = Values.toString(ctx, args[1]);
var isDebug = Values.toBoolean(args[2]);
var env = Values.wrapper(args[2], Environment.class); var env = Values.wrapper(args[2], Environment.class);
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(ctx, "function", Parsing.compile(Engine.functions, new TreeSet<>(), env, Filename.parse(filename), source)); System.out.println(source);
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()); 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; return res;
}); });
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
@ -64,12 +78,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() {
@ -103,6 +112,15 @@ 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) this.compile = compile; if (compile != null) this.compile = compile;
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);

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

@ -75,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;

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,
@ -59,12 +67,14 @@ public class SimpleDebugger implements Debugger {
public final Filename filename; public final Filename filename;
public final String source; public final String source;
public final TreeSet<Location> breakpoints; public final TreeSet<Location> breakpoints;
public final SourceMap map;
public Source(int id, Filename filename, String source, TreeSet<Location> breakpoints) { public Source(int id, Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
this.id = id; this.id = id;
this.filename = filename; this.filename = filename;
this.source = source; this.source = source;
this.breakpoints = breakpoints; this.breakpoints = breakpoints;
this.map = map;
} }
} }
private static class Breakpoint { private static class Breakpoint {
@ -104,7 +114,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 +122,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 +129,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 +171,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 +192,64 @@ 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 Location toCompiled(Location location) {
var id = filenameToId.get(location.filename());
if (id == null) return location;
var map = idToSource.get(id).map;
if (map == null) return location;
return map.toCompiled(location);
}
private Location toOriginal(Location location) {
var id = filenameToId.get(location.filename());
if (id == null) return location;
var map = idToSource.get(id).map;
if (map == null) return location;
return map.toOriginal(location);
}
private synchronized void updateFrames(Context ctx) {
var frame = ctx.peekFrame(); var frame = ctx.peekFrame();
if (frame == null) return; if (frame == null) return;
@ -205,7 +268,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 +311,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 +338,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 +348,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 +393,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 +446,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 +493,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 +508,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 +590,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 +615,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 +659,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 +676,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 +685,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 +734,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 +804,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 +812,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,49 +827,38 @@ 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, map);
idToSource.put(id, src); idToSource.put(id, src);
filenameToId.put(filename, id); filenameToId.put(filename, id);
@ -720,87 +878,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(toCompiled(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,72 @@ 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; 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;
private PendingResult(boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) {
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(false, false, false, null, null, 0);
}
public static PendingResult ofReturn(Object value) {
return new PendingResult(true, false, false, value, null, 0);
}
public static PendingResult ofThrow(EngineException error) {
return new PendingResult(false, false, true, null, error, 0);
}
public static PendingResult ofJump(int codePtr) {
return new PendingResult(false, true, false, null, null, codePtr);
} }
} }
@ -51,11 +91,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 +144,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 +184,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);
@ -166,7 +201,7 @@ public class CodeFrame {
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) {
@ -177,111 +212,75 @@ public class CodeFrame {
catch (EngineException e) { error = e; } catch (EngineException e) { error = e; }
} }
while (!tryStack.empty()) { if (!tryStack.empty()) {
var tryCtx = tryStack.peek(); var tryCtx = tryStack.peek();
var newState = -1; var pendingResult = PendingResult.ofNone();
boolean shouldCatch = false, shouldFinally = false, shouldExit = this.popTryFlag;
switch (tryCtx.state) { if (tryCtx.state != TryState.FINALLY) {
case TryCtx.STATE_TRY: if (error != null && tryCtx.hasCatch()) shouldCatch = true;
if (error != null) { else if (error != null && tryCtx.hasFinally()) {
if (tryCtx.hasCatch) { pendingResult = PendingResult.ofThrow(error);
tryCtx.err = error; shouldFinally = true;
newState = TryCtx.STATE_CATCH; }
} else if (returnValue != Runners.NO_RETURN && tryCtx.hasFinally()) {
else if (tryCtx.hasFinally) { pendingResult = PendingResult.ofReturn(returnValue);
tryCtx.err = error; shouldFinally = true;
newState = TryCtx.STATE_FINALLY_THREW; }
} else if (jumpFlag && !tryCtx.inBounds(codePtr) && tryCtx.hasFinally()) {
break; pendingResult = PendingResult.ofJump(codePtr);
} shouldFinally = true;
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;
} }
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.hasCatch() && shouldCatch) {
if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value));
if (newState == -1) { codePtr = tryCtx.catchStart;
var err = tryCtx.err; stackPtr = tryCtx.restoreStackPtr;
tryStack.pop(); tryStack.pop();
if (!tryStack.isEmpty()) tryStack.peek().err = err; tryStack.push(tryCtx._catch(error));
continue; error = null;
returnValue = Runners.NO_RETURN;
} }
else if (tryCtx.hasFinally() && shouldFinally) {
if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
tryCtx.state = newState; codePtr = tryCtx.finallyStart;
switch (newState) { stackPtr = tryCtx.restoreStackPtr;
case TryCtx.STATE_CATCH: tryStack.pop();
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); tryStack.push(tryCtx._finally(pendingResult));
codePtr = tryCtx.catchStart; error = null;
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true); returnValue = Runners.NO_RETURN;
break; }
default: else if (shouldExit) {
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));
}
else {
codePtr = tryCtx.end;
if (tryCtx.result.isJump) codePtr = tryCtx.result.ptr;
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (tryCtx.result.isThrow) error = tryCtx.result.error;
}
} }
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,27 +97,26 @@ 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 offset = instr.get(0), count = instr.get(1);
@ -326,7 +325,8 @@ 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 MOVE: return execMove(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

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

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

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 {

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;
@ -11,7 +12,6 @@ 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.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.Buffer;
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; import me.topchetoeu.jscript.parsing.Parsing;
@ -179,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

@ -1,97 +1,108 @@
package me.topchetoeu.jscript.mapping; package me.topchetoeu.jscript.mapping;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Map; import java.util.List;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
public class SourceMap implements LocationMap { public class SourceMap {
private final TreeMap<Location, Location> orgToSrc = new TreeMap<>(); private final TreeMap<Long, Long> origToComp = new TreeMap<>();
private final TreeMap<Location, Location> srcToOrg = new TreeMap<>(); private final TreeMap<Long, Long> compToOrig = new TreeMap<>();
public Location toSource(Location loc) { public Location toCompiled(Location loc) { return convert(loc, origToComp); }
return convert(loc, orgToSrc); 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 Location toOriginal(Location loc) { public SourceMap apply(SourceMap map) {
return convert(loc, srcToOrg); var res = new SourceMap();
}
public static Location convert(Location loc, TreeMap<Location, Location> map) { for (var el : new ArrayList<>(origToComp.entrySet())) {
if (map.containsKey(loc)) return loc; var mapped = convert(el.getValue(), map.origToComp);
add(el.getKey(), mapped);
var srcA = map.floorKey(loc);
return srcA == null ? loc : srcA;
}
public void chain(LocationMap map) {
for (var key : orgToSrc.keySet()) {
orgToSrc.put(key, map.toSource(key));
} }
for (var key : srcToOrg.keySet()) { for (var el : new ArrayList<>(compToOrig.entrySet())) {
srcToOrg.put(map.toOriginal(key), key); var mapped = convert(el.getKey(), map.compToOrig);
add(el.getValue(), mapped);
} }
return res;
} }
public SourceMap clone() { public SourceMap clone() {
var res = new SourceMap(); var res = new SourceMap();
res.orgToSrc.putAll(this.orgToSrc); res.origToComp.putAll(this.origToComp);
res.srcToOrg.putAll(this.srcToOrg); res.compToOrig.putAll(this.compToOrig);
return res; return res;
} }
public void split(Map<Filename, TreeMap<Location, Location>> maps) {
for (var el : orgToSrc.entrySet()) {
var map = maps.get(el.getKey().filename());
if (map == null) maps.put(el.getKey().filename(), map = new TreeMap<>());
map.put(el.getKey(), el.getValue());
map = maps.get(el.getValue().filename());
if (map == null) maps.put(el.getValue().filename(), map = new TreeMap<>());
map.put(el.getValue(), el.getKey());
}
}
public static SourceMap parse(String raw) { public static SourceMap parse(String raw) {
var mapping = VLQ.decodeMapping(raw);
var res = new SourceMap(); var res = new SourceMap();
var json = JSON.parse(null, raw).map();
var sources = json.list("sources").stream().map(v -> v.string()).toArray(String[]::new);
var dstFilename = Filename.parse(json.string("file"));
var mapping = VLQ.decodeMapping(json.string("mappings"));
var srcI = 0; var compRow = 0l;
var srcRow = 0; var compCol = 0l;
var srcCol = 0;
for (var dstRow = 0; dstRow < mapping.length; dstRow++) { for (var origRow = 0; origRow < mapping.length; origRow++) {
var dstCol = 0; var origCol = 0;
for (var rawSeg : mapping[dstRow]) { for (var rawSeg : mapping[origRow]) {
dstCol += rawSeg.length > 0 ? rawSeg[0] : 0; if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files.");
srcI += rawSeg.length > 1 ? rawSeg[1] : 0; origCol += rawSeg.length > 0 ? rawSeg[0] : 0;
srcRow += rawSeg.length > 2 ? rawSeg[2] : 0; compRow += rawSeg.length > 2 ? rawSeg[2] : 0;
srcCol += rawSeg.length > 3 ? rawSeg[3] : 0; compCol += rawSeg.length > 3 ? rawSeg[3] : 0;
var src = new Location(srcRow + 1, srcCol + 1, Filename.parse(sources[srcI])); var compPacked = ((long)compRow << 32) | compCol;
var dst = new Location(dstRow + 1, dstCol + 1, dstFilename); var origPacked = ((long)origRow << 32) | origCol;
res.orgToSrc.put(src, dst); res.add(origPacked, compPacked);
res.srcToOrg.put(dst, src);
} }
} }
return res; 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) { public static SourceMap chain(SourceMap ...maps) {
if (maps.length == 0) return null; if (maps.length == 0) return null;
var res = maps[0]; var res = maps[0];
for (var i = 1; i < maps.length; i++) res.chain(maps[i]); for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]);
return res; 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

@ -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;
@ -806,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), false, 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) {
@ -868,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();
@ -879,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, false, 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);
@ -895,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();
@ -904,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);
@ -965,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), statement, 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);
} }
@ -1186,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);
@ -1232,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;
@ -1243,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);
@ -1509,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;
@ -1690,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(
@ -1716,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);
@ -1829,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),
@ -1872,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.loc, e)); target.add(Instruction.throwSyntax(e.loc, e));
} }
res.add(Instruction.ret(body.loc())); 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, "", new FunctionBody(subscope.localsCount(), 0, res.array(), subscope.captures(), subscope.locals()), new ValueVariable[0]); 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, new FunctionBody(Instruction.throwSyntax(e.loc, e)));
}
} }
} }

80
test.txt Normal file
View File

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

0
undefined Normal file
View File