Add VSMX bytecode interpreter
- VsmxInterpreter: stack machine executing all 74 VSMX opcodes (arithmetic, comparisons, bitwise, jumps, calls, arrays, objects). - Object model: VsmxBaseObject/Undefined/Null/Boolean/Number/String/ Object/Array/Function/Reference/LocalVarReference/Method/CallState/ NativeObject, plus BaseNativeObject for future native objects. - CLI 'rco' now runs the menu script headlessly and prints the 172 resulting global variables (state, chapter/language config, focus). - Validated against Batman Begins: script runs with zero unimplemented- opcode warnings and defines all menu functions (on_cancel, mm_on_push, trans_to_play, play_movie_beginning, ...).
This commit is contained in:
@@ -11,6 +11,9 @@ import dev.quoije.umd.player.PlaybackEngine;
|
||||
import dev.quoije.umd.playlist.Playlist;
|
||||
import dev.quoije.umd.rco.RcoFile;
|
||||
import dev.quoije.umd.rco.vsmx.Vsmx;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
|
||||
import dev.quoije.umd.sfo.Sfo;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -414,13 +417,23 @@ public final class Main {
|
||||
Vsmx vsmx = new Vsmx(vsmxData, "menu");
|
||||
System.out.printf("vsmx: %d code groups, %d texts, %d props, %d names%n",
|
||||
vsmx.codes().length, vsmx.texts().length, vsmx.properties().length, vsmx.names().length);
|
||||
List<String> lines = vsmx.disassemble();
|
||||
int shown = Math.min(lines.size(), 100);
|
||||
for (int i = 0; i < shown; i++) {
|
||||
System.out.println(" " + lines.get(i));
|
||||
|
||||
VsmxInterpreter interpreter = new VsmxInterpreter();
|
||||
interpreter.setVsmx(vsmx);
|
||||
VsmxObject globals = new VsmxObject(interpreter, null);
|
||||
interpreter.run(globals);
|
||||
|
||||
System.out.printf("global variables (%d):%n", globals.getPropertyNames().size());
|
||||
for (String varName : globals.getPropertyNames()) {
|
||||
VsmxBaseObject value = globals.getPropertyValue(varName);
|
||||
System.out.printf(" %s = %s%n", varName, value);
|
||||
}
|
||||
if (lines.size() > shown) {
|
||||
System.out.printf(" ... (%d more lines)%n", lines.size() - shown);
|
||||
|
||||
if (!interpreter.getWarnings().isEmpty()) {
|
||||
System.out.printf("warnings (%d):%n", interpreter.getWarnings().size());
|
||||
for (String warning : interpreter.getWarnings()) {
|
||||
System.out.println(" " + warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** An array object with an explicit length. */
|
||||
public class VsmxArray extends VsmxObject {
|
||||
|
||||
private static final String CLASS_NAME = "Array";
|
||||
private int length;
|
||||
|
||||
public VsmxArray(VsmxInterpreter interpreter) {
|
||||
super(interpreter, CLASS_NAME);
|
||||
}
|
||||
|
||||
public VsmxArray(VsmxInterpreter interpreter, int size) {
|
||||
super(interpreter, CLASS_NAME);
|
||||
if (size > 0) {
|
||||
length = size;
|
||||
for (int i = 0; i < size; i++) {
|
||||
create(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
private void create(int index) {
|
||||
super.setPropertyValue(Integer.toString(index), VsmxUndefined.singleton);
|
||||
}
|
||||
|
||||
private void delete(int index) {
|
||||
super.deletePropertyValue(Integer.toString(index));
|
||||
}
|
||||
|
||||
private void updateLength(int index) {
|
||||
if (index >= length) {
|
||||
for (int i = length; i <= index; i++) {
|
||||
create(i);
|
||||
}
|
||||
length = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getPropertyValue(String name) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
return new VsmxNumber(interpreter, length);
|
||||
}
|
||||
int index = getIndex(name);
|
||||
if (index >= 0) {
|
||||
return getPropertyValue(index);
|
||||
}
|
||||
return super.getPropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValue(String name, VsmxBaseObject value) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
int newLength = value.getIntValue();
|
||||
if (newLength > length) {
|
||||
for (int i = length; i < newLength; i++) {
|
||||
create(i);
|
||||
}
|
||||
} else if (newLength < length) {
|
||||
for (int i = newLength; i < length; i++) {
|
||||
delete(i);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
int index = getIndex(name);
|
||||
if (index >= 0) {
|
||||
setPropertyValue(index, value);
|
||||
} else {
|
||||
super.setPropertyValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePropertyValue(String name) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
return;
|
||||
}
|
||||
int index = getIndex(name);
|
||||
if (index >= 0) {
|
||||
deletePropertyValue(index);
|
||||
} else {
|
||||
super.deletePropertyValue(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getPropertyValue(int index) {
|
||||
if (index < 0) {
|
||||
return VsmxUndefined.singleton;
|
||||
}
|
||||
updateLength(index);
|
||||
return super.getPropertyValue(Integer.toString(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValue(int index, VsmxBaseObject value) {
|
||||
if (index >= 0) {
|
||||
updateLength(index);
|
||||
super.setPropertyValue(Integer.toString(index), value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePropertyValue(int index) {
|
||||
if (index >= 0) {
|
||||
if (index == length - 1) {
|
||||
delete(index);
|
||||
length = index;
|
||||
} else if (index < length) {
|
||||
create(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPropertyValue(String name) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
return super.hasPropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBooleanValue() {
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
if (value instanceof VsmxArray) {
|
||||
if (getLength() == 0 && ((VsmxArray) value).getLength() == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("[length=").append(length);
|
||||
for (String name : getPropertyNames()) {
|
||||
if (getIndex(name) < 0) {
|
||||
s.append(", ").append(name).append('=').append(properties.get(name));
|
||||
}
|
||||
}
|
||||
return s.append(']').toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base of every value in the VSMX script runtime. Ported from JPCSP.
|
||||
*/
|
||||
public abstract class VsmxBaseObject {
|
||||
|
||||
protected static final String LENGTH_NAME = "length";
|
||||
protected static final String PROTOTYPE_NAME = "prototype";
|
||||
protected static final String CALL_NAME = "call";
|
||||
|
||||
protected VsmxInterpreter interpreter;
|
||||
|
||||
protected VsmxBaseObject(VsmxInterpreter interpreter) {
|
||||
this.interpreter = interpreter;
|
||||
}
|
||||
|
||||
protected void setInterpreter(VsmxInterpreter interpreter) {
|
||||
this.interpreter = interpreter;
|
||||
}
|
||||
|
||||
public VsmxInterpreter getInterpreter() {
|
||||
return interpreter;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getValue() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getValueWithArguments(int numberOfArguments) {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
public float getFloatValue() {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public int getIntValue() {
|
||||
return (int) getFloatValue();
|
||||
}
|
||||
|
||||
public boolean getBooleanValue() {
|
||||
return getFloatValue() != 0f;
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return getFloatValue() == value.getFloatValue();
|
||||
}
|
||||
|
||||
public boolean identity(VsmxBaseObject value) {
|
||||
return this == value;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getPropertyValue(String name) {
|
||||
if (PROTOTYPE_NAME.equals(name)) {
|
||||
VsmxObject prototype = getPrototype();
|
||||
if (prototype != null) {
|
||||
return prototype;
|
||||
}
|
||||
}
|
||||
return VsmxUndefined.singleton;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getPropertyValue(int index) {
|
||||
return getPropertyValue(Integer.toString(index));
|
||||
}
|
||||
|
||||
public void setPropertyValue(String name, VsmxBaseObject value) {
|
||||
}
|
||||
|
||||
public void setPropertyValue(int index, VsmxBaseObject value) {
|
||||
setPropertyValue(Integer.toString(index), value);
|
||||
}
|
||||
|
||||
public void deletePropertyValue(String name) {
|
||||
setPropertyValue(name, VsmxUndefined.singleton);
|
||||
}
|
||||
|
||||
public void deletePropertyValue(int index) {
|
||||
deletePropertyValue(Integer.toString(index));
|
||||
}
|
||||
|
||||
public boolean hasPropertyValue(String name) {
|
||||
return !VsmxUndefined.singleton.equals(getPropertyValue(name));
|
||||
}
|
||||
|
||||
public List<String> getPropertyNames() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public void setFloatValue(float value) {
|
||||
}
|
||||
|
||||
public abstract String typeOf();
|
||||
|
||||
public abstract String getClassName();
|
||||
|
||||
protected VsmxObject getPrototype() {
|
||||
String className = getClassName();
|
||||
if (className == null) {
|
||||
return null;
|
||||
}
|
||||
if (!interpreter.getGlobalVariables().hasPropertyValue(className)) {
|
||||
return null;
|
||||
}
|
||||
VsmxBaseObject classObject = interpreter.getGlobalVariables().getPropertyValue(className).getPrototype();
|
||||
if (!(classObject instanceof VsmxObject)) {
|
||||
classObject = new VsmxObject(interpreter, className);
|
||||
interpreter.getGlobalVariables().setPropertyValue(className, classObject);
|
||||
}
|
||||
return (VsmxObject) classObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (getFloatValue() == (float) getIntValue()) {
|
||||
return Integer.toString(getIntValue());
|
||||
}
|
||||
return Float.toString(getFloatValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** Boolean value, interned into two singletons. */
|
||||
public final class VsmxBoolean extends VsmxBaseObject {
|
||||
|
||||
private static final VsmxBoolean singletonTrue = new VsmxBoolean(true);
|
||||
private static final VsmxBoolean singletonFalse = new VsmxBoolean(false);
|
||||
|
||||
private boolean value;
|
||||
|
||||
private VsmxBoolean(boolean value) {
|
||||
super(null);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static void init(VsmxInterpreter interpreter) {
|
||||
singletonTrue.setInterpreter(interpreter);
|
||||
singletonFalse.setInterpreter(interpreter);
|
||||
}
|
||||
|
||||
public static VsmxBoolean getValue(boolean value) {
|
||||
return value ? singletonTrue : singletonFalse;
|
||||
}
|
||||
|
||||
public static VsmxBoolean getValue(int value) {
|
||||
return getValue(value != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloatValue() {
|
||||
return value ? 1f : 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntValue() {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBooleanValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "Boolean";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
|
||||
/** A single interpreter call frame (this object, locals, operand stack). */
|
||||
public final class VsmxCallState {
|
||||
|
||||
private final VsmxBaseObject thisObject;
|
||||
private final VsmxBaseObject[] localVariables;
|
||||
private final Deque<VsmxBaseObject> stack = new ArrayDeque<>();
|
||||
private final int returnPc;
|
||||
private final boolean returnThis;
|
||||
private final boolean exitAfterCall;
|
||||
|
||||
public VsmxCallState(VsmxBaseObject thisObject, int numberOfLocalVariables,
|
||||
int returnPc, boolean returnThis, boolean exitAfterCall) {
|
||||
this.thisObject = thisObject;
|
||||
this.localVariables = new VsmxBaseObject[numberOfLocalVariables];
|
||||
Arrays.fill(localVariables, VsmxUndefined.singleton);
|
||||
this.returnPc = returnPc;
|
||||
this.returnThis = returnThis;
|
||||
this.exitAfterCall = exitAfterCall;
|
||||
}
|
||||
|
||||
public int getReturnPc() {
|
||||
return returnPc;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getThisObject() {
|
||||
return thisObject;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getLocalVar(int i) {
|
||||
if (i <= 0 || i > localVariables.length) {
|
||||
return VsmxUndefined.singleton;
|
||||
}
|
||||
return localVariables[i - 1];
|
||||
}
|
||||
|
||||
public void setLocalVar(int i, VsmxBaseObject value) {
|
||||
if (i > 0 && i <= localVariables.length) {
|
||||
localVariables[i - 1] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Deque<VsmxBaseObject> getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
public boolean getReturnThis() {
|
||||
return returnThis;
|
||||
}
|
||||
|
||||
public boolean getExitAfterCall() {
|
||||
return exitAfterCall;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("CallState[returnPc=").append(returnPc).append(", this=").append(thisObject);
|
||||
for (int i = 1; i <= localVariables.length; i++) {
|
||||
s.append(", var").append(i).append('=').append(getLocalVar(i));
|
||||
}
|
||||
return s.append(']').toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** A compiled function value. */
|
||||
public class VsmxFunction extends VsmxBaseObject {
|
||||
|
||||
private final int args;
|
||||
private final int localVars;
|
||||
private final int startLine;
|
||||
private final VsmxObject prototype;
|
||||
|
||||
public VsmxFunction(VsmxInterpreter interpreter, int args, int localVars, int startLine) {
|
||||
super(interpreter);
|
||||
this.args = args;
|
||||
this.localVars = localVars;
|
||||
this.startLine = startLine;
|
||||
this.prototype = new VsmxObject(interpreter, "Function");
|
||||
}
|
||||
|
||||
public int getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public int getLocalVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
public int getStartLine() {
|
||||
return startLine;
|
||||
}
|
||||
|
||||
public void call(VsmxCallState callState) {
|
||||
}
|
||||
|
||||
public VsmxBaseObject getReturnValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "function";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "Function";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VsmxObject getPrototype() {
|
||||
return prototype;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Function(args=" + args + ", localVars=" + localVars + ", startLine=" + startLine + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
import dev.quoije.umd.rco.vsmx.Vsmx;
|
||||
import dev.quoije.umd.rco.vsmx.VsmxCode;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interpreter for VSMX bytecode (the compiled menu script). Ported from
|
||||
* JPCSP's {@code VSMXInterpreter}.
|
||||
*/
|
||||
public final class VsmxInterpreter {
|
||||
|
||||
private Vsmx.Group[] codes;
|
||||
private String[] texts;
|
||||
private String[] properties;
|
||||
private String[] names;
|
||||
private String name;
|
||||
|
||||
private int pc;
|
||||
private boolean exit;
|
||||
private Deque<VsmxBaseObject> stack;
|
||||
private Deque<VsmxCallState> callStates;
|
||||
private VsmxCallState callState;
|
||||
private VsmxObject globalVariables;
|
||||
private final List<String> warnings = new ArrayList<>();
|
||||
|
||||
public void setVsmx(Vsmx vsmx) {
|
||||
this.codes = vsmx.codes();
|
||||
this.texts = vsmx.texts();
|
||||
this.properties = vsmx.properties();
|
||||
this.names = vsmx.names();
|
||||
this.name = vsmx.name();
|
||||
}
|
||||
|
||||
public VsmxObject getGlobalVariables() {
|
||||
return globalVariables;
|
||||
}
|
||||
|
||||
public List<String> getWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
private void warn(String message) {
|
||||
warnings.add(message);
|
||||
}
|
||||
|
||||
private VsmxBaseObject[] popValues(int n) {
|
||||
VsmxBaseObject[] values = new VsmxBaseObject[n];
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
values[i] = stack.pop().getValue();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private void pushCallState(VsmxBaseObject thisObject, int numberOfLocalVariables, boolean returnThis, boolean exitAfterCall) {
|
||||
if (callState != null) {
|
||||
callStates.push(callState);
|
||||
}
|
||||
callState = new VsmxCallState(thisObject, numberOfLocalVariables, pc, returnThis, exitAfterCall);
|
||||
stack = callState.getStack();
|
||||
}
|
||||
|
||||
private void popCallState() {
|
||||
if (callStates.isEmpty()) {
|
||||
callState = null;
|
||||
stack = null;
|
||||
} else {
|
||||
callState = callStates.pop();
|
||||
stack = callState.getStack();
|
||||
}
|
||||
}
|
||||
|
||||
private void interpret(Vsmx.Group code) {
|
||||
VsmxBaseObject o1, o2, o3, o, r;
|
||||
VsmxBaseObject[] arguments;
|
||||
float f1, f2, f;
|
||||
String s1, s2, s;
|
||||
int i1, i2, i;
|
||||
boolean b;
|
||||
|
||||
switch (code.opcode()) {
|
||||
case VsmxCode.VID_NOTHING -> { }
|
||||
case VsmxCode.VID_OPERATOR_ASSIGN -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop();
|
||||
if (o2 instanceof VsmxReference ref) {
|
||||
ref.assign(o1);
|
||||
stack.push(o1);
|
||||
} else {
|
||||
warn("Line#" + (pc - 1) + " non-ref assignment");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_ADD -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
if (o1 instanceof VsmxString || o2 instanceof VsmxString) {
|
||||
s1 = o1.getStringValue();
|
||||
s2 = o2.getStringValue();
|
||||
stack.push(new VsmxString(this, s2 + s1));
|
||||
} else {
|
||||
f1 = o1.getFloatValue();
|
||||
f2 = o2.getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f2 + f1));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_SUBTRACT -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f2 - f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_MULTIPLY -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f2 * f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_DIVIDE -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f2 / f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_MOD -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f2 % f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_POSITIVE -> {
|
||||
f = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_NEGATE -> {
|
||||
f = stack.pop().getFloatValue();
|
||||
stack.push(new VsmxNumber(this, -f));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_NOT -> {
|
||||
b = stack.pop().getBooleanValue();
|
||||
stack.push(VsmxBoolean.getValue(!b));
|
||||
}
|
||||
case VsmxCode.VID_P_INCREMENT -> {
|
||||
o = stack.pop();
|
||||
f = o.getFloatValue() + 1f;
|
||||
stack.push(new VsmxNumber(this, f));
|
||||
if (o instanceof VsmxReference ref) {
|
||||
ref.assign(new VsmxNumber(this, f));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_P_DECREMENT -> {
|
||||
o = stack.pop();
|
||||
f = o.getFloatValue() - 1f;
|
||||
stack.push(new VsmxNumber(this, f));
|
||||
if (o instanceof VsmxReference ref) {
|
||||
ref.assign(new VsmxNumber(this, f));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_INCREMENT -> {
|
||||
o = stack.pop();
|
||||
f = o.getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f));
|
||||
if (o instanceof VsmxReference ref) {
|
||||
ref.assign(new VsmxNumber(this, f + 1f));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_DECREMENT -> {
|
||||
o = stack.pop();
|
||||
f = o.getFloatValue();
|
||||
stack.push(new VsmxNumber(this, f));
|
||||
if (o instanceof VsmxReference ref) {
|
||||
ref.assign(new VsmxNumber(this, f - 1f));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_EQUAL -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
stack.push(VsmxBoolean.getValue(o1.equals(o2)));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_NOT_EQUAL -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
stack.push(VsmxBoolean.getValue(!o1.equals(o2)));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_IDENTITY -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
stack.push(VsmxBoolean.getValue(o1.identity(o2)));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_NON_IDENTITY -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
stack.push(VsmxBoolean.getValue(!o1.identity(o2)));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_LT -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(VsmxBoolean.getValue(f2 < f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_LTE -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(VsmxBoolean.getValue(f2 <= f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_GTE -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(VsmxBoolean.getValue(f2 >= f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_GT -> {
|
||||
f1 = stack.pop().getFloatValue();
|
||||
f2 = stack.pop().getFloatValue();
|
||||
stack.push(VsmxBoolean.getValue(f2 > f1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_INSTANCEOF -> warn("Line#" + (pc - 1) + " unimplemented INSTANCEOF");
|
||||
case VsmxCode.VID_OPERATOR_IN -> warn("Line#" + (pc - 1) + " unimplemented IN");
|
||||
case VsmxCode.VID_OPERATOR_TYPEOF -> {
|
||||
o = stack.pop().getValue();
|
||||
stack.push(new VsmxString(this, o.typeOf()));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_B_AND -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i1 & i2));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_B_XOR -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i1 ^ i2));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_B_OR -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i1 | i2));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_B_NOT -> {
|
||||
i = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, ~i));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_LSHIFT -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i2 << i1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_RSHIFT -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i2 >> i1));
|
||||
}
|
||||
case VsmxCode.VID_OPERATOR_URSHIFT -> {
|
||||
i1 = stack.pop().getIntValue();
|
||||
i2 = stack.pop().getIntValue();
|
||||
stack.push(new VsmxNumber(this, i2 >>> i1));
|
||||
}
|
||||
case VsmxCode.VID_STACK_COPY -> stack.push(stack.peek());
|
||||
case VsmxCode.VID_STACK_SWAP -> {
|
||||
o1 = stack.pop();
|
||||
o2 = stack.pop();
|
||||
stack.push(o1);
|
||||
stack.push(o2);
|
||||
}
|
||||
case VsmxCode.VID_END_STMT -> stack.clear();
|
||||
case VsmxCode.VID_CONST_NULL -> stack.push(VsmxNull.singleton);
|
||||
case VsmxCode.VID_CONST_EMPTYARRAY -> stack.push(new VsmxArray(this));
|
||||
case VsmxCode.VID_CONST_BOOL -> stack.push(VsmxBoolean.getValue(code.value()));
|
||||
case VsmxCode.VID_CONST_INT -> stack.push(new VsmxNumber(this, code.value()));
|
||||
case VsmxCode.VID_CONST_FLOAT -> stack.push(new VsmxNumber(this, code.floatValue()));
|
||||
case VsmxCode.VID_CONST_STRING -> stack.push(new VsmxString(this, texts[code.value()]));
|
||||
case VsmxCode.VID_CONST_OBJECT -> { }
|
||||
case VsmxCode.VID_FUNCTION ->
|
||||
stack.push(new VsmxFunction(this, (code.id() >> 8) & 0xFF, (code.id() >> 24) & 0xFF, code.value()));
|
||||
case VsmxCode.VID_ARRAY -> stack.push(new VsmxArray(this));
|
||||
case VsmxCode.VID_THIS -> stack.push(callState.getThisObject());
|
||||
case VsmxCode.VID_UNNAMED_VAR -> stack.push(new VsmxLocalVarReference(this, callState, code.value()));
|
||||
case VsmxCode.VID_VARIABLE -> stack.push(new VsmxReference(this, globalVariables, names[code.value()]));
|
||||
case VsmxCode.VID_PROPERTY -> {
|
||||
o = stack.pop().getValue();
|
||||
if (o instanceof VsmxObject obj) {
|
||||
stack.push(new VsmxReference(this, obj, properties[code.value()]));
|
||||
} else {
|
||||
stack.push(o.getPropertyValue(properties[code.value()]));
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_METHOD -> {
|
||||
o = stack.pop().getValue();
|
||||
stack.push(new VsmxMethod(this, o, properties[code.value()]));
|
||||
}
|
||||
case VsmxCode.VID_SET_ATTR -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop();
|
||||
o2.setPropertyValue(properties[code.value()], o1);
|
||||
}
|
||||
case VsmxCode.VID_UNSET -> stack.pop().deletePropertyValue(properties[code.value()]);
|
||||
case VsmxCode.VID_OBJ_ADD_ATTR -> warn("Line#" + (pc - 1) + " unimplemented OBJ_ADD_ATTR");
|
||||
case VsmxCode.VID_ARRAY_INDEX -> {
|
||||
o1 = stack.pop();
|
||||
o2 = stack.pop().getValue();
|
||||
if (o2 instanceof VsmxArray arr) {
|
||||
o = new VsmxReference(this, arr, o1.getIntValue());
|
||||
} else if (o2 instanceof VsmxObject obj) {
|
||||
o = new VsmxReference(this, obj, o1.getStringValue());
|
||||
} else {
|
||||
o = o2.getPropertyValue(o1.getStringValue());
|
||||
}
|
||||
stack.push(o);
|
||||
}
|
||||
case VsmxCode.VID_ARRAY_INDEX_KEEP_OBJ -> {
|
||||
o1 = stack.pop();
|
||||
o2 = stack.peek().getValue();
|
||||
if (o2 instanceof VsmxArray) {
|
||||
o = o2.getPropertyValue(o1.getIntValue());
|
||||
} else {
|
||||
o = o2.getPropertyValue(o1.getStringValue());
|
||||
}
|
||||
stack.push(o);
|
||||
}
|
||||
case VsmxCode.VID_ARRAY_INDEX_ASSIGN -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop();
|
||||
o3 = stack.pop().getValue();
|
||||
if (o3 instanceof VsmxArray) {
|
||||
o3.setPropertyValue(o2.getIntValue(), o1);
|
||||
} else {
|
||||
warn("Line#" + (pc - 1) + " non-array index assignment");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_ARRAY_DELETE -> {
|
||||
o1 = stack.pop();
|
||||
o2 = stack.pop().getValue();
|
||||
if (o2 instanceof VsmxArray) {
|
||||
o2.deletePropertyValue(o1.getIntValue());
|
||||
} else {
|
||||
warn("Line#" + (pc - 1) + " non-array delete");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_ARRAY_PUSH -> {
|
||||
o1 = stack.pop().getValue();
|
||||
o2 = stack.pop().getValue();
|
||||
if (o2 instanceof VsmxArray arr) {
|
||||
int length = arr.getLength();
|
||||
o2.setPropertyValue(length, o1);
|
||||
stack.push(o2);
|
||||
} else {
|
||||
warn("Line#" + (pc - 1) + " non-array push");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_JUMP -> pc = code.value();
|
||||
case VsmxCode.VID_JUMP_TRUE -> {
|
||||
b = stack.pop().getBooleanValue();
|
||||
if (b) {
|
||||
pc = code.value();
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_JUMP_FALSE -> {
|
||||
b = !stack.pop().getBooleanValue();
|
||||
if (b) {
|
||||
pc = code.value();
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_CALL_FUNC -> {
|
||||
arguments = popValues(code.value());
|
||||
o = stack.pop().getValueWithArguments(code.value());
|
||||
if (o instanceof VsmxFunction function) {
|
||||
callFunction(function, VsmxNull.singleton, arguments, code.value(), false);
|
||||
} else {
|
||||
stack.push(VsmxNull.singleton);
|
||||
warn("Line#" + (pc - 1) + " non-function call");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_CALL_METHOD -> {
|
||||
arguments = popValues(code.value());
|
||||
o = stack.pop().getValueWithArguments(code.value());
|
||||
if (o instanceof VsmxMethod method) {
|
||||
VsmxFunction function = method.getFunction(code.value(), arguments);
|
||||
if (function == null) {
|
||||
stack.push(VsmxNull.singleton);
|
||||
warn("Line#" + (pc - 1) + " non-existing method " + method.getName() + "()");
|
||||
} else {
|
||||
callFunction(function, method.getThisObject(), method.getArguments(), method.getNumberOfArguments(), false);
|
||||
}
|
||||
} else if (o instanceof VsmxFunction function) {
|
||||
o = stack.pop().getValue();
|
||||
callFunction(function, o, arguments, code.value(), false);
|
||||
} else {
|
||||
stack.push(VsmxNull.singleton);
|
||||
warn("Line#" + (pc - 1) + " non-method call");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_CALL_NEW -> {
|
||||
arguments = popValues(code.value());
|
||||
r = stack.pop();
|
||||
o = r.getValue();
|
||||
if (o instanceof VsmxArray) {
|
||||
if (code.value() == 0) {
|
||||
stack.push(new VsmxArray(this));
|
||||
} else if (code.value() == 1) {
|
||||
stack.push(new VsmxArray(this, arguments[0].getIntValue()));
|
||||
} else {
|
||||
VsmxArray array = new VsmxArray(this, code.value());
|
||||
for (int j = 0; j < code.value(); j++) {
|
||||
array.setPropertyValue(j, arguments[j]);
|
||||
}
|
||||
stack.push(array);
|
||||
}
|
||||
} else if (o instanceof VsmxFunction function) {
|
||||
String className = r instanceof VsmxReference ref ? ref.getRefProperty() : null;
|
||||
VsmxObject thisObject = new VsmxObject(this, className);
|
||||
callFunction(function, thisObject, arguments, code.value(), true);
|
||||
} else if (o instanceof VsmxObject) {
|
||||
if (code.value() == 0) {
|
||||
stack.push(new VsmxObject(this, null));
|
||||
} else {
|
||||
warn("Line#" + (pc - 1) + " wrong number of arguments for new Object");
|
||||
}
|
||||
} else {
|
||||
stack.push(new VsmxArray(this));
|
||||
warn("Line#" + (pc - 1) + " unimplemented CALL_NEW");
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_RETURN -> {
|
||||
o = stack.pop().getValue();
|
||||
if (callState.getReturnThis()) {
|
||||
o = callState.getThisObject();
|
||||
}
|
||||
pc = callState.getReturnPc();
|
||||
if (callState.getExitAfterCall()) {
|
||||
exit = true;
|
||||
}
|
||||
popCallState();
|
||||
if (callState == null) {
|
||||
exit = true;
|
||||
} else {
|
||||
stack.push(o);
|
||||
}
|
||||
}
|
||||
case VsmxCode.VID_THROW -> warn("Line#" + (pc - 1) + " unimplemented THROW");
|
||||
case VsmxCode.VID_TRY_BLOCK_IN -> warn("Line#" + (pc - 1) + " unimplemented TRY_BLOCK_IN");
|
||||
case VsmxCode.VID_TRY_BLOCK_OUT -> warn("Line#" + (pc - 1) + " unimplemented TRY_BLOCK_OUT");
|
||||
case VsmxCode.VID_CATCH_FINALLY_BLOCK_IN -> warn("Line#" + (pc - 1) + " unimplemented CATCH_FINALLY_BLOCK_IN");
|
||||
case VsmxCode.VID_CATCH_FINALLY_BLOCK_OUT -> warn("Line#" + (pc - 1) + " unimplemented CATCH_FINALLY_BLOCK_OUT");
|
||||
case VsmxCode.VID_END -> exit = true;
|
||||
case VsmxCode.VID_DEBUG_FILE -> { }
|
||||
case VsmxCode.VID_DEBUG_LINE -> { }
|
||||
case VsmxCode.VID_MAKE_FLOAT_ARRAY -> warn("Line#" + (pc - 1) + " unimplemented MAKE_FLOAT_ARRAY");
|
||||
default -> warn("Line#" + (pc - 1) + " unimplemented opcode " + Integer.toHexString(code.opcode()));
|
||||
}
|
||||
}
|
||||
|
||||
private void interpret() {
|
||||
exit = false;
|
||||
while (!exit) {
|
||||
Vsmx.Group code = codes[pc];
|
||||
pc++;
|
||||
interpret(code);
|
||||
}
|
||||
exit = false;
|
||||
}
|
||||
|
||||
/** Runs the script's main body, populating the given global variables object. */
|
||||
public synchronized void run(VsmxObject globalVariables) {
|
||||
pc = 0;
|
||||
exit = false;
|
||||
callStates = new ArrayDeque<>();
|
||||
pushCallState(VsmxNull.singleton, 0, false, true);
|
||||
this.globalVariables = globalVariables;
|
||||
|
||||
VsmxBoolean.init(this);
|
||||
VsmxNull.init(this);
|
||||
VsmxUndefined.init(this);
|
||||
|
||||
interpret();
|
||||
|
||||
callStates.clear();
|
||||
callState = null;
|
||||
}
|
||||
|
||||
private void callFunction(VsmxFunction function, VsmxBaseObject thisObject, VsmxBaseObject[] arguments,
|
||||
int numberArguments, boolean returnThis) {
|
||||
pushCallState(thisObject, function.getLocalVars() + function.getArgs(), returnThis, false);
|
||||
for (int i = 1; i <= function.getArgs() && i <= numberArguments; i++) {
|
||||
callState.setLocalVar(i, arguments[i - 1]);
|
||||
}
|
||||
function.call(callState);
|
||||
|
||||
int startLine = function.getStartLine();
|
||||
if (startLine >= 0 && startLine < codes.length) {
|
||||
pc = startLine;
|
||||
} else {
|
||||
popCallState();
|
||||
VsmxBaseObject returnValue = function.getReturnValue();
|
||||
if (returnThis) {
|
||||
stack.push(thisObject);
|
||||
} else if (returnValue != null) {
|
||||
stack.push(returnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Interprets a function body in a fresh call frame. */
|
||||
public synchronized void interpretFunction(VsmxFunction function, VsmxBaseObject object, VsmxBaseObject[] arguments) {
|
||||
pushCallState(object, function.getLocalVars(), false, true);
|
||||
for (int i = 1; i <= function.getArgs(); i++) {
|
||||
if (arguments == null || i > arguments.length) {
|
||||
callState.setLocalVar(i, VsmxNull.singleton);
|
||||
} else {
|
||||
callState.setLocalVar(i, arguments[i - 1]);
|
||||
}
|
||||
}
|
||||
pc = function.getStartLine();
|
||||
interpret();
|
||||
}
|
||||
|
||||
/** Interprets a script event handler by name (e.g. "script:/main/onPush"). */
|
||||
public synchronized void interpretScript(VsmxBaseObject object, String script) {
|
||||
if (script == null) {
|
||||
return;
|
||||
}
|
||||
if (object == null) {
|
||||
object = VsmxNull.singleton;
|
||||
}
|
||||
String scriptPrefix = "script:/" + name + "/";
|
||||
if (script.startsWith(scriptPrefix)) {
|
||||
String functionName = script.substring(scriptPrefix.length());
|
||||
VsmxBaseObject functionObject = globalVariables.getPropertyValue(functionName);
|
||||
if (functionObject instanceof VsmxFunction function) {
|
||||
interpretFunction(function, object, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** A reference to a local variable of the current call frame. */
|
||||
public final class VsmxLocalVarReference extends VsmxReference {
|
||||
|
||||
private final VsmxCallState callState;
|
||||
private final int index;
|
||||
|
||||
public VsmxLocalVarReference(VsmxInterpreter interpreter, VsmxCallState callState, int index) {
|
||||
super(interpreter, null, (String) null);
|
||||
this.callState = callState;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VsmxBaseObject getRef(int numberOfArguments) {
|
||||
return callState.getLocalVar(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(VsmxBaseObject value) {
|
||||
callState.setLocalVar(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalVar#" + index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/**
|
||||
* A method reference: an object plus a method name, resolved at call time.
|
||||
*/
|
||||
public class VsmxMethod extends VsmxBaseObject {
|
||||
|
||||
private final VsmxBaseObject object;
|
||||
private final String name;
|
||||
private VsmxBaseObject thisObject;
|
||||
private int numberOfArguments;
|
||||
private VsmxBaseObject[] arguments;
|
||||
|
||||
public VsmxMethod(VsmxInterpreter interpreter, VsmxBaseObject object, String name) {
|
||||
super(interpreter);
|
||||
this.object = object;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public VsmxBaseObject getThisObject() {
|
||||
return thisObject;
|
||||
}
|
||||
|
||||
public VsmxBaseObject[] getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public int getNumberOfArguments() {
|
||||
return numberOfArguments;
|
||||
}
|
||||
|
||||
public VsmxFunction getFunction(int numberOfArguments, VsmxBaseObject[] arguments) {
|
||||
this.numberOfArguments = numberOfArguments;
|
||||
this.arguments = arguments;
|
||||
thisObject = object.getValue();
|
||||
|
||||
if (object.hasPropertyValue(name)) {
|
||||
VsmxBaseObject function = object.getPropertyValue(name).getValue();
|
||||
if (function instanceof VsmxFunction) {
|
||||
return (VsmxFunction) function;
|
||||
}
|
||||
}
|
||||
|
||||
if (object instanceof VsmxFunction && CALL_NAME.equals(name)) {
|
||||
if (numberOfArguments > 0) {
|
||||
this.numberOfArguments--;
|
||||
if (arguments.length > 0) {
|
||||
thisObject = arguments[0];
|
||||
this.arguments = new VsmxBaseObject[this.numberOfArguments];
|
||||
System.arraycopy(arguments, 1, this.arguments, 0, this.numberOfArguments);
|
||||
}
|
||||
}
|
||||
return (VsmxFunction) object;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "function";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return object + "." + name + "()";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
|
||||
|
||||
/** A VSMX object wrapping a native Java object. */
|
||||
public class VsmxNativeObject extends VsmxObject {
|
||||
|
||||
private final BaseNativeObject object;
|
||||
|
||||
public VsmxNativeObject(VsmxInterpreter interpreter, BaseNativeObject object) {
|
||||
super(interpreter, null);
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
public BaseNativeObject getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return object == null ? super.toString() : object.toString() + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** The singleton {@code null} value. */
|
||||
public final class VsmxNull extends VsmxBaseObject {
|
||||
|
||||
public static final VsmxNull singleton = new VsmxNull();
|
||||
|
||||
private VsmxNull() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public static void init(VsmxInterpreter interpreter) {
|
||||
singleton.setInterpreter(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return this == value || value == VsmxUndefined.singleton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "Null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** Numeric value, stored as a float. */
|
||||
public final class VsmxNumber extends VsmxBaseObject {
|
||||
|
||||
private float value;
|
||||
|
||||
public VsmxNumber(VsmxInterpreter interpreter, float value) {
|
||||
super(interpreter);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public VsmxNumber(VsmxInterpreter interpreter, int value) {
|
||||
super(interpreter);
|
||||
this.value = (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "Number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean identity(VsmxBaseObject value) {
|
||||
if (value instanceof VsmxNumber) {
|
||||
return getFloatValue() == value.getFloatValue();
|
||||
}
|
||||
return super.identity(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** A plain script object with dynamic named properties. */
|
||||
public class VsmxObject extends VsmxBaseObject {
|
||||
|
||||
protected final Map<String, VsmxBaseObject> properties = new LinkedHashMap<>();
|
||||
private final String className;
|
||||
private final List<String> sortedPropertyNames = new ArrayList<>();
|
||||
|
||||
public VsmxObject(VsmxInterpreter interpreter, String className) {
|
||||
super(interpreter);
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
private void addProperty(String name, VsmxBaseObject value) {
|
||||
properties.put(name, value);
|
||||
sortedPropertyNames.add(name);
|
||||
}
|
||||
|
||||
protected static int getIndex(String name) {
|
||||
try {
|
||||
return Integer.parseInt(name);
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getPropertyValue(String name) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
return new VsmxNumber(interpreter, properties.size());
|
||||
}
|
||||
if (PROTOTYPE_NAME.equals(name)) {
|
||||
VsmxObject prototype = getPrototype();
|
||||
if (prototype != null) {
|
||||
return prototype;
|
||||
}
|
||||
return VsmxUndefined.singleton;
|
||||
}
|
||||
|
||||
VsmxBaseObject value = properties.get(name);
|
||||
if (value == null) {
|
||||
int index = getIndex(name);
|
||||
if (index >= 0) {
|
||||
if (index < properties.size()) {
|
||||
value = properties.get(sortedPropertyNames.get(index));
|
||||
} else {
|
||||
value = VsmxUndefined.singleton;
|
||||
}
|
||||
} else {
|
||||
VsmxObject prototype = getPrototype();
|
||||
if (prototype != null && prototype.properties.containsKey(name)) {
|
||||
value = prototype.getPropertyValue(name);
|
||||
} else {
|
||||
value = VsmxUndefined.singleton;
|
||||
addProperty(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPropertyNames() {
|
||||
return sortedPropertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValue(String name, VsmxBaseObject value) {
|
||||
if (properties.containsKey(name)) {
|
||||
properties.put(name, value);
|
||||
} else {
|
||||
addProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePropertyValue(String name) {
|
||||
properties.remove(name);
|
||||
sortedPropertyNames.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPropertyValue(String name) {
|
||||
if (PROTOTYPE_NAME.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
if (properties.containsKey(name)) {
|
||||
return true;
|
||||
}
|
||||
VsmxObject prototype = getPrototype();
|
||||
return prototype != null && prototype.hasPropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
if (value instanceof VsmxObject) {
|
||||
return this == value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "object";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("[");
|
||||
for (Map.Entry<String, VsmxBaseObject> entry : properties.entrySet()) {
|
||||
if (s.length() > 1) {
|
||||
s.append(", ");
|
||||
}
|
||||
s.append(entry.getKey()).append('=').append(entry.getValue());
|
||||
}
|
||||
return s.append(']').toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/**
|
||||
* A reference to a property or array index of an object, so assignments and
|
||||
* increments/decrements can write back through it.
|
||||
*/
|
||||
public class VsmxReference extends VsmxBaseObject {
|
||||
|
||||
private final VsmxObject refObject;
|
||||
private final String refProperty;
|
||||
private final int refIndex;
|
||||
|
||||
public VsmxReference(VsmxInterpreter interpreter, VsmxObject refObject, String refProperty) {
|
||||
super(interpreter);
|
||||
this.refObject = refObject;
|
||||
this.refProperty = refProperty;
|
||||
this.refIndex = -1;
|
||||
}
|
||||
|
||||
public VsmxReference(VsmxInterpreter interpreter, VsmxObject refObject, int refIndex) {
|
||||
super(interpreter);
|
||||
this.refObject = refObject;
|
||||
this.refProperty = null;
|
||||
this.refIndex = refIndex;
|
||||
}
|
||||
|
||||
public void assign(VsmxBaseObject value) {
|
||||
if (refProperty == null) {
|
||||
refObject.setPropertyValue(refIndex, value.getValue());
|
||||
} else {
|
||||
refObject.setPropertyValue(refProperty, value.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public String getRefProperty() {
|
||||
if (refProperty == null) {
|
||||
return Integer.toString(refIndex);
|
||||
}
|
||||
return refProperty;
|
||||
}
|
||||
|
||||
protected VsmxBaseObject getRef() {
|
||||
return getRef(0);
|
||||
}
|
||||
|
||||
protected VsmxBaseObject getRef(int numberOfArguments) {
|
||||
if (refProperty == null) {
|
||||
return refObject.getPropertyValue(refIndex);
|
||||
}
|
||||
return refObject.getPropertyValue(refProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getValue() {
|
||||
return getRef();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getValueWithArguments(int numberOfArguments) {
|
||||
return getRef(numberOfArguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloatValue() {
|
||||
return getRef().getFloatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntValue() {
|
||||
return getRef().getIntValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBooleanValue() {
|
||||
return getRef().getBooleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStringValue() {
|
||||
return getRef().getStringValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return getRef().equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getPropertyValue(String name) {
|
||||
return getRef().getPropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValue(String name, VsmxBaseObject value) {
|
||||
getRef().setPropertyValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePropertyValue(String name) {
|
||||
getRef().deletePropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValue(float value) {
|
||||
getRef().setFloatValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return getRef().typeOf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return getRef().getClassName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (refProperty == null) {
|
||||
return "@OBJ[" + refIndex + "]=" + getRef();
|
||||
}
|
||||
return "@OBJ." + refProperty + "=" + getRef();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** String value. */
|
||||
public final class VsmxString extends VsmxBaseObject {
|
||||
|
||||
private String value;
|
||||
|
||||
public VsmxString(VsmxInterpreter interpreter, String value) {
|
||||
super(interpreter);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloatValue() {
|
||||
return Float.parseFloat(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValue(float value) {
|
||||
this.value = Float.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStringValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VsmxBaseObject getPropertyValue(String name) {
|
||||
if (LENGTH_NAME.equals(name)) {
|
||||
return new VsmxNumber(interpreter, value.length());
|
||||
}
|
||||
return super.getPropertyValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return getStringValue().equals(value.getStringValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean identity(VsmxBaseObject value) {
|
||||
if (value instanceof VsmxString) {
|
||||
return getStringValue().equals(value.getStringValue());
|
||||
}
|
||||
return super.identity(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "string";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "String";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||
|
||||
/** The singleton {@code undefined} value. */
|
||||
public final class VsmxUndefined extends VsmxBaseObject {
|
||||
|
||||
public static final VsmxUndefined singleton = new VsmxUndefined();
|
||||
|
||||
private VsmxUndefined() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public static void init(VsmxInterpreter interpreter) {
|
||||
singleton.setInterpreter(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(VsmxBaseObject value) {
|
||||
return this == value || value == VsmxNull.singleton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeOf() {
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return "Undefined";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "undefined";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package dev.quoije.umd.rco.vsmx.objects;
|
||||
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||
|
||||
/**
|
||||
* Base of native (Java-implemented) objects exposed to the VSMX script
|
||||
* (Controller, MoviePlayer, Resource, etc.).
|
||||
*/
|
||||
public abstract class BaseNativeObject {
|
||||
|
||||
private VsmxNativeObject object;
|
||||
private String name;
|
||||
|
||||
public VsmxNativeObject getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
public void setObject(VsmxNativeObject object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void onDisplayUpdated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + (name != null ? "[name=" + name + "]" : "");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user