Module support #11

Merged
TopchetoEU merged 20 commits from TopchetoEU/modules into master 2023-12-26 12:20:55 +00:00
89 changed files with 8831 additions and 8831 deletions
Showing only changes of commit caf9131cde - Show all commits

View File

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

View File

@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.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 BreakStatement extends Statement { public class BreakStatement extends Statement {
public final String label; public final String label;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "break", label)); target.add(Instruction.nop(loc(), "break", label));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public BreakStatement(Location loc, String label) { public BreakStatement(Location loc, String label) {
super(loc); super(loc);
this.label = label; this.label = label;
} }
} }

View File

@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.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 ContinueStatement extends Statement { public class ContinueStatement extends Statement {
public final String label; public final String label;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "cont", label)); target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public ContinueStatement(Location loc, String label) { public ContinueStatement(Location loc, String label) {
super(loc); super(loc);
this.label = label; this.label = label;
} }
} }

View File

@ -1,19 +1,19 @@
package me.topchetoeu.jscript.compilation.control; 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.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 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(loc())); target.add(Instruction.debug(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public DebugStatement(Location loc) { public DebugStatement(Location loc) {
super(loc); super(loc);
} }
} }

View File

@ -1,27 +1,27 @@
package me.topchetoeu.jscript.compilation.control; 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.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 DeleteStatement extends Statement { public class DeleteStatement extends Statement {
public final Statement key; public final Statement key;
public final Statement value; public final Statement value;
@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);
key.compile(target, scope, true); key.compile(target, scope, true);
target.add(Instruction.delete(loc())); target.add(Instruction.delete(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), true)); if (pollute) target.add(Instruction.loadValue(loc(), true));
} }
public DeleteStatement(Location loc, Statement key, Statement value) { public DeleteStatement(Location loc, Statement key, Statement value) {
super(loc); super(loc);
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
} }

View File

@ -1,37 +1,37 @@
package me.topchetoeu.jscript.compilation.control; 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.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.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DoWhileStatement extends Statement { public class DoWhileStatement extends Statement {
public final Statement condition, body; public final Statement condition, body;
public final String label; public final String label;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
body.declare(globScope); body.declare(globScope);
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size(); int start = target.size();
body.compile(target, scope, false, BreakpointType.STEP_OVER); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
condition.compile(target, scope, true, BreakpointType.STEP_OVER); 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(loc(), start - end)); target.add(Instruction.jmpIf(loc(), start - end));
} }
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc); super(loc);
this.label = label; this.label = label;
this.condition = condition; this.condition = condition;
this.body = body; this.body = body;
} }
} }

View File

@ -1,73 +1,73 @@
package me.topchetoeu.jscript.compilation.control; 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.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.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;
public class ForInStatement extends Statement { public class ForInStatement extends Statement {
public final String varName; public final String varName;
public final boolean isDeclaration; public final boolean isDeclaration;
public final Statement varValue, object, body; public final Statement varValue, object, body;
public final String label; public final String label;
public final Location varLocation; public final Location varLocation;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
body.declare(globScope); body.declare(globScope);
if (isDeclaration) globScope.define(varName); if (isDeclaration) globScope.define(varName);
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
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(loc(), (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(loc(), scope.getKey(varName))); target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
} }
object.compile(target, scope, true, BreakpointType.STEP_OVER); object.compile(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(loc(), true)); target.add(Instruction.keys(loc(), true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), null)); target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.operation(loc(), Operation.EQUALS)); target.add(Instruction.operation(loc(), Operation.EQUALS));
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop(loc())); target.add(Instruction.nop(loc()));
target.add(Instruction.loadMember(varLocation, "value")); target.add(Instruction.loadMember(varLocation, "value"));
target.add(Instruction.storeVar(object.loc(), key)); target.add(Instruction.storeVar(object.loc(), key));
target.setDebug(BreakpointType.STEP_OVER); target.setDebug(BreakpointType.STEP_OVER);
body.compile(target, scope, false, BreakpointType.STEP_OVER); 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(loc(), start - end)); target.add(Instruction.jmp(loc(), start - end));
target.add(Instruction.discard(loc())); target.add(Instruction.discard(loc()));
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1)); target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
target.get(first).locate(loc()); target.get(first).locate(loc());
} }
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) {
super(loc); super(loc);
this.varLocation = varLocation; this.varLocation = varLocation;
this.label = label; this.label = label;
this.isDeclaration = isDecl; this.isDeclaration = isDecl;
this.varName = varName; this.varName = varName;
this.varValue = varValue; this.varValue = varValue;
this.object = object; this.object = object;
this.body = body; this.body = body;
} }
} }

View File

@ -1,47 +1,47 @@
package me.topchetoeu.jscript.compilation.control; 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.Instruction.BreakpointType;
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.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ForStatement extends Statement { public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body; public final Statement declaration, assignment, condition, body;
public final String label; public final String label;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
declaration.declare(globScope); declaration.declare(globScope);
body.declare(globScope); body.declare(globScope);
} }
@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, BreakpointType.STEP_OVER); declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
int start = target.size(); int start = target.size();
condition.compile(target, scope, true, BreakpointType.STEP_OVER); condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
body.compile(target, scope, false, BreakpointType.STEP_OVER); body.compile(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size(); int beforeAssign = target.size();
assignment.compile(target, scope, false, BreakpointType.STEP_OVER); 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(loc(), start - end)); target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1)); target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
super(loc); super(loc);
this.label = label; this.label = label;
this.declaration = declaration; this.declaration = declaration;
this.condition = condition; this.condition = condition;
this.assignment = assignment; this.assignment = assignment;
this.body = body; this.body = body;
} }
} }

View File

@ -1,52 +1,52 @@
package me.topchetoeu.jscript.compilation.control; 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.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.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IfStatement extends Statement { public class IfStatement extends Statement {
public final Statement condition, body, elseBody; public final Statement condition, body, elseBody;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
body.declare(globScope); body.declare(globScope);
if (elseBody != null) elseBody.declare(globScope); if (elseBody != null) elseBody.declare(globScope);
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, scope, true, breakpoint); condition.compile(target, scope, true, breakpoint);
if (elseBody == null) { if (elseBody == null) {
int i = target.size(); int i = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint); body.compile(target, scope, pollute, breakpoint);
int endI = target.size(); int endI = target.size();
target.set(i, Instruction.jmpIfNot(loc(), endI - i)); target.set(i, Instruction.jmpIfNot(loc(), endI - i));
} }
else { else {
int start = target.size(); int start = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint); body.compile(target, scope, pollute, breakpoint);
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
int mid = target.size(); int mid = target.size();
elseBody.compile(target, scope, pollute, breakpoint); elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size(); int end = target.size();
target.set(start, Instruction.jmpIfNot(loc(), mid - start)); target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1)); target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
} }
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.STEP_IN); compile(target, scope, pollute, BreakpointType.STEP_IN);
} }
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
super(loc); super(loc);
this.condition = condition; this.condition = condition;
this.body = body; this.body = body;
this.elseBody = elseBody; this.elseBody = elseBody;
} }
} }

View File

@ -1,23 +1,23 @@
package me.topchetoeu.jscript.compilation.control; 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.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 ReturnStatement extends Statement { public class ReturnStatement extends Statement {
public final Statement value; public final Statement value;
@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(loc(), null)); 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(loc())); target.add(Instruction.ret(loc()));
} }
public ReturnStatement(Location loc, Statement value) { public ReturnStatement(Location loc, Statement value) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
} }

View File

@ -1,87 +1,87 @@
package me.topchetoeu.jscript.compilation.control; package me.topchetoeu.jscript.compilation.control;
import java.util.HashMap; import java.util.HashMap;
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.compilation.Instruction.BreakpointType; 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;
public class SwitchStatement extends Statement { public class SwitchStatement extends Statement {
public static class SwitchCase { public static class SwitchCase {
public final Statement value; public final Statement value;
public final int statementI; public final int statementI;
public SwitchCase(Statement value, int statementI) { public SwitchCase(Statement value, int statementI) {
this.value = value; this.value = value;
this.statementI = statementI; this.statementI = statementI;
} }
} }
public final Statement value; public final Statement value;
public final SwitchCase[] cases; public final SwitchCase[] cases;
public final Statement[] body; public final Statement[] body;
public final int defaultI; public final int defaultI;
@Override @Override
public void declare(ScopeRecord varsScope) { public void declare(ScopeRecord varsScope) {
for (var stm : body) stm.declare(varsScope); for (var stm : body) stm.declare(varsScope);
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
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, BreakpointType.STEP_OVER); value.compile(target, scope, true, BreakpointType.STEP_OVER);
for (var ccase : cases) { for (var ccase : cases) {
target.add(Instruction.dup(loc())); target.add(Instruction.dup(loc()));
ccase.value.compile(target, scope, true); ccase.value.compile(target, scope, true);
target.add(Instruction.operation(loc(), Operation.EQUALS)); target.add(Instruction.operation(loc(), Operation.EQUALS));
caseToStatement.put(target.size(), ccase.statementI); caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
} }
int start = target.size(); int start = target.size();
target.add(Instruction.nop(null)); 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.compile(target, scope, false, BreakpointType.STEP_OVER); stm.compile(target, scope, false, BreakpointType.STEP_OVER);
} }
int end = target.size(); int end = target.size();
target.add(Instruction.discard(loc())); target.add(Instruction.discard(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null)); if (pollute) target.add(Instruction.loadValue(loc(), null));
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start)); if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start)); 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(loc(), 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 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(loc(), i - el.getKey())); target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
} }
} }
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
super(loc); super(loc);
this.value = value; this.value = value;
this.defaultI = defaultI; this.defaultI = defaultI;
this.cases = cases; this.cases = cases;
this.body = body; this.body = body;
} }
} }

View File

@ -1,22 +1,22 @@
package me.topchetoeu.jscript.compilation.control; 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.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 ThrowStatement extends Statement { public class ThrowStatement extends Statement {
public final Statement value; public final Statement value;
@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(loc())); target.add(Instruction.throwInstr(loc()));
} }
public ThrowStatement(Location loc, Statement value) { public ThrowStatement(Location loc, Statement value) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
} }

View File

@ -1,61 +1,61 @@
package me.topchetoeu.jscript.compilation.control; 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.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.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;
public class TryStatement extends Statement { public class TryStatement extends Statement {
public final Statement tryBody; public final Statement tryBody;
public final Statement catchBody; public final Statement catchBody;
public final Statement finallyBody; public final Statement finallyBody;
public final String name; public final String name;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
tryBody.declare(globScope); tryBody.declare(globScope);
if (catchBody != null) catchBody.declare(globScope); if (catchBody != null) catchBody.declare(globScope);
if (finallyBody != null) finallyBody.declare(globScope); if (finallyBody != null) finallyBody.declare(globScope);
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
int start = target.size(), catchStart = -1, finallyStart = -1; int start = target.size(), catchStart = -1, finallyStart = -1;
tryBody.compile(target, scope, false); tryBody.compile(target, scope, false);
target.add(Instruction.tryEnd(loc())); target.add(Instruction.tryEnd(loc()));
if (catchBody != null) { if (catchBody != null) {
catchStart = target.size() - start; 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();
target.add(Instruction.tryEnd(loc())); target.add(Instruction.tryEnd(loc()));
} }
if (finallyBody != null) { if (finallyBody != null) {
finallyStart = target.size() - start; finallyStart = target.size() - start;
finallyBody.compile(target, scope, false); finallyBody.compile(target, scope, false);
target.add(Instruction.tryEnd(loc())); target.add(Instruction.tryEnd(loc()));
} }
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start)); target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
target.setDebug(start - 1, BreakpointType.STEP_OVER); target.setDebug(start - 1, BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.loadValue(loc(), null)); 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) {
super(loc); super(loc);
this.tryBody = tryBody; this.tryBody = tryBody;
this.catchBody = catchBody; this.catchBody = catchBody;
this.finallyBody = finallyBody; this.finallyBody = finallyBody;
this.name = name; this.name = name;
} }
} }

View File

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

View File

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

View File

@ -1,51 +1,51 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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.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; public final boolean isNew;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
if (isNew) func.compile(target, scope, true); if (isNew) func.compile(target, scope, true);
else if (func instanceof IndexStatement) { 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(loc(), null)); 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);
if (isNew) target.add(Instruction.callNew(loc(), args.length)); if (isNew) target.add(Instruction.callNew(loc(), args.length));
else target.add(Instruction.call(loc(), args.length)); else target.add(Instruction.call(loc(), args.length));
target.setDebug(type); target.setDebug(type);
if (!pollute) target.add(Instruction.discard(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) {
compile(target, scope, pollute, BreakpointType.STEP_IN); compile(target, scope, pollute, BreakpointType.STEP_IN);
} }
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc); super(loc);
this.isNew = isNew; this.isNew = isNew;
this.func = func; this.func = func;
this.args = args; this.args = args;
} }
public CallStatement(Location loc, boolean isNew, 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.isNew = isNew;
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key)); this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
this.args = args; this.args = args;
} }
} }

View File

@ -1,32 +1,32 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement { public class ChangeStatement extends Statement {
public final AssignableStatement value; public final AssignableStatement value;
public final double addAmount; public final double addAmount;
public final boolean postfix; public final boolean postfix;
@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(loc())); if (!pollute) target.add(Instruction.discard(loc()));
else if (postfix) { else if (postfix) {
target.add(Instruction.loadValue(loc(), addAmount)); target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(loc(), Operation.SUBTRACT)); target.add(Instruction.operation(loc(), Operation.SUBTRACT));
} }
} }
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
super(loc); super(loc);
this.value = value; this.value = value;
this.addAmount = addAmount; this.addAmount = addAmount;
this.postfix = postfix; this.postfix = postfix;
} }
} }

View File

@ -1,23 +1,23 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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 ConstantStatement extends Statement { public class ConstantStatement extends Statement {
public final Object value; public final Object value;
@Override 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) {
if (pollute) target.add(Instruction.loadValue(loc(), value)); if (pollute) target.add(Instruction.loadValue(loc(), value));
} }
public ConstantStatement(Location loc, Object val) { public ConstantStatement(Location loc, Object val) {
super(loc); super(loc);
this.value = val; this.value = val;
} }
} }

View File

@ -1,24 +1,24 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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 DiscardStatement extends Statement { public class DiscardStatement extends Statement {
public final Statement value; public final Statement value;
@Override public boolean pure() { return value.pure(); } @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)); if (pollute) target.add(Instruction.loadValue(loc(), null));
} }
public DiscardStatement(Location loc, Statement val) { public DiscardStatement(Location loc, Statement val) {
super(loc); super(loc);
this.value = val; this.value = val;
} }
} }

View File

@ -1,139 +1,139 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.Random; import java.util.Random;
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.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.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 varName; public final String varName;
public final String[] args; public final String[] args;
public final boolean statement; public final boolean statement;
public final Location end; public final Location end;
private static Random rand = new Random(); private static Random rand = new Random();
@Override public boolean pure() { return varName == null && statement; } @Override public boolean pure() { return varName == null && statement; }
@Override @Override
public void declare(ScopeRecord scope) { public void declare(ScopeRecord scope) {
if (varName != null && statement) scope.define(varName); if (varName != null && statement) scope.define(varName);
} }
public static void checkBreakAndCont(CompileTarget target, int start) { public static void checkBreakAndCont(CompileTarget target, int start) {
for (int i = start; i < target.size(); i++) { for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) { if (target.get(i).type == Type.NOP) {
if (target.get(i).is(0, "break") ) { if (target.get(i).is(0, "break") ) {
throw new SyntaxException(target.get(i).location, "Break was placed outside a loop."); throw new SyntaxException(target.get(i).location, "Break was placed outside a loop.");
} }
if (target.get(i).is(0, "cont")) { if (target.get(i).is(0, "cont")) {
throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop."); throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop.");
} }
} }
} }
} }
private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) { 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])) {
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
} }
} }
} }
var id = rand.nextLong(); var id = rand.nextLong();
var subscope = scope.child(); var subscope = scope.child();
var subtarget = new CompileTarget(target.functions, target.breakpoints); 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++) {
subtarget.add(Instruction.loadVar(loc(), argsVar)); subtarget.add(Instruction.loadVar(loc(), argsVar));
subtarget.add(Instruction.loadMember(loc(), i)); subtarget.add(Instruction.loadMember(loc(), i));
subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i]))); subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
} }
} }
if (!statement && this.varName != null) { if (!statement && this.varName != null) {
subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName))); subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
subtarget.setDebug(bp); subtarget.setDebug(bp);
} }
body.declare(subscope); body.declare(subscope);
body.compile(subtarget, subscope, false); body.compile(subtarget, subscope, false);
subtarget.add(Instruction.ret(end)); subtarget.add(Instruction.ret(end));
checkBreakAndCont(subtarget, 0); checkBreakAndCont(subtarget, 0);
if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures())); if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
target.functions.put(id, new FunctionBody( target.functions.put(id, new FunctionBody(
subscope.localsCount(), args.length, subscope.localsCount(), args.length,
subtarget.array(), subscope.captures(), subscope.locals() subtarget.array(), subscope.captures(), subscope.locals()
)); ));
return id; return id;
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName; if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement; var hasVar = this.varName != null && statement;
var hasName = name != null; var hasName = name != null;
compileBody(target, scope, pollute || hasVar || hasName, bp); compileBody(target, scope, pollute || hasVar || hasName, bp);
if (hasName) { if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup(loc())); if (pollute || hasVar) target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), "name")); target.add(Instruction.loadValue(loc(), "name"));
target.add(Instruction.loadValue(loc(), name)); target.add(Instruction.loadValue(loc(), name));
target.add(Instruction.storeMember(loc())); target.add(Instruction.storeMember(loc()));
} }
if (hasVar) { if (hasVar) {
var key = scope.getKey(this.varName); var key = scope.getKey(this.varName);
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key)); if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false)); target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
} }
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
compile(target, scope, pollute, name, BreakpointType.NONE); compile(target, scope, pollute, name, BreakpointType.NONE);
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) {
compile(target, scope, pollute, (String)null, bp); compile(target, scope, pollute, (String)null, bp);
} }
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, (String)null, BreakpointType.NONE); compile(target, scope, pollute, (String)null, BreakpointType.NONE);
} }
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) { public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc); super(loc);
this.end = end; this.end = end;
this.varName = varName; this.varName = varName;
this.statement = statement; 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) { 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); if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
else stm.compile(target, scope, pollute); else stm.compile(target, scope, pollute);
} }
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) { 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); if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
else stm.compile(target, scope, pollute, bp); else stm.compile(target, scope, pollute, bp);
} }
} }

View File

@ -1,20 +1,20 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
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.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class GlobalThisStatement extends Statement { public class GlobalThisStatement extends Statement {
@Override 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) {
if (pollute) target.add(Instruction.loadGlob(loc())); if (pollute) target.add(Instruction.loadGlob(loc()));
} }
public GlobalThisStatement(Location loc) { public GlobalThisStatement(Location loc) {
super(loc); super(loc);
} }
} }

View File

@ -1,48 +1,48 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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.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;
public class IndexAssignStatement extends Statement { public class IndexAssignStatement extends Statement {
public final Statement object; public final Statement object;
public final Statement index; public final Statement index;
public final Statement value; public final Statement value;
public final Operation operation; public final Operation operation;
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
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(loc(), 2)); target.add(Instruction.dup(loc(), 2));
target.add(Instruction.loadMember(loc())); target.add(Instruction.loadMember(loc()));
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.operation(loc(), operation)); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeMember(loc(), pollute)); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN); 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(loc(), pollute)); target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN); target.setDebug(BreakpointType.STEP_IN);
} }
} }
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) { public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
super(loc); super(loc);
this.object = object; this.object = object;
this.index = index; this.index = index;
this.value = value; this.value = value;
this.operation = operation; this.operation = operation;
} }
} }

View File

@ -1,49 +1,49 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.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;
public class IndexStatement extends AssignableStatement { public class IndexStatement extends AssignableStatement {
public final Statement object; public final Statement object;
public final Statement index; public final Statement index;
@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(loc())); if (dupObj) target.add(Instruction.dup(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value)); target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
target.setDebug(BreakpointType.STEP_IN); target.setDebug(BreakpointType.STEP_IN);
return; return;
} }
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.loadMember(loc())); target.add(Instruction.loadMember(loc()));
target.setDebug(BreakpointType.STEP_IN); target.setDebug(BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard(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) {
compile(target, scope, false, pollute); compile(target, scope, false, pollute);
} }
public IndexStatement(Location loc, Statement object, Statement index) { public IndexStatement(Location loc, Statement object, Statement index) {
super(loc); super(loc);
this.object = object; this.object = object;
this.index = index; this.index = index;
} }
public IndexStatement(Location loc, Statement object, Object index) { public IndexStatement(Location loc, Statement object, Object index) {
super(loc); super(loc);
this.object = object; this.object = object;
this.index = new ConstantStatement(loc, index); this.index = new ConstantStatement(loc, index);
} }
} }

View File

@ -1,39 +1,39 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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;
import me.topchetoeu.jscript.engine.values.Values; 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 public boolean pure() { return first.pure() && second.pure(); } @Override 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) {
if (first instanceof ConstantStatement) { if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) { if (Values.not(((ConstantStatement)first).value)) {
first.compile(target, scope, pollute); first.compile(target, scope, pollute);
} }
else second.compile(target, scope, pollute); else second.compile(target, scope, pollute);
return; return;
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start)); 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) {
super(loc); super(loc);
this.first = first; this.first = first;
this.second = second; this.second = second;
} }
} }

View File

@ -1,39 +1,39 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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;
import me.topchetoeu.jscript.engine.values.Values; 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 public boolean pure() { return first.pure() && second.pure(); } @Override 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) {
if (first instanceof ConstantStatement) { if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) { if (Values.not(((ConstantStatement)first).value)) {
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
} }
else first.compile(target, scope, pollute); else first.compile(target, scope, pollute);
return; return;
} }
first.compile(target, scope, true); first.compile(target, scope, true);
if (pollute) target.add(Instruction.dup(loc())); if (pollute) target.add(Instruction.dup(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop(null)); target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc())); if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(loc(), target.size() - start)); 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) {
super(loc); super(loc);
this.first = first; this.first = first;
this.second = second; this.second = second;
} }
} }

View File

@ -1,63 +1,63 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
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 ObjectStatement extends Statement { public class ObjectStatement extends Statement {
public final Map<Object, Statement> map; public final Map<Object, Statement> map;
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() { @Override public boolean pure() {
for (var el : map.values()) { for (var el : map.values()) {
if (!el.pure()) return false; if (!el.pure()) return false;
} }
return true; return true;
} }
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadObj(loc())); target.add(Instruction.loadObj(loc()));
for (var el : map.entrySet()) { for (var el : map.entrySet()) {
target.add(Instruction.dup(loc())); target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), el.getKey())); target.add(Instruction.loadValue(loc(), el.getKey()));
var val = el.getValue(); var val = el.getValue();
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString()); FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
target.add(Instruction.storeMember(loc())); target.add(Instruction.storeMember(loc()));
} }
var keys = new ArrayList<Object>(); var keys = new ArrayList<Object>();
keys.addAll(getters.keySet()); keys.addAll(getters.keySet());
keys.addAll(setters.keySet()); keys.addAll(setters.keySet());
for (var key : keys) { for (var key : keys) {
if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key)); if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
else target.add(Instruction.loadValue(loc(), (Double)key)); 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(loc(), null)); 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(loc(), null)); else target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.defProp(loc())); target.add(Instruction.defProp(loc()));
} }
if (!pollute) target.add(Instruction.discard(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) {
super(loc); super(loc);
this.map = map; this.map = map;
this.getters = getters; this.getters = getters;
this.setters = setters; this.setters = setters;
} }
} }

View File

@ -1,37 +1,37 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
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() { @Override public boolean pure() {
for (var el : args) { for (var el : args) {
if (!el.pure()) return false; if (!el.pure()) return false;
} }
return true; 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(loc(), operation)); if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard(loc())); else target.add(Instruction.discard(loc()));
} }
public OperationStatement(Location loc, Operation operation, Statement ...args) { public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc); super(loc);
this.operation = operation; this.operation = operation;
this.args = args; this.args = args;
} }
} }

View File

@ -1,26 +1,26 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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 RegexStatement extends Statement { public class RegexStatement extends Statement {
public final String pattern, flags; public final String pattern, flags;
// Not really pure, since a function is called, but can be ignored. // Not really pure, since a function is called, but can be ignored.
@Override 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(loc(), pattern, flags)); target.add(Instruction.loadRegex(loc(), pattern, flags));
if (!pollute) target.add(Instruction.discard(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public RegexStatement(Location loc, String pattern, String flags) { public RegexStatement(Location loc, String pattern, String flags) {
super(loc); super(loc);
this.pattern = pattern; this.pattern = pattern;
this.flags = flags; this.flags = flags;
} }
} }

View File

@ -1,33 +1,33 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.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 TypeofStatement extends Statement { public class TypeofStatement extends Statement {
public final Statement value; public final Statement value;
// Not really pure, since a variable from the global scope could be accessed, // Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted // which could lead to code execution, that would get omitted
@Override 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) {
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(loc(), (String)i)); target.add(Instruction.typeof(loc(), (String)i));
return; return;
} }
} }
value.compile(target, scope, pollute); value.compile(target, scope, pollute);
target.add(Instruction.typeof(loc())); target.add(Instruction.typeof(loc()));
} }
public TypeofStatement(Location loc, Statement value) { public TypeofStatement(Location loc, Statement value) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
} }

View File

@ -1,38 +1,38 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
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.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableAssignStatement extends Statement { public class VariableAssignStatement extends Statement {
public final String name; public final String name;
public final Statement value; public final Statement value;
public final Operation operation; public final Operation operation;
@Override public boolean pure() { return false; } @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(loc(), i)); target.add(Instruction.loadVar(loc(), i));
FunctionStatement.compileWithName(value, target, scope, true, name); FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.operation(loc(), operation)); target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeVar(loc(), i, pollute)); target.add(Instruction.storeVar(loc(), i, pollute));
} }
else { else {
FunctionStatement.compileWithName(value, target, scope, true, name); FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.storeVar(loc(), i, pollute)); target.add(Instruction.storeVar(loc(), i, pollute));
} }
} }
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
super(loc); super(loc);
this.name = name; this.name = name;
this.value = val; this.value = val;
this.operation = operation; this.operation = operation;
} }
} }

View File

@ -1,23 +1,23 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
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.engine.scope.ScopeRecord; 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 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) {
if (pollute) target.add(Instruction.loadVar(loc(), index)); if (pollute) target.add(Instruction.loadVar(loc(), index));
} }
public VariableIndexStatement(Location loc, int i) { public VariableIndexStatement(Location loc, int i) {
super(loc); super(loc);
this.index = i; this.index = i;
} }
} }

View File

@ -1,32 +1,32 @@
package me.topchetoeu.jscript.compilation.values; package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement; 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.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord; 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 public boolean pure() { return false; } @Override public boolean pure() { return false; }
@Override @Override
public Statement toAssign(Statement val, Operation operation) { public Statement toAssign(Statement val, Operation operation) {
return new VariableAssignStatement(loc(), name, val, operation); return new VariableAssignStatement(loc(), name, val, operation);
} }
@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(loc(), i)); target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard(loc())); if (!pollute) target.add(Instruction.discard(loc()));
} }
public VariableStatement(Location loc, String name) { public VariableStatement(Location loc, String name) {
super(loc); super(loc);
this.name = name; this.name = name;
} }
} }

View File

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

View File

@ -1,175 +1,175 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
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.FunctionBody; import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.debug.DebugController; import me.topchetoeu.jscript.engine.debug.DebugController;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable; 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; 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 {
public final Filename filename; public final Filename filename;
public final String raw; public final String raw;
private FunctionValue compiled = null; private FunctionValue compiled = null;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public Object call(Context ctx, Object thisArg, Object ...args) {
if (compiled == null) compiled = ctx.compile(filename, raw); if (compiled == null) compiled = ctx.compile(filename, raw);
return compiled.call(ctx, thisArg, args); return compiled.call(ctx, thisArg, args);
} }
public UncompiledFunction(Filename filename, String raw) { public UncompiledFunction(Filename filename, String raw) {
super(filename + "", 0); super(filename + "", 0);
this.filename = filename; this.filename = filename;
this.raw = raw; this.raw = raw;
} }
} }
private static class Task implements Comparable<Task> { private static class Task implements Comparable<Task> {
public final FunctionValue func; public final FunctionValue func;
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Context ctx; public final Context ctx;
public final boolean micro; public final boolean micro;
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) { public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.ctx = ctx; this.ctx = ctx;
this.func = func; this.func = func;
this.thisArg = thisArg; this.thisArg = thisArg;
this.args = args; this.args = args;
this.micro = micro; this.micro = micro;
} }
@Override @Override
public int compareTo(Task other) { public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1); return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
} }
} }
private static int nextId = 0; private static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>(); public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
public final int id = ++nextId; public final int id = ++nextId;
public final boolean debugging; public final boolean debugging;
public int maxStackFrames = 10000; public int maxStackFrames = 10000;
private final HashMap<Filename, String> sources = new HashMap<>(); private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>(); private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private final HashMap<Filename, SourceMap> maps = new HashMap<>(); private final HashMap<Filename, SourceMap> maps = new HashMap<>();
public Location mapToCompiled(Location location) { public Location mapToCompiled(Location location) {
var map = maps.get(location.filename()); var map = maps.get(location.filename());
if (map == null) return location; if (map == null) return location;
return map.toCompiled(location); return map.toCompiled(location);
} }
public Location mapToOriginal(Location location) { public Location mapToOriginal(Location location) {
var map = maps.get(location.filename()); var map = maps.get(location.filename());
if (map == null) return location; if (map == null) return location;
return map.toOriginal(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 synchronized 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()) debugger.onSource( for (var source : sources.entrySet()) debugger.onSource(
source.getKey(), source.getValue(), source.getKey(), source.getValue(),
bpts.get(source.getKey()), bpts.get(source.getKey()),
maps.get(source.getKey()) maps.get(source.getKey())
); );
this.debugger = debugger; this.debugger = debugger;
return true; return true;
} }
public synchronized 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;
} }
private void runTask(Task task) { private void runTask(Task task) {
try { try {
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
} }
catch (RuntimeException e) { catch (RuntimeException e) {
if (e instanceof InterruptException) throw e; if (e instanceof InterruptException) throw e;
task.notifier.error(e); task.notifier.error(e);
} }
} }
public void run(boolean untilEmpty) { public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) { while (!untilEmpty || !tasks.isEmpty()) {
try { try {
runTask(tasks.take()); runTask(tasks.take());
} }
catch (InterruptedException | InterruptException e) { catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e)); for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break; break;
} }
} }
} }
public Thread start() { public Thread start() {
if (this.thread == null) { if (this.thread == null) {
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id); this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
this.thread.start(); this.thread.start();
} }
return this.thread; return this.thread;
} }
public void stop() { public void stop() {
thread.interrupt(); thread.interrupt();
thread = null; thread = null;
} }
public boolean inExecThread() { public boolean inExecThread() {
return Thread.currentThread() == thread; return Thread.currentThread() == thread;
} }
public synchronized boolean isRunning() { public synchronized boolean isRunning() {
return this.thread != null; return this.thread != null;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro); var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
tasks.add(msg); tasks.add(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
} }
@Override @Override
public void onFramePush(Context ctx, CodeFrame frame) { public void onFramePush(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePush(ctx, 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);
} }
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
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, SourceMap map) { @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, map); 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); maps.put(filename, map);
} }
public Engine(boolean debugging) { public Engine(boolean debugging) {
this.debugging = debugging; this.debugging = debugging;
} }
} }

View File

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

View File

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

View File

@ -1,34 +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 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,245 +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.Reading; import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.exceptions.UncheckedIOException; import me.topchetoeu.jscript.exceptions.UncheckedIOException;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer { public class DebugServer {
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); 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 byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier(); private final Notifier connNotifier = new Notifier();
private static void send(HttpRequest req, String val) throws IOException { private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes()); req.writeResponse(200, "OK", "application/json", val.getBytes());
} }
// SILENCE JAVA // SILENCE JAVA
private MessageDigest getDigestInstance() { private MessageDigest getDigestInstance() {
try { try {
return MessageDigest.getInstance("sha1"); 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) { private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func); var res = new Thread(func);
res.setName(name); res.setName(name);
res.start(); res.start();
return res; return res;
} }
private void handle(WebSocket ws, Debugger debugger) { private void handle(WebSocket ws, Debugger debugger) {
WebSocketMessage raw; WebSocketMessage raw;
debugger.connect(); debugger.connect();
while ((raw = ws.receive()) != null) { while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) { if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message.")); ws.send(new V8Error("Expected a text message."));
continue; continue;
} }
V8Message msg; V8Message msg;
try { try {
msg = new V8Message(raw.textData()); msg = new V8Message(raw.textData());
} }
catch (SyntaxException e) { catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage())); ws.send(new V8Error(e.getMessage()));
return; return;
} }
try { try {
switch (msg.name) { switch (msg.name) {
case "Debugger.enable": case "Debugger.enable":
connNotifier.next(); connNotifier.next();
debugger.enable(msg); continue; debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue; case "Debugger.disable": debugger.disable(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 = Reading.resourceToStream("debugger/favicon.png").readAllBytes(); this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes(); this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
this.index = Reading.resourceToString("debugger/index.html") this.index = Reading.resourceToString("debugger/index.html")
.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

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

View File

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

View File

@ -1,9 +1,9 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
public class InstructionResult { public class InstructionResult {
public final Object value; public final Object value;
public InstructionResult(Object value) { public InstructionResult(Object value) {
this.value = value; this.value = value;
} }
} }

View File

@ -1,350 +1,350 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.Collections; import java.util.Collections;
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.Engine; import me.topchetoeu.jscript.engine.Engine;
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.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
public class Runners { public class Runners {
public static final Object NO_RETURN = new Object(); public static final Object NO_RETURN = new Object();
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) { public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
return frame.pop(); return frame.pop();
} }
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
throw new EngineException(frame.pop()); throw new EngineException(frame.pop());
} }
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) { public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
throw EngineException.ofSyntax((String)instr.get(0)); throw EngineException.ofSyntax((String)instr.get(0));
} }
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) { public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var func = frame.pop(); var func = frame.pop();
var thisArg = frame.pop(); var thisArg = frame.pop();
frame.push(ctx, Values.call(ctx, func, thisArg, callArgs)); frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) { public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
ctx.environment().global.define(name); ctx.environment().global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) { public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
var setter = frame.pop(); var setter = frame.pop();
var getter = frame.pop(); var getter = frame.pop();
var name = frame.pop(); var name = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined."); if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined."); if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object."); if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false);
frame.push(ctx, obj); frame.push(ctx, obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) { public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
var type = frame.pop(); var type = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
if (!Values.isPrimitive(type)) { if (!Values.isPrimitive(type)) {
var proto = Values.getMember(ctx, type, "prototype"); var proto = Values.getMember(ctx, type, "prototype");
frame.push(ctx, Values.isInstanceOf(ctx, obj, proto)); frame.push(ctx, Values.isInstanceOf(ctx, obj, proto));
} }
else { else {
frame.push(ctx, false); frame.push(ctx, false);
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) { public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var members = Values.getMembers(ctx, val, false, false); var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members); Collections.reverse(members);
frame.push(ctx, null); frame.push(ctx, null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue; if (el instanceof Symbol) continue;
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el); obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj); frame.push(ctx, obj);
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) { public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
int start = frame.codePtr + 1; int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0); int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1); int finallyStart = (int)instr.get(1);
if (finallyStart >= 0) finallyStart += start; if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start; if (catchStart >= 0) catchStart += start;
int end = (int)instr.get(2) + start; int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart); 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) { public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
frame.popTryFlag = true; frame.popTryFlag = true;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) { public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
int count = instr.get(0); int count = instr.get(0);
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
frame.push(ctx, frame.peek(count - 1)); frame.push(ctx, frame.peek(count - 1));
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, instr.get(0)); frame.push(ctx, instr.get(0));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i)); if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
else frame.push(ctx, frame.scope.get((int)i).get(ctx)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, new ObjectValue()); frame.push(ctx, new ObjectValue());
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.environment().global.obj); frame.push(ctx, ctx.environment().global.obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) {
var res = new ArrayValue(); var res = new ArrayValue();
res.setSize(instr.get(0)); res.setSize(instr.get(0));
frame.push(ctx, res); frame.push(ctx, res);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
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);
var captures = new ValueVariable[instr.params.length - 1]; var captures = new ValueVariable[instr.params.length - 1];
for (var i = 1; i < instr.params.length; i++) { for (var i = 1; i < instr.params.length; i++) {
captures[i - 1] = frame.scope.get(instr.get(i)); captures[i - 1] = frame.scope.get(instr.get(i));
} }
var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures); var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
frame.push(ctx, func); frame.push(ctx, func);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
try { try {
frame.push(ctx, Values.getMember(ctx, obj, key)); frame.push(ctx, Values.getMember(ctx, obj, key));
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage()); throw EngineException.ofType(e.getMessage());
} }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, instr.get(0)); frame.push(ctx, instr.get(0));
return execLoadMember(ctx, instr, frame); return execLoadMember(ctx, instr, frame);
} }
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) { public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) {
frame.pop(); frame.pop();
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) { public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
if ((boolean)instr.get(0)) frame.push(ctx, val); if ((boolean)instr.get(0)) frame.push(ctx, val);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val); if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) { public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) {
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function); frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) { public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) { public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.toBoolean(frame.pop())) { if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
} }
else frame.codePtr ++; else frame.codePtr ++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) { public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.not(frame.pop())) { if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
} }
else frame.codePtr ++; else frame.codePtr ++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) { public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
var obj = frame.pop(); var obj = frame.pop();
var index = frame.pop(); var index = frame.pop();
frame.push(ctx, Values.hasMember(ctx, obj, index, false)); frame.push(ctx, Values.hasMember(ctx, obj, index, false));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) { public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
String name = instr.get(0); String name = instr.get(0);
Object obj; Object obj;
if (name != null) { if (name != null) {
if (ctx.environment().global.has(ctx, name)) { if (ctx.environment().global.has(ctx, name)) {
obj = ctx.environment().global.get(ctx, name); obj = ctx.environment().global.get(ctx, name);
} }
else obj = null; else obj = null;
} }
else obj = frame.pop(); else obj = frame.pop();
frame.push(ctx, Values.type(obj)); frame.push(ctx, Values.type(obj));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) { public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
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.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) { public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
Operation op = instr.get(0); Operation op = instr.get(0);
var args = new Object[op.operands]; var args = new Object[op.operands];
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
frame.push(ctx, Values.operation(ctx, op, args)); frame.push(ctx, Values.operation(ctx, op, args));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) { public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
switch (instr.type) { switch (instr.type) {
case NOP: return execNop(ctx, instr, frame); case NOP: return execNop(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame); case RETURN: return execReturn(ctx, instr, frame);
case THROW: return execThrow(ctx, instr, frame); case THROW: return execThrow(ctx, instr, frame);
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_START: return execTryStart(ctx, instr, frame); case TRY_START: return execTryStart(ctx, instr, frame);
case TRY_END: return execTryEnd(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 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);
case LOAD_ARR: return execLoadArr(ctx, instr, frame); case LOAD_ARR: return execLoadArr(ctx, instr, frame);
case LOAD_FUNC: return execLoadFunc(ctx, instr, frame); case LOAD_FUNC: return execLoadFunc(ctx, instr, frame);
case LOAD_MEMBER: return execLoadMember(ctx, instr, frame); case LOAD_MEMBER: return execLoadMember(ctx, instr, frame);
case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame); case LOAD_VAL_MEMBER: return execLoadKeyMember(ctx, instr, frame);
case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame); case LOAD_REGEX: return execLoadRegEx(ctx, instr, frame);
case LOAD_GLOB: return execLoadGlob(ctx, instr, frame); case LOAD_GLOB: return execLoadGlob(ctx, instr, frame);
case DISCARD: return execDiscard(ctx, instr, frame); case DISCARD: return execDiscard(ctx, instr, frame);
case STORE_MEMBER: return execStoreMember(ctx, instr, frame); case STORE_MEMBER: return execStoreMember(ctx, instr, frame);
case STORE_VAR: return execStoreVar(ctx, instr, frame); case STORE_VAR: return execStoreVar(ctx, instr, frame);
case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame); case STORE_SELF_FUNC: return execStoreSelfFunc(ctx, instr, frame);
case MAKE_VAR: return execMakeVar(ctx, instr, frame); case MAKE_VAR: return execMakeVar(ctx, instr, frame);
case KEYS: return execKeys(ctx, instr, frame); case KEYS: return execKeys(ctx, instr, frame);
case DEF_PROP: return execDefProp(ctx, instr, frame); case DEF_PROP: return execDefProp(ctx, instr, frame);
case TYPEOF: return execTypeof(ctx, instr, frame); case TYPEOF: return execTypeof(ctx, instr, frame);
case DELETE: return execDelete(ctx, instr, frame); case DELETE: return execDelete(ctx, instr, frame);
case JMP: return execJmp(ctx, instr, frame); case JMP: return execJmp(ctx, instr, frame);
case JMP_IF: return execJmpIf(ctx, instr, frame); case JMP_IF: return execJmpIf(ctx, instr, frame);
case JMP_IFN: return execJmpIfNot(ctx, instr, frame); case JMP_IFN: return execJmpIfNot(ctx, instr, frame);
case OPERATION: return execOperation(ctx, instr, frame); case OPERATION: return execOperation(ctx, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
} }
} }
} }

View File

@ -1,78 +1,78 @@
package me.topchetoeu.jscript.engine.scope; package me.topchetoeu.jscript.engine.scope;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord { public class GlobalScope implements ScopeRecord {
public final ObjectValue obj; public final ObjectValue obj;
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);
} }
public Object getKey(String name) { public Object getKey(String name) {
return name; return name;
} }
public GlobalScope globalChild() { public GlobalScope globalChild() {
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.setPrototype(null, this.obj); obj.setPrototype(null, this.obj);
return new GlobalScope(obj); return new GlobalScope(obj);
} }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(); return new LocalScopeRecord();
} }
public Object define(String name) { public Object define(String name) {
if (obj.hasMember(null, name, true)) return name; if (obj.hasMember(null, name, true)) return name;
obj.defineProperty(null, name, null); obj.defineProperty(null, name, null);
return name; return name;
} }
public void define(String name, Variable val) { public void define(String name, Variable val) {
obj.defineProperty(null, name, obj.defineProperty(null, name,
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)), new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }), new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
true, true true, true
); );
} }
public void define(Context ctx, String name, boolean readonly, Object val) { public void define(Context ctx, String name, boolean readonly, Object val) {
obj.defineProperty(ctx, name, val, readonly, true, true); obj.defineProperty(ctx, name, val, readonly, true, true);
} }
public void define(String ...names) { public void define(String ...names) {
for (var n : names) define(n); for (var n : names) define(n);
} }
public void define(boolean readonly, FunctionValue val) { public void define(boolean readonly, FunctionValue val) {
define(null, val.name, readonly, val); define(null, val.name, readonly, val);
} }
public Object get(Context ctx, String name) { public Object get(Context ctx, String name) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name); else return obj.getMember(ctx, name);
} }
public void set(Context ctx, String name, Object val) { public void set(Context ctx, String name, Object val) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
} }
public Set<String> keys() { public Set<String> keys() {
var res = new HashSet<String>(); var res = new HashSet<String>();
for (var key : keys()) { for (var key : keys()) {
if (key instanceof String) res.add((String)key); if (key instanceof String) res.add((String)key);
} }
return res; return res;
} }
public GlobalScope() { public GlobalScope() {
this.obj = new ObjectValue(); this.obj = new ObjectValue();
} }
public GlobalScope(ObjectValue val) { public GlobalScope(ObjectValue val) {
this.obj = val; this.obj = val;
} }
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.engine.scope; 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 LocalScopeRecord child(); public LocalScopeRecord child();
} }

View File

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

View File

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

View File

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

View File

@ -1,57 +1,57 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
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.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.ValueVariable; 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 Instruction[] body; public final Instruction[] body;
public final String[] captureNames, localNames; public final String[] captureNames, localNames;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public Environment environment; public Environment environment;
public Location loc() { public Location loc() {
for (var instr : body) { for (var instr : body) {
if (instr.location != null) return instr.location; if (instr.location != null) return instr.location;
} }
return null; return null;
} }
public String readable() { public String readable() {
var loc = loc(); var loc = loc();
if (loc == null) return name; if (loc == null) return name;
else if (name.equals("")) return loc.toString(); else if (name.equals("")) return loc.toString();
else return name + "@" + loc; else return name + "@" + loc;
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public Object call(Context ctx, Object thisArg, Object ...args) {
var frame = new CodeFrame(ctx, thisArg, args, this); var frame = new CodeFrame(ctx, thisArg, args, this);
try { try {
ctx.pushFrame(frame); ctx.pushFrame(frame);
while (true) { while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res; if (res != Runners.NO_RETURN) return res;
} }
} }
finally { finally {
ctx.popFrame(frame); ctx.popFrame(frame);
} }
} }
public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) { public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) {
super(name, body.argsN); 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 = body.localsN; this.localsN = body.localsN;
this.body = body.instructions; this.body = body.instructions;
} }
} }

View File

@ -1,70 +1,70 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public abstract class FunctionValue extends ObjectValue { public abstract class FunctionValue extends ObjectValue {
public String name = ""; public String name = "";
public boolean special = false; public boolean special = false;
public int length; public int length;
@Override @Override
public String toString() { public String toString() {
return String.format("function %s(...)", name); return String.format("function %s(...)", name);
} }
public abstract Object call(Context ctx, Object thisArg, Object ...args); public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) { public Object call(Context ctx) {
return call(ctx, null); return call(ctx, null);
} }
@Override @Override
protected Object getField(Context ctx, Object key) { protected Object getField(Context ctx, Object key) {
if ("name".equals(key)) return name; if ("name".equals(key)) return name;
if ("length".equals(key)) return length; if ("length".equals(key)) return length;
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 ("name".equals(key)) name = Values.toString(ctx, val); if ("name".equals(key)) name = Values.toString(ctx, val);
else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val); else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val); else return super.setField(ctx, key, val);
return true; return true;
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) { protected boolean hasField(Context ctx, Object key) {
if ("name".equals(key)) return true; if ("name".equals(key)) return true;
if ("length".equals(key)) return true; if ("length".equals(key)) return true;
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable); var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) { if (includeNonEnumerable) {
res.add("name"); res.add("name");
res.add("length"); res.add("length");
} }
return res; return res;
} }
public FunctionValue(String name, int length) { public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION); super(PlaceholderProto.FUNCTION);
if (name == null) name = ""; if (name == null) name = "";
this.length = length; this.length = length;
this.name = name; this.name = name;
nonConfigurableSet.add("name"); nonConfigurableSet.add("name");
nonEnumerableSet.add("name"); nonEnumerableSet.add("name");
nonWritableSet.add("length"); nonWritableSet.add("length");
nonConfigurableSet.add("length"); nonConfigurableSet.add("length");
nonEnumerableSet.add("length"); nonEnumerableSet.add("length");
var proto = new ObjectValue(); var proto = new ObjectValue();
proto.defineProperty(null, "constructor", this, true, false, false); proto.defineProperty(null, "constructor", this, true, false, false);
this.defineProperty(null, "prototype", proto, true, false, false); this.defineProperty(null, "prototype", proto, true, false, false);
} }
} }

View File

@ -1,25 +1,25 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public class NativeFunction extends FunctionValue { public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner { public static interface NativeFunctionRunner {
Object run(Context ctx, Object thisArg, Object[] args); Object run(Context ctx, Object thisArg, Object[] args);
} }
public final NativeFunctionRunner action; public final NativeFunctionRunner action;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public Object call(Context ctx, Object thisArg, Object ...args) {
return action.run(ctx, thisArg, args); return action.run(ctx, thisArg, args);
} }
public NativeFunction(String name, NativeFunctionRunner action) { public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0); super(name, 0);
this.action = action; this.action = action;
} }
public NativeFunction(NativeFunctionRunner action) { public NativeFunction(NativeFunctionRunner action) {
super("", 0); super("", 0);
this.action = action; this.action = action;
} }
} }

View File

@ -1,32 +1,32 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public class NativeWrapper extends ObjectValue { public class NativeWrapper extends ObjectValue {
private static final Object NATIVE_PROTO = new Object(); private static final Object NATIVE_PROTO = new Object();
public final Object wrapped; public final Object wrapped;
@Override @Override
public ObjectValue getPrototype(Context ctx) { public ObjectValue getPrototype(Context ctx) {
if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass()); if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
else return super.getPrototype(ctx); else return super.getPrototype(ctx);
} }
@Override @Override
public String toString() { public String toString() {
return wrapped.toString(); return wrapped.toString();
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return wrapped.equals(obj); return wrapped.equals(obj);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return wrapped.hashCode(); return wrapped.hashCode();
} }
public NativeWrapper(Object wrapped) { public NativeWrapper(Object wrapped) {
this.wrapped = wrapped; this.wrapped = wrapped;
prototype = NATIVE_PROTO; prototype = NATIVE_PROTO;
} }
} }

View File

@ -1,344 +1,344 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
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;
public class ObjectValue { public class ObjectValue {
public static enum PlaceholderProto { public static enum PlaceholderProto {
NONE, NONE,
OBJECT, OBJECT,
ARRAY, ARRAY,
FUNCTION, FUNCTION,
ERROR, ERROR,
SYNTAX_ERROR, SYNTAX_ERROR,
TYPE_ERROR, TYPE_ERROR,
RANGE_ERROR, RANGE_ERROR,
} }
public static enum State { public static enum State {
NORMAL, NORMAL,
NO_EXTENSIONS, NO_EXTENSIONS,
SEALED, SEALED,
FROZEN, FROZEN,
} }
public static class Property { public static class Property {
public final FunctionValue getter; public final FunctionValue getter;
public final FunctionValue setter; public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) { public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter; this.getter = getter;
this.setter = setter; this.setter = setter;
} }
} }
private static final Object OBJ_PROTO = new Object(); private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object(); private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object(); private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object(); private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object(); private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object(); private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object(); private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype; protected Object prototype;
public State state = State.NORMAL; public State state = State.NORMAL;
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>(); public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>(); public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>(); public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
public final boolean memberWritable(Object key) { public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false; if (state == State.FROZEN) return false;
return !values.containsKey(key) || !nonWritableSet.contains(key); return !values.containsKey(key) || !nonWritableSet.contains(key);
} }
public final boolean memberConfigurable(Object key) { public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false; if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key); return !nonConfigurableSet.contains(key);
} }
public final boolean memberEnumerable(Object key) { public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key); return !nonEnumerableSet.contains(key);
} }
public final boolean extensible() { public final boolean extensible() {
return state == State.NORMAL; return state == State.NORMAL;
} }
public final void preventExtensions() { public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS; if (state == State.NORMAL) state = State.NO_EXTENSIONS;
} }
public final void seal() { public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED; if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
} }
public final void freeze() { public final void freeze() {
state = State.FROZEN; state = State.FROZEN;
} }
public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) { public final boolean defineProperty(Context ctx, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
boolean reconfigured = boolean reconfigured =
writable != memberWritable(key) || writable != memberWritable(key) ||
configurable != memberConfigurable(key) || configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key); enumerable != memberEnumerable(key);
if (!reconfigured) { if (!reconfigured) {
if (!memberWritable(key)) { if (!memberWritable(key)) {
var a = values.get(key); var a = values.get(key);
var b = val; var b = val;
if (a == null || b == null) return a == null && b == null; if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b); return a == b || a.equals(b);
} }
values.put(key, val); values.put(key, val);
return true; return true;
} }
if ( if (
properties.containsKey(key) && properties.containsKey(key) &&
values.get(key) == val && values.get(key) == val &&
!reconfigured !reconfigured
) return true; ) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
properties.remove(key); properties.remove(key);
values.remove(key); values.remove(key);
if (!writable) nonWritableSet.add(key); if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key); if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key); if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val); values.put(key, val);
return true; return true;
} }
public final boolean defineProperty(Context ctx, Object key, Object val) { public final boolean defineProperty(Context ctx, Object key, Object val) {
return defineProperty(ctx, key, val, true, true, true); return defineProperty(ctx, key, val, true, true, true);
} }
public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { public final boolean defineProperty(Context ctx, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if ( if (
properties.containsKey(key) && properties.containsKey(key) &&
properties.get(key).getter == getter && properties.get(key).getter == getter &&
properties.get(key).setter == setter && properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) && !configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key) !enumerable == nonEnumerableSet.contains(key)
) return true; ) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
properties.remove(key); properties.remove(key);
values.remove(key); values.remove(key);
if (!configurable) nonConfigurableSet.add(key); if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key); if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter)); properties.put(key, new Property(getter, setter));
return true; return true;
} }
public ObjectValue getPrototype(Context ctx) { public ObjectValue getPrototype(Context ctx) {
try { try {
if (prototype == OBJ_PROTO) return ctx.environment().proto("object"); if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
if (prototype == ARR_PROTO) return ctx.environment().proto("array"); if (prototype == ARR_PROTO) return ctx.environment().proto("array");
if (prototype == FUNC_PROTO) return ctx.environment().proto("function"); if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
if (prototype == ERR_PROTO) return ctx.environment().proto("error"); if (prototype == ERR_PROTO) return ctx.environment().proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr"); if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr"); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr"); if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
} }
catch (NullPointerException e) { return null; } catch (NullPointerException e) { return null; }
return (ObjectValue)prototype; return (ObjectValue)prototype;
} }
public final boolean setPrototype(Context ctx, Object val) { public final boolean setPrototype(Context ctx, Object val) {
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (!extensible()) return false; if (!extensible()) return false;
if (val == null || val == Values.NULL) { if (val == null || val == Values.NULL) {
prototype = null; prototype = null;
return true; return true;
} }
else if (Values.isObject(val)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.environment() != null) { if (ctx != null && ctx.environment() != null) {
if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO; if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO; else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO; else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO; else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO; else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj; else prototype = obj;
} }
else prototype = obj; else prototype = obj;
return true; return true;
} }
return false; return false;
} }
public final boolean setPrototype(PlaceholderProto val) { public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false; if (!extensible()) return false;
switch (val) { switch (val) {
case OBJECT: prototype = OBJ_PROTO; break; case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break; case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break; case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break; case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break; case NONE: prototype = null; break;
} }
return true; return true;
} }
protected Property getProperty(Context ctx, Object key) { protected Property getProperty(Context ctx, Object key) {
if (properties.containsKey(key)) return properties.get(key); if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key); if (proto != null) return proto.getProperty(ctx, key);
else return null; else return null;
} }
protected Object getField(Context ctx, Object key) { protected Object getField(Context ctx, Object key) {
if (values.containsKey(key)) return values.get(key); if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key); if (proto != null) return proto.getField(ctx, key);
else return null; else return null;
} }
protected boolean setField(Context ctx, Object key, Object val) { protected boolean setField(Context ctx, Object key, Object val) {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key); ((FunctionValue)val).name = Values.toString(ctx, key);
} }
values.put(key, val); values.put(key, val);
return true; return true;
} }
protected void deleteField(Context ctx, Object key) { protected void deleteField(Context ctx, Object key) {
values.remove(key); values.remove(key);
} }
protected boolean hasField(Context ctx, Object key) { protected boolean hasField(Context ctx, Object key) {
return values.containsKey(key); return values.containsKey(key);
} }
public final Object getMember(Context ctx, Object key, Object thisArg) { public final Object getMember(Context ctx, Object key, Object thisArg) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) { if ("__proto__".equals(key)) {
var res = getPrototype(ctx); var res = getPrototype(ctx);
return res == null ? Values.NULL : res; return res == null ? Values.NULL : res;
} }
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
if (prop != null) { if (prop != null) {
if (prop.getter == null) return null; if (prop.getter == null) return null;
else return prop.getter.call(ctx, Values.normalize(ctx, thisArg)); else return prop.getter.call(ctx, Values.normalize(ctx, thisArg));
} }
else return getField(ctx, key); else return getField(ctx, key);
} }
public final Object getMember(Context ctx, Object key) { public final Object getMember(Context ctx, Object key) {
return getMember(ctx, key, this); return getMember(ctx, key, this);
} }
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) { public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
if (prop != null) { if (prop != null) {
if (prop.setter == null) return false; if (prop.setter == null) return false;
prop.setter.call(ctx, Values.normalize(ctx, thisArg), val); prop.setter.call(ctx, Values.normalize(ctx, thisArg), val);
return true; return true;
} }
else if (onlyProps) return false; else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false; else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) { else if (key == null) {
values.put(key, val); values.put(key, val);
return true; return true;
} }
else if ("__proto__".equals(key)) return setPrototype(ctx, val); else if ("__proto__".equals(key)) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false; else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val); else return setField(ctx, key, val);
} }
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) { public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) {
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps); return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
} }
public final boolean hasMember(Context ctx, Object key, boolean own) { public final boolean hasMember(Context ctx, Object key, boolean own) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (key != null && "__proto__".equals(key)) return true; if (key != null && "__proto__".equals(key)) return true;
if (hasField(ctx, key)) return true; if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true; if (properties.containsKey(key)) return true;
if (own) return false; if (own) return false;
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own); return proto != null && proto.hasMember(ctx, key, own);
} }
public final boolean deleteMember(Context ctx, Object key) { public final boolean deleteMember(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
properties.remove(key); properties.remove(key);
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
deleteField(ctx, key); deleteField(ctx, key);
return true; return true;
} }
public final ObjectValue getMemberDescriptor(Context ctx, Object key) { public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
var prop = properties.get(key); var prop = properties.get(key);
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(ctx, "configurable", memberConfigurable(key)); res.defineProperty(ctx, "configurable", memberConfigurable(key));
res.defineProperty(ctx, "enumerable", memberEnumerable(key)); res.defineProperty(ctx, "enumerable", memberEnumerable(key));
if (prop != null) { if (prop != null) {
res.defineProperty(ctx, "get", prop.getter); res.defineProperty(ctx, "get", prop.getter);
res.defineProperty(ctx, "set", prop.setter); res.defineProperty(ctx, "set", prop.setter);
} }
else if (hasField(ctx, key)) { else if (hasField(ctx, key)) {
res.defineProperty(ctx, "value", values.get(key)); res.defineProperty(ctx, "value", values.get(key));
res.defineProperty(ctx, "writable", memberWritable(key)); res.defineProperty(ctx, "writable", memberWritable(key));
} }
else return null; else return null;
return res; return res;
} }
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>(); var res = new ArrayList<Object>();
for (var key : values.keySet()) { for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key); res.add(key);
} }
for (var key : properties.keySet()) { for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key); res.add(key);
} }
return res; return res;
} }
public ObjectValue(Context ctx, Map<?, ?> values) { public ObjectValue(Context ctx, Map<?, ?> values) {
this(PlaceholderProto.OBJECT); this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) { for (var el : values.entrySet()) {
defineProperty(ctx, el.getKey(), el.getValue()); defineProperty(ctx, el.getKey(), el.getValue());
} }
} }
public ObjectValue(PlaceholderProto proto) { public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__"); nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__"); nonEnumerableSet.add("__proto__");
setPrototype(proto); setPrototype(proto);
} }
public ObjectValue() { public ObjectValue() {
this(PlaceholderProto.OBJECT); this(PlaceholderProto.OBJECT);
} }
} }

View File

@ -1,54 +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;
} }
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true; if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true;
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override
protected void deleteField(Context ctx, Object key) { protected void deleteField(Context ctx, Object key) {
if (names.containsKey(key)) return; if (names.containsKey(key)) return;
super.deleteField(ctx, key); super.deleteField(ctx, key);
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) { protected boolean hasField(Context ctx, Object key) {
if (names.containsKey(key)) return true; if (names.containsKey(key)) return true;
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
public List<Object> keys(boolean includeNonEnumerable) { public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable); var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet()); res.addAll(names.keySet());
return res; return res;
} }
public ScopeValue(ValueVariable[] variables, String[] names) { public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables; this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) { for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i); this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]); this.nonConfigurableSet.add(names[i]);
} }
} }
} }

View File

@ -1,15 +1,15 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
public final class Symbol { public final class Symbol {
public final String value; public final String value;
public Symbol(String value) { public Symbol(String value) {
this.value = value; this.value = value;
} }
@Override @Override
public String toString() { public String toString() {
if (value == null) return "Symbol"; if (value == null) return "Symbol";
else return "@@" + value; else return "@@" + value;
} }
} }

View File

@ -1,25 +1,25 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
public interface Awaitable<T> { public interface Awaitable<T> {
T await() throws FinishedException; T await() throws FinishedException;
default Observable<T> toObservable() { default Observable<T> toObservable() {
return sub -> { return sub -> {
var thread = new Thread(() -> { var thread = new Thread(() -> {
try { try {
sub.next(await()); sub.next(await());
sub.finish(); sub.finish();
} }
catch (InterruptException | FinishedException e) { sub.finish(); } catch (InterruptException | FinishedException e) { sub.finish(); }
catch (RuntimeException e) { catch (RuntimeException e) {
sub.error(e); sub.error(e);
} }
}, "Awaiter"); }, "Awaiter");
thread.start(); thread.start();
return () -> thread.interrupt(); return () -> thread.interrupt();
}; };
} }
} }

View File

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

View File

@ -1,49 +1,49 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import java.util.HashSet; import java.util.HashSet;
public class Event<T> implements Observer<T>, Observable<T> { public class Event<T> implements Observer<T>, Observable<T> {
private HashSet<Observer<T>> handlers = new HashSet<>(); private HashSet<Observer<T>> handlers = new HashSet<>();
public Handle on(Observer<T> handler) { public Handle on(Observer<T> handler) {
if (handlers == null) { if (handlers == null) {
handler.finish(); handler.finish();
return () -> {}; return () -> {};
} }
handlers.add(handler); handlers.add(handler);
return () -> { return () -> {
if (handlers == null) return; if (handlers == null) return;
handlers.remove(handler); handlers.remove(handler);
}; };
} }
public boolean isFinished() { public boolean isFinished() {
return handlers == null; return handlers == null;
} }
public void next(T value) { public void next(T value) {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.next(value); handler.next(value);
} }
} }
public void error(RuntimeException value) { public void error(RuntimeException value) {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.error(value); handler.error(value);
} }
handlers.clear(); handlers.clear();
handlers = null; handlers = null;
} }
public void finish() { public void finish() {
if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
for (var handler : handlers) { for (var handler : handlers) {
handler.finish(); handler.finish();
} }
handlers.clear(); handlers.clear();
handlers = null; handlers = null;
} }
} }

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public class FinishedException extends RuntimeException { public class FinishedException extends RuntimeException {
public FinishedException() { public FinishedException() {
super("The observable has ended."); super("The observable has ended.");
} }
} }

View File

@ -1,5 +1,5 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Handle { public interface Handle {
void free(); void free();
} }

View File

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

View File

@ -1,75 +1,75 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Observable<T> { public interface Observable<T> {
Handle on(Observer<T> val); Handle on(Observer<T> val);
default Handle once(Observer<T> observer) { default Handle once(Observer<T> observer) {
// Java is fucking retarded // Java is fucking retarded
var unhandler = new Handle[1]; var unhandler = new Handle[1];
var shouldUnsub = new boolean[1]; var shouldUnsub = new boolean[1];
unhandler[0] = on(new Observer<>() { unhandler[0] = on(new Observer<>() {
public void next(T data) { public void next(T data) {
observer.next(data); observer.next(data);
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
observer.error(err); observer.error(err);
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
public void finish() { public void finish() {
observer.finish(); observer.finish();
if (unhandler[0] == null) shouldUnsub[0] = true; if (unhandler[0] == null) shouldUnsub[0] = true;
else unhandler[0].free(); else unhandler[0].free();
} }
}); });
if (shouldUnsub[0]) { if (shouldUnsub[0]) {
unhandler[0].free(); unhandler[0].free();
return () -> {}; return () -> {};
} }
else return unhandler[0]; else return unhandler[0];
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default Awaitable<T> toAwaitable() { default Awaitable<T> toAwaitable() {
return () -> { return () -> {
var notifier = new Notifier(); var notifier = new Notifier();
var valRef = new Object[1]; var valRef = new Object[1];
var isErrRef = new boolean[1]; var isErrRef = new boolean[1];
once(new Observer<>() { once(new Observer<>() {
public void next(T data) { public void next(T data) {
valRef[0] = data; valRef[0] = data;
notifier.next(); notifier.next();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
isErrRef[0] = true; isErrRef[0] = true;
valRef[0] = err; valRef[0] = err;
notifier.next(); notifier.next();
} }
public void finish() { public void finish() {
isErrRef[0] = true; isErrRef[0] = true;
valRef[0] = new FinishedException(); valRef[0] = new FinishedException();
notifier.next(); notifier.next();
} }
}); });
notifier.await(); notifier.await();
if (isErrRef[0]) throw (RuntimeException)valRef[0]; if (isErrRef[0]) throw (RuntimeException)valRef[0];
else return (T)valRef[0]; else return (T)valRef[0];
}; };
} }
default Observable<T> encapsulate() { default Observable<T> encapsulate() {
return val -> on(val); return val -> on(val);
} }
default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) { default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) {
return sub -> on(pipe.apply(sub)); return sub -> on(pipe.apply(sub));
} }
default WarmObservable<T> warmUp() { default WarmObservable<T> warmUp() {
return new WarmObservable<>(this); return new WarmObservable<>(this);
} }
} }

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Observer<T> { public interface Observer<T> {
public void next(T data); public void next(T data);
public default void error(RuntimeException err) {} public default void error(RuntimeException err) {}
public default void finish() { } public default void finish() { }
} }

View File

@ -1,59 +1,59 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
public interface Pipe<T, T2> { public interface Pipe<T, T2> {
Observer<T> apply(Observer<T2> obs); Observer<T> apply(Observer<T2> obs);
// void next(T val, Observer<T2> target); // void next(T val, Observer<T2> target);
// default void error(RuntimeException err, Observer<T2> target) { // default void error(RuntimeException err, Observer<T2> target) {
// target.error(err); // target.error(err);
// } // }
// default void finish(Observer<T2> target) { // default void finish(Observer<T2> target) {
// target.finish(); // target.finish();
// } // }
public static interface MapFunc<T1, T2> { public static interface MapFunc<T1, T2> {
T2 map(T1 val); T2 map(T1 val);
} }
public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) { public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) {
return o -> val -> o.next(func.map(val)); return o -> val -> o.next(func.map(val));
} }
public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) { public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) {
return o -> val -> { return o -> val -> {
if (func.map(val)) o.next(val); if (func.map(val)) o.next(val);
}; };
} }
public static <T> Pipe<T, T> skip(int n) { public static <T> Pipe<T, T> skip(int n) {
var i = new int[1]; var i = new int[1];
return target -> val -> { return target -> val -> {
if (i[0] >= n) target.next(val); if (i[0] >= n) target.next(val);
else i[0]++; else i[0]++;
}; };
} }
public static <T> Pipe<T, T> limit(int n) { public static <T> Pipe<T, T> limit(int n) {
return target -> new Observer<T>() { return target -> new Observer<T>() {
private int i; private int i;
public void next(T val) { public void next(T val) {
if (i >= n) target.finish(); if (i >= n) target.finish();
else { else {
target.next(val); target.next(val);
i++; i++;
} }
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
if (i < n) target.error(err); if (i < n) target.error(err);
} }
public void finish() { public void finish() {
if (i < n) target.finish(); if (i < n) target.finish();
} }
}; };
} }
public static <T> Pipe<T, T> first() { public static <T> Pipe<T, T> first() {
return limit(1); return limit(1);
} }
public static <T> Pipe<Observable<T>, T> merge() { public static <T> Pipe<Observable<T>, T> merge() {
return target -> val -> val.on(target); return target -> val -> val.on(target);
} }
} }

View File

@ -1,46 +1,46 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import java.util.HashSet; import java.util.HashSet;
public class WarmObservable<T> implements Observable<T>, Handle { public class WarmObservable<T> implements Observable<T>, Handle {
private HashSet<Observer<T>> observers = new HashSet<>(); private HashSet<Observer<T>> observers = new HashSet<>();
private Handle handle; private Handle handle;
@Override @Override
public Handle on(Observer<T> val) { public Handle on(Observer<T> val) {
if (observers == null) return () -> {}; if (observers == null) return () -> {};
observers.add(val); observers.add(val);
return () -> observers.remove(val); return () -> observers.remove(val);
} }
@Override @Override
public void free() { public void free() {
if (observers == null) return; if (observers == null) return;
handle.free(); handle.free();
handle = null; handle = null;
observers = null; observers = null;
} }
public WarmObservable(Observable<T> observable) { public WarmObservable(Observable<T> observable) {
observable.on(new Observer<>() { observable.on(new Observer<>() {
public void next(T data) { public void next(T data) {
for (var obs : observers) obs.next(data); for (var obs : observers) obs.next(data);
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
for (var obs : observers) obs.error(err); for (var obs : observers) obs.error(err);
handle = null; handle = null;
observers = null; observers = null;
} }
public void finish() { public void finish() {
for (var obs : observers) obs.finish(); for (var obs : observers) obs.finish();
handle = null; handle = null;
observers = null; observers = null;
} }
}); });
} }
@Override @Override
public WarmObservable<T> warmUp() { public WarmObservable<T> warmUp() {
return this; return this;
} }
} }

View File

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

View File

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

View File

@ -1,67 +1,67 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import me.topchetoeu.jscript.Buffer; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File { public class MemoryFile implements File {
private int ptr; private int ptr;
private Mode mode; private Mode mode;
private Buffer data; private Buffer data;
private String filename; private String filename;
public Buffer data() { return data; } public Buffer data() { return data; }
@Override @Override
public int read(byte[] buff) { public int read(byte[] buff) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
var res = data.read(ptr, buff); var res = data.read(ptr, buff);
ptr += res; ptr += res;
return res; return res;
} }
@Override @Override
public void write(byte[] buff) { public void write(byte[] buff) {
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff); data.write(ptr, buff);
ptr += buff.length; ptr += buff.length;
} }
@Override @Override
public long getPtr() { public long getPtr() {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
return ptr; return ptr;
} }
@Override @Override
public void setPtr(long offset, int pos) { public void setPtr(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
if (pos == 0) ptr = (int)offset; if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset; else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = data.length() - (int)offset; else if (pos == 2) ptr = data.length() - (int)offset;
} }
@Override @Override
public void close() { public void close() {
mode = Mode.NONE; mode = Mode.NONE;
ptr = 0; ptr = 0;
} }
@Override @Override
public Mode mode() { public Mode mode() {
if (data == null) return Mode.NONE; if (data == null) return Mode.NONE;
return mode; return mode;
} }
public MemoryFile(String filename, Buffer buff, Mode mode) { public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename; this.filename = filename;
this.data = buff; this.data = buff;
this.mode = mode; this.mode = mode;
} }
public static MemoryFile fromFileList(String filename, java.io.File[] list) { public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder(); 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,90 +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.Buffer; import me.topchetoeu.jscript.Buffer;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem { public class MemoryFilesystem implements Filesystem {
public final Mode mode; public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>(); private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>(); private HashSet<Path> folders = new HashSet<>();
private Path getPath(String name) { private Path getPath(String name) {
return Path.of("/" + name.replace("\\", "/")).normalize(); return Path.of("/" + name.replace("\\", "/")).normalize();
} }
@Override @Override
public void create(String path, EntryType type) { public void create(String path, EntryType type) {
var _path = getPath(path); var _path = getPath(path);
switch (type) { switch (type) {
case FILE: case FILE:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer()); files.put(_path, new Buffer());
break; break;
case FOLDER: case FOLDER:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
folders.add(_path); folders.add(_path);
break; break;
default: default:
case NONE: case NONE:
if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
} }
} }
@Override @Override
public File open(String path, Mode perms) { public File open(String path, Mode perms) {
var _path = getPath(path); var _path = getPath(path);
var pcount = _path.getNameCount(); var pcount = _path.getNameCount();
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms); if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
else if (folders.contains(_path)) { else if (folders.contains(_path)) {
var res = new StringBuilder(); var res = new StringBuilder();
for (var folder : folders) { for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue; if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(_path)) continue; if (!folder.startsWith(_path)) continue;
res.append(folder.toFile().getName()).append('\n'); res.append(folder.toFile().getName()).append('\n');
} }
for (var file : files.keySet()) { for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue; if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(_path)) continue; if (!file.startsWith(_path)) continue;
res.append(file.toFile().getName()).append('\n'); 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 @Override
public FileStat stat(String path) { public FileStat stat(String path) {
var _path = getPath(path); var _path = getPath(path);
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER);
else throw new FilesystemException(path, FSCode.DOESNT_EXIST); else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
} }
public MemoryFilesystem put(String path, byte[] data) { public MemoryFilesystem put(String path, byte[] data) {
var _path = getPath(path); var _path = getPath(path);
var _curr = "/"; var _curr = "/";
for (var seg : _path) { for (var seg : _path) {
create(_curr, EntryType.FOLDER); create(_curr, EntryType.FOLDER);
_curr += seg + "/"; _curr += seg + "/";
} }
files.put(_path, new Buffer(data)); files.put(_path, new Buffer(data));
return this; return this;
} }
public MemoryFilesystem(Mode mode) { public MemoryFilesystem(Mode mode) {
this.mode = mode; this.mode = mode;
folders.add(Path.of("/")); folders.add(Path.of("/"));
} }
} }

View File

@ -1,13 +1,13 @@
package me.topchetoeu.jscript.interop; package me.topchetoeu.jscript.interop;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Native { public @interface Native {
public String value() default ""; public String value() default "";
public boolean thisArg() default false; public boolean thisArg() default false;
} }

View File

@ -1,13 +1,13 @@
package me.topchetoeu.jscript.interop; package me.topchetoeu.jscript.interop;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target({ ElementType.METHOD }) @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface NativeGetter { public @interface NativeGetter {
public String value() default ""; public String value() default "";
public boolean thisArg() default false; public boolean thisArg() default false;
} }

View File

@ -1,13 +1,13 @@
package me.topchetoeu.jscript.interop; package me.topchetoeu.jscript.interop;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target({ ElementType.METHOD }) @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface NativeSetter { public @interface NativeSetter {
public String value() default ""; public String value() default "";
public boolean thisArg() default false; public boolean thisArg() default false;
} }

View File

@ -1,127 +1,127 @@
package me.topchetoeu.jscript.interop; package me.topchetoeu.jscript.interop;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeWrapper; import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
public class OverloadFunction extends FunctionValue { public class OverloadFunction extends FunctionValue {
public final List<Overload> overloads = new ArrayList<>(); public final List<Overload> overloads = new ArrayList<>();
public Object call(Context ctx, Object thisArg, Object ...args) { public Object call(Context ctx, Object thisArg, Object ...args) {
loop: for (var overload : overloads) { loop: for (var overload : overloads) {
Object[] newArgs = new Object[overload.params.length]; Object[] newArgs = new Object[overload.params.length];
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class; boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
int start = (consumesEngine ? 1 : 0) + (overload.passThis ? 1 : 0); int start = (consumesEngine ? 1 : 0) + (overload.passThis ? 1 : 0);
int end = overload.params.length - (overload.variadic ? 1 : 0); int end = overload.params.length - (overload.variadic ? 1 : 0);
for (var i = start; i < end; i++) { for (var i = start; i < end; i++) {
Object val; Object val;
if (i - start >= args.length) val = null; if (i - start >= args.length) val = null;
else val = args[i - start]; else val = args[i - start];
try { try {
newArgs[i] = Values.convert(ctx, val, overload.params[i]); newArgs[i] = Values.convert(ctx, val, overload.params[i]);
} }
catch (ConvertException e) { catch (ConvertException e) {
if (overloads.size() > 1) continue loop; if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target));
} }
} }
if (overload.variadic) { if (overload.variadic) {
var type = overload.params[overload.params.length - 1].getComponentType(); var type = overload.params[overload.params.length - 1].getComponentType();
var n = Math.max(args.length - end + start, 0); var n = Math.max(args.length - end + start, 0);
Object varArg = Array.newInstance(type, n); Object varArg = Array.newInstance(type, n);
for (var i = 0; i < n; i++) { for (var i = 0; i < n; i++) {
try { try {
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type)); Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
} }
catch (ConvertException e) { catch (ConvertException e) {
if (overloads.size() > 1) continue loop; if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target));
} }
} }
newArgs[newArgs.length - 1] = varArg; newArgs[newArgs.length - 1] = varArg;
} }
var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg; var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg;
Object _this; Object _this;
try { try {
_this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType); _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
} }
catch (ConvertException e) { catch (ConvertException e) {
if (overloads.size() > 1) continue loop; if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target)); else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target));
} }
if (consumesEngine) newArgs[0] = ctx; if (consumesEngine) newArgs[0] = ctx;
if (overload.passThis) { if (overload.passThis) {
newArgs[consumesEngine ? 1 : 0] = _this; newArgs[consumesEngine ? 1 : 0] = _this;
_this = null; _this = null;
} }
try { try {
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
} }
catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); } catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
catch (IllegalAccessException | IllegalArgumentException e) { continue; } catch (IllegalAccessException | IllegalArgumentException e) { continue; }
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(ctx, 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(ctx, 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();
} }
else { else {
var target = e.getTargetException(); var target = e.getTargetException();
var targetClass = target.getClass(); var targetClass = target.getClass();
var err = new NativeWrapper(e.getTargetException()); var err = new NativeWrapper(e.getTargetException());
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(ctx, name, loc); throw new EngineException(err).add(ctx, name, loc);
} }
} }
catch (ReflectiveOperationException e) { catch (ReflectiveOperationException e) {
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL); throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
} }
} }
throw EngineException.ofType("No overload found for native method."); throw EngineException.ofType("No overload found for native method.");
} }
public OverloadFunction add(Overload overload) { public OverloadFunction add(Overload overload) {
this.overloads.add(overload); this.overloads.add(overload);
return this; return this;
} }
public OverloadFunction(String name) { public OverloadFunction(String name) {
super(name, 0); super(name, 0);
} }
public static OverloadFunction of(String name, Overload overload) { public static OverloadFunction of(String name, Overload overload) {
if (overload == null) return null; if (overload == null) return null;
else return new OverloadFunction(name).add(overload); else return new OverloadFunction(name).add(overload);
} }
} }

View File

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

View File

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

View File

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

View File

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

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) {
return ArrayValue.of(ctx, map return ArrayValue.of(ctx, map
.entrySet() .entrySet()
.stream() .stream()
.map(v -> new ArrayValue(ctx, v.getKey(), v.getValue())) .map(v -> new ArrayValue(ctx, v.getKey(), v.getValue()))
.collect(Collectors.toList()) .collect(Collectors.toList())
); );
} }
@Native public ObjectValue keys(Context ctx) { @Native public ObjectValue keys(Context ctx) {
return ArrayValue.of(ctx, map.keySet()); return ArrayValue.of(ctx, map.keySet());
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
return ArrayValue.of(ctx, map.values()); 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,367 +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, _0, _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, _0, _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;
var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ? var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ?
((NativeWrapper)_thisArg).wrapped : ((NativeWrapper)_thisArg).wrapped :
_thisArg; _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); }
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true; if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true;
return null; return null;
}); });
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle); if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle);
else { else {
Object next; Object next;
try { next = Values.getMember(ctx, thisArg, "then"); } try { next = Values.getMember(ctx, thisArg, "then"); }
catch (IllegalArgumentException e) { next = null; } catch (IllegalArgumentException e) { next = null; }
try { try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle); if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle);
else res.fulfill(ctx, fulfill.call(ctx, null, thisArg)); else res.fulfill(ctx, fulfill.call(ctx, null, thisArg));
} }
catch (EngineException err) { catch (EngineException err) {
res.reject(ctx, fulfill.call(ctx, null, err.value)); res.reject(ctx, fulfill.call(ctx, null, err.value));
} }
} }
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(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) {
return then(ctx, thisArg, null, _onReject); return then(ctx, thisArg, null, _onReject);
} }
/** /**
* 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(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) {
return then(ctx, thisArg, return then(ctx, thisArg,
new NativeFunction(null, (e, th, _args) -> { new NativeFunction(null, (e, th, _args) -> {
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
return _args.length > 0 ? _args[0] : null; return _args.length > 0 ? _args[0] : null;
}), }),
new NativeFunction(null, (e, th, _args) -> { new NativeFunction(null, (e, th, _args) -> {
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
throw new EngineException(_args.length > 0 ? _args[0] : null); 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_PENDING = 0;
private static final int STATE_FULFILLED = 1; private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2; private static final int STATE_REJECTED = 2;
private int state = STATE_PENDING; private int state = STATE_PENDING;
private boolean handled = false; private boolean handled = false;
private Object val; private Object val;
public synchronized void fulfill(Context ctx, Object val) { public synchronized void fulfill(Context ctx, Object val) {
if (this.state != STATE_PENDING) return; if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.fulfill(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; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
); );
else { else {
Object then; Object then;
try { then = Values.getMember(ctx, val, "then"); } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { then = null; } catch (IllegalArgumentException e) { then = null; }
try { try {
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.fulfill(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; }) new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
); );
else { else {
this.val = val; this.val = val;
this.state = STATE_FULFILLED; this.state = STATE_FULFILLED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) { for (var handle : handles) {
handle.fulfilled.call(handle.ctx, null, val); handle.fulfilled.call(handle.ctx, null, val);
} }
handles = null; handles = null;
return null; return null;
}), null); }), null);
} }
} }
catch (EngineException err) { catch (EngineException err) {
this.reject(ctx, err.value); this.reject(ctx, err.value);
} }
} }
} }
public synchronized void reject(Context ctx, Object val) { public synchronized void reject(Context ctx, Object val) {
if (this.state != STATE_PENDING) return; if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, 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; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
); );
else { else {
Object then; Object then;
try { then = Values.getMember(ctx, val, "then"); } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { then = null; } catch (IllegalArgumentException e) { then = null; }
try { try {
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, 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; }) new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
); );
else { else {
this.val = val; this.val = val;
this.state = STATE_REJECTED; this.state = STATE_REJECTED;
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) handle.rejected.call(handle.ctx, null, val); for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (!handled) { if (!handled) {
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
} }
handles = null; handles = null;
return null; return null;
}), null); }), null);
} }
} }
catch (EngineException err) { catch (EngineException err) {
this.reject(ctx, err.value); this.reject(ctx, err.value);
} }
} }
} }
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val);
else if (state == STATE_REJECTED) { else if (state == STATE_REJECTED) {
ctx.engine.pushMsg(true, ctx, reject, null, val); ctx.engine.pushMsg(true, ctx, reject, null, val);
handled = true; handled = true;
} }
else handles.add(new Handle(ctx, fulfill, reject)); else handles.add(new Handle(ctx, fulfill, reject));
} }
@Override @Native public String toString() { @Override @Native public String toString() {
if (state == STATE_PENDING) return "Promise (pending)"; if (state == STATE_PENDING) return "Promise (pending)";
else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
else return "Promise (rejected)"; 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) { @Native public PromiseLib(Context ctx, FunctionValue func) {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
try { try {
func.call( func.call(
ctx, null, ctx, null,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
fulfill(e, args.length > 0 ? args[0] : null); fulfill(e, args.length > 0 ? args[0] : null);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
reject(e, args.length > 0 ? args[0] : null); reject(e, args.length > 0 ? args[0] : null);
return null; return null;
}) })
); );
} }
catch (EngineException e) { catch (EngineException e) {
reject(ctx, e.value); reject(ctx, e.value);
} }
} }
private PromiseLib(int state, Object val) { private PromiseLib(int state, Object val) {
this.state = state; this.state = state;
this.val = val; this.val = val;
} }
public PromiseLib() { public PromiseLib() {
this(STATE_PENDING, null); this(STATE_PENDING, null);
} }
public static PromiseLib await(Context ctx, PromiseRunner runner) { public static PromiseLib await(Context ctx, PromiseRunner runner) {
var res = new PromiseLib(); var res = new PromiseLib();
new Thread(() -> { new Thread(() -> {
try { try {
res.fulfill(ctx, runner.run()); res.fulfill(ctx, runner.run());
} }
catch (EngineException e) { catch (EngineException e) {
res.reject(ctx, e.value); res.reject(ctx, e.value);
} }
}, "Promisifier").start(); }, "Promisifier").start();
return res; return res;
} }
} }

View File

@ -1,60 +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) {
return ArrayValue.of(ctx, 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()));
} }
@Native public ObjectValue keys(Context ctx) { @Native public ObjectValue keys(Context ctx) {
return ArrayValue.of(ctx, set); return ArrayValue.of(ctx, set);
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
return ArrayValue.of(ctx, set); return ArrayValue.of(ctx, set);
} }
@Native public Object add(Object key) { @Native public Object add(Object key) {
return set.add(key); return set.add(key);
} }
@Native public boolean delete(Object key) { @Native public boolean delete(Object key) {
return set.remove(key); return set.remove(key);
} }
@Native public boolean has(Object key) { @Native public boolean has(Object key) {
return set.contains(key); return set.contains(key);
} }
@Native public void clear() { @Native public void clear() {
set.clear(); set.clear();
} }
@NativeGetter public int size() { @NativeGetter public int size() {
return set.size(); return set.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<>(set); var keys = new ArrayList<>(set);
for (var el : keys) func.call(ctx, thisArg, el, el, this); for (var el : keys) func.call(ctx, thisArg, el, el, this);
} }
@Native public SetLib(Context ctx, Object iterable) { @Native public SetLib(Context ctx, Object iterable) {
for (var el : Values.fromJSIterator(ctx, iterable)) add(el); for (var el : Values.fromJSIterator(ctx, iterable)) add(el);
} }
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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