Add support for source mappings #10

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

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,53 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript;
public class Buffer { public class Buffer {
private byte[] data; private byte[] data;
private int length; private int length;
public void write(int i, byte[] val) { public void write(int i, byte[] val) {
if (i + val.length > data.length) { if (i + val.length > data.length) {
var newCap = i + val.length + 1; var newCap = i + val.length + 1;
if (newCap < data.length * 2) newCap = data.length * 2; if (newCap < data.length * 2) newCap = data.length * 2;
var tmp = new byte[newCap]; var tmp = new byte[newCap];
System.arraycopy(this.data, 0, tmp, 0, length); System.arraycopy(this.data, 0, tmp, 0, length);
this.data = tmp; this.data = tmp;
} }
System.arraycopy(val, 0, data, i, val.length); System.arraycopy(val, 0, data, i, val.length);
if (i + val.length > length) length = i + val.length; if (i + val.length > length) length = i + val.length;
} }
public int read(int i, byte[] buff) { public int read(int i, byte[] buff) {
int n = buff.length; int n = buff.length;
if (i + n > length) n = length - i; if (i + n > length) n = length - i;
System.arraycopy(data, i, buff, 0, n); System.arraycopy(data, i, buff, 0, n);
return n; return n;
} }
public byte[] data() { public void append(byte b) {
var res = new byte[length]; write(length, new byte[] { b });
System.arraycopy(this.data, 0, res, 0, length); }
return res;
} public byte[] data() {
public int length() { var res = new byte[length];
return length; System.arraycopy(this.data, 0, res, 0, length);
} return res;
}
public Buffer(byte[] data) { public int length() {
this.data = new byte[data.length]; return length;
this.length = data.length; }
System.arraycopy(data, 0, this.data, 0, data.length);
} public Buffer(byte[] data) {
} this.data = new byte[data.length];
this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length);
}
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
}

View File

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

View File

@ -1,165 +1,170 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment; 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.Values; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.filesystem.Mode;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
public class Main { import me.topchetoeu.jscript.lib.Internals;
public static class Printer implements Observer<Object> {
public void next(Object data) { public class Main {
Values.printValue(null, data); public static class Printer implements Observer<Object> {
System.out.println(); public void next(Object data) {
} Values.printValue(null, data);
System.out.println();
public void error(RuntimeException err) { }
Values.printError(err, null);
} public void error(RuntimeException err) {
Values.printError(err, null);
public void finish() { }
engineTask.interrupt();
} public void finish() {
} engineTask.interrupt();
}
static Thread engineTask, debugTask; }
static Engine engine = new Engine(true);
static DebugServer debugServer = new DebugServer(); static Thread engineTask, debugTask;
static Environment environment = new Environment(null, null, null); static Engine engine = new Engine(true);
static DebugServer debugServer = new DebugServer();
static int j = 0; static Environment environment = new Environment(null, null, null);
static boolean exited = false;
static String[] args; static int j = 0;
static boolean exited = false;
private static void reader() { static String[] args;
try {
for (var arg : args) { private static void reader() {
try { try {
if (arg.equals("--ts")) initTypescript(); for (var arg : args) {
else { try {
var file = Path.of(arg); if (arg.equals("--ts")) initTypescript();
var raw = Files.readString(file); else {
var res = engine.pushMsg( var file = Path.of(arg);
false, new Context(engine, environment), var raw = Files.readString(file);
Filename.fromFile(file.toFile()), var res = engine.pushMsg(
raw, null false, new Context(engine, environment),
).await(); Filename.fromFile(file.toFile()),
Values.printValue(null, res); raw, null
System.out.println(); ).await();
} Values.printValue(null, res);
} System.out.println();
catch (EngineException e) { Values.printError(e, null); } }
} }
for (var i = 0; ; i++) { catch (EngineException e) { Values.printError(e, null); }
try { }
var raw = Reading.read(); for (var i = 0; ; i++) {
try {
if (raw == null) break; var raw = Reading.read();
var res = engine.pushMsg(
false, new Context(engine, environment), if (raw == null) break;
new Filename("jscript", "repl/" + i + ".js"), var res = engine.pushMsg(
raw, null false, new Context(engine, environment),
).await(); new Filename("jscript", "repl/" + i + ".js"),
Values.printValue(null, res); raw, null
System.out.println(); ).await();
} Values.printValue(null, res);
catch (EngineException e) { Values.printError(e, null); } System.out.println();
catch (SyntaxException e) { Values.printError(e, null); } }
} catch (EngineException e) { Values.printError(e, null); }
} catch (SyntaxException e) { Values.printError(e, null); }
catch (IOException e) { }
System.out.println(e.toString()); }
exited = true; catch (IOException e) {
} System.out.println(e.toString());
catch (RuntimeException ex) { exited = true;
if (!exited) { }
System.out.println("Internal error ocurred:"); catch (RuntimeException ex) {
ex.printStackTrace(); if (!exited) {
} System.out.println("Internal error ocurred:");
} ex.printStackTrace();
if (exited) { }
debugTask.interrupt(); }
engineTask.interrupt(); if (exited) {
} debugTask.interrupt();
} engineTask.interrupt();
}
private static void initEnv() { }
environment = Internals.apply(environment);
private static void initEnv() {
environment.global.define("exit", _ctx -> { environment = Internals.apply(environment);
exited = true;
throw new InterruptException(); environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
}); exited = true;
environment.global.define("go", _ctx -> { throw new InterruptException();
try { }));
var f = Path.of("do.js"); environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); try {
return func.call(_ctx); var f = Path.of("do.js");
} var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
catch (IOException e) { return func.call(_ctx);
throw new EngineException("Couldn't open do.js"); }
} catch (IOException e) {
}); throw new EngineException("Couldn't open do.js");
}
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); }));
environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
} environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
private static void initEngine() { environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); }
engineTask = engine.start(); private static void initEngine() {
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
} engineTask = engine.start();
private static void initTypescript() { debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
try { }
var tsEnv = Internals.apply(new Environment(null, null, null)); private static void initTypescript() {
var bsEnv = Internals.apply(new Environment(null, null, null)); try {
var tsEnv = Internals.apply(new Environment(null, null, null));
engine.pushMsg( tsEnv.stackVisible = false;
false, new Context(engine, tsEnv), tsEnv.global.define(null, "module", false, new ObjectValue());
new Filename("jscript", "ts.js"), var bsEnv = Internals.apply(new Environment(null, null, null));
Reading.resourceToString("js/ts.js"), null bsEnv.stackVisible = false;
).await();
System.out.println("Loaded typescript!"); engine.pushMsg(
false, new Context(engine, tsEnv),
var ctx = new Context(engine, bsEnv); new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
engine.pushMsg( ).await();
false, ctx, System.out.println("Loaded typescript!");
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) var ctx = new Context(engine, bsEnv);
).await();
} engine.pushMsg(
catch (EngineException e) { false, ctx,
Values.printError(e, "(while initializing TS)"); 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"))
} ).await();
}
public static void main(String args[]) { catch (EngineException e) {
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); Values.printError(e, "(while initializing TS)");
}
Main.args = args; }
var reader = new Thread(Main::reader);
public static void main(String args[]) {
initEnv(); System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
initEngine();
Main.args = args;
reader.setDaemon(true); var reader = new Thread(Main::reader);
reader.setName("STD Reader");
reader.start(); initEnv();
} initEngine();
}
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +1,41 @@
package me.topchetoeu.jscript.compilation.control; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
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;
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;
@Override }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(statements.length).locate(loc())); return true;
var i = 0; }
for (var el : statements) {
if (el != null) { @Override
target.add(Instruction.dup().locate(loc())); public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadValue(i).locate(loc())); target.add(Instruction.loadArr(loc(), statements.length));
el.compile(target, scope, true);
target.add(Instruction.storeMember().locate(loc())); for (var i = 0; i < statements.length; i++) {
} var el = statements[i];
i++; if (el != null) {
} target.add(Instruction.dup(loc()));
if (!pollute) target.add(Instruction.discard().locate(loc())); target.add(Instruction.loadValue(loc(), i));
} el.compile(target, scope, true);
target.add(Instruction.storeMember(loc()));
public ArrayStatement(Location loc, Statement[] statements) { }
super(loc); }
this.statements = statements;
} if (!pollute) target.add(Instruction.discard(loc()));
} }
public ArrayStatement(Location loc, Statement[] statements) {
super(loc);
this.statements = statements;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,111 +1,125 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Arrays;
import java.util.List; import java.util.Collections;
import java.util.Stack; import java.util.List;
import java.util.TreeSet; import java.util.Stack;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename; import java.util.stream.Collectors;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class Context { import me.topchetoeu.jscript.mapping.SourceMap;
private final Stack<Environment> env = new Stack<>();
private final ArrayList<CodeFrame> frames = new ArrayList<>(); public class Context {
public final Engine engine; private final Stack<Environment> env = new Stack<>();
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public Environment environment() { public final Engine engine;
return env.empty() ? null : env.peek();
} public Environment environment() {
return env.empty() ? null : env.peek();
public Context pushEnv(Environment env) { }
this.env.push(env);
return this; public Context pushEnv(Environment env) {
} this.env.push(env);
public void popEnv() { return this;
if (!env.empty()) this.env.pop(); }
} public void popEnv() {
if (!env.empty()) this.env.pop();
public FunctionValue compile(Filename filename, String raw) { }
var env = environment();
var transpiled = env.compile.call(this, null, raw, filename.toString(), env); public FunctionValue compile(Filename filename, String raw) {
String source = null; var env = environment();
FunctionValue runner = null; var result = env.compile.call(this, null, raw, filename.toString(), env);
if (transpiled instanceof ObjectValue) { var function = (FunctionValue)Values.getMember(this, result, "function");
source = Values.toString(this, Values.getMember(this, transpiled, "source")); if (!engine.debugging) return function;
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner; var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
} var breakpoints = new TreeSet<>(
else source = Values.toString(this, transpiled); Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
.map(v -> Location.parse(Values.toString(this, v)))
var breakpoints = new TreeSet<Location>(); .collect(Collectors.toList())
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source); );
engine.onSource(filename, source, breakpoints); var maps = new SourceMap[rawMapChain.length];
if (runner != null) res = (FunctionValue)runner.call(this, null, res); for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
return res; var map = SourceMap.chain(maps);
}
if (map != null) {
var newBreakpoints = new TreeSet<Location>();
public void pushFrame(CodeFrame frame) { for (var bp : breakpoints) {
frames.add(frame); bp = map.toCompiled(bp);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); if (bp != null) newBreakpoints.add(bp);
pushEnv(frame.function.environment); }
} breakpoints = newBreakpoints;
public boolean popFrame(CodeFrame frame) { }
if (frames.size() == 0) return false;
if (frames.get(frames.size() - 1) != frame) return false; engine.onSource(filename, raw, breakpoints, map);
frames.remove(frames.size() - 1);
popEnv(); return function;
engine.onFramePop(this, frame); }
return true;
}
public CodeFrame peekFrame() { public void pushFrame(CodeFrame frame) {
if (frames.size() == 0) return null; frames.add(frame);
return frames.get(frames.size() - 1); if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
} pushEnv(frame.function.environment);
engine.onFramePush(this, frame);
public List<CodeFrame> frames() { }
return Collections.unmodifiableList(frames); public boolean popFrame(CodeFrame frame) {
} if (frames.size() == 0) return false;
public List<String> stackTrace() { if (frames.get(frames.size() - 1) != frame) return false;
var res = new ArrayList<String>(); frames.remove(frames.size() - 1);
popEnv();
for (var i = frames.size() - 1; i >= 0; i--) { engine.onFramePop(this, frame);
var el = frames.get(i); return true;
var name = el.function.name; }
Location loc = null; public CodeFrame peekFrame() {
if (frames.size() == 0) return null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; return frames.get(frames.size() - 1);
if (loc == null) loc = el.function.loc(); }
var trace = ""; public List<CodeFrame> frames() {
return Collections.unmodifiableList(frames);
if (loc != null) trace += "at " + loc.toString() + " "; }
if (name != null && !name.equals("")) trace += "in " + name + " "; public List<String> stackTrace() {
var res = new ArrayList<String>();
trace = trace.trim();
for (var i = frames.size() - 1; i >= 0; i--) {
if (!trace.equals("")) res.add(trace); var el = frames.get(i);
} var name = el.function.name;
Location loc = null;
return res;
} for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
public Context(Engine engine) {
this.engine = engine; var trace = "";
}
public Context(Engine engine, Environment env) { if (loc != null) trace += "at " + loc.toString() + " ";
this(engine); if (name != null && !name.equals("")) trace += "in " + name + " ";
this.pushEnv(env);
trace = trace.trim();
}
} if (!trace.equals("")) res.add(trace);
}
return res;
}
public Context(Engine engine) {
this.engine = engine;
}
public Context(Engine engine, Environment env) {
this(engine);
this.pushEnv(env);
}
}

View File

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

View File

@ -1,100 +1,131 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.filesystem.RootFilesystem; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.permissions.Permission; import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.permissions.PermissionsProvider; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
public class Environment implements PermissionsProvider { import me.topchetoeu.jscript.interop.NativeSetter;
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.parsing.Parsing;
public final Data data = new Data(); import me.topchetoeu.jscript.permissions.Permission;
public static final HashMap<String, Symbol> symbols = new HashMap<>(); import me.topchetoeu.jscript.permissions.PermissionsProvider;
public GlobalScope global; public class Environment implements PermissionsProvider {
public WrappersProvider wrappers; private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this); public final Data data = new Data();
public static final HashMap<String, Symbol> symbols = new HashMap<>();
private static int nextId = 0;
public GlobalScope global;
@Native public int id = ++nextId; public WrappersProvider wrappers;
public PermissionsProvider permissions = null;
@Native public FunctionValue compile; public final RootFilesystem filesystem = new RootFilesystem(this);
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); private static int nextId = 0;
});
@Native public boolean stackVisible = true;
public Environment addData(Data data) { @Native public int id = ++nextId;
this.data.addAll(data);
return this; @Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
} var source = Values.toString(ctx, args[0]);
var filename = Values.toString(ctx, args[1]);
@Native public ObjectValue proto(String name) { var isDebug = Values.toBoolean(args[2]);
return prototypes.get(name);
} var env = Values.wrapper(args[2], Environment.class);
@Native public void setProto(String name, ObjectValue val) { var res = new ObjectValue();
prototypes.put(name, val);
} var target = Parsing.compile(env, Filename.parse(filename), source);
Engine.functions.putAll(target.functions);
@Native public Symbol symbol(String name) { Engine.functions.remove(0l);
if (symbols.containsKey(name)) return symbols.get(name);
else { res.defineProperty(ctx, "function", target.func(env));
var res = new Symbol(name); res.defineProperty(ctx, "mapChain", new ArrayValue());
symbols.put(name, res);
return res;
} if (isDebug) {
} res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
}
@NativeGetter("global") public ObjectValue getGlobal() {
return global.obj; return res;
} });
@NativeSetter("global") public void setGlobal(ObjectValue val) { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
global = new GlobalScope(val); throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
} });
@Native public Environment fork() { public Environment addData(Data data) {
var res = new Environment(compile, null, global); this.data.addAll(data);
res.wrappers = wrappers.fork(res); return this;
res.regexConstructor = regexConstructor; }
res.prototypes = new HashMap<>(prototypes);
return res; @Native public ObjectValue proto(String name) {
} return prototypes.get(name);
@Native public Environment child() { }
var res = fork(); @Native public void setProto(String name, ObjectValue val) {
res.global = res.global.globalChild(); prototypes.put(name, val);
return res; }
}
@Native public Symbol symbol(String name) {
@Override public boolean hasPermission(Permission perm, char delim) { return getSymbol(name);
return permissions == null || permissions.hasPermission(perm, delim); }
}
@Override public boolean hasPermission(Permission perm) { @NativeGetter("global") public ObjectValue getGlobal() {
return permissions == null || permissions.hasPermission(perm); return global.obj;
} }
@NativeSetter("global") public void setGlobal(ObjectValue val) {
public Context context(Engine engine) { global = new GlobalScope(val);
return new Context(engine).pushEnv(this); }
}
@Native public Environment fork() {
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { var res = new Environment(compile, null, global);
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]); res.wrappers = wrappers.fork(res);
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); res.regexConstructor = regexConstructor;
if (global == null) global = new GlobalScope(); res.prototypes = new HashMap<>(prototypes);
return res;
this.wrappers = nativeConverter; }
this.compile = compile; @Native public Environment child() {
this.global = global; var res = fork();
} res.global = res.global.globalChild();
} return res;
}
@Override public boolean hasPermission(Permission perm, char delim) {
return permissions == null || permissions.hasPermission(perm, delim);
}
@Override public boolean hasPermission(Permission perm) {
return permissions == null || permissions.hasPermission(perm);
}
public Context context(Engine engine) {
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) {
if (compile != null) this.compile = compile;
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope();
this.wrappers = nativeConverter;
this.global = global;
}
}

View File

@ -1,40 +1,51 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.util.TreeSet; import java.util.TreeSet;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction; 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 /**
* @param breakpoints * Called when a script has been loaded
*/ * @param filename The name of the source
void onSource(Filename filename, String source, TreeSet<Location> breakpoints); * @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
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. */
* This function might pause in order to await debugging commands. void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
* @param ctx The context of execution
* @param frame The frame in which execution is occuring /**
* @param instruction The instruction which was or will be executed * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* @param loc The most recent location the code frame has been at * This function might pause in order to await debugging commands.
* @param returnVal The return value of the instruction, Runners.NO_RETURN if none * @param ctx The context of execution
* @param error The error that the instruction threw, null if none * @param frame The frame in which execution is occuring
* @param caught Whether or not the error has been caught * @param instruction The instruction which was or will be executed
* @return Whether or not the frame should restart * @param loc The most recent location the code frame has been at
*/ * @param returnVal The return value of the instruction, Runners.NO_RETURN if none
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); * @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
/** * @return Whether or not the frame should restart
* Called immediatly after a frame has been popped out of the frame stack. */
* This function might pause in order to await debugging commands. boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
* @param ctx The context of execution
* @param frame The code frame which was popped out /**
*/ * Called immediatly before a frame has been pushed on the frame stack.
void onFramePop(Context ctx, CodeFrame frame); * 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.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Context ctx, CodeFrame frame);
}

View File

@ -1,35 +1,34 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
public interface DebugHandler { 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);
void getScriptSource(V8Message msg);
void getScriptSource(V8Message msg); void getPossibleBreakpoints(V8Message msg);
void getPossibleBreakpoints(V8Message msg);
void resume(V8Message msg);
void resume(V8Message msg); void pause(V8Message msg);
void pause(V8Message msg);
void stepInto(V8Message msg);
void stepInto(V8Message msg); void stepOut(V8Message msg);
void stepOut(V8Message msg); void stepOver(V8Message msg);
void stepOver(V8Message msg);
void setPauseOnExceptions(V8Message msg);
void setPauseOnExceptions(V8Message msg);
void evaluateOnCallFrame(V8Message msg);
void evaluateOnCallFrame(V8Message msg);
void getProperties(V8Message msg);
void getProperties(V8Message msg); void releaseObjectGroup(V8Message msg);
void releaseObjectGroup(V8Message msg); void releaseObject(V8Message msg);
void releaseObject(V8Message msg); /**
/** * This method might not execute the actual code for well-known requests
* This method might not execute the actual code for well-known requests */
*/ void callFunctionOn(V8Message msg);
void callFunctionOn(V8Message msg);
void runtimeEnable(V8Message msg);
void runtimeEnable(V8Message msg); }
}

View File

@ -1,246 +1,245 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.Metadata;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedIOException; import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.exceptions.UncheckedIOException;
import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer {
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); public class DebugServer {
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
private final byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier(); private final byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier();
private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes()); private static void send(HttpRequest req, String val) throws IOException {
} req.writeResponse(200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() { // SILENCE JAVA
try { private MessageDigest getDigestInstance() {
return MessageDigest.getInstance("sha1"); try {
} return MessageDigest.getInstance("sha1");
catch (Throwable e) { throw new UncheckedException(e); } }
catch (Throwable e) { throw new UncheckedException(e); }
}
}
private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func); private static Thread runAsync(Runnable func, String name) {
res.setName(name); var res = new Thread(func);
res.start(); res.setName(name);
return res; res.start();
} return res;
}
private void handle(WebSocket ws, Debugger debugger) {
WebSocketMessage raw; private void handle(WebSocket ws, Debugger debugger) {
WebSocketMessage raw;
debugger.connect();
debugger.connect();
while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) { while ((raw = ws.receive()) != null) {
ws.send(new V8Error("Expected a text message.")); if (raw.type != Type.Text) {
continue; ws.send(new V8Error("Expected a text message."));
} continue;
}
V8Message msg;
V8Message msg;
try {
msg = new V8Message(raw.textData()); try {
} msg = new V8Message(raw.textData());
catch (SyntaxException e) { }
ws.send(new V8Error(e.getMessage())); catch (SyntaxException e) {
return; ws.send(new V8Error(e.getMessage()));
} return;
}
try {
switch (msg.name) { try {
case "Debugger.enable": switch (msg.name) {
connNotifier.next(); case "Debugger.enable":
debugger.enable(msg); continue; connNotifier.next();
case "Debugger.disable": debugger.disable(msg); continue; debugger.enable(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;
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue; case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue; case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
case "Debugger.resume": debugger.resume(msg); continue; case "Debugger.resume": debugger.resume(msg); continue;
case "Debugger.pause": debugger.pause(msg); continue; case "Debugger.pause": debugger.pause(msg); continue;
case "Debugger.stepInto": debugger.stepInto(msg); continue; case "Debugger.stepInto": debugger.stepInto(msg); continue;
case "Debugger.stepOut": debugger.stepOut(msg); continue; case "Debugger.stepOut": debugger.stepOut(msg); continue;
case "Debugger.stepOver": debugger.stepOver(msg); continue; case "Debugger.stepOver": debugger.stepOver(msg); continue;
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
case "Runtime.releaseObject": debugger.releaseObject(msg); continue; case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
case "Runtime.getProperties": debugger.getProperties(msg); continue; case "Runtime.getProperties": debugger.getProperties(msg); continue;
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
// case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; // case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue;
case "Runtime.enable": debugger.runtimeEnable(msg); continue; case "Runtime.enable": debugger.runtimeEnable(msg); continue;
} }
if ( if (
msg.name.startsWith("DOM.") || msg.name.startsWith("DOM.") ||
msg.name.startsWith("DOMDebugger.") || msg.name.startsWith("DOMDebugger.") ||
msg.name.startsWith("Emulation.") || msg.name.startsWith("Emulation.") ||
msg.name.startsWith("Input.") || msg.name.startsWith("Input.") ||
msg.name.startsWith("Network.") || msg.name.startsWith("Network.") ||
msg.name.startsWith("Page.") msg.name.startsWith("Page.")
) ws.send(new V8Error("This isn't a browser...")); ) ws.send(new V8Error("This isn't a browser..."));
else ws.send(new V8Error("This API is not supported yet.")); else ws.send(new V8Error("This API is not supported yet."));
} }
catch (Throwable e) { catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
throw new UncheckedException(e); throw new UncheckedException(e);
} }
} }
debugger.disconnect(); debugger.disconnect();
} }
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
var key = req.headers.get("sec-websocket-key"); var key = req.headers.get("sec-websocket-key");
if (key == null) { if (key == null) {
req.writeResponse( req.writeResponse(
426, "Upgrade Required", "text/txt", 426, "Upgrade Required", "text/txt",
"Expected a WS upgrade".getBytes() "Expected a WS upgrade".getBytes()
); );
return; return;
} }
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
)); ));
req.writeCode(101, "Switching Protocols"); req.writeCode(101, "Switching Protocols");
req.writeHeader("Connection", "Upgrade"); req.writeHeader("Connection", "Upgrade");
req.writeHeader("Sec-WebSocket-Accept", resKey); req.writeHeader("Sec-WebSocket-Accept", resKey);
req.writeLastHeader("Upgrade", "WebSocket"); req.writeLastHeader("Upgrade", "WebSocket");
var ws = new WebSocket(socket); var ws = new WebSocket(socket);
var debugger = debuggerProvider.getDebugger(ws, req); var debugger = debuggerProvider.getDebugger(ws, req);
if (debugger == null) { if (debugger == null) {
ws.close(); ws.close();
return; return;
} }
runAsync(() -> { runAsync(() -> {
try { handle(ws, debugger); } try { handle(ws, debugger); }
catch (RuntimeException e) { catch (RuntimeException e) {
ws.send(new V8Error(e.getMessage())); ws.send(new V8Error(e.getMessage()));
} }
finally { ws.close(); debugger.disconnect(); } finally { ws.close(); debugger.disconnect(); }
}, "Debug Handler"); }, "Debug Handler");
} }
public void awaitConnection() { public void awaitConnection() {
connNotifier.await(); connNotifier.await();
} }
public void run(InetSocketAddress address) { public void run(InetSocketAddress address) {
try { try {
ServerSocket server = new ServerSocket(); ServerSocket server = new ServerSocket();
server.bind(address); server.bind(address);
try { try {
while (true) { while (true) {
var socket = server.accept(); var socket = server.accept();
var req = HttpRequest.read(socket); var req = HttpRequest.read(socket);
if (req == null) continue; if (req == null) continue;
switch (req.path) { switch (req.path) {
case "/json/version": case "/json/version":
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
break; break;
case "/json/list": case "/json/list":
case "/json": { case "/json": {
var res = new JSONList(); var res = new JSONList();
for (var el : targets.entrySet()) { for (var el : targets.entrySet()) {
res.add(new JSONMap() res.add(new JSONMap()
.set("description", "JScript debugger") .set("description", "JScript debugger")
.set("favicon", "/favicon.ico") .set("favicon", "/favicon.ico")
.set("id", el.getKey()) .set("id", el.getKey())
.set("type", "node") .set("type", "node")
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey()) .set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
); );
} }
send(req, JSON.stringify(res)); send(req, JSON.stringify(res));
break; break;
} }
case "/json/protocol": case "/json/protocol":
req.writeResponse(200, "OK", "application/json", protocol); req.writeResponse(200, "OK", "application/json", protocol);
break; break;
case "/json/new": case "/json/new":
case "/json/activate": case "/json/activate":
case "/json/close": case "/json/close":
case "/devtools/inspector.html": case "/devtools/inspector.html":
req.writeResponse( req.writeResponse(
501, "Not Implemented", "text/txt", 501, "Not Implemented", "text/txt",
"This feature isn't (and probably won't be) implemented.".getBytes() "This feature isn't (and probably won't be) implemented.".getBytes()
); );
break; break;
case "/": case "/":
case "/index.html": case "/index.html":
req.writeResponse(200, "OK", "text/html", index); req.writeResponse(200, "OK", "text/html", index);
break; break;
case "/favicon.ico": case "/favicon.ico":
req.writeResponse(200, "OK", "image/png", favicon); req.writeResponse(200, "OK", "image/png", favicon);
break; break;
default: default:
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) { if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
onWsConnect(req, socket, targets.get(req.path.substring(1))); onWsConnect(req, socket, targets.get(req.path.substring(1)));
} }
break; break;
} }
} }
} }
finally { server.close(); } finally { server.close(); }
} }
catch (IOException e) { throw new UncheckedIOException(e); } catch (IOException e) { throw new UncheckedIOException(e); }
} }
public Thread start(InetSocketAddress address, boolean daemon) { public Thread start(InetSocketAddress address, boolean daemon) {
var res = new Thread(() -> run(address), "Debug Server"); var res = new Thread(() -> run(address), "Debug Server");
res.setDaemon(daemon); res.setDaemon(daemon);
res.start(); res.start();
return res; return res;
} }
public DebugServer() { public DebugServer() {
try { try {
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); this.index = Reading.resourceToString("debugger/index.html")
this.index = index .replace("${NAME}", Metadata.name())
.replace("${NAME}", Metadata.name()) .replace("${VERSION}", Metadata.version())
.replace("${VERSION}", Metadata.version()) .replace("${AUTHOR}", Metadata.author())
.replace("${AUTHOR}", Metadata.author()) .getBytes();
.getBytes(); }
} catch (IOException e) { throw new UncheckedIOException(e); }
catch (IOException e) { throw new UncheckedIOException(e); } }
} }
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,51 +1,54 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class ScopeValue extends ObjectValue { public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables; public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>(); public final HashMap<String, Integer> names = new HashMap<>();
@Override @Override
protected Object getField(Context ctx, Object key) { protected Object getField(Context ctx, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ctx); if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) { protected boolean setField(Context ctx, Object key, Object val) {
if (names.containsKey(key)) { if (names.containsKey(key)) {
variables[names.get(key)].set(ctx, val); variables[names.get(key)].set(ctx, val);
return true; return true;
} }
return super.setField(ctx, key, val); var proto = getPrototype(ctx);
} if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true;
@Override
protected void deleteField(Context ctx, Object key) { return super.setField(ctx, key, val);
if (names.containsKey(key)) return; }
super.deleteField(ctx, key); @Override
} protected void deleteField(Context ctx, Object key) {
@Override if (names.containsKey(key)) return;
protected boolean hasField(Context ctx, Object key) { super.deleteField(ctx, key);
if (names.containsKey(key)) return true; }
return super.hasField(ctx, key); @Override
} protected boolean hasField(Context ctx, Object key) {
@Override if (names.containsKey(key)) return true;
public List<Object> keys(boolean includeNonEnumerable) { return super.hasField(ctx, key);
var res = super.keys(includeNonEnumerable); }
res.addAll(names.keySet()); @Override
return res; public List<Object> keys(boolean includeNonEnumerable) {
} var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
public ScopeValue(ValueVariable[] variables, String[] names) { return res;
this.variables = variables; }
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i); public ScopeValue(ValueVariable[] variables, String[] names) {
this.nonConfigurableSet.add(names[i]); this.variables = variables;
} for (var i = 0; i < names.length && i < variables.length; i++) {
} this.names.put(names[i], i);
} this.nonConfigurableSet.add(names[i]);
}
}
}

View File

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

View File

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

View File

@ -1,39 +1,41 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
public interface File { import me.topchetoeu.jscript.Buffer;
int read(byte[] buff);
void write(byte[] buff); public interface File {
long getPtr(); int read(byte[] buff);
void setPtr(long offset, int pos); void write(byte[] buff);
void close(); long getPtr();
Mode mode(); void setPtr(long offset, int pos);
void close();
default String readToString() { Mode mode();
setPtr(0, 2);
long len = getPtr(); default String readToString() {
if (len < 0) return null; setPtr(0, 2);
long len = getPtr();
setPtr(0, 0); if (len < 0) return null;
byte[] res = new byte[(int)len]; setPtr(0, 0);
read(res);
byte[] res = new byte[(int)len];
return new String(res); read(res);
}
default String readLine() { return new String(res);
var res = new Buffer(new byte[0]); }
var buff = new byte[1]; default String readLine() {
var res = new Buffer();
while (true) { var buff = new byte[1];
if (read(buff) == 0) {
if (res.length() == 0) return null; while (true) {
else break; if (read(buff) == 0) {
} if (res.length() == 0) return null;
else break;
if (buff[0] == '\n') break; }
res.write(res.length(), buff); if (buff[0] == '\n') break;
}
return new String(res.data()); res.write(res.length(), buff);
} }
return new String(res.data());
}
} }

View File

@ -1,66 +1,67 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File {
private int ptr; public class MemoryFile implements File {
private Mode mode; private int ptr;
private Buffer data; private Mode mode;
private String filename; private Buffer data;
private String filename;
public Buffer data() { return data; }
public Buffer data() { return data; }
@Override
public int read(byte[] buff) { @Override
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); public int read(byte[] buff) {
var res = data.read(ptr, buff); if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
ptr += res; var res = data.read(ptr, buff);
return res; ptr += res;
} return res;
@Override }
public void write(byte[] buff) { @Override
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); public void write(byte[] buff) {
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff);
ptr += buff.length; data.write(ptr, buff);
} ptr += buff.length;
}
@Override
public long getPtr() { @Override
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); public long getPtr() {
return ptr; if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
} return ptr;
@Override }
public void setPtr(long offset, int pos) { @Override
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); public void setPtr(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset; if (pos == 0) ptr = (int)offset;
else if (pos == 2) ptr = data.length() - (int)offset; else if (pos == 1) ptr += (int)offset;
} else if (pos == 2) ptr = data.length() - (int)offset;
}
@Override
public void close() { @Override
mode = Mode.NONE; public void close() {
ptr = 0; mode = Mode.NONE;
} ptr = 0;
@Override }
public Mode mode() { @Override
if (data == null) return Mode.NONE; public Mode mode() {
return mode; if (data == null) return Mode.NONE;
} return mode;
}
public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename; public MemoryFile(String filename, Buffer buff, Mode mode) {
this.data = buff; this.filename = filename;
this.mode = mode; this.data = buff;
} this.mode = mode;
}
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder(); public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder();
for (var el : list) res.append(el.getName()).append('\n');
for (var el : list) res.append(el.getName()).append('\n');
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
} return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
} }
}

View File

@ -1,89 +1,90 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.nio.file.Path; 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.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem {
public final Mode mode; public class MemoryFilesystem implements Filesystem {
private HashMap<Path, Buffer> files = new HashMap<>(); public final Mode mode;
private HashSet<Path> folders = new HashSet<>(); private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private Path getPath(String name) {
return Path.of("/" + name.replace("\\", "/")).normalize(); private Path getPath(String name) {
} return Path.of("/" + name.replace("\\", "/")).normalize();
}
@Override
public void create(String path, EntryType type) { @Override
var _path = getPath(path); public void create(String path, EntryType type) {
var _path = getPath(path);
switch (type) {
case FILE: switch (type) {
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); case FILE:
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer(new byte[0])); if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
break; files.put(_path, new Buffer());
case FOLDER: break;
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); case FOLDER:
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
folders.add(_path); if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
break; folders.add(_path);
default: break;
case NONE: default:
if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); case NONE:
} if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
} }
}
@Override
public File open(String path, Mode perms) { @Override
var _path = getPath(path); public File open(String path, Mode perms) {
var pcount = _path.getNameCount(); var _path = getPath(path);
var pcount = _path.getNameCount();
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
else if (folders.contains(_path)) { if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
var res = new StringBuilder(); else if (folders.contains(_path)) {
for (var folder : folders) { var res = new StringBuilder();
if (pcount + 1 != folder.getNameCount()) continue; for (var folder : folders) {
if (!folder.startsWith(_path)) continue; if (pcount + 1 != folder.getNameCount()) continue;
res.append(folder.toFile().getName()).append('\n'); if (!folder.startsWith(_path)) continue;
} res.append(folder.toFile().getName()).append('\n');
for (var file : files.keySet()) { }
if (pcount + 1 != file.getNameCount()) continue; for (var file : files.keySet()) {
if (!file.startsWith(_path)) continue; if (pcount + 1 != file.getNameCount()) continue;
res.append(file.toFile().getName()).append('\n'); if (!file.startsWith(_path)) continue;
} res.append(file.toFile().getName()).append('\n');
return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); }
} return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
else throw new FilesystemException(path, FSCode.DOESNT_EXIST); }
} else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
}
@Override
public FileStat stat(String path) { @Override
var _path = getPath(path); public FileStat stat(String path) {
var _path = getPath(path);
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
else throw new FilesystemException(path, FSCode.DOESNT_EXIST); else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER);
} else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = getPath(path); public MemoryFilesystem put(String path, byte[] data) {
var _curr = "/"; var _path = getPath(path);
var _curr = "/";
for (var seg : _path) {
create(_curr, EntryType.FOLDER); for (var seg : _path) {
_curr += seg + "/"; create(_curr, EntryType.FOLDER);
} _curr += seg + "/";
}
files.put(_path, new Buffer(data));
return this; files.put(_path, new Buffer(data));
} return this;
}
public MemoryFilesystem(Mode mode) {
this.mode = mode; public MemoryFilesystem(Mode mode) {
folders.add(Path.of("/")); this.mode = mode;
} folders.add(Path.of("/"));
} }
}

View File

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

View File

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

View File

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

View File

@ -1,164 +1,221 @@
package me.topchetoeu.jscript.lib; 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.Reading; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.DataKey;
import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class Internals { import me.topchetoeu.jscript.interop.NativeGetter;
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>(); import me.topchetoeu.jscript.parsing.Parsing;
private static final DataKey<Integer> I = new DataKey<>();
public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
@Native public static Object log(Context ctx, Object ...args) { private static final DataKey<Integer> I = new DataKey<>();
for (var arg : args) {
Values.printValue(ctx, arg);
System.out.print(" "); @Native public static Object log(Context ctx, Object ...args) {
} for (var arg : args) {
System.out.println(); Values.printValue(ctx, arg);
System.out.print(" ");
if (args.length == 0) return null; }
else return args[0]; System.out.println();
}
@Native public static String readline(Context ctx) { if (args.length == 0) return null;
try { else return args[0];
return Reading.read(); }
} @Native public static String readline(Context ctx) {
catch (IOException e) { try {
e.printStackTrace(); return Reading.read();
return null; }
} catch (IOException e) {
} e.printStackTrace();
return null;
@Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { }
var thread = new Thread(() -> { }
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000); @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> {
try { var ms = (long)delay;
Thread.sleep(ms, ns); var ns = (int)((delay - ms) * 10000000);
}
catch (InterruptedException e) { return; } try {
Thread.sleep(ms, ns);
ctx.engine.pushMsg(false, ctx, func, null, args); }
}); catch (InterruptedException e) { return; }
thread.start();
ctx.engine.pushMsg(false, ctx, func, null, args);
int i = ctx.environment().data.increase(I, 1, 0); });
var threads = ctx.environment().data.get(THREADS, new HashMap<>()); thread.start();
threads.put(++i, thread);
return i; int i = ctx.environment().data.increase(I, 1, 0);
} var threads = ctx.environment().data.get(THREADS, new HashMap<>());
@Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { threads.put(++i, thread);
var thread = new Thread(() -> { return i;
var ms = (long)delay; }
var ns = (int)((delay - ms) * 10000000); @Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> {
while (true) { var ms = (long)delay;
try { var ns = (int)((delay - ms) * 10000000);
Thread.sleep(ms, ns);
} while (true) {
catch (InterruptedException e) { return; } try {
Thread.sleep(ms, ns);
ctx.engine.pushMsg(false, ctx, func, null, args); }
} catch (InterruptedException e) { return; }
});
thread.start(); ctx.engine.pushMsg(false, ctx, func, null, args);
}
int i = ctx.environment().data.increase(I, 1, 0); });
var threads = ctx.environment().data.get(THREADS, new HashMap<>()); thread.start();
threads.put(++i, thread);
return i; int i = ctx.environment().data.increase(I, 1, 0);
} var threads = ctx.environment().data.get(THREADS, new HashMap<>());
threads.put(++i, thread);
@Native public static void clearTimeout(Context ctx, int i) { return i;
var threads = ctx.environment().data.get(THREADS, new HashMap<>()); }
var thread = threads.remove(i); @Native public static void clearTimeout(Context ctx, int i) {
if (thread != null) thread.interrupt(); var threads = ctx.environment().data.get(THREADS, new HashMap<>());
}
@Native public static void clearInterval(Context ctx, int i) { var thread = threads.remove(i);
clearTimeout(ctx, i); if (thread != null) thread.interrupt();
} }
@Native public static void clearInterval(Context ctx, int i) {
@Native public static double parseInt(Context ctx, String val) { clearTimeout(ctx, i);
return NumberLib.parseInt(ctx, val); }
}
@Native public static double parseFloat(Context ctx, String val) { @Native public static double parseInt(Context ctx, String val) {
return NumberLib.parseFloat(ctx, val); return NumberLib.parseInt(ctx, val);
} }
@Native public static double parseFloat(Context ctx, String val) {
@Native public static boolean isNaN(Context ctx, double val) { return NumberLib.parseFloat(ctx, val);
return NumberLib.isNaN(ctx, val); }
}
@Native public static boolean isFinite(Context ctx, double val) { @Native public static boolean isNaN(Context ctx, double val) {
return NumberLib.isFinite(ctx, val); return NumberLib.isNaN(ctx, val);
} }
@Native public static boolean isInfinite(Context ctx, double val) { @Native public static boolean isFinite(Context ctx, double val) {
return NumberLib.isInfinite(ctx, val); return NumberLib.isFinite(ctx, val);
} }
@Native public static boolean isInfinite(Context ctx, double val) {
@NativeGetter public static double NaN(Context ctx) { return NumberLib.isInfinite(ctx, val);
return Double.NaN; }
}
@NativeGetter public static double Infinity(Context ctx) { @NativeGetter public static double NaN(Context ctx) {
return Double.POSITIVE_INFINITY; return Double.NaN;
} }
@NativeGetter public static double Infinity(Context ctx) {
public static Environment apply(Environment env) { return Double.POSITIVE_INFINITY;
var wp = env.wrappers; }
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); private static final String HEX = "0123456789ABCDEF";
glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); private static String encodeUriAny(String str, String keepAlphabet) {
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); if (str == null) str = "undefined";
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); var bytes = str.getBytes();
var sb = new StringBuilder(bytes.length);
glob.define(null, "Date", false, wp.getConstr(DateLib.class));
glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); for (byte c : bytes) {
glob.define(null, "Function", false, wp.getConstr(FunctionLib.class)); if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
glob.define(null, "Array", false, wp.getConstr(ArrayLib.class)); else {
sb.append('%');
glob.define(null, "Boolean", false, wp.getConstr(BooleanLib.class)); sb.append(HEX.charAt(c / 16));
glob.define(null, "Number", false, wp.getConstr(NumberLib.class)); sb.append(HEX.charAt(c % 16));
glob.define(null, "String", false, wp.getConstr(StringLib.class)); }
glob.define(null, "Symbol", false, wp.getConstr(SymbolLib.class)); }
glob.define(null, "Promise", false, wp.getConstr(PromiseLib.class)); return sb.toString();
glob.define(null, "RegExp", false, wp.getConstr(RegExpLib.class)); }
glob.define(null, "Map", false, wp.getConstr(MapLib.class)); private static String decodeUriAny(String str, String keepAlphabet) {
glob.define(null, "Set", false, wp.getConstr(SetLib.class)); if (str == null) str = "undefined";
glob.define(null, "Error", false, wp.getConstr(ErrorLib.class)); var res = new Buffer();
glob.define(null, "SyntaxError", false, wp.getConstr(SyntaxErrorLib.class)); var bytes = str.getBytes();
glob.define(null, "TypeError", false, wp.getConstr(TypeErrorLib.class));
glob.define(null, "RangeError", false, wp.getConstr(RangeErrorLib.class)); for (var i = 0; i < bytes.length; i++) {
var c = bytes[i];
env.setProto("object", wp.getProto(ObjectLib.class)); if (c == '%') {
env.setProto("function", wp.getProto(FunctionLib.class)); if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
env.setProto("array", wp.getProto(ArrayLib.class)); var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
if (!Parsing.isAny((char)b, keepAlphabet)) {
env.setProto("bool", wp.getProto(BooleanLib.class)); i += 2;
env.setProto("number", wp.getProto(NumberLib.class)); res.append((byte)b);
env.setProto("string", wp.getProto(StringLib.class)); continue;
env.setProto("symbol", wp.getProto(SymbolLib.class)); }
}
env.setProto("error", wp.getProto(ErrorLib.class)); res.append(c);
env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class)); }
env.setProto("typeErr", wp.getProto(TypeErrorLib.class));
env.setProto("rangeErr", wp.getProto(RangeErrorLib.class)); return new String(res.data());
}
wp.getProto(ObjectLib.class).setPrototype(null, null);
env.regexConstructor = wp.getConstr(RegExpLib.class); @Native public static String encodeURIComponent(String str) {
return encodeUriAny(str, ".-_!~*'()");
return env; }
} @Native public static String decodeURIComponent(String str) {
} return decodeUriAny(str, "");
}
@Native public static String encodeURI(String str) {
return encodeUriAny(str, ";,/?:@&=+$#.-_!~*'()");
}
@Native public static String decodeURI(String str) {
return decodeUriAny(str, ",/?:@&=+$#.");
}
public static Environment apply(Environment env) {
var wp = env.wrappers;
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class));
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(false, wp.getConstr(DateLib.class));
glob.define(false, wp.getConstr(ObjectLib.class));
glob.define(false, wp.getConstr(FunctionLib.class));
glob.define(false, wp.getConstr(ArrayLib.class));
glob.define(false, wp.getConstr(BooleanLib.class));
glob.define(false, wp.getConstr(NumberLib.class));
glob.define(false, wp.getConstr(StringLib.class));
glob.define(false, wp.getConstr(SymbolLib.class));
glob.define(false, wp.getConstr(PromiseLib.class));
glob.define(false, wp.getConstr(RegExpLib.class));
glob.define(false, wp.getConstr(MapLib.class));
glob.define(false, wp.getConstr(SetLib.class));
glob.define(false, wp.getConstr(ErrorLib.class));
glob.define(false, wp.getConstr(SyntaxErrorLib.class));
glob.define(false, wp.getConstr(TypeErrorLib.class));
glob.define(false, wp.getConstr(RangeErrorLib.class));
env.setProto("object", wp.getProto(ObjectLib.class));
env.setProto("function", wp.getProto(FunctionLib.class));
env.setProto("array", wp.getProto(ArrayLib.class));
env.setProto("bool", wp.getProto(BooleanLib.class));
env.setProto("number", wp.getProto(NumberLib.class));
env.setProto("string", wp.getProto(StringLib.class));
env.setProto("symbol", wp.getProto(SymbolLib.class));
env.setProto("error", wp.getProto(ErrorLib.class));
env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class));
env.setProto("typeErr", wp.getProto(TypeErrorLib.class));
env.setProto("rangeErr", wp.getProto(RangeErrorLib.class));
wp.getProto(ObjectLib.class).setPrototype(null, null);
env.regexConstructor = wp.getConstr(RegExpLib.class);
return env;
}
}

View File

@ -1,78 +1,78 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
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;
import me.topchetoeu.jscript.engine.values.ObjectValue; 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.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
@Native("Map") public class MapLib { @Native("Map") public class MapLib {
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>(); private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Native("@@Symbol.typeName") public final String name = "Map"; @Native("@@Symbol.typeName") public final String name = "Map";
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
return this.entries(ctx); return this.entries(ctx);
} }
@Native public void clear() { @Native public void clear() {
map.clear(); map.clear();
} }
@Native public boolean delete(Object key) { @Native public boolean delete(Object key) {
if (map.containsKey(key)) { if (map.containsKey(key)) {
map.remove(key); map.remove(key);
return true; return true;
} }
return false; return false;
} }
@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) { );
var res = new ArrayList<>(map.keySet()); }
return Values.toJSIterator(ctx, res.iterator()); @Native public ObjectValue keys(Context ctx) {
} return ArrayValue.of(ctx, map.keySet());
@Native public ObjectValue values(Context ctx) { }
var res = new ArrayList<>(map.values()); @Native public ObjectValue values(Context ctx) {
return Values.toJSIterator(ctx, res.iterator()); return ArrayValue.of(ctx, map.values());
} }
@Native public Object get(Object key) { @Native public Object get(Object key) {
return map.get(key); return map.get(key);
} }
@Native public MapLib set(Object key, Object val) { @Native public MapLib set(Object key, Object val) {
map.put(key, val); map.put(key, val);
return this; return this;
} }
@Native public boolean has(Object key) { @Native public boolean has(Object key) {
return map.containsKey(key); return map.containsKey(key);
} }
@NativeGetter public int size() { @NativeGetter public int size() {
return map.size(); return map.size();
} }
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
var keys = new ArrayList<>(map.keySet()); var keys = new ArrayList<>(map.keySet());
for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this); for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this);
} }
@Native public MapLib(Context ctx, Object iterable) { @Native public MapLib(Context ctx, Object iterable) {
for (var el : Values.fromJSIterator(ctx, iterable)) { for (var el : Values.fromJSIterator(ctx, iterable)) {
try { try {
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
} }
catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) { }
} }
} }
} }

View File

@ -1,366 +1,367 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
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;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.NativeWrapper; import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
@Native("Promise") public class PromiseLib { @Native("Promise") public class PromiseLib {
public static interface PromiseRunner { public static interface PromiseRunner {
Object run(); Object run();
} }
private static class Handle { private static class Handle {
public final Context ctx; public final Context ctx;
public final FunctionValue fulfilled; public final FunctionValue fulfilled;
public final FunctionValue rejected; public final FunctionValue rejected;
public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) { public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) {
this.ctx = ctx; this.ctx = ctx;
this.fulfilled = fulfilled; this.fulfilled = fulfilled;
this.rejected = rejected; this.rejected = rejected;
} }
} }
@Native("resolve") @Native("resolve")
public static PromiseLib ofResolved(Context ctx, Object val) { public static PromiseLib ofResolved(Context ctx, Object val) {
var res = new PromiseLib(); var res = new PromiseLib();
res.fulfill(ctx, val); res.fulfill(ctx, val);
return res; return res;
} }
@Native("reject") @Native("reject")
public static PromiseLib ofRejected(Context ctx, Object val) { public static PromiseLib ofRejected(Context ctx, Object val) {
var res = new PromiseLib(); var res = new PromiseLib();
res.reject(ctx, val); res.reject(ctx, val);
return res; return res;
} }
@Native public static PromiseLib any(Context ctx, Object _promises) { @Native public static PromiseLib any(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new PromiseLib(); var res = new PromiseLib();
var errors = new ArrayValue(); var errors = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
then(ctx, val, then(ctx, val,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
errors.set(ctx, index, args[0]); errors.set(ctx, index, args[0]);
n[0]--; n[0]--;
if (n[0] <= 0) res.reject(e, errors); if (n[0] <= 0) res.reject(e, errors);
return null; return null;
}) })
); );
} }
return res; return res;
} }
@Native public static PromiseLib race(Context ctx, Object _promises) { @Native public static PromiseLib race(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var res = new PromiseLib(); var res = new PromiseLib();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var val = promises.get(i); var val = promises.get(i);
then(ctx, val, then(ctx, val,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
); );
} }
return res; return res;
} }
@Native public static PromiseLib all(Context ctx, Object _promises) { @Native public static PromiseLib all(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new PromiseLib(); var res = new PromiseLib();
var result = new ArrayValue(); var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
then(ctx, val, then(ctx, val,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, args[0]); result.set(ctx, index, args[0]);
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
); );
} }
if (n[0] <= 0) res.fulfill(ctx, result); if (n[0] <= 0) res.fulfill(ctx, result);
return res; return res;
} }
@Native public static PromiseLib allSettled(Context ctx, Object _promises) { @Native public static PromiseLib allSettled(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new PromiseLib(); var res = new PromiseLib();
var result = new ArrayValue(); var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
then(ctx, val, then(ctx, val,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of( result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "fulfilled", "status", "fulfilled",
"value", args[0] "value", args[0]
))); )));
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of( result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "rejected", "status", "rejected",
"reason", args[0] "reason", args[0]
))); )));
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}) })
); );
} }
if (n[0] <= 0) res.fulfill(ctx, result); if (n[0] <= 0) res.fulfill(ctx, result);
return res; return res;
} }
/** /**
* 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)); }
catch (EngineException err) { res.reject(ctx, err.value); } catch (EngineException err) { res.reject(ctx, err.value); }
return null; return null;
}); });
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); }
return null; if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true;
}); return null;
});
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle);
else { if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle);
Object next; else {
try { next = Values.getMember(ctx, thisArg, "then"); } Object next;
catch (IllegalArgumentException e) { next = null; } try { next = Values.getMember(ctx, thisArg, "then"); }
catch (IllegalArgumentException e) { next = null; }
try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle); try {
else res.fulfill(ctx, fulfill.call(ctx, null, thisArg)); if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle);
} else res.fulfill(ctx, fulfill.call(ctx, null, thisArg));
catch (EngineException err) { }
res.reject(ctx, fulfill.call(ctx, null, err.value)); catch (EngineException err) {
} res.reject(ctx, fulfill.call(ctx, null, err.value));
} }
}
return res;
} return res;
/** }
* Thread safe - you can call this from anywhere /**
* HOWEVER, it's strongly recommended to use this only in javascript * Thread safe - you can call this from anywhere
*/ * HOWEVER, it's strongly recommended to use this only in javascript
@Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { */
return then(ctx, thisArg, null, _onReject); @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) {
} return then(ctx, thisArg, null, _onReject);
/** }
* Thread safe - you can call this from anywhere /**
* HOWEVER, it's strongly recommended to use this only in javascript * Thread safe - you can call this from anywhere
*/ * HOWEVER, it's strongly recommended to use this only in javascript
@Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { */
return then(ctx, thisArg, @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) {
new NativeFunction(null, (e, th, _args) -> { return then(ctx, thisArg,
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); new NativeFunction(null, (e, th, _args) -> {
return _args.length > 0 ? _args[0] : null; if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
}), return _args.length > 0 ? _args[0] : null;
new NativeFunction(null, (e, th, _args) -> { }),
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); new NativeFunction(null, (e, th, _args) -> {
throw new EngineException(_args.length > 0 ? _args[0] : null); if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
}) throw new EngineException(_args.length > 0 ? _args[0] : null);
); })
} );
}
private List<Handle> handles = new ArrayList<>();
private List<Handle> handles = new ArrayList<>();
private static final int STATE_PENDING = 0;
private static final int STATE_FULFILLED = 1; private static final int STATE_PENDING = 0;
private static final int STATE_REJECTED = 2; private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2;
private int state = STATE_PENDING;
private boolean handled = false; private int state = STATE_PENDING;
private Object val; private boolean handled = false;
private Object val;
public synchronized void fulfill(Context ctx, Object val) {
if (this.state != STATE_PENDING) return; public synchronized void fulfill(Context ctx, Object val) {
if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }), if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }),
); new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
else { );
Object then; else {
try { then = Values.getMember(ctx, val, "then"); } Object then;
catch (IllegalArgumentException e) { then = null; } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { then = null; }
try {
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, try {
new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }),
); new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
else { );
this.val = val; else {
this.state = STATE_FULFILLED; this.val = val;
this.state = STATE_FULFILLED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
handle.fulfilled.call(handle.ctx, null, val); for (var handle : handles) {
} handle.fulfilled.call(handle.ctx, null, val);
handles = null; }
return null; handles = null;
}), null); return null;
} }), null);
} }
catch (EngineException err) { }
this.reject(ctx, err.value); catch (EngineException err) {
} this.reject(ctx, err.value);
} }
} }
public synchronized void reject(Context ctx, Object val) { }
if (this.state != STATE_PENDING) return; public synchronized void reject(Context ctx, Object val) {
if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }), if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }),
); new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
else { );
Object then; else {
try { then = Values.getMember(ctx, val, "then"); } Object then;
catch (IllegalArgumentException e) { then = null; } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { then = null; }
try {
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, try {
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }),
); new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
else { );
this.val = val; else {
this.state = STATE_REJECTED; this.val = val;
this.state = STATE_REJECTED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) handle.rejected.call(handle.ctx, null, val); ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
if (!handled) { for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); if (!handled) {
} Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
handles = null; }
return null; handles = null;
}), null); return null;
} }), null);
} }
catch (EngineException err) { }
this.reject(ctx, err.value); catch (EngineException err) {
} this.reject(ctx, err.value);
} }
} }
}
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
else if (state == STATE_REJECTED) { if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val);
ctx.engine.pushMsg(true, ctx, reject, null, val); else if (state == STATE_REJECTED) {
handled = true; ctx.engine.pushMsg(true, ctx, reject, null, val);
} handled = true;
else handles.add(new Handle(ctx, fulfill, reject)); }
} else handles.add(new Handle(ctx, fulfill, reject));
}
@Override @Native public String toString() {
if (state == STATE_PENDING) return "Promise (pending)"; @Override @Native public String toString() {
else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; if (state == STATE_PENDING) return "Promise (pending)";
else return "Promise (rejected)"; else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
} else return "Promise (rejected)";
}
/**
* NOT THREAD SAFE - must be called from the engine executor thread /**
*/ * NOT THREAD SAFE - must be called from the engine executor thread
@Native public PromiseLib(Context ctx, FunctionValue func) { */
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); @Native public PromiseLib(Context ctx, FunctionValue func) {
try { if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
func.call( try {
ctx, null, func.call(
new NativeFunction(null, (e, th, args) -> { ctx, null,
fulfill(e, args.length > 0 ? args[0] : null); new NativeFunction(null, (e, th, args) -> {
return null; fulfill(e, args.length > 0 ? args[0] : null);
}), return null;
new NativeFunction(null, (e, th, args) -> { }),
reject(e, args.length > 0 ? args[0] : null); new NativeFunction(null, (e, th, args) -> {
return null; reject(e, args.length > 0 ? args[0] : null);
}) return null;
); })
} );
catch (EngineException e) { }
reject(ctx, e.value); catch (EngineException e) {
} reject(ctx, e.value);
} }
}
private PromiseLib(int state, Object val) {
this.state = state; private PromiseLib(int state, Object val) {
this.val = val; this.state = state;
} this.val = val;
public PromiseLib() { }
this(STATE_PENDING, null); public PromiseLib() {
} this(STATE_PENDING, null);
}
public static PromiseLib await(Context ctx, PromiseRunner runner) {
var res = new PromiseLib(); public static PromiseLib await(Context ctx, PromiseRunner runner) {
var res = new PromiseLib();
new Thread(() -> {
try { new Thread(() -> {
res.fulfill(ctx, runner.run()); try {
} res.fulfill(ctx, runner.run());
catch (EngineException e) { }
res.reject(ctx, e.value); catch (EngineException e) {
} res.reject(ctx, e.value);
}, "Promisifier").start(); }
}, "Promisifier").start();
return res;
} return res;
} }
}

View File

@ -1,63 +1,60 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
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;
import me.topchetoeu.jscript.engine.values.ObjectValue; 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.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
@Native("Set") public class SetLib { @Native("Set") public class SetLib {
private LinkedHashSet<Object> set = new LinkedHashSet<>(); private LinkedHashSet<Object> set = new LinkedHashSet<>();
@Native("@@Symbol.typeName") public final String name = "Set"; @Native("@@Symbol.typeName") public final String name = "Set";
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
return this.values(ctx); return this.values(ctx);
} }
@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) { return ArrayValue.of(ctx, set);
var res = new ArrayList<>(set); }
return Values.toJSIterator(ctx, res.iterator()); @Native public ObjectValue values(Context ctx) {
} return ArrayValue.of(ctx, set);
@Native public ObjectValue values(Context ctx) { }
var res = new ArrayList<>(set);
return Values.toJSIterator(ctx, res.iterator()); @Native public Object add(Object key) {
} return set.add(key);
}
@Native public Object add(Object key) { @Native public boolean delete(Object key) {
return set.add(key); return set.remove(key);
} }
@Native public boolean delete(Object key) { @Native public boolean has(Object key) {
return set.remove(key); return set.contains(key);
} }
@Native public boolean has(Object key) {
return set.contains(key); @Native public void clear() {
} set.clear();
}
@Native public void clear() {
set.clear(); @NativeGetter public int size() {
} return set.size();
}
@NativeGetter public int size() {
return set.size(); @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
} var keys = new ArrayList<>(set);
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { for (var el : keys) func.call(ctx, thisArg, el, el, this);
var keys = new ArrayList<>(set); }
for (var el : keys) func.call(ctx, thisArg, el, el, this); @Native public SetLib(Context ctx, Object iterable) {
} for (var el : Values.fromJSIterator(ctx, iterable)) add(el);
}
@Native public SetLib(Context ctx, Object iterable) { }
for (var el : Values.fromJSIterator(ctx, iterable)) add(el);
}
}

View File

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

View File

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

View File

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