Implementing breakpoint expression parser
This commit is contained in:
parent
fa1728b424
commit
53faa1193a
|
@ -0,0 +1,636 @@
|
|||
package vue;
|
||||
|
||||
// Java imports
|
||||
import java.util.*;
|
||||
|
||||
// Breakpoint definition
|
||||
public class Breakpoint {
|
||||
|
||||
// Instance fields
|
||||
private int errCode; // Error type
|
||||
private int errPosition; // Character of input error
|
||||
private String errText; // Offending token text
|
||||
private String expression; // Un-processed input
|
||||
private boolean isEnabled; // Breakpoint is active
|
||||
private boolean isValid; // Expression is valid and processed
|
||||
private String name; // Display name
|
||||
private Token[] tokens; // Evaluation tokens
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Classes //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Operator definition
|
||||
private static class OpDef {
|
||||
int id; // Identifier
|
||||
int precedence; // Operator precedence
|
||||
int type; // Operator category
|
||||
|
||||
// Constructor
|
||||
OpDef(int precedence, int type, int id) {
|
||||
this.id = id;
|
||||
this.precedence = precedence;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Expression token
|
||||
private static class Token {
|
||||
int id; // Operator or symbol identifier
|
||||
Token left; // Left operand
|
||||
Token parent; // Containing operator
|
||||
int precedence; // Operator precedence
|
||||
Token right; // Right operand
|
||||
int start; // Character position in expression
|
||||
String text; // Display text
|
||||
int type; // Token category
|
||||
Object value; // Literal value
|
||||
|
||||
// Constructor
|
||||
Token(int type, int start, String text) {
|
||||
this.start = start;
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Constants //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Error codes
|
||||
public static final int NONE = 0;
|
||||
public static final int BADTOKEN = 1;
|
||||
public static final int EARLYEOF = 2;
|
||||
public static final int EMPTY = 3;
|
||||
public static final int INVALID = 4;
|
||||
public static final int NESTING = 5;
|
||||
public static final int UNEXPECTED = 6;
|
||||
|
||||
// Token types
|
||||
private static final int BINARY = 0;
|
||||
private static final int CLOSE = 1;
|
||||
private static final int LITERAL = 2;
|
||||
private static final int OPEN = 3;
|
||||
private static final int SYMBOL = 4;
|
||||
private static final int UNARY = 5;
|
||||
|
||||
// Expected token modes adjacent to any given token
|
||||
private static final int MODES_AFTER = 0b010110;
|
||||
private static final int MODES_BEFORE = 0b000011;
|
||||
|
||||
// Token IDs
|
||||
private static final int ADD = 0;
|
||||
private static final int BITWISE_AND = 1;
|
||||
private static final int BITWISE_NOT = 2;
|
||||
private static final int BITWISE_OR = 3;
|
||||
private static final int BITWISE_XOR = 4;
|
||||
private static final int CEIL = 5;
|
||||
private static final int DIVIDE = 6;
|
||||
private static final int EQUAL = 7;
|
||||
private static final int FLOAT = 8;
|
||||
private static final int FLOOR = 9;
|
||||
private static final int GREATER_EQUAL_SIGNED = 10;
|
||||
private static final int GREATER_EQUAL_UNSIGNED = 11;
|
||||
private static final int GREATER_SIGNED = 12;
|
||||
private static final int GREATER_UNSIGNED = 13;
|
||||
private static final int GROUP = 14;
|
||||
private static final int LESS_EQUAL_SIGNED = 15;
|
||||
private static final int LESS_EQUAL_UNSIGNED = 16;
|
||||
private static final int LESS_SIGNED = 17;
|
||||
private static final int LESS_UNSIGNED = 18;
|
||||
private static final int LOGICAL_AND = 19;
|
||||
private static final int LOGICAL_NOT = 20;
|
||||
private static final int LOGICAL_OR = 21;
|
||||
private static final int LOGICAL_XOR = 22;
|
||||
private static final int MULTIPLY = 23;
|
||||
private static final int NEGATE = 24;
|
||||
private static final int NOT_EQUAL = 25;
|
||||
private static final int READ = 26;
|
||||
private static final int REMAINDER = 27;
|
||||
private static final int ROUND = 28;
|
||||
private static final int SHIFT_LEFT = 29;
|
||||
private static final int SHIFT_RIGHT = 30;
|
||||
private static final int SHIFT_RIGHT_ARITHMETIC = 31;
|
||||
private static final int SUBTRACT = 32;
|
||||
private static final int TRUNC = 33;
|
||||
private static final int WORD = 34;
|
||||
private static final int XFLOAT = 35;
|
||||
private static final int XWORD = 36;
|
||||
|
||||
// Token definitions
|
||||
private static final HashMap<String, OpDef> OPDEFS;
|
||||
|
||||
// Static initializer
|
||||
static {
|
||||
OPDEFS = new HashMap<String, OpDef>();
|
||||
OPDEFS.put("(" , new OpDef( 0, OPEN , GROUP ));
|
||||
OPDEFS.put(")" , new OpDef( 0, CLOSE , GROUP ));
|
||||
OPDEFS.put("[" , new OpDef( 0, OPEN , READ ));
|
||||
OPDEFS.put("]" , new OpDef( 0, CLOSE , READ ));
|
||||
OPDEFS.put("~" , new OpDef( 1, UNARY , BITWISE_NOT ));
|
||||
OPDEFS.put("!" , new OpDef( 1, UNARY , LOGICAL_NOT ));
|
||||
OPDEFS.put("-" , new OpDef( 1, UNARY , NEGATE ));
|
||||
OPDEFS.put("ceil" , new OpDef( 1, UNARY , CEIL ));
|
||||
OPDEFS.put("float" , new OpDef( 1, UNARY , FLOAT ));
|
||||
OPDEFS.put("floor" , new OpDef( 1, UNARY , FLOOR ));
|
||||
OPDEFS.put("round" , new OpDef( 1, UNARY , ROUND ));
|
||||
OPDEFS.put("trunc" , new OpDef( 1, UNARY , TRUNC ));
|
||||
OPDEFS.put("word" , new OpDef( 1, UNARY , WORD ));
|
||||
OPDEFS.put("xfloat", new OpDef( 1, UNARY , XFLOAT ));
|
||||
OPDEFS.put("xword" , new OpDef( 1, UNARY , XWORD ));
|
||||
OPDEFS.put("/" , new OpDef( 2, BINARY, DIVIDE ));
|
||||
OPDEFS.put("*" , new OpDef( 2, BINARY, MULTIPLY ));
|
||||
OPDEFS.put("%" , new OpDef( 2, BINARY, REMAINDER ));
|
||||
OPDEFS.put("+" , new OpDef( 3, BINARY, ADD ));
|
||||
OPDEFS.put("<<" , new OpDef( 4, BINARY, SHIFT_LEFT ));
|
||||
OPDEFS.put(">>" , new OpDef( 4, BINARY, SHIFT_RIGHT ));
|
||||
OPDEFS.put(">>>" , new OpDef( 4, BINARY, SHIFT_RIGHT_ARITHMETIC));
|
||||
OPDEFS.put(">" , new OpDef( 5, BINARY, GREATER_SIGNED ));
|
||||
OPDEFS.put(">_" , new OpDef( 5, BINARY, GREATER_UNSIGNED ));
|
||||
OPDEFS.put(">=" , new OpDef( 5, BINARY, GREATER_EQUAL_SIGNED ));
|
||||
OPDEFS.put(">=_" , new OpDef( 5, BINARY, GREATER_EQUAL_UNSIGNED));
|
||||
OPDEFS.put("<" , new OpDef( 5, BINARY, LESS_SIGNED ));
|
||||
OPDEFS.put("<_" , new OpDef( 5, BINARY, LESS_UNSIGNED ));
|
||||
OPDEFS.put("<=" , new OpDef( 5, BINARY, LESS_EQUAL_SIGNED ));
|
||||
OPDEFS.put("<=_" , new OpDef( 5, BINARY, LESS_EQUAL_UNSIGNED ));
|
||||
OPDEFS.put("==" , new OpDef( 6, BINARY, EQUAL ));
|
||||
OPDEFS.put("!=" , new OpDef( 6, BINARY, NOT_EQUAL ));
|
||||
OPDEFS.put("&" , new OpDef( 7, BINARY, BITWISE_AND ));
|
||||
OPDEFS.put("^" , new OpDef( 8, BINARY, BITWISE_XOR ));
|
||||
OPDEFS.put("|" , new OpDef( 9, BINARY, BITWISE_OR ));
|
||||
OPDEFS.put("&&" , new OpDef(10, BINARY, LOGICAL_AND ));
|
||||
OPDEFS.put("^^" , new OpDef(11, BINARY, LOGICAL_XOR ));
|
||||
OPDEFS.put("||" , new OpDef(12, BINARY, LOGICAL_OR ));
|
||||
};
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Constructors //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Default constructor
|
||||
public Breakpoint() {
|
||||
setExpression(null);
|
||||
name = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Public Methods //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Retrieve the most recent error code
|
||||
public int getErrorCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
// Retrieve the most recent error character position
|
||||
public int getErrorPosition() {
|
||||
return errPosition;
|
||||
}
|
||||
|
||||
// Retrieve the most recent error text
|
||||
public String getErrorText() {
|
||||
return errText;
|
||||
}
|
||||
|
||||
// Retrieve the most recent input expression
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
// Retrieve the display name
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Determine whether the breakpoint is enabled
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
// Determine whether the breakpoint is valid
|
||||
public boolean isValid() {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Specify and parse an expression
|
||||
public boolean setExpression(String expression) {
|
||||
|
||||
// Configure instance fields
|
||||
errCode = NONE;
|
||||
errPosition = 0;
|
||||
errText = "";
|
||||
this.expression = expression == null ? expression = "" : expression;
|
||||
|
||||
// Process the expression
|
||||
var tokens = parse();
|
||||
if (tokens == null || !validate(tokens))
|
||||
return isValid = false;
|
||||
tree(tokens);
|
||||
|
||||
// Produce an RPN-ordered list of tokens
|
||||
var tok = tokens.remove(0);
|
||||
while (tok != null) {
|
||||
|
||||
// Traverse to left child node
|
||||
if (tok.left != null) {
|
||||
tok = tok.left;
|
||||
tok.parent.left = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Traverse to right child node
|
||||
if (tok.right != null) {
|
||||
tok = tok.right;
|
||||
tok.parent.right = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// No children: add node to output
|
||||
System.out.println(tok.text);
|
||||
tokens.add(tok);
|
||||
tok = tok.parent;
|
||||
}
|
||||
this.tokens = tokens.toArray(new Token[tokens.size()]);
|
||||
|
||||
// The expression was successfully parsed
|
||||
return isValid = true;
|
||||
}
|
||||
|
||||
// Specify the display name
|
||||
public void setName(String name) {
|
||||
this.name = name == null ? "" : name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Package Methods //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Determine the required stack size to evaluate the expression
|
||||
int depth() {
|
||||
|
||||
// Error checking
|
||||
if (!isValid)
|
||||
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 LITERAL: // Fallthrough
|
||||
case SYMBOL : max = Math.max(max, ++size);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Private Methods //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Adjust a float value as needed
|
||||
private static float fixFloat(float value) {
|
||||
int bits = Float.floatToRawIntBits(value);
|
||||
int exp = bits & 0x7F800000;
|
||||
int digits = bits & 0x007FFFFF;
|
||||
return
|
||||
(bits & 0x7FFFFFFF) == 0 || // Zero
|
||||
exp == 0x7F800000 || // Indefinite
|
||||
exp == 0 && digits != 0 // Denormal
|
||||
? 0 : value;
|
||||
}
|
||||
|
||||
// Parse an expression into tokens
|
||||
private ArrayList<Token> parse() {
|
||||
var tokens = new ArrayList<Token>();
|
||||
|
||||
// Parse the expression
|
||||
var chars = (expression + " ").toCharArray();
|
||||
for (int x = 0; x < chars.length; x++) {
|
||||
char c = chars[x];
|
||||
|
||||
// Ignore whitespace
|
||||
if (c == ' ' || c == '\t')
|
||||
continue;
|
||||
|
||||
// Produce a token based on the first character
|
||||
Token tok =
|
||||
c >= '0' && c <= '9' || c == '.' ?
|
||||
parseLiteral(chars, x) :
|
||||
c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' ?
|
||||
parseSymbol (chars, x) :
|
||||
parseOperator (chars, x)
|
||||
;
|
||||
|
||||
// There was an error processing the token
|
||||
if (tok == null)
|
||||
return null;
|
||||
|
||||
// Advance to the next token
|
||||
tokens.add(tok);
|
||||
x += tok.text.length() - 1;
|
||||
} // x
|
||||
|
||||
// The expression contains no tokens
|
||||
if (tokens.size() == 0) {
|
||||
errCode = EMPTY;
|
||||
errPosition = 1;
|
||||
errText = "";
|
||||
return null;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Parse a literal
|
||||
private Token parseLiteral(char[] chars, int start) {
|
||||
boolean isFloat = chars[start] == '.'; // The figure is a float
|
||||
boolean isHex = false; // The figure is in hexadecimal
|
||||
|
||||
// Process through the end of the expression
|
||||
for (int x = start + 1; x < chars.length; x++) {
|
||||
char c = chars[x];
|
||||
|
||||
// The literal begins with "0x"
|
||||
if (c == 'x' || c == 'X') {
|
||||
|
||||
// "x" cannot appear here
|
||||
if (isFloat || x != start + 1 || chars[start] != '0') {
|
||||
errCode = UNEXPECTED;
|
||||
errPosition = x + 1;
|
||||
errText = Character.toString(c);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Configure as a hexadecimal integer
|
||||
isHex = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The literal contains "."
|
||||
if (c == '.') {
|
||||
|
||||
// "." cannot appear here
|
||||
if (isHex || isFloat) {
|
||||
errCode = UNEXPECTED;
|
||||
errPosition = x + 1;
|
||||
errText = Character.toString(c);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Configure as a float
|
||||
isFloat = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The character is part of the token
|
||||
if (
|
||||
c >= '0' && c <= '9' ||
|
||||
isHex && (c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F')
|
||||
) continue;
|
||||
|
||||
// Produce a new token
|
||||
var ret = new Token(LITERAL, start,
|
||||
new String(chars, start, x - start));
|
||||
|
||||
// Parse the literal value
|
||||
try {
|
||||
if (isHex) ret.value = (int)
|
||||
Long.parseLong(ret.text.substring(2), 16);
|
||||
else if (isFloat) ret.value =
|
||||
fixFloat(Float.parseFloat(ret.text));
|
||||
else ret.value =
|
||||
Integer.parseInt(ret.text);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Could not parse the value
|
||||
catch (Exception e) {
|
||||
errCode = UNEXPECTED;
|
||||
errPosition = x + 1;
|
||||
errText = ret.text;
|
||||
return null;
|
||||
}
|
||||
|
||||
} // x
|
||||
|
||||
return null; // Unreachable
|
||||
}
|
||||
|
||||
// Parse an operator
|
||||
private Token parseOperator(char[] chars, int start) {
|
||||
|
||||
// Process through the end of the expression
|
||||
for (int x = start + 1; x < chars.length; x++) {
|
||||
char c = chars[x];
|
||||
|
||||
// The character could be part of the token
|
||||
if (!(
|
||||
c >= 'a' && c <= 'z' ||
|
||||
c >= 'A' && c <= 'Z' ||
|
||||
c >= '0' && c <= '9' ||
|
||||
c == ' ' || c == '\t'
|
||||
)) continue;
|
||||
|
||||
// Produce a new token
|
||||
var ret = new Token(0, start, null);
|
||||
|
||||
// Find the longest operator match
|
||||
for (int length = x - start; length >= 1; length--) {
|
||||
String text = new String(chars, start, length);
|
||||
var def = OPDEFS.get(text);
|
||||
|
||||
// There is no matching operator
|
||||
if (def == null)
|
||||
continue;
|
||||
|
||||
// A matching operator was found
|
||||
ret.id = def.id;
|
||||
ret.precedence = def.precedence;
|
||||
ret.text = text;
|
||||
ret.type = def.type;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The operator was not identified
|
||||
errCode = BADTOKEN;
|
||||
errPosition = start + 1;
|
||||
errText = Character.toString(chars[start]);
|
||||
return null;
|
||||
} // x
|
||||
|
||||
return null; // Unreachable
|
||||
}
|
||||
|
||||
// Parse a symbol (which may be an operator)
|
||||
private Token parseSymbol(char[] chars, int start) {
|
||||
|
||||
// Process through the end of the expression
|
||||
for (int x = start + 1; x < chars.length; x++) {
|
||||
char c = chars[x];
|
||||
|
||||
// The character is part of the token
|
||||
if (
|
||||
c >= 'a' && c <= 'z' ||
|
||||
c >= 'A' && c <= 'Z' ||
|
||||
c >= '0' && c <= '9' ||
|
||||
c == '_' || c == '.'
|
||||
) continue;
|
||||
|
||||
// Produce a new token
|
||||
var ret = new Token(SYMBOL, start,
|
||||
new String(chars, start, x - start));
|
||||
|
||||
// The token is an operator
|
||||
var def = OPDEFS.get(ret.text.toLowerCase());
|
||||
if (def != null) {
|
||||
ret.id = def.id;
|
||||
ret.precedence = def.precedence;
|
||||
ret.type = def.type;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The token is a symbol
|
||||
return ret;
|
||||
} // x
|
||||
|
||||
return null; // Unreachable
|
||||
}
|
||||
|
||||
// Build an expression tree from a list of tokens
|
||||
private void tree(ArrayList<Token> tokens) {
|
||||
|
||||
// Process all operators
|
||||
while (tokens.size() > 1) {
|
||||
int end = tokens.size() - 1;
|
||||
int start = 0;
|
||||
|
||||
// Locate the bounds of the innermost nested group
|
||||
for (int x = 0; x < end; x++) {
|
||||
var tok = tokens.get(x);
|
||||
if (tok.type == OPEN)
|
||||
start = x + 1;
|
||||
if (tok.type != CLOSE)
|
||||
continue;
|
||||
end = x - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply unary operators
|
||||
for (int x = end; x >= start; x--) {
|
||||
var tok = tokens.get(x);
|
||||
if (tok.right != null || tok.type != UNARY)
|
||||
continue;
|
||||
tok.right = tokens.remove(x + 1);
|
||||
tok.right.parent = tok;
|
||||
end--;
|
||||
}
|
||||
|
||||
// Apply binary operators
|
||||
while (start != end) {
|
||||
int index = -1;
|
||||
Token tok = null;
|
||||
|
||||
// Locate the left-most operator with the highest precedence
|
||||
for (int x = start; x < end; x++) {
|
||||
var tik = tokens.get(x);
|
||||
if (tik.right != null || tik.type != BINARY ||
|
||||
tok != null && tik.precedence >= tok.precedence)
|
||||
continue;
|
||||
index = x;
|
||||
tok = tik;
|
||||
}
|
||||
|
||||
// Apply the operator
|
||||
tok.right = tokens.remove(index + 1);
|
||||
tok.left = tokens.remove(index - 1);
|
||||
tok.left.parent = tok.right.parent = tok;
|
||||
end -= 2;
|
||||
}
|
||||
|
||||
// There are no group operators
|
||||
if (tokens.size() == 1)
|
||||
break;
|
||||
|
||||
// Apply the group operators
|
||||
tokens.remove(end + 1);
|
||||
if (tokens.remove(start - 1).id == READ) {
|
||||
var tok = new Token(UNARY, 0, "{Read Word}");
|
||||
tok.right = tokens.remove(start - 1);
|
||||
tok.right.parent = tok;
|
||||
tokens.add(start - 1, tok);
|
||||
}
|
||||
|
||||
} // size
|
||||
|
||||
}
|
||||
|
||||
// Ensure a sequence of tokens is valid
|
||||
private boolean validate(ArrayList<Token> tokens) {
|
||||
int mode = 0;
|
||||
var stack = new Stack<Token>();
|
||||
|
||||
// Validate all tokens
|
||||
for (var tok : tokens) {
|
||||
|
||||
// Expected token mode mismatch
|
||||
if ((MODES_BEFORE >> tok.type & 1) != mode) {
|
||||
|
||||
// The token is invalid
|
||||
if (tok.id != NEGATE) {
|
||||
errCode = INVALID;
|
||||
errPosition = tok.start;
|
||||
errText = tok.text;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert negate to subtract
|
||||
tok.id = SUBTRACT;
|
||||
tok.precedence = 3;
|
||||
tok.type = BINARY;
|
||||
}
|
||||
|
||||
// Nesting error
|
||||
if (tok.type == CLOSE &&
|
||||
(stack.empty() || stack.pop().id != tok.id)) {
|
||||
errCode = NESTING;
|
||||
errPosition = tok.start;
|
||||
errText = tok.text;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The token opens a group
|
||||
if (tok.type == OPEN)
|
||||
stack.push(tok);
|
||||
|
||||
// The token is valid
|
||||
mode = MODES_AFTER >> tok.type & 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// A group was not closed
|
||||
if (!stack.empty()) {
|
||||
errCode = EARLYEOF;
|
||||
errPosition = expression.length();
|
||||
errText = stack.pop().text;
|
||||
}
|
||||
|
||||
// Successfully parsed the expression
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue