From 59e6f34a01013f58061c1511f6e362686756f634 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:37:13 +0300 Subject: [PATCH] refactor: clean up REPL stringification code --- .../jscript/runtime/values/Value.java | 106 +----------------- .../values/functions/FunctionValue.java | 20 ++++ .../values/objects/ArrayLikeValue.java | 90 +++++++++++++++ .../runtime/values/objects/ObjectValue.java | 80 +++++++++++++ .../values/primitives/StringValue.java | 9 ++ .../runtime/values/primitives/VoidValue.java | 8 ++ .../values/primitives/numbers/IntValue.java | 11 ++ 7 files changed, 224 insertions(+), 100 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 0604cdd..2979392 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -3,27 +3,25 @@ package me.topchetoeu.jscript.runtime.values; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; 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.runtime.values.primitives.BoolValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; @@ -429,105 +427,13 @@ public abstract class Value { } } - private final String toReadable(Environment env, HashSet passed, int tab) { - if (passed.contains(this)) return "[circular]"; - - if (this instanceof ObjectValue obj) { - var res = new StringBuilder(); - var dbg = DebugContext.get(env); - var printed = true; - var keys = this.getMembers(env, true, false); - - if (this instanceof FunctionValue func) { - res.append(this.toString()); - var loc = dbg.getMapOrEmpty(func).start(); - - if (loc != null) res.append(" @ " + loc); - - if ( - func.prototype instanceof ObjectValue objProto && - objProto.getMember(env, "constructor") == func && - objProto.getOwnMembers(env, true).size() + objProto.getOwnSymbolMembers(env, true).size() == 1 - ) { keys.remove("constructor"); } - } - else if (this instanceof ArrayValue) { - res.append("["); - var arr = (ArrayValue)this; - - for (int i = 0; i < arr.size(); i++) { - if (i != 0) res.append(", "); - else res.append(" "); - - if (arr.hasMember(env, i, true)) { - res.append(arr.getMember(env, i).toReadable(env, passed, tab)); - keys.remove(i + ""); - } - else res.append(""); - } - - res.append(" ] "); - } - else printed = false; - - - passed.add(this); - - if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) { - if (!printed) res.append("{}"); - } - else if (!printed) { - if (tab > 3) return "{...}"; - res.append("{\n"); - - for (var entry : obj.getOwnSymbolMembers(env, true)) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append("[" + entry.value + "]" + ": "); - - var member = obj.getOwnMember(env, entry); - if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else if (member instanceof PropertyMember prop) { - if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); - else if (prop.getter == null) res.append("[Setter]"); - else if (prop.setter == null) res.append("[Getter]"); - else res.append("[Getter/Setter]"); - } - else res.append("[???]"); - - res.append(",\n"); - } - for (var entry : obj.getOwnMembers(env, true)) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(entry + ": "); - - var member = obj.getOwnMember(env, entry); - if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else if (member instanceof PropertyMember prop) { - if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); - else if (prop.getter == null) res.append("[Setter]"); - else if (prop.setter == null) res.append("[Getter]"); - else res.append("[Getter/Setter]"); - } - else res.append("[???]"); - - res.append(",\n"); - } - - for (int i = 0; i < tab; i++) res.append(" "); - res.append("}"); - } - - passed.remove(this); - return res.toString(); - } - 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 if (this instanceof NumberValue num && num.isLong()) return num.getLong() + "i"; - else return this.toString(env); + /** @internal */ + public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(toString(env)); } public final String toReadable(Environment ext) { - return toReadable(ext, new HashSet<>(), 0); + return String.join("\n", toReadableLines(ext, new HashSet<>())); } public static final ObjectValue global(Environment env) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 7e49161..6bfbb8d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -1,6 +1,12 @@ package me.topchetoeu.jscript.runtime.values.functions; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; @@ -83,6 +89,20 @@ public abstract class FunctionValue extends ObjectValue { @Override public StringValue type() { return StringValue.of("function"); } + @Override public List toReadableLines(Environment env, HashSet passed) { + var dbg = DebugContext.get(env); + var res = new StringBuilder(this.toString()); + var loc = dbg.getMapOrEmpty(this).start(); + + if (loc != null) res.append(" @ " + loc); + + var lines = new LinkedList(super.toReadableLines(env, passed)); + if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString()); + lines.set(0, res.toString() + " " + lines.getFirst()); + + return lines; + } + public void setName(String val) { if (this.name == null || this.name.equals("")) this.name = val; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java index 4b39653..6de8e2f 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -1,6 +1,10 @@ package me.topchetoeu.jscript.runtime.values.objects; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; @@ -104,4 +108,90 @@ public abstract class ArrayLikeValue extends ObjectValue { return res; } + + private LinkedList toReadableBase(Environment env, HashSet passed, HashSet ignoredKeys) { + var stringified = new LinkedList>(); + + passed.add(this); + + var emptyN = 0; + + for (int i = 0; i < size(); i++) { + if (has(i)) { + String emptyStr = null; + + if (emptyN == 1) emptyStr = ""; + else if (emptyN > 1) emptyStr = ""; + + if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ","))); + emptyN = 0; + + stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed))); + ignoredKeys.add(i + ""); + + var entry = stringified.getLast(); + entry.set(entry.size() - 1, entry.getLast() + ","); + } + else { + emptyN++; + } + } + + String emptyStr = null; + + if (emptyN == 1) emptyStr = ""; + else if (emptyN > 1) emptyStr = ""; + + if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr))); + else if (stringified.size() > 0) { + var lastEntry = stringified.getLast(); + lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1)); + } + + + passed.remove(this); + + if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]")); + var concat = new StringBuilder(); + for (var entry : stringified) { + // We make a one-liner only when all members are one-liners + if (entry.size() != 1) { + concat = null; + break; + } + + if (concat.length() != 0) concat.append(" "); + concat.append(entry.get(0)); + } + + // We don't want too long one-liners + if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]")); + + var res = new LinkedList(); + + res.add("["); + + for (var entry : stringified) { + for (var line : entry) { + res.add(" " + line); + } + } + res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1)); + res.add("]"); + + return res; + } + + @Override public List toReadableLines(Environment env, HashSet passed) { + var ignored = new HashSet(); + var lines = toReadableBase(env, passed, ignored); + + var superLines = new LinkedList(super.toReadableLines(env, passed, ignored)); + if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines; + + lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst()); + lines.addAll(superLines.subList(1, superLines.size())); + + return lines; + } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index c492bbe..616b775 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -1,7 +1,11 @@ package me.topchetoeu.jscript.runtime.values.objects; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; @@ -10,6 +14,7 @@ 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.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; @@ -134,6 +139,81 @@ public class ObjectValue extends Value { return setPrototype(_env -> val); } + private final LinkedList memberToReadable(Environment env, String key, Member member, HashSet passed) { + if (member instanceof PropertyMember prop) { + if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]")); + else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]")); + else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]")); + else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]")); + } + else { + var res = new LinkedList(); + var first = true; + + for (var line : member.get(env, this).toReadableLines(env, passed)) { + if (first) res.add(key + ": " + line); + else res.add(line); + first = false; + } + + return res; + } + } + + public List toReadableLines(Environment env, HashSet passed, HashSet ignoredKeys) { + passed.add(this); + + var stringified = new LinkedList>(); + + for (var entry : getOwnSymbolMembers(env, true)) { + var member = getOwnMember(env, entry); + stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed)); + } + for (var entry : getOwnMembers(env, true)) { + if (ignoredKeys.contains(entry)) continue; + + var member = getOwnMember(env, entry); + stringified.add(memberToReadable(env, entry, member, passed)); + } + + passed.remove(this); + + if (stringified.size() == 0) return Arrays.asList("{}"); + var concat = new StringBuilder(); + for (var entry : stringified) { + // We make a one-liner only when all members are one-liners + if (entry.size() != 1) { + concat = null; + break; + } + + if (concat.length() != 0) concat.append(", "); + concat.append(entry.get(0)); + } + + // We don't want too long one-liners + if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }"); + + var res = new LinkedList(); + + res.add("{"); + + for (var entry : stringified) { + for (var line : entry) { + res.add(" " + line); + } + + res.set(res.size() - 1, res.getLast() + ","); + } + res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1)); + res.add("}"); + + return res; + } + @Override public List toReadableLines(Environment env, HashSet passed) { + return toReadableLines(env, passed, new HashSet<>()); + } + public final boolean setPrototype(PrototypeProvider val) { if (!getState().extendable) return false; prototype = val; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index c715c51..7789e06 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -1,10 +1,15 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.WeakHashMap; import me.topchetoeu.jscript.common.environment.Environment; +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.values.KeyCache; @@ -67,6 +72,10 @@ public final class StringValue extends PrimitiveValue { return res; } + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(JSON.stringify(JSONElement.string(value))); + } + private StringValue(String value) { this.value = value; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 0949ef3..07c53d6 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -1,5 +1,9 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -22,6 +26,10 @@ public final class VoidValue extends PrimitiveValue { throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); } + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(name); + } + public VoidValue(String name, String type) { this.name = name; this.type = type; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java index 9d90a75..5124c1b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java @@ -1,5 +1,12 @@ package me.topchetoeu.jscript.runtime.values.primitives.numbers; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; + public final class IntValue extends NumberValue { public final long value; @@ -26,6 +33,10 @@ public final class IntValue extends NumberValue { else return false; } + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(value + "i"); + } + public IntValue(long value) { this.value = value; }