diff --git a/makefile b/makefile index c1de7e5..6d71cef 100644 --- a/makefile +++ b/makefile @@ -7,10 +7,10 @@ include_windows = jni-windows-include # Default goal .PHONY: default default: - @echo + @echo $(include_linux) @echo "Planet Virtual Boy Emulator" @echo " https://www.planetvb.com/" - @echo " August 14, 2020" + @echo " October 13, 2020" @echo @echo "Intended build environment: Debian i386 or amd64" @echo " gcc-multilib" diff --git a/src/desktop/Main.java b/src/desktop/Main.java index f4ddab6..4175123 100644 --- a/src/desktop/Main.java +++ b/src/desktop/Main.java @@ -51,15 +51,30 @@ public class Main { // Begin application operations new App(useNative); -/* - var brk = new Breakpoint(); - String exp = "([sp - 8] & 3) << 6 != 12.0 + r6 * ecr && [0x0500008C + r8] == 0"; - System.out.println(exp); + + var brk = new Breakpoint(null); + + String exp = + "([sp - 8] & 3) << 6 != 12.0 + r6 * ecr && [0x0500008C + r8] == 0"; + System.out.println("\n" + exp); if (brk.setCondition(exp)) System.out.println(brk.debug()); - else System.out.println(brk.getErrorCode() + "\t" + - brk.getErrorPosition() + ":" + brk.getErrorText()); -*/ + else System.out.println( + brk.getErrorCode (Breakpoint.CONDITION) + "\t" + + brk.getErrorPosition(Breakpoint.CONDITION) + ":" + + brk.getErrorText (Breakpoint.CONDITION) + ); + + exp = "123, 456 - 987 , f0-fffffff2"; + System.out.println("\n" + exp); + if (brk.setAddresses(exp)) + System.out.println(brk.test()); + else System.out.println( + brk.getErrorCode (Breakpoint.ADDRESS) + "\t" + + brk.getErrorPosition(Breakpoint.ADDRESS) + ":" + + brk.getErrorText (Breakpoint.ADDRESS) + ); + } } diff --git a/src/desktop/vue/Breakpoint.java b/src/desktop/vue/Breakpoint.java index 8589860..9a1c6c6 100644 --- a/src/desktop/vue/Breakpoint.java +++ b/src/desktop/vue/Breakpoint.java @@ -8,13 +8,15 @@ public class Breakpoint { // Instance fields private int[][] addresses; // Applicable address ranges + private String addressText; // Un-processed address ranges private String condition; // Un-processed condition - private int errCode; // Condition error code - private int errPosition; // Character of condition error - private String errText; // Offending condition text + private int[] errCode; // Error codes + private int[] errPosition; // Character position of errors + private String[] errText; // Offending error text private boolean isEnabled; // Breakpoint is active private String name; // Display name private Token[] tokens; // Evaluation tokens + private Vue vue; // Containing emulation context @@ -23,14 +25,19 @@ public class Breakpoint { /////////////////////////////////////////////////////////////////////////// // Error codes - public static final int NONE = 0; - public static final int BADLITERAL = 1; - public static final int BADTOKEN = 2; - public static final int EARLYEOF = 3; - public static final int EMPTY = 4; - public static final int INVALID = 5; - public static final int NESTING = 6; - public static final int UNEXPECTED = 7; + public static final int BADTYPE = -1; + public static final int NONE = 0; + public static final int BADLITERAL = 1; + public static final int BADTOKEN = 2; + public static final int EARLYEOF = 3; + public static final int EMPTY = 4; + public static final int INVALID = 5; + public static final int NESTING = 6; + public static final int UNEXPECTED = 7; + + // Error types + public static final int ADDRESS = 0; + public static final int CONDITION = 1; // Token types private static final int BINARY = 4; @@ -91,7 +98,7 @@ public class Breakpoint { private static final HashMap SYMDEFS; // Functional symbol IDs - private static final int ADDRESS = 0; + //private static final int ADDRESS = 0; // Same as error type private static final int CODE = 1; private static final int COND = 2; private static final int DISP = 3; @@ -298,14 +305,16 @@ public class Breakpoint { /////////////////////////////////////////////////////////////////////////// // Default constructor - public Breakpoint() { + public Breakpoint(Vue vue) { addresses = new int[0][]; + addressText = ""; condition = ""; - errCode = NONE; - errPosition = 0; - errText = ""; + errCode = new int [] { NONE, NONE }; + errPosition = new int [] { 0, 0 }; + errText = new String[] { "", "" }; name = ""; tokens = new Token[0]; + this.vue = vue; } @@ -366,24 +375,48 @@ public class Breakpoint { return ret.toString(); } + // Produce a string representation of the internal address ranges + public String test() { + var ret = new StringBuilder(); + for (int x = 0; x < addresses.length; x++) { + var range = addresses[x]; + if (x > 0) + ret.append("\n"); + ret.append(String.format("%08X", range[0])); + if (range[0] != range[1]) + ret.append(String.format("-%08X", range[1])); + } + return ret.toString(); + } + + // Evaluate the condition against its emulation context + public boolean evaluate() { + return vue == null ? false : vue.evaluate(this); + } + + // Retrieve the most recent input addresses + public String getAddresses() { + return addressText; + } + // Retrieve the most recent input condition public String getCondition() { return condition; } // Retrieve the most recent error code - public int getErrorCode() { - return errCode; + public int getErrorCode(int type) { + return type < 0 || type > 1 ? BADTYPE : errCode[type]; } // Retrieve the most recent error character position - public int getErrorPosition() { - return errPosition; + public int getErrorPosition(int type) { + return type < 0 || type > 1 ? BADTYPE : errPosition[type]; } // Retrieve the most recent error text - public String getErrorText() { - return errText; + public String getErrorText(int type) { + return type < 0 || type > 1 ? null : errText[type]; } // Retrieve the display name @@ -397,24 +430,137 @@ public class Breakpoint { } // Specify and parse a list of address ranges - public boolean setAddresses(int[][] addresses) { + public boolean setAddresses(String addresses) { + Integer first = null; // Value of first address + int mode = 0; // Processing state + var ranges = new ArrayList(); // Output + int start = 0; // Position of address literal + int x = 0; // Input position - // Error checking - if (addresses == null) + // Configure instance fields + addressText = addresses == null ? addresses = "" : addresses; + + // Parse the input + var chars = (addresses + " ").toCharArray(); + for (x = 0; x < chars.length; x++) { + char c = chars[x]; + boolean white = c == ' ' || c == '\t'; + boolean digit = + c >= '0' && c <= '9' || + c >= 'A' && c <= 'F' || + c >= 'a' && c <= 'f' + ; + + // Before + if (mode == 0) { + + // Ignore whitespace + if (white) + continue; + + // Begin an address + if (digit) { + start = x; + mode = 1; // During + continue; + } + + } + + // During + else if (mode == 1) { + + // Ignore digits + if (digit) + continue; + + // The end of the address has been found + String text = new String(chars, start, x - start); + int addr = 0; + try { addr = (int) Long.parseLong(text, 16); } + + // Could not parse the address + catch (Exception e) { + errCode [ADDRESS] = BADLITERAL; + errPosition[ADDRESS] = start + 1; + errText [ADDRESS] = text; + if (vue != null) + vue.update(this); + return false; + } + + // Processed the first address + if (first == null) + first = addr; + + // Processed the second address + else { + ranges.add(new int[] { first, addr }); + first = null; + } + + // Begin searching for a delimiter + mode = 2; // After + x--; + continue; + } + + // After + else if (mode == 2) { + + // Ignore whitespace + if (white) + continue; + + // Begin searching for the second address + if (c == '-' && first != null) { + mode = 0; // Before + continue; + } + + // All addresses in the range have been processed + if (c == ',') { + if (first != null) { + ranges.add(new int[] { first, first }); + first = null; + } + mode = 0; // Before + continue; + } + + } + + // Invalid character + errCode [ADDRESS] = UNEXPECTED; + errPosition[ADDRESS] = x + 1; + errText [ADDRESS] = Character.toString(c); + if (vue != null) + vue.update(this); return false; - for (var range : addresses) - if (range == null || range.length > 2) - return false; + } // x - // Accept the new address ranges - this.addresses = new int[addresses.length][2]; - for (int x = 0; x < addresses.length; x++) { - var range = addresses[x]; - this.addresses[x] = new int[] { - range[0], - range[range.length - 1] - }; + // Unexpected end of input + if (mode == 0 && ranges.size() > 0) { + errCode [ADDRESS] = EARLYEOF; + errPosition[ADDRESS] = x + 1; + errText [ADDRESS] = ""; + if (vue != null) + vue.update(this); + return false; } + + // A one-address range was processed + if (first != null) + ranges.add(new int[] { first, first }); + + // Parsing was successful + this.addresses = ranges.toArray(new int[ranges.size()][]); + this.addressText = addresses; + errCode [ADDRESS] = NONE; + errPosition[ADDRESS] = 0; + errText [ADDRESS] = ""; + if (vue != null) + vue.update(this); return true; } @@ -423,20 +569,26 @@ public class Breakpoint { // Configure instance fields this.condition = condition == null ? condition = "" : condition; - errCode = NONE; - errPosition = 0; - errText = ""; - tokens = new Token[0]; + errCode [CONDITION] = NONE; + errPosition[CONDITION] = 0; + errText [CONDITION] = ""; + tokens = new Token[0]; // Process the expression var tokens = parse(); - if (tokens == null || !validate(tokens)) + if (tokens == null || !validate(tokens)) { + if (vue != null) + vue.update(this); return false; + } tree(tokens); // The expression is empty - if (tokens.size() == 0) + if (tokens.size() == 0) { + if (vue != null) + vue.update(this); return true; + } // Produce an RPN-ordered list of tokens var tok = tokens.remove(0); @@ -463,12 +615,16 @@ public class Breakpoint { this.tokens = tokens.toArray(new Token[tokens.size()]); // The expression was successfully parsed + if (vue != null) + vue.update(this); return true; } // Specify the display name public void setName(String name) { this.name = name == null ? "" : name; + if (vue != null) + vue.update(this); } @@ -477,12 +633,12 @@ public class Breakpoint { // Package Methods // /////////////////////////////////////////////////////////////////////////// - // Evaluate the condition for an emulation context + // Evaluate the condition for a Java emulation context boolean evaluate(JavaVue vue, int[] stack) { // The condition is empty if (tokens.length == 0) - return isEnabled && errCode == NONE; + return isEnabled && errCode[CONDITION] == NONE; // Process tokens int size = 0; @@ -505,21 +661,26 @@ public class Breakpoint { int depth() { // Error checking - if (errCode != NONE) + if (errCode[CONDITION] != NONE) return 0; // Count the maximum size of the stack int max = 0; int size = 0; for (var tok : tokens) switch (tok.type) { - case BINARY : size--; break; - case FLOAT : // Fallthrough - case SYMBOL : // Fallthrough - case WORD : max = Math.max(max, ++size); + case BINARY: size--; break; + case FLOAT : // Fallthrough + case SYMBOL: // Fallthrough + case WORD : max = Math.max(max, ++size); } return max; } + // The breakpoint is being removed from its emulation context + void remove() { + vue = null; + } + /////////////////////////////////////////////////////////////////////////// @@ -586,9 +747,9 @@ public class Breakpoint { // "x" cannot appear here if (isFloat || x != start + 1 || chars[start] != '0') { - errCode = UNEXPECTED; - errPosition = x + 1; - errText = Character.toString(c); + errCode [CONDITION] = UNEXPECTED; + errPosition[CONDITION] = x + 1; + errText [CONDITION] = Character.toString(c); return null; } @@ -602,9 +763,9 @@ public class Breakpoint { // "." cannot appear here if (isHex || isFloat) { - errCode = UNEXPECTED; - errPosition = x + 1; - errText = Character.toString(c); + errCode [CONDITION] = UNEXPECTED; + errPosition[CONDITION] = x + 1; + errText [CONDITION] = Character.toString(c); return null; } @@ -637,9 +798,9 @@ public class Breakpoint { // Could not parse the value catch (Exception e) { - errCode = BADLITERAL; - errPosition = x + 1; - errText = ret.text; + errCode [CONDITION] = BADLITERAL; + errPosition[CONDITION] = x + 1; + errText [CONDITION] = ret.text; return null; } @@ -684,9 +845,9 @@ public class Breakpoint { } // The operator was not identified - errCode = BADTOKEN; - errPosition = start + 1; - errText = Character.toString(chars[start]); + errCode [CONDITION] = BADTOKEN; + errPosition[CONDITION] = start + 1; + errText [CONDITION] = Character.toString(chars[start]); return null; } // x @@ -739,9 +900,9 @@ public class Breakpoint { } // The token is not recognized - errCode = BADTOKEN; - errPosition = x + 1; - errText = ret.text; + errCode [CONDITION] = BADTOKEN; + errPosition[CONDITION] = x + 1; + errText [CONDITION] = ret.text; return null; } // x @@ -830,9 +991,9 @@ public class Breakpoint { // The token is invalid if (tok.id != NEGATE) { - errCode = INVALID; - errPosition = tok.start; - errText = tok.text; + errCode [CONDITION] = INVALID; + errPosition[CONDITION] = tok.start; + errText [CONDITION] = tok.text; return false; } @@ -845,9 +1006,9 @@ public class Breakpoint { // Nesting error if (tok.type == CLOSE && (stack.empty() || stack.pop().id != tok.id)) { - errCode = NESTING; - errPosition = tok.start; - errText = tok.text; + errCode [CONDITION] = NESTING; + errPosition[CONDITION] = tok.start; + errText [CONDITION] = tok.text; return false; } @@ -862,9 +1023,9 @@ public class Breakpoint { // A group was not closed if (!stack.empty()) { - errCode = EARLYEOF; - errPosition = condition.length(); - errText = stack.pop().text; + errCode [CONDITION] = EARLYEOF; + errPosition[CONDITION] = condition.length(); + errText [CONDITION] = stack.pop().text; } // Successfully parsed the condition diff --git a/src/desktop/vue/JavaVue.java b/src/desktop/vue/JavaVue.java index 92679b7..0ff3188 100644 --- a/src/desktop/vue/JavaVue.java +++ b/src/desktop/vue/JavaVue.java @@ -87,12 +87,6 @@ class JavaVue extends Vue { return Math.max(0, maxCycles); } - // Evaluate the condition in a breakpoint - public boolean evaluate(Breakpoint brk) { - return brk != null ? false : - brk.evaluate(this, new int[brk.depth() * 2]); - } - // Retrieve the application break code public int getBreakCode() { return breakCode; @@ -249,6 +243,11 @@ class JavaVue extends Vue { // Package Methods // /////////////////////////////////////////////////////////////////////////// + // Evaluate the condition in a breakpoint + boolean evaluate(Breakpoint brk) { + return false; + } + // Exception break handler int onException(Ecxeption exp) { return 0; diff --git a/src/desktop/vue/NativeVue.c b/src/desktop/vue/NativeVue.c index ac5e4a0..56efc1d 100644 --- a/src/desktop/vue/NativeVue.c +++ b/src/desktop/vue/NativeVue.c @@ -84,10 +84,10 @@ JNIEXPORT jlong JNICALL Java_vue_NativeVue_construct JNIEXPORT void JNICALL Java_vue_NativeVue_dispose (JNIEnv *env, jobject vue, jlong handle) { Core *core = *(Core **)&handle; - if (core->vue.pak.rom != NULL) - free(core->vue.pak.rom); - if (core->vue.pak.ram != NULL) - free(core->vue.pak.ram); + if (core->breakpoints != NULL) free(core->breakpoints); + if (core->stack != NULL) free(core->stack ); + if (core->vue.pak.rom != NULL) free(core->vue.pak.rom); + if (core->vue.pak.ram != NULL) free(core->vue.pak.ram); free(core); } @@ -98,13 +98,6 @@ JNIEXPORT jint JNICALL Java_vue_NativeVue_emulate return vueEmulate(&core->vue, maxCycles); } -// Evaluate the condition in a breakpoint -JNIEXPORT jboolean JNICALL Java_vue_NativeVue_evaluate - (JNIEnv *env, jobject vue, jlong handle, jobject brk) { - Core *core = *(Core **)&handle; - return JNI_FALSE; -} - // Retrieve the application break code JNIEXPORT jint JNICALL Java_vue_NativeVue_getBreakCode (JNIEnv *env, jobject vue, jlong handle) { diff --git a/src/desktop/vue/NativeVue.java b/src/desktop/vue/NativeVue.java index d5e14d7..279c49f 100644 --- a/src/desktop/vue/NativeVue.java +++ b/src/desktop/vue/NativeVue.java @@ -34,11 +34,6 @@ class NativeVue extends Vue { public int emulate(int maxCycles) { return emulate(handle, maxCycles); } - // Evaluate the condition in a breakpoint - private native boolean evaluate(long handle, Breakpoint brk); - public boolean evaluate(Breakpoint brk) - { return evaluate(handle, brk); } - // Retrieve the application break code private native int getBreakCode(long handle); public int getBreakCode() { return getBreakCode(handle); } @@ -94,4 +89,15 @@ class NativeVue extends Vue { public boolean writeBytes(int address, byte[] src, int offset, int length) { return writeBytes(handle, address, src, offset, length); } + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // Evaluate the condition in a breakpoint + boolean evaluate(Breakpoint brk) { + return false; + } + } diff --git a/src/desktop/vue/Vue.java b/src/desktop/vue/Vue.java index 97e737a..951f4c4 100644 --- a/src/desktop/vue/Vue.java +++ b/src/desktop/vue/Vue.java @@ -1,8 +1,14 @@ package vue; +// Java imports +import java.util.*; + // Template for emulation core implementations public abstract class Vue { + // Instance fields + ArrayList breakpoints; // Current breakpoints + // Static fields private static String nativeID = null; // ID of loaded native library @@ -152,19 +158,53 @@ public abstract class Vue { + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + Vue() { + breakpoints = new ArrayList(); + } + + + /////////////////////////////////////////////////////////////////////////// // Public Methods // /////////////////////////////////////////////////////////////////////////// + // Produce a new breakpoint and add it to the collection + public Breakpoint breakpoint() { + var brk = new Breakpoint(this); + breakpoints.add(brk); + return brk; + } + + // Produce an array of the current breakpoint collection + public Breakpoint[] listBreakpoints() { + return breakpoints.toArray(new Breakpoint[breakpoints.size()]); + } + + // Remove a breakpoint from the collection + public boolean remove(Breakpoint brk) { + if (!breakpoints.remove(brk)) + return false; + brk.remove(); + return true; + } + + + + /////////////////////////////////////////////////////////////////////////// + // Abstract Methods // + /////////////////////////////////////////////////////////////////////////// + // Release any used resources public abstract void dispose(); // Process the simulation public abstract int emulate(int maxCycles); - // Evaluate the condition in a breakpoint - public abstract boolean evaluate(Breakpoint brk); - // Retrieve the application break code public abstract int getBreakCode(); @@ -200,4 +240,17 @@ public abstract class Vue { public abstract boolean writeBytes(int address, byte[] src, int offset, int length); + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // Evaluate the condition in a breakpoint + abstract boolean evaluate(Breakpoint brk); + + // A breakpoint has been updated + void update(Breakpoint brk) { + } + }