everything all at once

This commit is contained in:
TopchetoEU 2024-08-30 17:36:33 +03:00
parent f09feae08f
commit d0ccf00f14
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
95 changed files with 1976 additions and 2377 deletions

View File

@ -1,5 +1,8 @@
package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
@ -8,18 +11,36 @@ import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
public interface Compiler {
public static final Compiler DEFAULT = (env, filename, raw) -> {
var res = ES5.compile(filename, raw);
var body = res.body();
DebugContext.get(env).onSource(filename, raw);
registerFunc(env, body, res);
return body;
};
public Key<Compiler> KEY = new Key<>();
public FunctionBody compile(Filename filename, String source);
public FunctionBody compile(Environment env, Filename filename, String source);
public static Compiler get(Environment ext) {
return ext.get(KEY, (filename, src) -> {
return ext.get(KEY, (env, filename, src) -> {
throw EngineException.ofError("No compiler attached to engine.");
});
}
public static CodeFunction compile(Environment env, Filename filename, String raw) {
DebugContext.get(env).onSource(filename, raw);
return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]);
private static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
var map = res.map();
DebugContext.get(env).onFunctionLoad(body, map);
for (var i = 0; i < body.children.length; i++) {
registerFunc(env, body.children[i], res.children.get(i));
}
}
public static CodeFunction compileFunc(Environment env, Filename filename, String raw) {
return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new ValueVariable[0]);
}
}

View File

@ -9,48 +9,46 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
NOP(0),
RETURN(1),
THROW(2),
THROW_SYNTAX(3),
DELETE(4),
TRY_START(5),
TRY_END(6),
NOP(0x00),
RETURN(0x01),
THROW(0x02),
THROW_SYNTAX(0x03),
DELETE(0x04),
TRY_START(0x05),
TRY_END(0x06),
CALL(7),
CALL_NEW(8),
JMP_IF(9),
JMP_IFN(10),
JMP(11),
CALL(0x10),
CALL_MEMBER(0x11),
CALL_NEW(0x12),
JMP_IF(0x13),
JMP_IFN(0x14),
JMP(0x15),
PUSH_UNDEFINED(12),
PUSH_NULL(13),
PUSH_BOOL(14),
PUSH_NUMBER(15),
PUSH_STRING(16),
PUSH_UNDEFINED(0x20),
PUSH_NULL(0x21),
PUSH_BOOL(0x22),
PUSH_NUMBER(0x23),
PUSH_STRING(0x24),
DUP(0x25),
DISCARD(0x26),
LOAD_VAR(17),
LOAD_MEMBER(18),
LOAD_GLOB(20),
LOAD_FUNC(0x30),
LOAD_ARR(0x31),
LOAD_OBJ(0x32),
STORE_SELF_FUNC(0x33),
LOAD_REGEX(0x34),
LOAD_FUNC(21),
LOAD_ARR(22),
LOAD_OBJ(23),
STORE_SELF_FUNC(24),
LOAD_REGEX(25),
LOAD_VAR(0x40),
LOAD_MEMBER(0x41),
LOAD_GLOB(0x42),
STORE_VAR(0x43),
STORE_MEMBER(0x44),
DUP(26),
STORE_VAR(27),
STORE_MEMBER(28),
DISCARD(29),
MAKE_VAR(30),
DEF_PROP(31),
KEYS(32),
TYPEOF(33),
OPERATION(34);
MAKE_VAR(0x50),
DEF_PROP(0x51),
KEYS(0x52),
TYPEOF(0x53),
OPERATION(0x54);
private static final HashMap<Integer, Type> types = new HashMap<>();
public final int numeric;
@ -125,8 +123,12 @@ public class Instruction {
writer.writeByte(rawType);
switch (type) {
case CALL: writer.writeInt(get(0)); break;
case CALL_NEW: writer.writeInt(get(0)); break;
case CALL:
case CALL_NEW:
case CALL_MEMBER:
writer.writeInt(get(0));
writer.writeUTF(get(1));
break;
case DUP: writer.writeInt(get(0)); break;
case JMP: writer.writeInt(get(0)); break;
case JMP_IF: writer.writeInt(get(0)); break;
@ -140,6 +142,7 @@ public class Instruction {
}
writer.writeInt(get(0));
writer.writeUTF(get(0));
break;
}
case LOAD_REGEX: writer.writeUTF(get(0)); break;
@ -174,8 +177,9 @@ public class Instruction {
var flag = (rawType & 128) != 0;
switch (type) {
case CALL: return call(stream.readInt());
case CALL_NEW: return callNew(stream.readInt());
case CALL: return call(stream.readInt(), stream.readUTF());
case CALL_NEW: return callNew(stream.readInt(), stream.readUTF());
case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF());
case DEF_PROP: return defProp();
case DELETE: return delete();
case DISCARD: return discard();
@ -192,7 +196,7 @@ public class Instruction {
captures[i] = stream.readInt();
}
return loadFunc(stream.readInt(), captures);
return loadFunc(stream.readInt(), stream.readUTF(), captures);
}
case LOAD_GLOB: return loadGlob();
case LOAD_MEMBER: return loadMember();
@ -251,11 +255,23 @@ public class Instruction {
return new Instruction(Type.NOP, params);
}
public static Instruction call(int argn, String name) {
return new Instruction(Type.CALL, argn, name);
}
public static Instruction call(int argn) {
return new Instruction(Type.CALL, argn);
return call(argn, "");
}
public static Instruction callMember(int argn, String name) {
return new Instruction(Type.CALL_MEMBER, argn, name);
}
public static Instruction callMember(int argn) {
return new Instruction(Type.CALL_MEMBER, argn, "");
}
public static Instruction callNew(int argn, String name) {
return new Instruction(Type.CALL_NEW, argn, name);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn);
return new Instruction(Type.CALL_NEW, argn, "");
}
public static Instruction jmp(int offset) {
return new Instruction(Type.JMP, offset);
@ -299,10 +315,13 @@ public class Instruction {
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(int id, int[] captures) {
var args = new Object[1 + captures.length];
public static Instruction loadFunc(int id, String name, int[] captures) {
if (name == null) name = "";
var args = new Object[2 + captures.length];
args[0] = id;
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
args[1] = name;
for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i];
return new Instruction(Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {

View File

@ -1,23 +0,0 @@
package me.topchetoeu.jscript.common;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class RefTracker {
public static void onDestroy(Object obj, Runnable runnable) {
var queue = new ReferenceQueue<>();
var ref = new WeakReference<>(obj, queue);
obj = null;
var th = new Thread(() -> {
try {
queue.remove();
ref.get();
runnable.run();
}
catch (InterruptedException e) { return; }
});
th.setDaemon(true);
th.start();
}
}

View File

@ -1,5 +0,0 @@
package me.topchetoeu.jscript.common;
public interface ResultRunnable<T> {
T run();
}

View File

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

View File

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

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.common.json;
import java.math.BigDecimal;
import java.util.Map;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class JSON {
@ -16,23 +17,9 @@ public class JSON {
return ParseRes.res(JSONElement.string(res.result), res.n);
}
public static ParseRes<JSONElement> parseNumber(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (src.is(i + n, "-")) {
n++;
var res = Parsing.parseNumber(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a number after minus");
n += res.n;
return ParseRes.res(JSONElement.number(-res.result), n);
}
else {
var res = Parsing.parseNumber(src, i + n).addN(n);
if (!res.isSuccess()) return res.chainError();
n += res.n;
return ParseRes.res(JSONElement.number(res.result), n);
}
var res = Parsing.parseNumber(src, i, true);
if (!res.isSuccess()) return res.chainError();
else return ParseRes.res(JSONElement.number(res.result), res.n);
}
public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
var id = Parsing.parseIdentifier(src, i);
@ -121,7 +108,13 @@ public class JSON {
}
public static String stringify(JSONElement el) {
if (el.isNumber()) return Double.toString(el.number());
if (el.isNumber()) {
var d = el.number();
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
}
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) {

View File

@ -11,9 +11,9 @@ import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
public class FunctionMap {

View File

@ -1,4 +1,4 @@
package me.topchetoeu.jscript.common;
package me.topchetoeu.jscript.common.parsing;
import java.io.File;
import java.nio.file.Path;

View File

@ -1,4 +1,4 @@
package me.topchetoeu.jscript.common;
package me.topchetoeu.jscript.common.parsing;
import java.util.ArrayList;
import java.util.Objects;

View File

@ -1,6 +1,4 @@
package me.topchetoeu.jscript.compilation.parsing;
import me.topchetoeu.jscript.common.Location;
package me.topchetoeu.jscript.common.parsing;
public class ParseRes<T> {
public static interface Parser<T> {
@ -41,11 +39,6 @@ public class ParseRes<T> {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed.");
return new ParseRes<>(state, error, null, 0);
}
public TestRes toTest() {
if (isSuccess()) return TestRes.res(n);
else if (isError()) return TestRes.error(null, error);
else return TestRes.failed();
}
public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); }

View File

@ -0,0 +1,383 @@
package me.topchetoeu.jscript.common.parsing;
import me.topchetoeu.jscript.compilation.values.constants.NumberStatement;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
// TODO: this has to be rewritten
// @SourceFile
public class Parsing {
public static boolean isDigit(Character c) {
return c != null && c >= '0' && c <= '9';
}
public static boolean isAny(char c, String alphabet) {
return alphabet.contains(Character.toString(c));
}
public static int fromHex(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0';
return -1;
}
public static int skipEmpty(Source src, int i) {
int n = 0;
while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++;
return n;
}
public static ParseRes<Character> parseChar(Source src, int i) {
int n = 0;
if (src.is(i + n, '\\')) {
n++;
char c = src.at(i + n++);
if (c == 'b') return ParseRes.res('\b', n);
else if (c == 't') return ParseRes.res('\t', n);
else if (c == 'n') return ParseRes.res('\n', n);
else if (c == 'f') return ParseRes.res('\f', n);
else if (c == 'r') return ParseRes.res('\r', n);
else if (c == '0') {
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else return ParseRes.res('\0', n);
}
else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else if (c == 'x') {
var newC = 0;
for (var j = 0; j < 2; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence.");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence.");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == 'u') {
var newC = 0;
for (var j = 0; j < 4; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == '\n') return ParseRes.res(null, n);
}
return ParseRes.res(src.at(i + n), n + 1);
}
public static ParseRes<String> parseIdentifier(Source src, int i) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
res.append(c);
n++;
first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else return ParseRes.res(res.toString(), n);
}
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
res.append(c);
n++;
first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
else return ParseRes.failed();
}
public static boolean isIdentifier(Source src, int i, String test) {
return parseIdentifier(src, i, test).isSuccess();
}
public static ParseRes<String> parseOperator(Source src, int i, String op) {
var n = skipEmpty(src, i);
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
else return ParseRes.failed();
}
private static ParseRes<Double> parseHex(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = Parsing.fromHex(src.at(i + n, '\0'));
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 16;
res += digit;
}
}
private static ParseRes<Double> parseOct(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = src.at(i + n, '\0') - '0';
if (digit < 0 || digit > 9) break;
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 8;
res += digit;
}
return ParseRes.res(res, n);
}
public static ParseRes<String> parseString(Source src, int i) {
var n = skipEmpty(src, i);
char quote;
if (src.is(i + n, '\'')) quote = '\'';
else if (src.is(i + n, '"')) quote = '"';
else return ParseRes.failed();
n++;
var res = new StringBuilder();
while (true) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
if (src.is(i + n, quote)) {
n++;
return ParseRes.res(res.toString(), n);
}
var charRes = parseChar(src, i + n);
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
n += charRes.n;
if (charRes.result != null) res.append(charRes.result);
}
}
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
var n = skipEmpty(src, i);
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
n += 2;
var res = parseOct(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
else if (src.is(i + n, '0')) {
n++;
parsedAny = true;
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
}
while (src.is(i + n, Parsing::isDigit)) {
parsedAny = true;
whole *= 10;
whole += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true;
n++;
while (src.is(i + n, Parsing::isDigit)) {
fract += src.at(i + n++) - '0';
fract /= 10;
}
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
parsedAny = true;
boolean expNegative = false;
boolean parsedE = false;
if (src.is(i + n, '-')) {
expNegative = true;
n++;
}
else if (src.is(i + n, '+')) n++;
while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
exponent *= 10;
if (expNegative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-(whole + fract) * NumberStatement.power(10, exponent), n);
else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n);
}
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
var n = skipEmpty(src, i);
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
while (src.is(i + n, Parsing::isDigit)) {
parsedAny = true;
whole *= 10;
whole += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true;
n++;
while (src.is(i + n, Parsing::isDigit)) {
fract += src.at(i + n++) - '0';
fract /= 10;
}
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
parsedAny = true;
boolean expNegative = false;
boolean parsedE = false;
if (src.is(i + n, '-')) {
expNegative = true;
n++;
}
else if (src.is(i + n, '+')) n++;
while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
exponent *= 10;
if (expNegative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-(whole + fract) * NumberStatement.power(10, exponent), n);
else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n);
}
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
var n = skipEmpty(src, i);
double result = 0;
boolean parsedAny = false;
boolean negative = false;
if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
while (true) {
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n)));
if (digit < 0) break;
parsedAny = true;
result += digit;
result *= alphabet.length();
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-result, n);
else return ParseRes.res(-result, n);
}
}

View File

@ -1,10 +1,7 @@
package me.topchetoeu.jscript.compilation.parsing;
package me.topchetoeu.jscript.common.parsing;
import java.util.function.Predicate;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
public class Source {
public final Filename filename;
public final String src;

View File

@ -1,7 +1,4 @@
package me.topchetoeu.jscript.compilation.parsing;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location;
package me.topchetoeu.jscript.common.parsing;
public class SourceLocation extends Location {
private int[] lineStarts;

View File

@ -1,11 +1,7 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.parsing.Operator;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.common.parsing.Location;
public abstract class AssignableStatement extends Statement {
public abstract Statement toAssign(Statement val, Operation operation);
@ -13,38 +9,53 @@ public abstract class AssignableStatement extends Statement {
protected AssignableStatement(Location loc) {
super(loc);
}
// private static final Map<String, Operation> operations = Map.ofEntries(
// Map.entry("*=", Operation.MULTIPLY),
// Map.entry("/=", Operation.DIVIDE),
// Map.entry("%=", Operation.MODULO),
// Map.entry("-=", Operation.SUBTRACT),
// Map.entry("+=", Operation.ADD),
// Map.entry(">>=", Operation.SHIFT_RIGHT),
// Map.entry("<<=", Operation.SHIFT_LEFT),
// Map.entry(">>>=", Operation.USHIFT_RIGHT),
// Map.entry("&=", Operation.AND),
// Map.entry("^=", Operation.XOR),
// Map.entry("|=", Operation.OR)
// );
// private static final List<String> operatorsByLength = operations.keySet().stream().sorted().collect(Collectors.toList());
public static ParseRes<? extends Statement> parse(Source src, int i, Statement prev, int precedence) {
if (precedence > 2) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
// public static ParseRes<? extends Statement> parse(Source src, int i, Statement prev, int precedence) {
// if (precedence > 2) return ParseRes.failed();
for (var op : Operator.opsByLength) {
if (!op.assignable || !src.is(i + n, op.readable + "=")) continue;
n += op.readable.length() + 1;
// var n = Parsing.skipEmpty(src, i);
if (!(prev instanceof AssignableStatement)) {
return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator");
}
// for (var op : operatorsByLength) {
// if (!src.is(i + n, op)) continue;
// n += op.length() + 1;
var res = Parsing.parseValue(src, i + n, 2);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s=' operator", op.readable));
n += res.n;
// if (!(prev instanceof AssignableStatement)) {
// return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator");
// }
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, op.operation), n);
}
// var res = Parsing.parseValue(src, i + n, 2);
// if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s=' operator", op));
// n += res.n;
if (!src.is(i + n, "=")) return ParseRes.failed();
n++;
// return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operations.get(op)), n);
// }
if (!(prev instanceof AssignableStatement)) {
return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator");
}
// if (!src.is(i + n, "=")) return ParseRes.failed();
// n++;
var res = Parsing.parseValue(src, i + n, 2);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '=' operator");
n += res.n;
// if (!(prev instanceof AssignableStatement)) {
// return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator");
// }
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, null), n);
}
// var res = Parsing.parseValue(src, i + n, 2);
// if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '=' operator");
// n += res.n;
// return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, null), n);
// }
}

View File

@ -6,10 +6,10 @@ import java.util.Vector;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
public class CompileResult {

View File

@ -5,11 +5,11 @@ import java.util.List;
import java.util.Vector;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
public class CompoundStatement extends Statement {
@ -75,7 +75,7 @@ public class CompoundStatement extends Statement {
if (!src.is(i + n, ",")) return ParseRes.failed();
n++;
var res = Parsing.parseValue(src, i + n, 2);
var res = ES5.parseExpression(src, i + n, 2);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the comma");
n += res.n;
@ -102,7 +102,7 @@ public class CompoundStatement extends Statement {
continue;
}
var res = Parsing.parseStatement(src, i + n);
var res = ES5.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;

View File

@ -0,0 +1,302 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.control.BreakStatement;
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
import me.topchetoeu.jscript.compilation.control.DebugStatement;
import me.topchetoeu.jscript.compilation.control.DeleteStatement;
import me.topchetoeu.jscript.compilation.control.DoWhileStatement;
import me.topchetoeu.jscript.compilation.control.ForInStatement;
import me.topchetoeu.jscript.compilation.control.ForOfStatement;
import me.topchetoeu.jscript.compilation.control.ForStatement;
import me.topchetoeu.jscript.compilation.control.IfStatement;
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
import me.topchetoeu.jscript.compilation.control.SwitchStatement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.compilation.control.TryStatement;
import me.topchetoeu.jscript.compilation.control.WhileStatement;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.compilation.values.ArrayStatement;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.compilation.values.GlobalThisStatement;
import me.topchetoeu.jscript.compilation.values.ObjectStatement;
import me.topchetoeu.jscript.compilation.values.RegexStatement;
import me.topchetoeu.jscript.compilation.values.VariableStatement;
import me.topchetoeu.jscript.compilation.values.constants.BoolStatement;
import me.topchetoeu.jscript.compilation.values.constants.NullStatement;
import me.topchetoeu.jscript.compilation.values.constants.NumberStatement;
import me.topchetoeu.jscript.compilation.values.constants.StringStatement;
import me.topchetoeu.jscript.compilation.values.operations.CallStatement;
import me.topchetoeu.jscript.compilation.values.operations.ChangeStatement;
import me.topchetoeu.jscript.compilation.values.operations.DiscardStatement;
import me.topchetoeu.jscript.compilation.values.operations.IndexStatement;
import me.topchetoeu.jscript.compilation.values.operations.OperationStatement;
import me.topchetoeu.jscript.compilation.values.operations.TypeofStatement;
import me.topchetoeu.jscript.compilation.values.operations.VariableIndexStatement;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class ES5 {
static final Set<String> reserved = Set.of(
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
"finally", "for", "do", "while", "switch", "case", "default", "new",
"function", "var", "return", "throw", "typeof", "delete", "break",
"continue", "debugger", "implements", "interface", "package", "private",
"protected", "public", "static"
);
public static ParseRes<? extends Statement> parseParens(Source src, int i) {
int n = 0;
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError();
n += openParen.n;
var res = ES5.parseExpression(src, i + n, 0);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
n += res.n;
var closeParen = Parsing.parseOperator(src, i + n, ")");
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
n += closeParen.n;
return ParseRes.res(res.result, n);
}
public static ParseRes<? extends Statement> parseSimple(Source src, int i, boolean statement) {
return ParseRes.first(src, i,
(a, b) -> statement ? ParseRes.failed() : ObjectStatement.parse(a, b),
(a, b) -> statement ? ParseRes.failed() : FunctionStatement.parseFunction(a, b, false),
ES5::parseLiteral,
StringStatement::parse,
RegexStatement::parse,
NumberStatement::parse,
ChangeStatement::parsePrefixDecrease,
ChangeStatement::parsePrefixIncrease,
OperationStatement::parsePrefix,
ArrayStatement::parse,
ES5::parseParens,
CallStatement::parseNew,
TypeofStatement::parse,
DiscardStatement::parse,
DeleteStatement::parse,
VariableStatement::parse
);
}
public static ParseRes<? extends Statement> parseLiteral(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var id = Parsing.parseIdentifier(src, i);
if (!id.isSuccess()) return id.chainError();
n += id.n;
if (id.result.equals("true")) return ParseRes.res(new BoolStatement(loc, true), n);
if (id.result.equals("false")) return ParseRes.res(new BoolStatement(loc, false), n);
if (id.result.equals("undefined")) return ParseRes.res(new DiscardStatement(loc, null), n);
if (id.result.equals("null")) return ParseRes.res(new NullStatement(loc), n);
if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), n);
if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), n);
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), n);
return ParseRes.failed();
}
public static ParseRes<? extends Statement> parseExpression(Source src, int i, int precedence, boolean statement) {
var n = Parsing.skipEmpty(src, i);
Statement prev = null;
while (true) {
if (prev == null) {
var res = parseSimple(src, i + n, statement);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
}
else if (res.isError()) return res.chainError();
else break;
}
else {
var _prev = prev;
ParseRes<Statement> res = ParseRes.first(src, i + n,
(s, j) -> OperationStatement.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationStatement.parseIn(s, j, _prev, precedence),
(s, j) -> ChangeStatement.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> ChangeStatement.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> OperationStatement.parseOperator(s, j, _prev, precedence),
(s, j) -> IfStatement.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexStatement.parseMember(s, j, _prev, precedence),
(s, j) -> IndexStatement.parseIndex(s, j, _prev, precedence),
(s, j) -> CallStatement.parseCall(s, j, _prev, precedence),
(s, j) -> CompoundStatement.parseComma(s, j, _prev, precedence)
);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
continue;
}
else if (res.isError()) return res.chainError();
break;
}
}
if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n);
}
public static ParseRes<? extends Statement> parseExpression(Source src, int i, int precedence) {
return parseExpression(src, i, precedence, false);
}
public static ParseRes<? extends Statement> parseExpressionStatement(Source src, int i) {
var res = parseExpression(src, i, 0, true);
if (!res.isSuccess()) return res.chainError();
var end = ES5.parseStatementEnd(src, i + res.n);
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
return res.addN(end.n);
}
public static ParseRes<? extends Statement> parseStatement(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (src.is(i + n, ";")) return ParseRes.res(new DiscardStatement(src.loc(i+ n), null), n + 1);
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
ParseRes<? extends Statement> res = ParseRes.first(src, i + n,
VariableDeclareStatement::parse,
ReturnStatement::parse,
ThrowStatement::parse,
ContinueStatement::parse,
BreakStatement::parse,
DebugStatement::parse,
IfStatement::parse,
WhileStatement::parse,
SwitchStatement::parse,
ForStatement::parse,
ForInStatement::parse,
ForOfStatement::parse,
DoWhileStatement::parse,
TryStatement::parse,
CompoundStatement::parse,
(s, j) -> FunctionStatement.parseFunction(s, j, true),
ES5::parseExpressionStatement
);
return res.addN(n);
}
public static Statement[] parse(Filename filename, String raw) {
var src = new Source(filename, raw);
var list = new ArrayList<Statement>();
int i = 0;
while (true) {
if (i >= src.size()) break;
var res = parseStatement(src, i);
if (res.isError()) throw new SyntaxException(src.loc(i), res.error);
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
i += res.n;
list.add(res.result);
}
return list.toArray(Statement[]::new);
}
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (i >= src.size()) return ParseRes.res(true, n + 1);
for (var j = i; j < i + n; j++) {
if (src.is(j, '\n')) return ParseRes.res(true, n);
}
if (src.is(i + n, ';')) return ParseRes.res(true, n + 1);
if (src.is(i + n, '}')) return ParseRes.res(true, n);
return ParseRes.failed();
}
public static boolean checkVarName(String name) {
return !ES5.reserved.contains(name);
}
public static ParseRes<List<String>> parseParamList(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list.");
n += openParen.n;
var args = new ArrayList<String>();
var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
var argRes = Parsing.parseIdentifier(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
n += argRes.n;
n += Parsing.skipEmpty(src, i);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
}
else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace.");
}
}
return ParseRes.res(args, n);
}
public static CompileResult compile(Statement ...statements) {
var target = new CompileResult(new LocalScopeRecord());
var stm = new CompoundStatement(null, true, statements);
target.scope.define("this");
target.scope.define("arguments");
try {
stm.compile(target, true);
FunctionStatement.checkBreakAndCont(target, 0);
}
catch (SyntaxException e) {
target = new CompileResult(new LocalScopeRecord());
target.scope.define("this");
target.scope.define("arguments");
target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
}
target.add(Instruction.ret()).setLocation(stm.loc());
return target;
}
public static CompileResult compile(Filename filename, String raw) {
return ES5.compile(ES5.parse(filename, raw));
}
}

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
public abstract class Statement {
private Location _loc;

View File

@ -4,11 +4,11 @@ import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
public class VariableDeclareStatement extends Statement {
@ -63,7 +63,7 @@ public class VariableDeclareStatement extends Statement {
var res = new ArrayList<Pair>();
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new VariableDeclareStatement(loc, res), n);
@ -75,7 +75,7 @@ public class VariableDeclareStatement extends Statement {
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
n += name.n;
if (!Parsing.checkVarName(name.result)) {
if (!ES5.checkVarName(name.result)) {
return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result));
}
@ -85,7 +85,7 @@ public class VariableDeclareStatement extends Statement {
if (src.is(i + n, "=")) {
n++;
var valRes = Parsing.parseValue(src, i + n, 2);
var valRes = ES5.parseExpression(src, i + n, 2);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
n += valRes.n;
@ -100,7 +100,7 @@ public class VariableDeclareStatement extends Statement {
continue;
}
end = Parsing.parseStatementEnd(src, i + n);
end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class BreakStatement extends Statement {
public final String label;
@ -28,7 +29,7 @@ public class BreakStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
n += 5;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new BreakStatement(loc, null), n);
@ -38,7 +39,7 @@ public class BreakStatement extends Statement {
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += label.n;
end = Parsing.parseStatementEnd(src, i + n);
end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new BreakStatement(loc, label.result), n);

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ContinueStatement extends Statement {
public final String label;
@ -28,7 +29,7 @@ public class ContinueStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
n += 8;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ContinueStatement(loc, null), n);
@ -38,7 +39,7 @@ public class ContinueStatement extends Statement {
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += label.n;
end = Parsing.parseStatementEnd(src, i + n);
end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ContinueStatement(loc, label.result), n);

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class DebugStatement extends Statement {
@Override public void compile(CompileResult target, boolean pollute) {
@ -25,7 +26,7 @@ public class DebugStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
n += 8;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new DebugStatement(loc), n);

View File

@ -1,15 +1,16 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.IndexStatement;
import me.topchetoeu.jscript.compilation.values.VariableStatement;
import me.topchetoeu.jscript.compilation.values.constants.BoolStatement;
import me.topchetoeu.jscript.compilation.values.operations.IndexStatement;
public class DeleteStatement extends Statement {
public final Statement key;
@ -31,7 +32,7 @@ public class DeleteStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
n += 6;
var valRes = Parsing.parseValue(src, i + n, 15);
var valRes = ES5.parseExpression(src, i + n, 15);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
n += valRes.n;

View File

@ -1,13 +1,14 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class DoWhileStatement extends Statement {
public final Statement condition, body;
@ -47,7 +48,7 @@ public class DoWhileStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
n += 2;
var bodyRes = Parsing.parseStatement(src, i + n);
var bodyRes = ES5.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
n += bodyRes.n;
@ -58,7 +59,7 @@ public class DoWhileStatement extends Statement {
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
n++;
var condRes = Parsing.parseValue(src, i + n, 0);
var condRes = ES5.parseExpression(src, i + n, 0);
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
@ -66,7 +67,7 @@ public class DoWhileStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition.");
n++;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n);

View File

@ -1,14 +1,15 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ForInStatement extends Statement {
public final String varName;
@ -90,7 +91,7 @@ public class ForInStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
n += 2;
var obj = Parsing.parseValue(src, i + n, 0);
var obj = ES5.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
@ -98,7 +99,7 @@ public class ForInStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = Parsing.parseStatement(src, i + n);
var bodyRes = ES5.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
n += bodyRes.n;

View File

@ -1,13 +1,14 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ForOfStatement extends Statement {
public final String varName;
@ -102,7 +103,7 @@ public class ForOfStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "fo")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration");
n += 2;
var obj = Parsing.parseValue(src, i + n, 0);
var obj = ES5.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
@ -110,7 +111,7 @@ public class ForOfStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = Parsing.parseStatement(src, i + n);
var bodyRes = ES5.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body");
n += bodyRes.n;

View File

@ -1,15 +1,16 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.VariableDeclareStatement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.DiscardStatement;
import me.topchetoeu.jscript.compilation.values.operations.DiscardStatement;
public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body;
@ -57,7 +58,7 @@ public class ForStatement extends Statement {
private static ParseRes<Statement> parseCondition(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var res = Parsing.parseValue(src, i + n, 0);
var res = ES5.parseExpression(src, i + n, 0);
if (!res.isSuccess()) return res.chainError();
n += res.n;
n += Parsing.skipEmpty(src, i + n);
@ -66,7 +67,7 @@ public class ForStatement extends Statement {
else return ParseRes.res(res.result, n + 1);
}
private static ParseRes<? extends Statement> parseUpdater(Source src, int i) {
return Parsing.parseValue(src, i, 0);
return ES5.parseExpression(src, i, 0);
}
public static ParseRes<ForStatement> parse(Source src, int i) {
@ -107,7 +108,7 @@ public class ForStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
n++;
var body = Parsing.parseStatement(src, i + n);
var body = ES5.parseStatement(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
n += body.n;

View File

@ -1,13 +1,14 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class IfStatement extends Statement {
public final Statement condition, body, elseBody;
@ -58,7 +59,7 @@ public class IfStatement extends Statement {
var loc = src.loc(i + n);
n++;
var a = Parsing.parseValue(src, i + n, 2);
var a = ES5.parseExpression(src, i + n, 2);
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
n += a.n;
n += Parsing.skipEmpty(src, i);
@ -66,7 +67,7 @@ public class IfStatement extends Statement {
if (!src.is(i + n, ":")) return ParseRes.failed();
n++;
var b = Parsing.parseValue(src, i + n, 2);
var b = ES5.parseExpression(src, i + n, 2);
if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator.");
n += b.n;
@ -83,7 +84,7 @@ public class IfStatement extends Statement {
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
n++;
var condRes = Parsing.parseValue(src, i + n, 0);
var condRes = ES5.parseExpression(src, i + n, 0);
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
@ -91,7 +92,7 @@ public class IfStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition.");
n++;
var res = Parsing.parseStatement(src, i + n);
var res = ES5.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
n += res.n;
@ -99,7 +100,7 @@ public class IfStatement extends Statement {
if (!elseKw.isSuccess()) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n);
n += elseKw.n;
var elseRes = Parsing.parseStatement(src, i + n);
var elseRes = ES5.parseStatement(src, i + n);
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
n += elseRes.n;

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ReturnStatement extends Statement {
public final Statement value;
@ -30,21 +31,21 @@ public class ReturnStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
n += 6;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ReturnStatement(loc, null), n);
}
var val = Parsing.parseValue(src, i + n, 0);
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
var val = ES5.parseExpression(src, i + n, 0);
if (val.isError()) return val.chainError();
n += val.n;
end = Parsing.parseStatementEnd(src, i + n);
end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ReturnStatement(loc, val.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
else return end.chainError(src.loc(i + n), "Expected end of statement or a return value");
}
}

View File

@ -4,15 +4,16 @@ import java.util.ArrayList;
import java.util.HashMap;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class SwitchStatement extends Statement {
public static class SwitchCase {
@ -89,7 +90,7 @@ public class SwitchStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
n += 4;
var valRes = Parsing.parseValue(src, i + n, 0);
var valRes = ES5.parseExpression(src, i + n, 0);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'case'");
n += valRes.n;
@ -121,7 +122,7 @@ public class SwitchStatement extends Statement {
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
n++;
var valRes = Parsing.parseValue(src, i + n, 0);
var valRes = ES5.parseExpression(src, i + n, 0);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a switch value");
n += valRes.n;
n += Parsing.skipEmpty(src, i + n);
@ -166,7 +167,7 @@ public class SwitchStatement extends Statement {
}
if (caseRes.isError()) return caseRes.chainError();
var stm = Parsing.parseStatement(src, i + n);
var stm = ES5.parseStatement(src, i + n);
if (stm.isSuccess()) {
n += stm.n;
statements.add(stm.result);

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ThrowStatement extends Statement {
public final Statement value;
@ -29,17 +30,17 @@ public class ThrowStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
n += 5;
var end = Parsing.parseStatementEnd(src, i + n);
var end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ThrowStatement(loc, null), n);
}
var val = Parsing.parseValue(src, i + n, 0);
var val = ES5.parseExpression(src, i + n, 0);
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value");
n += val.n;
end = Parsing.parseStatementEnd(src, i + n);
end = ES5.parseStatementEnd(src, i + n);
if (end.isSuccess()) {
n += end.n;
return ParseRes.res(new ThrowStatement(loc, val.result), n);

View File

@ -1,14 +1,14 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class TryStatement extends Statement {
public final Statement tryBody;

View File

@ -1,14 +1,15 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class WhileStatement extends Statement {
public final Statement condition, body;
@ -82,7 +83,7 @@ public class WhileStatement extends Statement {
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
n++;
var condRes = Parsing.parseValue(src, i + n, 0);
var condRes = ES5.parseExpression(src, i + n, 0);
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a while condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
@ -90,7 +91,7 @@ public class WhileStatement extends Statement {
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition.");
n++;
var res = Parsing.parseStatement(src, i + n);
var res = ES5.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a while body.");
n += res.n;

View File

@ -1,48 +0,0 @@
package me.topchetoeu.jscript.compilation.parsing;
import java.util.Arrays;
import java.util.LinkedHashSet;
import me.topchetoeu.jscript.common.Operation;
public enum Operator {
MULTIPLY("*", Operation.MULTIPLY, 13, true),
DIVIDE("/", Operation.DIVIDE, 12, true),
MODULO("%", Operation.MODULO, 12, true),
SUBTRACT("-", Operation.SUBTRACT, 11, true),
ADD("+", Operation.ADD, 11, true),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10, true),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10, true),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10, true),
GREATER(">", Operation.GREATER, 9, false),
LESS("<", Operation.LESS, 9, false),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9, false),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9, false),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8, false),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8, false),
EQUALS("==", Operation.LOOSE_EQUALS, 8, false),
LOOSE_EQUALS("===", Operation.EQUALS, 8, false),
AND("&", Operation.AND, 7, true),
XOR("^", Operation.XOR, 6, true),
OR("|", Operation.OR, 5, true);
public final String readable;
public final Operation operation;
public final int precedence;
public final boolean assignable;
public static final LinkedHashSet<Operator> opsByLength = new LinkedHashSet<Operator>();
static {
var vals = Operator.values();
Arrays.sort(vals, (a, b) -> b.readable.length() - a.readable.length());
for (var el : vals) opsByLength.add(el);
}
private Operator(String value, Operation funcName, int precedence, boolean assignable) {
this.readable = value;
this.operation = funcName;
this.precedence = precedence;
this.assignable = assignable;
}
}

View File

@ -1,539 +0,0 @@
package me.topchetoeu.jscript.compilation.parsing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord;
import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.compilation.values.constants.BoolStatement;
import me.topchetoeu.jscript.compilation.values.constants.NullStatement;
import me.topchetoeu.jscript.compilation.values.constants.NumberStatement;
import me.topchetoeu.jscript.compilation.values.constants.StringStatement;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
// TODO: this has to be rewritten
// @SourceFile
public class Parsing {
public static final HashMap<Long, ArrayList<Instruction>> functions = new HashMap<>();
private static final Set<String> reserved = Set.of(
"true", "false", "void", "null", "this", "if", "else", "try", "catch",
"finally", "for", "do", "while", "switch", "case", "default", "new",
"function", "var", "return", "throw", "typeof", "delete", "break",
"continue", "debugger", "implements", "interface", "package", "private",
"protected", "public", "static",
// Although ES5 allow these, we will comply to ES6 here
"const", "let",
// These are allowed too, however our parser considers them keywords
// We allow yield and await, because they're part of the custom async and generator functions
"undefined", "arguments", "globalThis", "window", "self"
);
public static boolean isDigit(Character c) {
return c != null && c >= '0' && c <= '9';
}
public static boolean isAny(char c, String alphabet) {
return alphabet.contains(Character.toString(c));
}
public static int fromHex(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0';
return -1;
}
public static int skipEmpty(Source src, int i) {
int n = 0;
while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++;
return n;
}
public static ParseRes<Character> parseChar(Source src, int i) {
int n = 0;
if (src.is(i + n, '\\')) {
n++;
char c = src.at(i + n++);
if (c == 'b') return ParseRes.res('\b', n);
else if (c == 't') return ParseRes.res('\t', n);
else if (c == 'n') return ParseRes.res('\n', n);
else if (c == 'f') return ParseRes.res('\f', n);
else if (c == 'r') return ParseRes.res('\r', n);
else if (c == '0') {
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else return ParseRes.res('\0', n);
}
else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else if (c == 'x') {
var newC = 0;
for (var j = 0; j < 2; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence.");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence.");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == 'u') {
var newC = 0;
for (var j = 0; j < 4; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
n++;
newC = (newC << 4) | val;
}
return ParseRes.res((char)newC, n);
}
else if (c == '\n') return ParseRes.res(null, n);
}
return ParseRes.res(src.at(i + n), n + 1);
}
public static ParseRes<String> parseIdentifier(Source src, int i) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = false;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
if (!first && !Character.isLetter(c) && c != '_' && c != '$') break;
res.append(c);
n++;
}
if (res.length() <= 0) return ParseRes.failed();
else return ParseRes.res(res.toString(), n);
}
public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
var n = skipEmpty(src, i);
var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
if (!first && !Character.isLetter(c) && c != '_' && c != '$') break;
first = false;
res.append(c);
n++;
}
if (res.length() <= 0) return ParseRes.failed();
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
else return ParseRes.failed();
}
public static boolean isIdentifier(Source src, int i, String test) {
return parseIdentifier(src, i, test).isSuccess();
}
public static ParseRes<String> parseOperator(Source src, int i, String op) {
var n = skipEmpty(src, i);
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
else return ParseRes.failed();
}
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
var n = skipEmpty(src, i);
if (i >= src.size()) return ParseRes.res(true, n + 1);
for (var j = i; j < i + n; j++) {
if (src.is(j, '\n')) return ParseRes.res(true, n);
}
if (src.is(i + n, ';')) return ParseRes.res(true, n + 1);
if (src.is(i + n, '}')) return ParseRes.res(true, n);
return ParseRes.failed();
}
public static boolean checkVarName(String name) {
return !reserved.contains(name);
}
public static ParseRes<List<String>> parseParamList(Source src, int i) {
var n = skipEmpty(src, i);
var openParen = parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list.");
n += openParen.n;
var args = new ArrayList<String>();
var closeParen = parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
var argRes = parseIdentifier(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
n++;
n += skipEmpty(src, i);
if (src.is(i + n, ",")) {
n++;
n += skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
}
else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace.");
}
}
return ParseRes.res(args, n);
}
public static ParseRes<? extends Statement> parseParens(Source src, int i) {
int n = 0;
var openParen = parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError();
n += openParen.n;
var res = parseValue(src, i + n, 0);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
n += res.n;
var closeParen = parseOperator(src, i + n, ")");
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
n += closeParen.n;
return ParseRes.res(res.result, n);
}
public static ParseRes<? extends Statement> parseSimple(Source src, int i, boolean statement) {
return ParseRes.first(src, i,
(a, b) -> statement ? ParseRes.failed() : ObjectStatement.parse(a, b),
(a, b) -> statement ? ParseRes.failed() : FunctionStatement.parseFunction(a, b, false),
VariableStatement::parse,
Parsing::parseLiteral,
StringStatement::parse,
RegexStatement::parse,
NumberStatement::parse,
ChangeStatement::parsePrefixDecrease,
ChangeStatement::parsePrefixIncrease,
OperationStatement::parsePrefix,
ArrayStatement::parse,
Parsing::parseParens,
CallStatement::parseNew,
TypeofStatement::parse,
DiscardStatement::parse,
DeleteStatement::parse
);
}
public static ParseRes<? extends Statement> parseLiteral(Source src, int i) {
var n = skipEmpty(src, i);
var loc = src.loc(i + n);
var id = parseIdentifier(src, i);
if (!id.isSuccess()) return id.chainError();
n += id.n;
if (id.result.equals("true")) return ParseRes.res(new BoolStatement(loc, true), n);
if (id.result.equals("false")) return ParseRes.res(new BoolStatement(loc, false), n);
if (id.result.equals("undefined")) return ParseRes.res(new DiscardStatement(loc, null), n);
if (id.result.equals("null")) return ParseRes.res(new NullStatement(loc), n);
if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), n);
if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), n);
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), n);
return ParseRes.failed();
}
public static ParseRes<? extends Statement> parseValue(Source src, int i, int precedence, boolean statement) {
var n = skipEmpty(src, i);
Statement prev = null;
while (true) {
if (prev == null) {
var res = parseSimple(src, i + n, statement);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
}
else if (res.isError()) return res.chainError();
else break;
}
else {
var _prev = prev;
ParseRes<Statement> res = ParseRes.first(src, i + n,
(s, j) -> OperationStatement.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationStatement.parseIn(s, j, _prev, precedence),
(s, j) -> LazyOrStatement.parse(s, j, _prev, precedence),
(s, j) -> LazyAndStatement.parse(s, j, _prev, precedence),
(s, j) -> ChangeStatement.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> ChangeStatement.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> AssignableStatement.parse(s, j, _prev, precedence),
(s, j) -> OperationStatement.parseOperator(s, j, _prev, precedence),
(s, j) -> IfStatement.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexStatement.parseMember(s, j, _prev, precedence),
(s, j) -> IndexStatement.parseIndex(s, j, _prev, precedence),
(s, j) -> CallStatement.parseCall(s, j, _prev, precedence),
(s, j) -> CompoundStatement.parseComma(s, j, _prev, precedence)
);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
continue;
}
else if (res.isError()) return res.chainError();
break;
}
}
if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n);
}
public static ParseRes<? extends Statement> parseValue(Source src, int i, int precedence) {
return parseValue(src, i, precedence, false);
}
public static ParseRes<? extends Statement> parseValueStatement(Source src, int i) {
var res = parseValue(src, i, 0, true);
if (!res.isSuccess()) return res.chainError();
var end = parseStatementEnd(src, i + res.n);
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
return res.addN(end.n);
}
public static ParseRes<? extends Statement> parseStatement(Source src, int i) {
var n = skipEmpty(src, i);
if (src.is(i + n, ";")) return ParseRes.res(new DiscardStatement(src.loc(i+ n), null), n + 1);
if (isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
ParseRes<? extends Statement> res = ParseRes.first(src, i + n,
VariableDeclareStatement::parse,
ReturnStatement::parse,
ThrowStatement::parse,
ContinueStatement::parse,
BreakStatement::parse,
DebugStatement::parse,
IfStatement::parse,
WhileStatement::parse,
SwitchStatement::parse,
ForStatement::parse,
ForInStatement::parse,
ForOfStatement::parse,
DoWhileStatement::parse,
TryStatement::parse,
CompoundStatement::parse,
(s, j) -> FunctionStatement.parseFunction(s, j, true),
Parsing::parseValueStatement
);
return res.addN(n);
}
public static Statement[] parse(Filename filename, String raw) {
var src = new Source(filename, raw);
var list = new ArrayList<Statement>();
int i = 0;
while (true) {
if (i >= src.size()) break;
var res = Parsing.parseStatement(src, i);
if (res.isError()) throw new SyntaxException(src.loc(i), res.error);
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
i += res.n;
list.add(res.result);
}
return list.toArray(Statement[]::new);
}
public static CompileResult compile(Statement ...statements) {
var target = new CompileResult(new LocalScopeRecord());
var stm = new CompoundStatement(null, true, statements);
target.scope.define("this");
target.scope.define("arguments");
try {
stm.compile(target, true);
FunctionStatement.checkBreakAndCont(target, 0);
}
catch (SyntaxException e) {
target = new CompileResult(new LocalScopeRecord());
target.scope.define("this");
target.scope.define("arguments");
target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
}
target.add(Instruction.ret()).setLocation(stm.loc());
return target;
}
public static CompileResult compile(Filename filename, String raw) {
return compile(parse(filename, raw));
}
private static ParseRes<Double> parseHex(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = Parsing.fromHex(src.at(i + n, '\0'));
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 16;
res += digit;
}
}
private static ParseRes<Double> parseOct(Source src, int i) {
int n = 0;
double res = 0;
while (true) {
int digit = src.at(i + n, '\0') - '0';
if (digit < 0 || digit > 9) break;
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
if (digit < 0) {
if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
}
n++;
res *= 8;
res += digit;
}
return ParseRes.res(res, n);
}
public static ParseRes<String> parseString(Source src, int i) {
var n = skipEmpty(src, i);
char quote;
if (src.is(i + n, '\'')) quote = '\'';
else if (src.is(i + n, '"')) quote = '"';
else return ParseRes.failed();
n++;
var res = new StringBuilder();
while (true) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
if (src.is(i + n, quote)) {
n++;
return ParseRes.res(res.toString(), n);
}
var charRes = parseChar(src, i + n);
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
n += charRes.n;
if (charRes.result != null) res.append(charRes.result);
}
}
public static ParseRes<Double> parseNumber(Source src, int i) {
var n = skipEmpty(src, i);
double whole = 0;
double fract = 0;
long exponent = 0;
boolean parsedAny = false;
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
else return res.addN(2);
}
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
n += 2;
var res = parseOct(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
else return res.addN(2);
}
else if (src.is(i + n, '0')) {
n++;
parsedAny = true;
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
}
while (src.is(i + n, Parsing::isDigit)) {
parsedAny = true;
whole *= 10;
whole += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true;
n++;
while (src.is(i + n, Parsing::isDigit)) {
fract += src.at(i + n++) - '0';
fract /= 10;
}
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
parsedAny = true;
boolean negative = src.is(i + n, '-');
boolean parsedE = false;
if (negative) n++;
while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
exponent *= 10;
if (negative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
if (!parsedAny) return ParseRes.failed();
else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,12 +3,13 @@ package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ArrayStatement extends Statement {
public final Statement[] statements;
@ -21,8 +22,7 @@ public class ArrayStatement extends Statement {
return true;
}
@Override
public void compile(CompileResult target, boolean pollute) {
@Override public void compile(CompileResult target, boolean pollute) {
target.add(Instruction.loadArr(statements.length));
for (var i = 0; i < statements.length; i++) {
@ -70,7 +70,7 @@ public class ArrayStatement extends Statement {
}
}
var res = Parsing.parseValue(src, i + n, 2);
var res = ES5.parseExpression(src, i + n, 2);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element.");
n += res.n;
n += Parsing.skipEmpty(src, i + n);

View File

@ -1,101 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class CallStatement extends Statement {
public final Statement func;
public final Statement[] args;
public final boolean isNew;
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
if (isNew) func.compile(target, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, true, true);
}
else {
target.add(Instruction.pushUndefined());
func.compile(target, true);
}
for (var arg : args) arg.compile(target, true);
if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type);
if (!pollute) target.add(Instruction.discard());
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
public static ParseRes<CallStatement> parseCall(Source src, int i, Statement prev, int precedence) {
if (precedence > 17) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "(")) return ParseRes.failed();
n++;
var args = new ArrayList<Statement>();
boolean prevArg = false;
while (true) {
var argRes = Parsing.parseValue(src, i + n, 2);
n += argRes.n;
n += Parsing.skipEmpty(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
prevArg = true;
}
else if (argRes.isError()) return argRes.chainError();
else if (prevArg && src.is(i + n, ",")) {
prevArg = false;
n++;
}
else if (src.is(i + n, ")")) {
n++;
break;
}
else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren");
else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren");
}
return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
}
public static ParseRes<CallStatement> parseNew(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed();
n += 3;
var valRes = Parsing.parseValue(src, i + n, 18);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword.");
n += valRes.n;
var callRes = CallStatement.parseCall(src, i + n, valRes.result, 0);
if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n);
if (callRes.isError()) return callRes.chainError();
n += callRes.n;
return ParseRes.res(new CallStatement(loc, true, callRes.result.func, callRes.result.args), n);
}
}

View File

@ -1,15 +1,16 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
@ -39,7 +40,7 @@ public class FunctionStatement extends Statement {
}
}
private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) {
private CompileResult compileBody(CompileResult target, String name, boolean pollute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])) {
@ -72,7 +73,7 @@ public class FunctionStatement extends Statement {
subtarget.add(Instruction.ret()).setLocation(end);
checkBreakAndCont(subtarget, 0);
if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures()));
if (pollute) target.add(Instruction.loadFunc(target.children.size(), name, subtarget.scope.getCaptures()));
return target.addChild(subtarget);
}
@ -80,16 +81,8 @@ public class FunctionStatement extends Statement {
if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement;
var hasName = name != null;
compileBody(target, pollute || hasVar || hasName, bp);
if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup());
target.add(Instruction.pushValue("name"));
target.add(Instruction.pushValue(name));
target.add(Instruction.storeMember());
}
compileBody(target, name, pollute || hasVar, bp);
if (hasVar) {
var key = target.scope.getKey(this.varName);
@ -140,12 +133,12 @@ public class FunctionStatement extends Statement {
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
var args = Parsing.parseParamList(src, i + n);
var args = ES5.parseParamList(src, i + n);
if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list");
n += args.n;
var res = CompoundStatement.parse(src, i + n);
if (!res.isSuccess()) res.chainError(src.loc(i + n), "Expected a compound statement for function.");
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a compound statement for function.");
n += res.n;
return ParseRes.res(new FunctionStatement(

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;

View File

@ -5,13 +5,14 @@ import java.util.LinkedHashMap;
import java.util.Map;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class ObjectStatement extends Statement {
public static class ObjProp {
@ -82,7 +83,7 @@ public class ObjectStatement extends Statement {
var res = ParseRes.first(src, i + n,
Parsing::parseIdentifier,
Parsing::parseString,
Parsing::parseNumber
(s, j) -> Parsing.parseNumber(s, j, false)
);
n += res.n;
@ -102,7 +103,7 @@ public class ObjectStatement extends Statement {
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
n += name.n;
var params = Parsing.parseParamList(src, i + n);
var params = ES5.parseParamList(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
n += params.n;
@ -153,7 +154,7 @@ public class ObjectStatement extends Statement {
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
n++;
var valRes = Parsing.parseValue(src, i + n, 2);
var valRes = ES5.parseExpression(src, i + n, 2);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list");
n += valRes.n;

View File

@ -1,110 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.Operator;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var arg : args) {
arg.compile(target, true);
}
if (pollute) target.add(Instruction.operation(operation));
else target.add(Instruction.discard());
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
public static ParseRes<OperationStatement> parsePrefix(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
Operation operation = null;
String op;
if (src.is(i + n, op = "+")) operation = Operation.POS;
else if (src.is(i + n, op = "-")) operation = Operation.NEG;
else if (src.is(i + n, op = "~")) operation = Operation.INVERSE;
else if (src.is(i + n, op = "!")) operation = Operation.NOT;
else return ParseRes.failed();
n++;
var res = Parsing.parseValue(src, i + n, 14);
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op));
}
public static ParseRes<OperationStatement> parseInstanceof(Source src, int i, Statement prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "instanceof");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = Parsing.parseValue(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'.");
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
public static ParseRes<OperationStatement> parseIn(Source src, int i, Statement prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "in");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = Parsing.parseValue(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'.");
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Operation.IN, valRes.result, prev), n);
}
public static ParseRes<? extends Statement> parseOperator(Source src, int i, Statement prev, int precedence) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
for (var op : Operator.opsByLength) {
if (!src.is(i + n, op.readable)) continue;
if (op.precedence < precedence) return ParseRes.failed();
n += op.readable.length();
var res = Parsing.parseValue(src, i + n, op.precedence + 1);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", op.readable));
n += res.n;
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
}
return ParseRes.failed();
}
}

View File

@ -1,12 +1,12 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class RegexStatement extends Statement {
public final String pattern, flags;

View File

@ -1,14 +1,16 @@
package me.topchetoeu.jscript.compilation.values;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.operations.VariableAssignStatement;
public class VariableStatement extends AssignableStatement {
public final String name;
@ -40,7 +42,7 @@ public class VariableStatement extends AssignableStatement {
if (!literal.isSuccess()) return literal.chainError();
n += literal.n;
if (!Parsing.checkVarName(literal.result)) {
if (!ES5.checkVarName(literal.result)) {
if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported.");
if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported.");
if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported.");

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;

View File

@ -1,12 +1,12 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class NumberStatement extends Statement {
public final double value;
@ -35,7 +35,7 @@ public class NumberStatement extends Statement {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var res = Parsing.parseNumber(src, i + n);
var res = Parsing.parseNumber(src, i + n, false);
if (res.isSuccess()) return ParseRes.res(new NumberStatement(loc, res.result), n + res.n);
else return res.chainError();
}

View File

@ -1,12 +1,12 @@
package me.topchetoeu.jscript.compilation.values.constants;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class StringStatement extends Statement {
public final String value;

View File

@ -0,0 +1,180 @@
package me.topchetoeu.jscript.compilation.values.operations;
import java.util.ArrayList;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ArrayStatement;
import me.topchetoeu.jscript.compilation.values.ObjectStatement;
import me.topchetoeu.jscript.compilation.values.VariableStatement;
import me.topchetoeu.jscript.compilation.values.constants.BoolStatement;
import me.topchetoeu.jscript.compilation.values.constants.NumberStatement;
import me.topchetoeu.jscript.compilation.values.constants.StringStatement;
public class CallStatement extends Statement {
public static boolean ATTACH_NAME = true;
public final Statement func;
public final Statement[] args;
public final boolean isNew;
private String generateName(Statement func, Statement index) {
String res = "(intermediate value)";
boolean shouldParen = false;
if (func instanceof ObjectStatement) {
var obj = (ObjectStatement)func;
shouldParen = true;
if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}";
else res = "{(intermediate value)}";
}
else if (func instanceof StringStatement) {
res = JSON.stringify(JSONElement.string(((StringStatement)func).value));
}
else if (func instanceof NumberStatement) {
res = JSON.stringify(JSONElement.number(((NumberStatement)func).value));
}
else if (func instanceof BoolStatement) {
res = ((BoolStatement)func).value ? "true" : "false";
}
else if (func instanceof VariableStatement) {
res = ((VariableStatement)func).name;
}
else if (func instanceof VariableIndexStatement) {
var i = ((VariableIndexStatement)func).index;
if (i == 0) res = "this";
else if (i == 1) res = "arguments";
}
else if (func instanceof ArrayStatement) {
var els = new ArrayList<String>();
for (var el : ((ArrayStatement)func).statements) {
if (el != null) els.add(generateName(el, null));
else els.add("(intermediate value)");
}
res = "[" + String.join(",", els) + "]";
}
if (index == null) return res;
if (shouldParen) res = "(" + res + ")";
if (index instanceof StringStatement) {
var val = ((StringStatement)index).value;
var bracket = JSON.stringify(JSONElement.string(val));
if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]";
if (Parsing.parseIdentifier(new Source(null, val), 0).n != val.length()) return res + "[" + bracket + "]";
return res + "." + val;
}
return res + "[" + generateName(index, null) + "]";
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
if (!isNew && func instanceof IndexStatement) {
var obj = ((IndexStatement)func).object;
var index = ((IndexStatement)func).index;
String name = "";
obj.compile(target, true);
index.compile(target, true);
for (var arg : args) arg.compile(target, true);
if (ATTACH_NAME) name = generateName(obj, index);
target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type);
}
else {
String name = "";
func.compile(target, true);
for (var arg : args) arg.compile(target, true);
if (ATTACH_NAME) name = generateName(func, null);
if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type);
else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type);
}
if (!pollute) target.add(Instruction.discard());
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
public static ParseRes<CallStatement> parseCall(Source src, int i, Statement prev, int precedence) {
if (precedence > 17) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, "(")) return ParseRes.failed();
n++;
var args = new ArrayList<Statement>();
boolean prevArg = false;
while (true) {
var argRes = ES5.parseExpression(src, i + n, 2);
n += argRes.n;
n += Parsing.skipEmpty(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
prevArg = true;
}
else if (argRes.isError()) return argRes.chainError();
else if (prevArg && src.is(i + n, ",")) {
prevArg = false;
n++;
}
else if (src.is(i + n, ")")) {
n++;
break;
}
else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren");
else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren");
}
return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
}
public static ParseRes<CallStatement> parseNew(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed();
n += 3;
var valRes = ES5.parseExpression(src, i + n, 18);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword.");
n += valRes.n;
var callRes = CallStatement.parseCall(src, i + n, valRes.result, 0);
if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n);
if (callRes.isError()) return callRes.chainError();
n += callRes.n;
return ParseRes.res(new CallStatement(loc, true, callRes.result.func, callRes.result.args), n);
}
}

View File

@ -1,14 +1,15 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.constants.NumberStatement;
public class ChangeStatement extends Statement {
@ -39,7 +40,7 @@ public class ChangeStatement extends Statement {
if (!src.is(i + n, "++")) return ParseRes.failed();
n += 2;
var res = Parsing.parseValue(src, i + n, 15);
var res = ES5.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");
@ -52,7 +53,7 @@ public class ChangeStatement extends Statement {
if (!src.is(i + n, "--")) return ParseRes.failed();
n += 2;
var res = Parsing.parseValue(src, i + n, 15);
var res = ES5.parseExpression(src, i + n, 15);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator.");
else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator.");

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class DiscardStatement extends Statement {
public final Statement value;
@ -30,7 +31,7 @@ public class DiscardStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed();
n += 4;
var valRes = Parsing.parseValue(src, i + n, 14);
var valRes = ES5.parseExpression(src, i + n, 14);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword.");
n += valRes.n;

View File

@ -1,9 +1,9 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;

View File

@ -1,15 +1,16 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.constants.StringStatement;
public class IndexStatement extends AssignableStatement {
@ -48,7 +49,7 @@ public class IndexStatement extends AssignableStatement {
if (!src.is(i + n, "[")) return ParseRes.failed();
n++;
var valRes = Parsing.parseValue(src, i + n, 0);
var valRes = ES5.parseExpression(src, i + n, 0);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression");
n += valRes.n;
n += Parsing.skipEmpty(src, i + n);

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class LazyAndStatement extends Statement {
public final Statement first, second;
@ -38,7 +39,7 @@ public class LazyAndStatement extends Statement {
var loc = src.loc(i + n);
n += 2;
var res = Parsing.parseValue(src, i + n, 4);
var res = ES5.parseExpression(src, i + n, 4);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator.");
n += res.n;

View File

@ -1,12 +1,13 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
public class LazyOrStatement extends Statement {
public final Statement first, second;
@ -38,7 +39,7 @@ public class LazyOrStatement extends Statement {
var loc = src.loc(i + n);
n += 2;
var res = Parsing.parseValue(src, i + n, 4);
var res = ES5.parseExpression(src, i + n, 4);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator.");
n += res.n;

View File

@ -0,0 +1,234 @@
package me.topchetoeu.jscript.compilation.values.operations;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
public class OperationStatement extends Statement {
private static interface OperatorFactory {
String token();
int precedence();
ParseRes<Statement> construct(Source src, int i, Statement prev);
}
private static class NormalOperatorFactory implements OperatorFactory {
public final String token;
public final int precedence;
public final Operation operation;
@Override public int precedence() { return precedence; }
@Override public String token() { return token; }
@Override public ParseRes<Statement> construct(Source src, int i, Statement prev) {
var loc = src.loc(i);
var other = ES5.parseExpression(src, i, precedence + 1);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(new OperationStatement(loc, operation, prev, (Statement)other.result), other.n);
}
public NormalOperatorFactory(String token, int precedence, Operation operation) {
this.token = token;
this.precedence = precedence;
this.operation = operation;
}
}
private static class AssignmentOperatorFactory implements OperatorFactory {
public final String token;
public final int precedence;
public final Operation operation;
@Override public int precedence() { return precedence; }
@Override public String token() { return token; }
@Override public ParseRes<Statement> construct(Source src, int i, Statement prev) {
var loc = src.loc(i);
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
var other = ES5.parseExpression(src, i, precedence);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
return ParseRes.res(((AssignableStatement)prev).toAssign(other.result, operation), other.n);
}
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
this.token = token;
this.precedence = precedence;
this.operation = operation;
}
}
private static class LazyAndFactory implements OperatorFactory {
@Override public int precedence() { return 4; }
@Override public String token() { return "&&"; }
@Override public ParseRes<Statement> construct(Source src, int i, Statement prev) {
var loc = src.loc(i);
var other = ES5.parseExpression(src, i, 5);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'");
return ParseRes.res(new LazyAndStatement(loc, prev, (Statement)other.result), other.n);
}
}
private static class LazyOrFactory implements OperatorFactory {
@Override public int precedence() { return 5; }
@Override public String token() { return "||"; }
@Override public ParseRes<Statement> construct(Source src, int i, Statement prev) {
var loc = src.loc(i);
var other = ES5.parseExpression(src, i, 6);
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'");
return ParseRes.res(new LazyOrStatement(loc, prev, (Statement)other.result), other.n);
}
}
public final Statement[] args;
public final Operation operation;
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
return true;
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var arg : args) {
arg.compile(target, true);
}
if (pollute) target.add(Instruction.operation(operation));
else target.add(Instruction.discard());
}
public OperationStatement(Location loc, Operation operation, Statement ...args) {
super(loc);
this.operation = operation;
this.args = args;
}
private static final Map<String, OperatorFactory> factories = Set.of(
new NormalOperatorFactory("*", 13, Operation.MULTIPLY),
new NormalOperatorFactory("/", 12, Operation.DIVIDE),
new NormalOperatorFactory("%", 12, Operation.MODULO),
new NormalOperatorFactory("-", 11, Operation.SUBTRACT),
new NormalOperatorFactory("+", 11, Operation.ADD),
new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT),
new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT),
new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT),
new NormalOperatorFactory(">", 9, Operation.GREATER),
new NormalOperatorFactory("<", 9, Operation.LESS),
new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS),
new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS),
new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS),
new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS),
new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS),
new NormalOperatorFactory("===", 8, Operation.EQUALS),
new NormalOperatorFactory("&", 7, Operation.AND),
new NormalOperatorFactory("^", 6, Operation.XOR),
new NormalOperatorFactory("|", 5, Operation.OR),
new AssignmentOperatorFactory("=", 2, null),
new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY),
new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE),
new AssignmentOperatorFactory("%=", 2, Operation.MODULO),
new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT),
new AssignmentOperatorFactory("+=", 2, Operation.ADD),
new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT),
new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT),
new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT),
new AssignmentOperatorFactory("&=", 2, Operation.AND),
new AssignmentOperatorFactory("^=", 2, Operation.XOR),
new AssignmentOperatorFactory("|=", 2, Operation.OR),
new LazyAndFactory(),
new LazyOrFactory()
).stream().collect(Collectors.toMap(v -> v.token(), v -> v));
private static final List<String> operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList());
public static ParseRes<OperationStatement> parsePrefix(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
Operation operation = null;
String op;
if (src.is(i + n, op = "+")) operation = Operation.POS;
else if (src.is(i + n, op = "-")) operation = Operation.NEG;
else if (src.is(i + n, op = "~")) operation = Operation.INVERSE;
else if (src.is(i + n, op = "!")) operation = Operation.NOT;
else return ParseRes.failed();
n++;
var res = ES5.parseExpression(src, i + n, 14);
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op));
}
public static ParseRes<OperationStatement> parseInstanceof(Source src, int i, Statement prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "instanceof");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = ES5.parseExpression(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'.");
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
public static ParseRes<OperationStatement> parseIn(Source src, int i, Statement prev, int precedence) {
if (precedence > 9) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var kw = Parsing.parseIdentifier(src, i + n, "in");
if (!kw.isSuccess()) return kw.chainError();
n += kw.n;
var valRes = ES5.parseExpression(src, i + n, 10);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'.");
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Operation.IN, valRes.result, prev), n);
}
public static ParseRes<? extends Statement> parseOperator(Source src, int i, Statement prev, int precedence) {
var n = Parsing.skipEmpty(src, i);
for (var token : operatorsByLength) {
var factory = factories.get(token);
if (!src.is(i + n, token)) continue;
if (factory.precedence() < precedence) ParseRes.failed();
n += token.length();
n += Parsing.skipEmpty(src, i + n);
var res = factory.construct(src, i + n, prev);
return res.addN(n);
// var res = Parsing.parseValue(src, i + n, prec + 1);
// if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", token));
// n += res.n;
// return ParseRes.res(new OperationStatement(loc, factories.get(token), prev, res.result), n);
}
return ParseRes.failed();
}
}

View File

@ -1,12 +1,14 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.ES5;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.parsing.ParseRes;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.compilation.parsing.Source;
import me.topchetoeu.jscript.compilation.values.VariableStatement;
public class TypeofStatement extends Statement {
public final Statement value;
@ -40,7 +42,7 @@ public class TypeofStatement extends Statement {
if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed();
n += 6;
var valRes = Parsing.parseValue(src, i + n, 15);
var valRes = ES5.parseExpression(src, i + n, 15);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword.");
n += valRes.n;

View File

@ -1,10 +1,11 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
public class VariableAssignStatement extends Statement {
public final String name;

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.compilation.values;
package me.topchetoeu.jscript.compilation.values.operations;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.Statement;

View File

@ -0,0 +1,13 @@
package me.topchetoeu.jscript.runtime;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
public class ArgumentsValue extends ArrayValue {
public final Frame frame;
public ArgumentsValue(Frame frame, Value... args) {
super(args);
this.frame = frame;
}
}

View File

@ -1,18 +1,19 @@
package me.topchetoeu.jscript.runtime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
public class Engine implements EventLoop {
private static class Task<T> implements Comparable<Task<?>> {
public final ResultRunnable<?> runnable;
public final DataNotifier<T> notifier = new DataNotifier<>();
public final Supplier<?> runnable;
public final CompletableFuture<T> notifier = new CompletableFuture<T>();
public final boolean micro;
public Task(ResultRunnable<T> runnable, boolean micro) {
public Task(Supplier<T> runnable, boolean micro) {
this.runnable = runnable;
this.micro = micro;
}
@ -26,8 +27,7 @@ public class Engine implements EventLoop {
private PriorityBlockingQueue<Task<?>> tasks = new PriorityBlockingQueue<>();
private Thread thread;
@Override
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
var msg = new Task<T>(runnable, micro);
tasks.add(msg);
return msg.notifier;
@ -40,15 +40,15 @@ public class Engine implements EventLoop {
var task = tasks.take();
try {
((Task<Object>)task).notifier.next(task.runnable.run());
((Task<Object>)task).notifier.complete(task.runnable.get());
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
task.notifier.completeExceptionally(e);
}
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
for (var msg : tasks) msg.notifier.cancel(false);
break;
}
}

View File

@ -1,9 +1,10 @@
package me.topchetoeu.jscript.runtime;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.Compiler;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.ResultRunnable;
import me.topchetoeu.jscript.common.events.DataNotifier;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
@ -16,21 +17,21 @@ public interface EventLoop {
public static EventLoop get(Environment ext) {
if (ext.hasNotNull(KEY)) return ext.get(KEY);
else return new EventLoop() {
@Override public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
throw EngineException.ofError("No event loop attached to environment.");
}
};
}
public <T> DataNotifier<T> pushMsg(ResultRunnable<T> runnable, boolean micro);
public default DataNotifier<Void> pushMsg(Runnable runnable, boolean micro) {
public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro);
public default Future<Void> pushMsg(Runnable runnable, boolean micro) {
return pushMsg(() -> { runnable.run(); return null; }, micro);
}
public default DataNotifier<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
return pushMsg(() -> func.call(env, thisArg, args), micro);
}
public default DataNotifier<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
return pushMsg(() -> Compiler.compile(env, filename, raw).call(env, thisArg, args), micro);
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).call(env, thisArg, args), micro);
}
}

View File

@ -13,11 +13,11 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
import me.topchetoeu.jscript.runtime.scope.LocalScope;
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.objects.ScopeValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
@ -101,6 +101,7 @@ public class Frame {
public final LocalScope scope;
public final Object thisArg;
public final Object[] args;
public final boolean isNew;
public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function;
public final Environment env;
@ -356,14 +357,14 @@ public class Frame {
*/
public ObjectValue getValStackScope() {
return new ObjectValue() {
@Override public Member getOwnMember(Environment env, Value key) {
@Override public Member getOwnMember(Environment env, KeyCache key) {
var res = super.getOwnMember(env, key);
if (res != null) return res;
var f = key.toNumber(env).value;
var i = (int)f;
var num = key.toNumber(env);
var i = key.toInt(env);
if (i < 0 || i >= stackPtr) return null;
if (num != i || i < 0 || i >= stackPtr) return null;
else return new FieldMember(false, true, true) {
@Override public Value get(Environment env, Value self) { return stack[i]; }
@Override public boolean set(Environment env, Value val, Value self) {
@ -391,12 +392,13 @@ public class Frame {
};
}
public Frame(Environment env, Value thisArg, Value[] args, CodeFunction func) {
public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) {
this.env = env;
this.args = args.clone();
this.args = args;
this.isNew = isNew;
this.scope = new LocalScope(func.body.localsN, func.captures);
this.scope.get(0).set(thisArg);
this.scope.get(1).value = new ArrayValue(args);
this.scope.get(1).value = new ArgumentsValue(this, args);
this.thisArg = thisArg;
this.function = func;

View File

@ -35,9 +35,18 @@ public class InstructionRunner {
private static Value execCall(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var func = frame.pop();
var thisArg = frame.pop();
frame.push(func.call(env, thisArg, callArgs));
frame.push(func.call(env, false, instr.get(1), VoidValue.UNDEFINED, callArgs));
frame.codePtr++;
return null;
}
private static Value execCallMember(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var index = frame.pop();
var obj = frame.pop();
frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs));
frame.codePtr++;
return null;
@ -46,7 +55,7 @@ public class InstructionRunner {
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
frame.push(funcObj.callNew(env, callArgs));
frame.push(funcObj.callNew(env, instr.get(1), callArgs));
frame.codePtr++;
return null;
@ -61,7 +70,7 @@ public class InstructionRunner {
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
var setterVal = frame.pop();
var getterVal = frame.pop();
var name = frame.pop();
var key = frame.pop();
var obj = frame.pop();
FunctionValue getter, setter;
@ -74,7 +83,7 @@ public class InstructionRunner {
else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal;
else throw EngineException.ofType("Setter must be a function or undefined.");
obj.defineOwnMember(env, name, new PropertyMember(getter, setter, true, true));
obj.defineOwnMember(env, key, new PropertyMember(getter, setter, true, true));
frame.push(obj);
frame.codePtr++;
@ -90,7 +99,7 @@ public class InstructionRunner {
for (var el : members) {
var obj = new ObjectValue();
obj.defineOwnMember(env, new StringValue("value"), FieldMember.of(new StringValue(el)));
obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el)));
frame.push(obj);
}
@ -147,7 +156,9 @@ public class InstructionRunner {
return null;
}
private static Value execLoadObj(Environment env, Instruction instr, Frame frame) {
frame.push(new ObjectValue());
var obj = new ObjectValue();
obj.setPrototype(Environment.OBJECT_PROTO);
frame.push(obj);
frame.codePtr++;
return null;
}
@ -165,13 +176,14 @@ public class InstructionRunner {
}
private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
int id = instr.get(0);
var captures = new ValueVariable[instr.params.length - 1];
String name = instr.get(1);
var captures = new ValueVariable[instr.params.length - 2];
for (var i = 1; i < instr.params.length; i++) {
captures[i - 1] = frame.scope.get(instr.get(i));
for (var i = 2; i < instr.params.length; i++) {
captures[i - 2] = frame.scope.get(instr.get(i));
}
var func = new CodeFunction(env, "", frame.function.body.children[id], captures);
var func = new CodeFunction(env, name, frame.function.body.children[id], captures);
frame.push(func);
@ -240,7 +252,7 @@ public class InstructionRunner {
return null;
}
private static Value execJmpIf(Environment env, Instruction instr, Frame frame) {
if (frame.pop().toBoolean().value) {
if (frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
@ -248,7 +260,7 @@ public class InstructionRunner {
return null;
}
private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) {
if (frame.pop().not().value) {
if (!frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
@ -306,6 +318,7 @@ public class InstructionRunner {
case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
case CALL: return execCall(env, instr, frame);
case CALL_NEW: return execCallNew(env, instr, frame);
case CALL_MEMBER: return execCallMember(env, instr, frame);
case TRY_START: return execTryStart(env, instr, frame);
case TRY_END: return execTryEnd(env, instr, frame);

View File

@ -27,7 +27,7 @@ public class JSONConverter {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineOwnMember(null, new StringValue(el.getKey()), FieldMember.of(toJs(el.getValue())));
res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue())));
}
return res;

View File

@ -3,12 +3,14 @@ package me.topchetoeu.jscript.runtime;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import me.topchetoeu.jscript.common.Compiler;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Metadata;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.InterruptException;
@ -16,6 +18,7 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.scope.GlobalScope;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class SimpleRepl {
@ -32,13 +35,16 @@ public class SimpleRepl {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file.toFile()),
raw, null
).await();
System.err.println(res.toReadable(environment));
try {
var res = engine.pushMsg(
false, environment,
Filename.fromFile(file.toFile()), raw, null
).get();
System.err.println(res.toReadable(environment));
}
catch (ExecutionException e) { throw e.getCause(); }
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
}
@ -48,9 +54,16 @@ public class SimpleRepl {
var raw = Reading.readline();
if (raw == null) break;
var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw);
var res = engine.pushMsg(false, environment, func, VoidValue.UNDEFINED).await();
System.err.println(res.toReadable(environment));
try {
var res = engine.pushMsg(
false, environment,
new Filename("jscript", "repl/" + i + ".js"), raw,
VoidValue.UNDEFINED
).get();
System.err.println(res.toReadable(environment));
}
catch (ExecutionException e) { throw e.getCause(); }
}
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }
}
@ -59,62 +72,36 @@ public class SimpleRepl {
System.out.println(e.toString());
engine.thread().interrupt();
}
catch (RuntimeException ex) {
if (ex instanceof InterruptException) return;
else {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
catch (CancellationException | InterruptedException e) { return; }
catch (Throwable ex) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
private static void initEnv() {
// glob.define(null, false, new NativeFunction("go", args -> {
// try {
// var f = Path.of("do.js");
// var func = Compiler.compile(args.env, new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
// return func.call(args.env);
// }
// catch (IOException e) {
// throw new EngineException("Couldn't open do.js");
// }
// }));
// var fs = new RootFilesystem(PermissionsProvider.get(environment));
// fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
// fs.protocols.put("file", new PhysicalFilesystem("."));
// fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err));
// environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS);
// environment.add(Filesystem.KEY, fs);
// environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs));
// environment.add(Compiler.KEY, new JSCompiler(environment));
environment.add(EventLoop.KEY, engine);
environment.add(GlobalScope.KEY, new GlobalScope());
// environment.add(EventLoop.KEY, engine);
environment.add(Compiler.KEY, (filename, source) -> {
return Parsing.compile(filename, source).body();
});
environment.add(DebugContext.KEY, new DebugContext());
environment.add(Compiler.KEY, Compiler.DEFAULT);
var glob = GlobalScope.get(environment);
glob.define(null, false, new NativeFunction("exit", args -> {
Thread.currentThread().interrupt();
throw new InterruptException();
}));
glob.define(null, false, new NativeFunction("log", args -> {
for (var el : args.args) {
System.out.print(el.toReadable(args.env));
if (el instanceof StringValue) System.out.print(((StringValue)el).value);
else System.out.print(el.toReadable(args.env));
}
return null;
}));
}
private static void initEngine() {
// var ctx = new DebugContext();
// environment.add(DebugContext.KEY, ctx);
// debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
engineTask = engine.start();
// debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
public static void main(String args[]) throws InterruptedException {

View File

@ -1,13 +0,0 @@
package me.topchetoeu.jscript.runtime;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public interface WrapperProvider {
public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj);
public WrapperProvider fork(Environment env);
}

View File

@ -5,11 +5,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;

View File

@ -2,10 +2,10 @@ package me.topchetoeu.jscript.runtime.debug;
import java.util.List;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.runtime.Frame;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;

View File

@ -1,11 +0,0 @@
package me.topchetoeu.jscript.runtime.exceptions;
public class ConvertException extends RuntimeException {
public final String source, target;
public ConvertException(String source, String target) {
super(String.format("Cannot convert '%s' to '%s'.", source, target));
this.source = source;
this.target = target;
}
}

View File

@ -3,13 +3,14 @@ package me.topchetoeu.jscript.runtime.exceptions;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public class EngineException extends RuntimeException {
public static class StackElement {
@ -69,7 +70,19 @@ public class EngineException extends RuntimeException {
ss.append(value.toString(env)).append('\n');
}
catch (EngineException e) {
ss.append("[Error while stringifying]\n");
var name = value.getMember(env, "name");
var desc = value.getMember(env, "message");
if (name.isPrimitive() && desc.isPrimitive()) {
if (name instanceof VoidValue) ss.append("Error: ");
else ss.append(name.toString(env).value + ": ");
if (desc instanceof VoidValue) ss.append("An error occurred");
else ss.append(desc.toString(env).value);
ss.append("\n");
}
else ss.append("[Error while stringifying]\n");
}
for (var line : stackTrace) {
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
@ -83,8 +96,8 @@ public class EngineException extends RuntimeException {
var res = new ObjectValue();
res.setPrototype(proto);
if (name != null) res.defineOwnMember(Environment.empty(), new StringValue("name"), FieldMember.of(new StringValue(name)));
res.defineOwnMember(Environment.empty(), new StringValue("message"), FieldMember.of(new StringValue(msg)));
if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name)));
res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg)));
return res;
}

View File

@ -1,6 +1,6 @@
package me.topchetoeu.jscript.runtime.exceptions;
import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.parsing.Location;
public class SyntaxException extends RuntimeException {
public final Location loc;

View File

@ -29,10 +29,10 @@ public class GlobalScope {
}
public void define(Environment ext, String name, Variable variable) {
object.defineOwnMember(ext, new StringValue(name), variable.toField(true, true));
object.defineOwnMember(ext, name, variable.toField(true, true));
}
public void define(Environment ext, boolean readonly, String name, Value val) {
object.defineOwnMember(ext, new StringValue(name), FieldMember.of(val, !readonly));
object.defineOwnMember(ext, name, FieldMember.of(val, !readonly));
}
public void define(Environment ext, boolean readonly, String ...names) {
for (var name : names) define(ext, name, new ValueVariable(readonly, VoidValue.UNDEFINED));
@ -42,12 +42,12 @@ public class GlobalScope {
}
public Value get(Environment env, String name) {
if (!object.hasMember(env, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return object.getMember(env, new StringValue(name));
if (!object.hasMember(env, name, false)) throw EngineException.ofSyntax(name + " is not defined");
else return object.getMember(env, name);
}
public void set(Environment ext, String name, Value val) {
if (!object.hasMember(ext, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!object.setMember(ext, new StringValue(name), val)) throw EngineException.ofSyntax("The global '" + name + "' is read-only.");
if (!object.hasMember(ext, name, false)) throw EngineException.ofSyntax(name + " is not defined");
if (!object.setMember(ext, name, val)) throw EngineException.ofSyntax("Assignment to constant variable");
}
public Set<String> keys() {

View File

@ -1,6 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
public enum ConvertHint {
TOSTRING,
VALUEOF,
}

View File

@ -0,0 +1,59 @@
package me.topchetoeu.jscript.runtime.values;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
public class KeyCache {
public final Value value;
private Integer intCache;
private Double doubleCache;
private Boolean booleanCache;
private String stringCache;
public String toString(Environment env) {
if (stringCache != null) return stringCache;
else return stringCache = value.toString(env).value;
}
public double toNumber(Environment env) {
if (doubleCache != null) return doubleCache;
else return doubleCache = value.toNumber(env).value;
}
public int toInt(Environment env) {
if (intCache != null) return intCache;
else return intCache = (int)toNumber(env);
}
public boolean toBoolean() {
if (booleanCache != null) return booleanCache;
else return booleanCache = value.toBoolean();
}
public SymbolValue toSymbol() {
if (value instanceof SymbolValue) return (SymbolValue)value;
else return null;
}
public boolean isSymbol() {
return value instanceof SymbolValue;
}
public KeyCache(Value value) {
this.value = value;
}
public KeyCache(String value) {
this.value = new StringValue(value);
this.stringCache = value;
this.booleanCache = !value.equals("");
}
public KeyCache(int value) {
this.value = new NumberValue(value);
this.intCache = value;
this.doubleCache = (double)value;
this.booleanCache = value != 0;
}
public KeyCache(double value) {
this.value = new NumberValue(value);
this.intCache = (int)value;
this.doubleCache = value;
this.booleanCache = value != 0;
}
}

View File

@ -4,7 +4,6 @@ import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
public interface Member {
@ -42,14 +41,14 @@ public interface Member {
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
if (getter == null) res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(getter));
if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, "getter", FieldMember.of(getter));
if (setter == null) res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(setter));
if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(VoidValue.UNDEFINED));
else res.defineOwnMember(env, "setter", FieldMember.of(setter));
res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable)));
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable)));
return res;
}
@ -98,10 +97,10 @@ public interface Member {
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
res.defineOwnMember(env, new StringValue("value"), FieldMember.of(get(env, self)));
res.defineOwnMember(env, new StringValue("writable"), FieldMember.of(BoolValue.of(writable)));
res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable)));
res.defineOwnMember(env, "value", FieldMember.of(get(env, self)));
res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable)));
res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable)));
res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable)));
return res;
}

View File

@ -53,31 +53,41 @@ public abstract class Value {
public abstract StringValue type();
public abstract boolean isPrimitive();
public abstract BoolValue toBoolean();
public final boolean isNaN() {
return this instanceof NumberValue && Double.isNaN(((NumberValue)this).value);
}
public Value call(Environment env, Value self, Value ...args) {
throw EngineException.ofType("Tried to call a non-function value.");
public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) {
if (name == null || name.equals("")) name = "(intermediate value)";
if (isNew) throw EngineException.ofType(name + " is not a constructor");
else throw EngineException.ofType(name + " is not a function");
}
public final Value callNew(Environment env, Value ...args) {
public final Value callNew(Environment env, String name, Value ...args) {
var res = new ObjectValue();
var proto = getMember(env, new StringValue("prototype"));
if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto);
else res.setPrototype(env, null);
var ret = this.call(env, res, args);
var ret = this.call(env, true, name, res, args);
if (!ret.isPrimitive()) return ret;
return res;
}
public final Value call(Environment env, Value self, Value ...args) {
return call(env, false, "", self, args);
}
public final Value callNew(Environment env, Value ...args) {
return callNew(env, "", args);
}
public abstract Value toPrimitive(Environment env);
public abstract NumberValue toNumber(Environment env);
public abstract StringValue toString(Environment env);
public abstract boolean toBoolean();
public final int toInt(Environment env) { return (int)toNumber(env).value; }
public final long toLong(Environment env) { return (long)toNumber(env).value; }
@ -134,11 +144,11 @@ public abstract class Value {
}
public CompareResult compare(Environment env, Value other) {
return toPrimitive(env).compare(env, other);
}
var a = this.toPrimitive(env);
var b = other.toPrimitive(env);
public final BoolValue not() {
return toBoolean().value ? BoolValue.FALSE : BoolValue.TRUE;
if (a instanceof StringValue && b instanceof StringValue) return a.compare(env, b);
else return a.toNumber(env).compare(env, b.toNumber(env));
}
public final boolean isInstanceOf(Environment env, Value proto) {
@ -172,7 +182,7 @@ public abstract class Value {
case LESS_EQUALS: return BoolValue.of(args[0].compare(env, args[1]).lessOrEqual());
case INVERSE: return args[0].bitwiseNot(env);
case NOT: return args[0].not();
case NOT: return BoolValue.of(!args[0].toBoolean());
case POS: return args[0].toNumber(env);
case NEG: return args[0].negative(env);
@ -187,16 +197,71 @@ public abstract class Value {
}
}
public abstract Member getOwnMember(Environment env, Value key);
public abstract Member getOwnMember(Environment env, KeyCache key);
public abstract Map<String, Member> getOwnMembers(Environment env);
public abstract Map<SymbolValue, Member> getOwnSymbolMembers(Environment env);
public abstract boolean defineOwnMember(Environment env, Value key, Member member);
public abstract boolean deleteOwnMember(Environment env, Value key);
public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member);
public abstract boolean deleteOwnMember(Environment env, KeyCache key);
public abstract ObjectValue getPrototype(Environment env);
public abstract boolean setPrototype(Environment env, ObjectValue val);
public final Value getMember(Environment env, Value key) {
public final Member getOwnMember(Environment env, Value key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, String key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, int key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, double key) {
return getOwnMember(env, new KeyCache(key));
}
public final boolean defineOwnMember(Environment env, Value key, Member member) {
return defineOwnMember(env, new KeyCache(key), member);
}
public final boolean defineOwnMember(Environment env, String key, Member member) {
return defineOwnMember(env, new KeyCache(key), member);
}
public final boolean defineOwnMember(Environment env, int key, Member member) {
return defineOwnMember(env, new KeyCache(key), member);
}
public final boolean defineOwnMember(Environment env, double key, Member member) {
return defineOwnMember(env, new KeyCache(key), member);
}
public final boolean defineOwnMember(Environment env, KeyCache key, Value val) {
return defineOwnMember(env, key, FieldMember.of(val));
}
public final boolean defineOwnMember(Environment env, Value key, Value val) {
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
}
public final boolean defineOwnMember(Environment env, String key, Value val) {
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
}
public final boolean defineOwnMember(Environment env, int key, Value val) {
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
}
public final boolean defineOwnMember(Environment env, double key, Value val) {
return defineOwnMember(env, new KeyCache(key), FieldMember.of(val));
}
public final boolean deleteOwnMember(Environment env, Value key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, String key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, int key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, double key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, KeyCache key) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key);
if (member != null) return member.get(env, obj);
@ -204,15 +269,51 @@ public abstract class Value {
return VoidValue.UNDEFINED;
}
public final boolean setMember(Environment env, Value key, Value val) {
public final Value getMember(Environment env, Value key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, String key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, int key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, double key) {
return getMember(env, new KeyCache(key));
}
public final boolean setMember(Environment env, KeyCache key, Value val) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key);
if (member != null) return member.set(env, val, obj);
if (member != null) {
if (member.set(env, val, obj)) {
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
return true;
}
else return false;
}
}
return defineOwnMember(env, key, FieldMember.of(val));
if (defineOwnMember(env, key, FieldMember.of(val))) {
if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
return true;
}
else return false;
}
public final boolean hasMember(Environment env, Value key, boolean own) {
public final boolean setMember(Environment env, Value key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, String key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, int key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, double key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean hasMember(Environment env, KeyCache key, boolean own) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
if (obj.getOwnMember(env, key) != null) return true;
if (own) return false;
@ -220,10 +321,36 @@ public abstract class Value {
return false;
}
public final boolean deleteMember(Environment env, Value key) {
public final boolean hasMember(Environment env, Value key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, String key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, int key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, double key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean deleteMember(Environment env, KeyCache key) {
if (!hasMember(env, key, true)) return true;
return deleteOwnMember(env, key);
}
public final boolean deleteMember(Environment env, Value key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, String key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, int key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, double key) {
return deleteMember(env, new KeyCache(key));
}
public final Map<String, Member> getMembers(Environment env, boolean own, boolean onlyEnumerable) {
var res = new LinkedHashMap<String, Member>();
var protos = new ArrayList<Value>();
@ -277,7 +404,7 @@ public abstract class Value {
return res;
}
public final ObjectValue getMemberDescriptor(Environment env, Value key) {
var member = getOwnMember(env, key);
var member = getOwnMember(env, new KeyCache(key));
if (member != null) return member.descriptor(env, this);
else return null;
@ -394,7 +521,7 @@ public abstract class Value {
var curr = supplier.call(env, VoidValue.UNDEFINED);
if (curr == null) { supplier = null; value = null; }
if (curr.getMember(env, new StringValue("done")).toBoolean().value) { supplier = null; value = null; }
if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; }
else {
this.value = curr.getMember(env, new StringValue("value"));
consumed = false;
@ -425,8 +552,8 @@ public abstract class Value {
return new NativeFunction("", args -> {
var obj = new ObjectValue();
if (!it.hasNext()) obj.defineOwnMember(args.env, new StringValue("done"), FieldMember.of(BoolValue.TRUE));
else obj.defineOwnMember(args.env, new StringValue("value"), FieldMember.of(it.next()));
if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE));
else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next()));
return obj;
});
@ -526,11 +653,11 @@ public abstract class Value {
}
else if (this instanceof VoidValue) return ((VoidValue)this).name;
else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value));
else if (this instanceof SymbolValue) return this.toString();
else return this.toString(env).value;
}
public final String toReadable(Environment ext) {
if (this instanceof StringValue) return ((StringValue)this).value;
return toReadable(ext, new HashSet<>(), 0);
}

View File

@ -1,765 +0,0 @@
package me.topchetoeu.jscript.runtime.values;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.common.Operation;
// import me.topchetoeu.jscript.lib.PromiseLib;
import me.topchetoeu.jscript.runtime.debug.DebugContext;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.ConvertException;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider;
public class Values {
public static enum CompareResult {
NOT_EQUAL,
EQUAL,
LESS,
GREATER;
public boolean less() { return this == LESS; }
public boolean greater() { return this == GREATER; }
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
public static CompareResult from(int cmp) {
if (cmp < 0) return LESS;
if (cmp > 0) return GREATER;
return EQUAL;
}
}
public static final Object NULL = new Object();
public static final Object NO_RETURN = new Object();
public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; }
public static boolean isWrapper(Object val, Class<?> clazz) {
if (!isWrapper(val)) return false;
var res = (NativeWrapper)val;
return res != null && clazz.isInstance(res.wrapped);
}
public static boolean isNan(Object val) { return val instanceof Number && Double.isNaN(number(val)); }
public static double number(Object val) {
if (val instanceof Number) return ((Number)val).doubleValue();
else return Double.NaN;
}
@SuppressWarnings("unchecked")
public static <T> T wrapper(Object val, Class<T> clazz) {
if (isWrapper(val)) val = ((NativeWrapper)val).wrapped;
if (val != null && clazz.isInstance(val)) return (T)val;
else return null;
}
public static String type(Object val) {
if (val == null) return "undefined";
if (val instanceof String) return "string";
if (val instanceof Number) return "number";
if (val instanceof Boolean) return "boolean";
if (val instanceof SymbolValue) return "symbol";
if (val instanceof FunctionValue) return "function";
return "object";
}
private static Object tryCallConvertFunc(Environment ext, Object obj, String name) {
var func = getMember(ext, obj, name);
if (func instanceof FunctionValue) {
var res = Values.call(ext, func, obj);
if (isPrimitive(res)) return res;
}
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
public static boolean isPrimitive(Object obj) {
return
obj instanceof Number ||
obj instanceof String ||
obj instanceof Boolean ||
obj instanceof SymbolValue ||
obj == null ||
obj == NULL;
}
public static Object toPrimitive(Environment ext, Object obj, ConvertHint hint) {
obj = normalize(ext, obj);
if (isPrimitive(obj)) return obj;
var first = hint == ConvertHint.VALUEOF ? "valueOf" : "toString";
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
if (ext != null) {
try { return tryCallConvertFunc(ext, obj, first); }
catch (EngineException unused) { return tryCallConvertFunc(ext, obj, second); }
}
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
public static boolean toBoolean(Object obj) {
if (obj == NULL || obj == null) return false;
if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
if (obj instanceof String && ((String)obj).equals("")) return false;
if (obj instanceof Boolean) return (Boolean)obj;
return true;
}
public static double toNumber(Environment ext, Object obj) {
var val = toPrimitive(ext, obj, ConvertHint.VALUEOF);
if (val instanceof Number) return number(val);
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
if (val instanceof String) {
try { return Double.parseDouble((String)val); }
catch (NumberFormatException e) { return Double.NaN; }
}
return Double.NaN;
}
public static String toString(Environment ext, Object obj) {
var val = toPrimitive(ext, obj, ConvertHint.VALUEOF);
if (val == null) return "undefined";
if (val == NULL) return "null";
if (val instanceof Number) {
var d = number(val);
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
}
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val;
if (val instanceof SymbolValue) return val.toString();
return "Unknown value";
}
public static Object add(Environment ext, Object a, Object b) {
if (a instanceof String || b instanceof String) return toString(ext, a) + toString(ext, b);
else return toNumber(ext, a) + toNumber(ext, b);
}
public static double subtract(Environment ext, Object a, Object b) {
return toNumber(ext, a) - toNumber(ext, b);
}
public static double multiply(Environment ext, Object a, Object b) {
return toNumber(ext, a) * toNumber(ext, b);
}
public static double divide(Environment ext, Object a, Object b) {
return toNumber(ext, a) / toNumber(ext, b);
}
public static double modulo(Environment ext, Object a, Object b) {
return toNumber(ext, a) % toNumber(ext, b);
}
public static double negative(Environment ext, Object obj) {
return -toNumber(ext, obj);
}
public static int and(Environment ext, Object a, Object b) {
return (int)toNumber(ext, a) & (int)toNumber(ext, b);
}
public static int or(Environment ext, Object a, Object b) {
return (int)toNumber(ext, a) | (int)toNumber(ext, b);
}
public static int xor(Environment ext, Object a, Object b) {
return (int)toNumber(ext, a) ^ (int)toNumber(ext, b);
}
public static int bitwiseNot(Environment ext, Object obj) {
return ~(int)toNumber(ext, obj);
}
public static int shiftLeft(Environment ext, Object a, Object b) {
return (int)toNumber(ext, a) << (int)toNumber(ext, b);
}
public static int shiftRight(Environment ext, Object a, Object b) {
return (int)toNumber(ext, a) >> (int)toNumber(ext, b);
}
public static long unsignedShiftRight(Environment ext, Object a, Object b) {
long _a = (long)toNumber(ext, a);
long _b = (long)toNumber(ext, b);
if (_a < 0) _a += 0x100000000l;
if (_b < 0) _b += 0x100000000l;
return _a >>> _b;
}
public static CompareResult compare(Environment ext, Object a, Object b) {
a = toPrimitive(ext, a, ConvertHint.VALUEOF);
b = toPrimitive(ext, b, ConvertHint.VALUEOF);
if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
var _a = toNumber(ext, a);
var _b = toNumber(ext, b);
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
return CompareResult.from(Double.compare(_a, _b));
}
public static boolean not(Object obj) {
return !toBoolean(obj);
}
public static boolean isInstanceOf(Environment ext, Object obj, Object proto) {
if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
var val = getPrototype(ext, obj);
while (val != null) {
if (val.equals(proto)) return true;
val = val.getPrototype(ext);
}
return false;
}
public static Object operation(Environment ext, Operation op, Object ...args) {
switch (op) {
case ADD: return add(ext, args[0], args[1]);
case SUBTRACT: return subtract(ext, args[0], args[1]);
case DIVIDE: return divide(ext, args[0], args[1]);
case MULTIPLY: return multiply(ext, args[0], args[1]);
case MODULO: return modulo(ext, args[0], args[1]);
case AND: return and(ext, args[0], args[1]);
case OR: return or(ext, args[0], args[1]);
case XOR: return xor(ext, args[0], args[1]);
case EQUALS: return strictEquals(ext, args[0], args[1]);
case NOT_EQUALS: return !strictEquals(ext, args[0], args[1]);
case LOOSE_EQUALS: return looseEqual(ext, args[0], args[1]);
case LOOSE_NOT_EQUALS: return !looseEqual(ext, args[0], args[1]);
case GREATER: return compare(ext, args[0], args[1]).greater();
case GREATER_EQUALS: return compare(ext, args[0], args[1]).greaterOrEqual();
case LESS: return compare(ext, args[0], args[1]).less();
case LESS_EQUALS: return compare(ext, args[0], args[1]).lessOrEqual();
case INVERSE: return bitwiseNot(ext, args[0]);
case NOT: return not(args[0]);
case POS: return toNumber(ext, args[0]);
case NEG: return negative(ext, args[0]);
case SHIFT_LEFT: return shiftLeft(ext, args[0], args[1]);
case SHIFT_RIGHT: return shiftRight(ext, args[0], args[1]);
case USHIFT_RIGHT: return unsignedShiftRight(ext, args[0], args[1]);
case IN: return hasMember(ext, args[1], args[0], false);
case INSTANCEOF: {
var proto = getMember(ext, args[1], "prototype");
return isInstanceOf(ext, args[0], proto);
}
default: return null;
}
}
public static Object getMember(Environment ctx, Object obj, Object key) {
obj = normalize(ctx, obj); key = normalize(ctx, key);
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj);
if (obj instanceof String && key instanceof Number) {
var i = number(key);
var s = (String)obj;
if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) {
return s.charAt((int)i) + "";
}
}
var proto = getPrototype(ctx, obj);
if (proto == null) return "__proto__".equals(key) ? NULL : null;
else if (key != null && "__proto__".equals(key)) return proto;
else return proto.getMember(ctx, key, obj);
}
public static Object getMemberPath(Environment ctx, Object obj, Object ...path) {
var res = obj;
for (var key : path) res = getMember(ctx, res, key);
return res;
}
public static boolean setMember(Environment ctx, Object obj, Object key, Object val) {
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false);
var proto = getPrototype(ctx, obj);
return proto.setMember(ctx, key, val, obj, true);
}
public static boolean hasMember(Environment ctx, Object obj, Object key, boolean own) {
if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key);
if ("__proto__".equals(key)) return true;
if (obj instanceof ObjectValue) return ((ObjectValue)obj).hasMember(ctx, key, own);
if (obj instanceof String && key instanceof Number) {
var i = number(key);
var s = (String)obj;
if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) return true;
}
if (own) return false;
var proto = getPrototype(ctx, obj);
return proto != null && proto.hasMember(ctx, key, own);
}
public static boolean deleteMember(Environment ext, Object obj, Object key) {
if (obj == null || obj == NULL) return false;
obj = normalize(ext, obj); key = normalize(ext, key);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).deleteMember(ext, key);
else return false;
}
public static ObjectValue getPrototype(Environment ext, Object obj) {
if (obj == null || obj == NULL) return null;
obj = normalize(ext, obj);
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ext);
if (ext == null) return null;
if (obj instanceof String) return ext.get(Environment.STRING_PROTO);
else if (obj instanceof Number) return ext.get(Environment.NUMBER_PROTO);
else if (obj instanceof Boolean) return ext.get(Environment.BOOL_PROTO);
else if (obj instanceof SymbolValue) return ext.get(Environment.SYMBOL_PROTO);
return null;
}
public static boolean setPrototype(Environment ext, Object obj, Object proto) {
obj = normalize(ext, obj);
return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ext, proto);
}
public static void makePrototypeChain(Environment ext, Object... chain) {
for(var i = 1; i < chain.length; i++) {
setPrototype(ext, chain[i], chain[i - 1]);
}
}
public static List<Object> getMembers(Environment ext, Object obj, boolean own, boolean includeNonEnumerable) {
List<Object> res = new ArrayList<>();
if (obj instanceof ObjectValue) res = ((ObjectValue)obj).keys(includeNonEnumerable);
if (obj instanceof String) {
for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i);
}
if (!own) {
var proto = getPrototype(ext, obj);
while (proto != null) {
res.addAll(proto.keys(includeNonEnumerable));
proto = getPrototype(ext, proto);
}
}
return res;
}
public static ObjectValue getMemberDescriptor(Environment ext, Object obj, Object key) {
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ext, key);
else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue();
var _i = ((Number)key).doubleValue();
if (i - _i != 0) return null;
if (i < 0 || i >= ((String)obj).length()) return null;
return new ObjectValue(ext, Map.of(
"value", ((String)obj).charAt(i) + "",
"writable", false,
"enumerable", true,
"configurable", false
));
}
else return null;
}
public static Object call(Environment ext, Object func, Object thisArg, Object ...args) {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value.");
return ((FunctionValue)func).call(ext, thisArg, args);
}
public static Object callNew(Environment ext, Object func, Object ...args) {
var res = new ObjectValue();
try {
var proto = Values.getMember(ext, func, "prototype");
setPrototype(ext, res, proto);
var ret = call(ext, func, res, args);
if (!isPrimitive(ret)) return ret;
return res;
}
catch (IllegalArgumentException e) {
throw EngineException.ofType("Tried to call new on an invalid constructor.");
}
}
public static boolean strictEquals(Environment ext, Object a, Object b) {
a = normalize(ext, a);
b = normalize(ext, b);
if (a == null || b == null) return a == null && b == null;
if (isNan(a) || isNan(b)) return false;
if (a instanceof Number && number(a) == -0.) a = 0.;
if (b instanceof Number && number(b) == -0.) b = 0.;
return a == b || a.equals(b);
}
public static boolean looseEqual(Environment ext, Object a, Object b) {
a = normalize(ext, a); b = normalize(ext, b);
// In loose equality, null is equivalent to undefined
if (a == NULL) a = null;
if (b == NULL) b = null;
if (a == null || b == null) return a == null && b == null;
// If both are objects, just compare their references
if (!isPrimitive(a) && !isPrimitive(b)) return a == b;
// Convert values to primitives
a = toPrimitive(ext, a, ConvertHint.VALUEOF);
b = toPrimitive(ext, b, ConvertHint.VALUEOF);
// Compare symbols by reference
if (a instanceof SymbolValue || b instanceof SymbolValue) return a == b;
if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b);
if (a instanceof Number || b instanceof Number) return strictEquals(ext, toNumber(ext, a), toNumber(ext, b));
// Default to strings
return toString(ext, a).equals(toString(ext, b));
}
public static Object normalize(Environment ext, Object val) {
if (val instanceof Number) return number(val);
if (isPrimitive(val) || val instanceof ObjectValue) return val;
if (val instanceof Character) return val + "";
if (val instanceof Map) {
var res = new ObjectValue();
for (var entry : ((Map<?, ?>)val).entrySet()) {
res.defineProperty(ext, entry.getKey(), entry.getValue());
}
return res;
}
if (val instanceof Iterable) {
var res = new ArrayValue();
for (var entry : ((Iterable<?>)val)) {
res.set(ext, res.size(), entry);
}
return res;
}
if (val instanceof Class) {
if (ext == null) return null;
else return NativeWrapperProvider.get(ext).getConstr((Class<?>)val);
}
return NativeWrapper.of(ext, val);
}
@SuppressWarnings("unchecked")
public static <T> T convert(Environment ext, Object obj, Class<T> clazz) {
if (clazz == Void.class) return null;
if (obj instanceof NativeWrapper) {
var res = ((NativeWrapper)obj).wrapped;
if (clazz.isInstance(res)) return (T)res;
}
if (clazz == null || clazz == Object.class) return (T)obj;
if (obj instanceof ArrayValue) {
if (clazz.isAssignableFrom(ArrayList.class)) {
var raw = ((ArrayValue)obj).toArray();
var res = new ArrayList<>();
for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class));
return (T)new ArrayList<>(res);
}
if (clazz.isAssignableFrom(HashSet.class)) {
var raw = ((ArrayValue)obj).toArray();
var res = new HashSet<>();
for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class));
return (T)new HashSet<>(res);
}
if (clazz.isArray()) {
var raw = ((ArrayValue)obj).toArray();
Object res = Array.newInstance(clazz.getComponentType(), raw.length);
for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ext, raw[i], Object.class));
return (T)res;
}
}
if (obj instanceof ObjectValue && clazz.isAssignableFrom(HashMap.class)) {
var res = new HashMap<>();
for (var el : ((ObjectValue)obj).values.entrySet()) res.put(
convert(ext, el.getKey(), null),
convert(ext, el.getValue(), null)
);
return (T)res;
}
if (clazz == String.class) return (T)toString(ext, obj);
if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj);
if (clazz == Byte.class || clazz == byte.class) return (T)(Byte)(byte)toNumber(ext, obj);
if (clazz == Integer.class || clazz == int.class) return (T)(Integer)(int)toNumber(ext, obj);
if (clazz == Long.class || clazz == long.class) return (T)(Long)(long)toNumber(ext, obj);
if (clazz == Short.class || clazz == short.class) return (T)(Short)(short)toNumber(ext, obj);
if (clazz == Float.class || clazz == float.class) return (T)(Float)(float)toNumber(ext, obj);
if (clazz == Double.class || clazz == double.class) return (T)(Double)toNumber(ext, obj);
if (clazz == Character.class || clazz == char.class) {
if (obj instanceof Number) return (T)(Character)(char)number(obj);
else {
var res = toString(ext, obj);
if (res.length() == 0) throw new ConvertException("\"\"", "Character");
else return (T)(Character)res.charAt(0);
}
}
if (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj;
if (clazz.isAssignableFrom(NativeWrapper.class)) {
return (T)NativeWrapper.of(ext, obj);
}
throw new ConvertException(type(obj), clazz.getSimpleName());
}
public static Iterable<Object> fromJSIterator(Environment ext, Object obj) {
return () -> {
try {
var symbol = SymbolValue.get("Symbol.iterator");
var iteratorFunc = getMember(ext, obj, symbol);
if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator();
var iterator = iteratorFunc instanceof FunctionValue ?
((FunctionValue)iteratorFunc).call(ext, obj, obj) :
iteratorFunc;
var nextFunc = getMember(ext, call(ext, iteratorFunc, obj), "next");
if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator();
return new Iterator<Object>() {
private Object value = null;
public boolean consumed = true;
private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() {
if (next == null) value = null;
else if (consumed) {
var curr = next.call(ext, iterator);
if (curr == null) { next = null; value = null; }
if (toBoolean(Values.getMember(ext, curr, "done"))) { next = null; value = null; }
else {
this.value = Values.getMember(ext, curr, "value");
consumed = false;
}
}
}
@Override
public boolean hasNext() {
loadNext();
return next != null;
}
@Override
public Object next() {
loadNext();
var res = value;
value = null;
consumed = true;
return res;
}
};
}
catch (IllegalArgumentException | NullPointerException e) {
return Collections.emptyIterator();
}
};
}
public static ObjectValue toJSIterator(Environment ext, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMember(ext, getMember(ext, ext.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
res.defineProperty(ext, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ext, "next", new NativeFunction("", args -> {
if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true));
else {
var obj = new ObjectValue();
obj.defineProperty(args.env, "value", it.next());
return obj;
}
}));
return res;
}
public static ObjectValue toJSIterator(Environment ext, Iterable<?> it) {
return toJSIterator(ext, it.iterator());
}
public static ObjectValue toJSAsyncIterator(Environment ext, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMemberPath(ext, ext.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
res.defineProperty(ext, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ext, "next", new NativeFunction("", args -> {
return PromiseLib.await(args.env, () -> {
if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true));
else {
var obj = new ObjectValue();
object.defineProperty(args.env, "value", it.next());
return object;
}
});
}));
return res;
}
private static boolean isEmptyFunc(ObjectValue val) {
if (!(val instanceof FunctionValue)) return false;
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
var proto = val.values.get("prototype");
if (!(proto instanceof ObjectValue)) return false;
var protoObj = (ObjectValue)proto;
if (protoObj.values.get("constructor") != val) return false;
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
return true;
}
private static String toReadable(Environment ext, Object val, HashSet<Object> passed, int tab) {
if (tab == 0 && val instanceof String) return (String)val;
if (passed.contains(val)) return "[circular]";
var printed = true;
var res = new StringBuilder();
var dbg = DebugContext.get(ext);
if (val instanceof FunctionValue) {
res.append(val.toString());
var loc = val instanceof CodeFunction ? dbg.getMapOrEmpty((CodeFunction)val).start() : null;
if (loc != null) res.append(" @ " + loc);
}
else if (val instanceof ArrayValue) {
res.append("[");
var obj = ((ArrayValue)val);
for (int i = 0; i < obj.size(); i++) {
if (i != 0) res.append(", ");
else res.append(" ");
if (obj.has(i)) res.append(toReadable(ext, obj.get(i), passed, tab));
else res.append("<empty>");
}
res.append(" ] ");
}
else if (val instanceof NativeWrapper) {
var obj = ((NativeWrapper)val).wrapped;
res.append("Native " + obj.toString() + " ");
}
else printed = false;
if (val instanceof ObjectValue) {
if (tab > 3) {
return "{...}";
}
passed.add(val);
var obj = (ObjectValue)val;
if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
if (!printed) res.append("{}\n");
}
else {
res.append("{\n");
for (var el : obj.values.entrySet()) {
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ext, el.getKey(), passed, tab + 1));
res.append(": ");
res.append(toReadable(ext, el.getValue(), passed, tab + 1));
res.append(",\n");
}
for (var el : obj.properties.entrySet()) {
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ext, el.getKey(), passed, tab + 1));
res.append(": [prop],\n");
}
for (int i = 0; i < tab; i++) res.append(" ");
res.append("}");
}
passed.remove(val);
}
else if (val == null) return "undefined";
else if (val == Values.NULL) return "null";
else if (val instanceof String) return "'" + val + "'";
else return Values.toString(ext, val);
return res.toString();
}
public static String toReadable(Environment ext, Object val) {
return toReadable(ext, val, new HashSet<>(), 0);
}
public static String errorToReadable(RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
if (err instanceof EngineException) {
var ee = ((EngineException)err);
try {
return prefix + " " + ee.toString(ee.env);
}
catch (EngineException ex) {
return prefix + " " + toReadable(ee.env, ee.value);
}
}
else if (err instanceof SyntaxException) {
return prefix + " SyntaxError " + ((SyntaxException)err).msg;
}
else if (err.getCause() instanceof InterruptedException) return "";
else {
var str = new ByteArrayOutputStream();
err.printStackTrace(new PrintStream(str));
return prefix + " internal error " + str.toString();
}
}
public static void printValue(Environment ext, Object val) {
System.out.print(toReadable(ext, val));
}
public static void printError(RuntimeException err, String prefix) {
System.out.println(errorToReadable(err, prefix));
}
}

View File

@ -9,6 +9,7 @@ public class Arguments {
public final Value self;
public final Value[] args;
public final Environment env;
public final boolean isNew;
public int n() {
return args.length;
@ -31,9 +32,10 @@ public class Arguments {
else return get(i);
}
public Arguments(Environment env, Value thisArg, Value... args) {
public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) {
this.env = env;
this.args = args;
this.self = thisArg;
this.isNew = isNew;
}
}

View File

@ -11,8 +11,8 @@ public class CodeFunction extends FunctionValue {
public final ValueVariable[] captures;
public Environment env;
@Override public Value call(Environment env, Value thisArg, Value ...args) {
var frame = new Frame(env, thisArg, args, this);
@Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) {
var frame = new Frame(env, isNew, thisArg, args, this);
frame.onPush();
try {

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.runtime.values.functions;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
@ -13,8 +14,12 @@ public abstract class FunctionValue extends ObjectValue {
public int length;
public Value prototype = new ObjectValue();
private final FieldMember nameField = new FieldMember(false, true, true) {
public boolean enableCall = true;
public boolean enableNew = true;
private final FieldMember nameField = new FieldMember(true, false, false) {
@Override public Value get(Environment env, Value self) {
if (name == null) return new StringValue("");
return new StringValue(name);
}
@Override public boolean set(Environment env, Value val, Value self) {
@ -22,7 +27,7 @@ public abstract class FunctionValue extends ObjectValue {
return true;
}
};
private final FieldMember lengthField = new FieldMember(false, true, false) {
private final FieldMember lengthField = new FieldMember(true, false, false) {
@Override public Value get(Environment env, Value self) {
return new NumberValue(length);
}
@ -30,7 +35,7 @@ public abstract class FunctionValue extends ObjectValue {
return false;
}
};
private final FieldMember prototypeField = new FieldMember(false, true, true) {
private final FieldMember prototypeField = new FieldMember(false, false, true) {
@Override public Value get(Environment env, Value self) {
return prototype;
}
@ -40,28 +45,40 @@ public abstract class FunctionValue extends ObjectValue {
}
};
protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args);
@Override public String toString() { return String.format("function %s(...)", name); }
@Override public abstract Value call(Environment ext, Value thisArg, Value ...args);
@Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) {
if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args);
if (!isNew && !enableCall) super.call(ext, isNew, name, thisArg, args);
@Override public Member getOwnMember(Environment env, Value key) {
var el = key.toString(env).value;
if (el.equals("length")) return lengthField;
if (el.equals("name")) return nameField;
if (el.equals("prototype")) return prototypeField;
return super.getOwnMember(env, key);
return onCall(ext, isNew, name, thisArg, args);
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
if (!super.deleteOwnMember(env, key)) return false;
var el = key.toString(env).value;
@Override public Member getOwnMember(Environment env, KeyCache key) {
switch (key.toString(env)) {
case "length": return lengthField;
case "name": return nameField;
case "prototype": return prototypeField;
default: return super.getOwnMember(env, key);
}
}
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
switch (key.toString(env)) {
case "length":
length = 0;
return true;
case "name":
name = "";
return true;
case "prototype":
return false;
default: return super.deleteOwnMember(env, key);
}
}
if (el.equals("length")) return false;
if (el.equals("name")) return false;
if (el.equals("prototype")) return false;
return true;
public void setName(String val) {
if (this.name == null || this.name.equals("")) this.name = val;
}
public FunctionValue(String name, int length) {
@ -71,7 +88,7 @@ public abstract class FunctionValue extends ObjectValue {
this.length = length;
this.name = name;
prototype.defineOwnMember(Environment.empty(), new StringValue("constructor"), FieldMember.of(this));
prototype.defineOwnMember(null, "constructor", FieldMember.of(this));
}
}

View File

@ -10,8 +10,8 @@ public class NativeFunction extends FunctionValue {
public final NativeFunctionRunner action;
@Override public Value call(Environment env, Value self, Value ...args) {
return action.run(new Arguments(env, self, args));
@Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) {
return action.run(new Arguments(env, isNew, self, args));
}
public NativeFunction(String name, NativeFunctionRunner action) {

View File

@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.Value;
@ -19,6 +20,16 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
private Value[] values;
private int size;
private final FieldMember lengthField = new FieldMember(false, false, true) {
@Override public Value get(Environment env, Value self) {
return new NumberValue(size);
}
@Override public boolean set(Environment env, Value val, Value self) {
size = val.toInt(env);
return true;
}
};
private class IndexField extends FieldMember {
private int i;
private ArrayValue arr;
@ -44,7 +55,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
var arr = new Value[index];
System.arraycopy(values, 0, arr, 0, values.length);
return arr;
return values = arr;
}
public int size() { return size; }
@ -95,11 +106,11 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
}
public void copyTo(Value[] arr, int sourceStart, int destStart, int count) {
var nullFill = values.length - destStart + count;
var nullFill = Math.max(0, arr.length - size - destStart);
count -= nullFill;
System.arraycopy(values, sourceStart, arr, destStart, count);
Arrays.fill(arr, count, nullFill, null);
Arrays.fill(arr, count, nullFill + count, null);
}
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
if (arr == this) {
@ -138,36 +149,37 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
});
}
@Override public Member getOwnMember(Environment env, Value key) {
@Override public Member getOwnMember(Environment env, KeyCache key) {
var res = super.getOwnMember(env, key);
if (res != null) return res;
var num = key.toNumber(env);
var i = num.toInt(env);
var i = key.toInt(env);
if (i == num.value && i >= 0 && i < size) return new IndexField(i, this);
if (i == num && i >= 0 && i < size && has(i)) return new IndexField(i, this);
else if (key.toString(env).equals("length")) return lengthField;
else return null;
}
@Override public boolean defineOwnMember(Environment env, Value key, Member member) {
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
if (!(member instanceof FieldMember) || hasMember(env, key, true)) return super.defineOwnMember(env, key, member);
if (!extensible) return false;
var num = key.toNumber(env);
var i = num.toInt(env);
var i = key.toInt(env);
if (i == num.value && i >= 0) {
if (i == num && i >= 0) {
set(i, ((FieldMember)member).get(env, this));
return true;
}
else return super.defineOwnMember(env, key, member);
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!super.deleteOwnMember(env, key)) return false;
var num = key.toNumber(env);
var i = num.toInt(env);
var i = key.toInt(env);
if (i == num.value && i >= 0 && i < size) return super.deleteOwnMember(env, key);
if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key);
else return true;
}
@ -175,9 +187,12 @@ public class ArrayValue extends ObjectValue implements Iterable<Value> {
var res = new LinkedHashMap<String, Member>();
for (var i = 0; i < size; i++) {
res.put(i + "", getOwnMember(env, new NumberValue(i)));
var member = getOwnMember(env, i);
if (member != null) res.put(i + "", member);
}
res.put("length", lengthField);
res.putAll(super.getOwnMembers(env));
return res;

View File

@ -7,10 +7,10 @@ import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.environment.Key;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
@ -66,7 +66,7 @@ public class ObjectValue extends Value {
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
@Override public StringValue toString(Environment env) { return toPrimitive(env).toString(env); }
@Override public BoolValue toBoolean() { return BoolValue.TRUE; }
@Override public boolean toBoolean() { return true; }
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
@Override public StringValue type() { return typeString; }
@ -76,33 +76,29 @@ public class ObjectValue extends Value {
extensible = false;
}
@Override public Member getOwnMember(Environment env, Value key) {
if (key instanceof SymbolValue) return symbolMembers.get(key);
else return members.get(key.toString(env).value);
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) return symbolMembers.get(key.toSymbol());
else return members.get(key.toString(env));
}
@Override public boolean defineOwnMember(Environment env, Value key, Member member) {
if (!(key instanceof SymbolValue)) key = key.toString(env);
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) {
var old = getOwnMember(env, key);
if (old != null && old.configure(env, member, this)) return true;
if (old != null && !old.configurable()) return false;
if (key instanceof SymbolValue) symbolMembers.put((SymbolValue)key, member);
else members.put(key.toString(env).value, member);
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member);
else members.put(key.toString(env), member);
return true;
}
@Override public boolean deleteOwnMember(Environment env, Value key) {
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!extensible) return false;
if (!(key instanceof SymbolValue)) key = key.toString(env);
var member = getOwnMember(env, key);
if (member == null) return true;
if (member.configurable()) return false;
if (key instanceof SymbolValue) symbolMembers.remove(key);
else members.remove(key.toString(env).value);
if (key.isSymbol()) symbolMembers.remove(key.toSymbol());
else members.remove(key.toString(env));
return true;
}
@ -114,7 +110,7 @@ public class ObjectValue extends Value {
}
@Override public ObjectValue getPrototype(Environment env) {
if (prototype == null) return null;
if (prototype == null || env == null) return null;
else return prototype.get(env);
}
@Override public final boolean setPrototype(Environment env, ObjectValue val) {

View File

@ -4,7 +4,6 @@ import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.scope.ValueVariable;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public class ScopeValue extends ObjectValue {
private class VariableField extends FieldMember {
@ -29,7 +28,7 @@ public class ScopeValue extends ObjectValue {
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
defineOwnMember(Environment.empty(), new StringValue(i + ""), new VariableField(i));
defineOwnMember(Environment.empty(), i, new VariableField(i));
}
}
}

View File

@ -13,7 +13,7 @@ public final class BoolValue extends PrimitiveValue {
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return this; }
@Override public boolean toBoolean() { return value; }
@Override public NumberValue toNumber(Environment ext) {
return value ? new NumberValue(1) : new NumberValue(0);
}

View File

@ -1,7 +1,9 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.math.BigDecimal;
import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
@ -14,21 +16,19 @@ public final class NumberValue extends PrimitiveValue {
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.of(value != 0); }
@Override public boolean toBoolean() { return value != 0; }
@Override public NumberValue toNumber(Environment ext) { return this; }
@Override public StringValue toString(Environment ext) { return new StringValue(toString()); }
@Override public String toString() {
var d = value;
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
}
@Override public String toString() { return JSON.stringify(JSONElement.number(value)); }
@Override public ObjectValue getPrototype(Environment env) {
return env.get(Environment.NUMBER_PROTO);
}
@Override public CompareResult compare(Environment env, Value other) {
if (other instanceof NumberValue) return CompareResult.from(Double.compare(value, ((NumberValue)other).value));
else return super.compare(env, other);
}
@Override public boolean strictEquals(Environment ext, Value other) {
other = other.toPrimitive(ext);
if (other instanceof NumberValue) return value == ((NumberValue)other).value;
@ -39,42 +39,22 @@ public final class NumberValue extends PrimitiveValue {
this.value = value;
}
public static double parseFloat(String val, boolean tolerant, String alphabet) {
val = val.trim();
public static NumberValue parseInt(String str, int radix, boolean relaxed) {
if (radix < 2 || radix > 36) return new NumberValue(Double.NaN);
int res = 0;
for (int i = 0; i >= val.length(); i++) {
var c = alphabet.indexOf(val.charAt(i));
if (c < 0) {
if (tolerant) return res;
else return Double.NaN;
}
res *= alphabet.length();
res += c;
str = str.trim();
var res = Parsing.parseInt(new Source(null, str), 0, "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, radix), true);
if (res.isSuccess()) {
if (relaxed || res.n == str.length()) return new NumberValue(res.result);
}
return res;
return new NumberValue(Double.NaN);
}
public static double parseInt(String val, boolean tolerant, String alphabet) {
val = val.trim();
int res = 0;
for (int i = 0; i >= val.length(); i++) {
var c = alphabet.indexOf(val.charAt(i));
if (c < 0) {
if (tolerant) return res;
else return Double.NaN;
}
res *= alphabet.length();
res += c;
public static NumberValue parseFloat(String str, boolean relaxed) {
str = str.trim();
var res = Parsing.parseFloat(new Source(null, str), 0, true);
if (res.isSuccess()) {
if (relaxed || res.n == str.length()) return new NumberValue(res.result);
}
return res;
return new NumberValue(Double.NaN);
}
}

View File

@ -3,19 +3,20 @@ package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public abstract class PrimitiveValue extends Value {
@Override public final boolean defineOwnMember(Environment env, Value key, Member member) { return false; }
@Override public final boolean deleteOwnMember(Environment env, Value key) { return false; }
@Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; }
@Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; }
@Override public final boolean isPrimitive() { return true; }
@Override public final Value toPrimitive(Environment env) { return this; }
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
@Override public Member getOwnMember(Environment env, Value key) { return null; }
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
@Override public Map<String, Member> getOwnMembers(Environment env) { return Map.of(); }
@Override public Map<SymbolValue, Member> getOwnSymbolMembers(Environment env) { return Map.of(); }
}

View File

@ -1,8 +1,12 @@
package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.Map;
import java.util.Objects;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
@ -12,10 +16,14 @@ public final class StringValue extends PrimitiveValue {
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.of(!value.equals("")); }
@Override public boolean toBoolean() { return !value.equals(""); }
@Override public NumberValue toNumber(Environment ext) {
try { return new NumberValue(Double.parseDouble(value)); }
catch (NumberFormatException e) { return new NumberValue(Double.NaN); }
var val = value.trim();
if (val.equals("")) return new NumberValue(0);
var res = Parsing.parseNumber(new Source(null, val), 0, true);
if (res.isSuccess() && res.n == val.length()) return new NumberValue(res.result);
else return new NumberValue(Double.NaN);
}
@Override public StringValue toString(Environment ext) { return this; }
@ -23,11 +31,20 @@ public final class StringValue extends PrimitiveValue {
return new StringValue(value + other.toString(ext).value);
}
@Override public CompareResult compare(Environment env, Value other) {
if (other instanceof StringValue) return CompareResult.from(value.compareTo(((StringValue)other).value));
else return super.compare(env, other);
}
@Override public boolean strictEquals(Environment ext, Value other) {
return (other instanceof StringValue) && Objects.equals(((StringValue)other).value, value);
}
@Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.STRING_PROTO); }
@Override public Map<String, Member> getOwnMembers(Environment env) {
// TODO Auto-generated method stub
return super.getOwnMembers(env);
}
public StringValue(String value) {
this.value = value;
}

View File

@ -13,14 +13,18 @@ public final class SymbolValue extends PrimitiveValue {
public final String value;
public Value key() {
return registry.containsKey(value) && registry.get(value) == this ? new StringValue(value) : VoidValue.UNDEFINED;
}
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.TRUE; }
@Override public boolean toBoolean() { return false; }
@Override public StringValue toString(Environment env) {
return new StringValue(toString());
throw EngineException.ofType("Cannot convert a Symbol value to a string");
}
@Override public NumberValue toNumber(Environment env) {
throw EngineException.ofType("Can't convert symbol to number");
throw EngineException.ofType("Cannot convert a Symbol value to a number");
}
@Override public boolean strictEquals(Environment ext, Value other) {
@ -29,8 +33,8 @@ public final class SymbolValue extends PrimitiveValue {
@Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.SYMBOL_PROTO); }
@Override public String toString() {
if (value == null) return "Symbol";
else return "@@" + value;
if (value == null) return "Symbol()";
else return "Symbol(" + value + ")";
}
public SymbolValue(String value) {

View File

@ -4,13 +4,14 @@ import java.util.Map;
import me.topchetoeu.jscript.runtime.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
public final class VoidValue extends PrimitiveValue {
public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined"));
public static final VoidValue NULL = new VoidValue("null", new StringValue("null"));
public static final VoidValue NULL = new VoidValue("null", new StringValue("object"));
private final StringValue namestring;
@ -18,7 +19,7 @@ public final class VoidValue extends PrimitiveValue {
public final StringValue typeString;
@Override public StringValue type() { return typeString; }
@Override public BoolValue toBoolean() { return BoolValue.FALSE; }
@Override public boolean toBoolean() { return false; }
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
@Override public StringValue toString(Environment ext) { return namestring; }
@ -34,8 +35,8 @@ public final class VoidValue extends PrimitiveValue {
}
@Override public ObjectValue getPrototype(Environment env) { return null; }
@Override public Member getOwnMember(Environment env, Value key) {
throw EngineException.ofError(String.format("Cannot read properties of %s (reading %s)", name, key.toString(env).value));
@Override public Member getOwnMember(Environment env, KeyCache key) {
throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env)));
}
@Override public Map<String, Member> getOwnMembers(Environment env) {
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name));
@ -44,6 +45,10 @@ public final class VoidValue extends PrimitiveValue {
throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name));
}
// @Override public Value call(Environment env, Value self, Value... args) {
// throw EngineException.ofType(String.format("Tried to call a value of %s", name));
// }
public VoidValue(String name, StringValue type) {
this.name = name;
this.typeString = type;