B2C/B2C/src/b2c/Parser.java

1400 lines
51 KiB
Java

package b2c;
import java.util.ArrayList;
import java.util.Arrays;
public class Parser {
final static int NO_OPTION = 0;
final static int WHOLE_INSTRUCTION = 1;
final static int CONV_TO_BCD = 2;
//final static int CONV_TO_INT = 3; //Use handleIntConversion instead
final static int CONV_TO_STR = 4;
final static int CONV_TO_CHARARRAY = 5;
final static int BCD_BUFFER = 0;
final static int STR_BUFFER = 1;
final static int LIST_BUFFER = 2;
final static int MAT_BUFFER = 3;
final static String[] bufferVars = {"bcdBuffer", "strBuffer", "listBuffer", "matBuffer"};
final static String[] bufferTypes = {"BCDvar", "Str", "List", "Mat"};
static String tabs = "\t";
//Be sure to clear these variables between programs.
static ArrayList<String> instructions = new ArrayList<String>();
static int instructionNumber = 0;
static int[] buffers = {0,0,0,0};
public static String parse(String content) {
return parse(content, NO_OPTION);
}
/* This is the main method for the parsing.
* It recursively parses each instruction it receives.
* Note that instructions must not have the \r at the end, or any \r at all.
* It obviously assumes that the instruction is valid Basic Casio code.
*
* When adding a method returning a value, always declare it like this:
*
* if (content.startsWith(method) {
* return supportAns(isWholeInstruction, <parsing result>);
* }
*
* This is to provide support for the Ans variable.
*/
public static String parse(String content, int option) {
System.out.println("Parsing instruction: " + printNonAscii(content));
if (content.equals("")) {
return "";
}
int matchResult = -1;
//comment
if (content.startsWith("'")) {
return "//"+content.substring(1);
}
//instruction colon ':' that counts as a \r
matchResult = checkMatch(content, ":");
if (matchResult >= 0) {
instructions.add(instructionNumber, content.substring(matchResult+1));
instructionNumber++;
return parse(content.substring(0, matchResult), WHOLE_INSTRUCTION) + "\n" + tabs +
parse(content.substring(matchResult+1), WHOLE_INSTRUCTION);
}
//The easy functions: those that always have their arguments after, AND that don't return anything. (exception of =>)
//Functions like Int or Frac have their arguments after but return a value so they can be used in a calculation.
//Those are listed in the alphabetical order of their opcodes.
//So Fill (0x7F47) is above Lbl (0xE2) which is above If (0xF700). (again, exception of => which is after IfEnd)
//Fill(
if (content.startsWith(new String(new char[]{0x7F,0x47}))) {
if (content.charAt(content.length()-1) == ')') {
content = content.substring(0, content.length()-1);
}
Integer[] args = parseArgs(content.substring(2));
if (args.length != 1) {
error("Fill method doesn't have 2 arguments! ("+args.length+")");
}
args[0] += 2;
if (content.substring(args[0]+1).startsWith(new String(new char[]{0x7F,0x40}))) {
return "B2C_fillMat(" + parse(content.substring(2, args[0]), CONV_TO_BCD) + ", "
+ getChar(content.substring(args[0]+1+2)) + ");";
} else if (content.substring(args[0]+1).startsWith(new String(new char[]{0x7F,0x51}))) {
return "B2C_fillList(" + parse(content.substring(2, args[0]), CONV_TO_BCD) + ", "
+ parse(content.substring(args[0]+1), CONV_TO_BCD) + ");";
} else {
error("2nd argument of Fill isn't a Mat or a List!");
}
}
//unary plus = no purpose
if (content.startsWith(new String(new char[]{0x89}))) {
return parse(content.substring(1), option);
}
//Lbl
if (content.startsWith(new String(new char[]{0xE2}))) {
content = content.substring(1); //remove the Lbl to exploit the variable
if (content.equals(new String(new char[]{0xCD}))) {
content = "radius";
} else if (content.equals(new String(new char[]{0xCE}))) {
content = "theta";
}
return "Lbl_"+content+":;"; //the ';' is needed because you can place Lbls just before IfEnd/WhileEnd/Next
}
//Goto
if (content.startsWith(new String(new char[]{0xEC}))) {
content = content.substring(1); //remove the Goto to exploit the variable
if (content.equals(new String(new char[]{0xCD}))) {
content = "radius";
} else if (content.equals(new String(new char[]{0xCE}))) {
content = "theta";
}
if (content.length() > 2) {
error("Instruction begins by Goto but includes something else!");
}
String[] gotosAreBad = {
"Gotos are the root of all evil.",
"I know you're writing in basic but still... gotos?",
"Get that goto out of there, we're not in assembly.",
"I sincerely hope you're not using that goto for a loop. Else you need to learn about do/while.",
"This is justified if and only if you are using this goto for a Menu.",
"You're lucky gotos work in C the exact way they do in basic.",
"Gotos are bad and you should feel bad.",
"The use of gotos in your program makes it read like a \"Chose your own adventure\" book.",
"http://xkcd.com/292/",
"If you're using that to break out of nested loops... I'll allow it.",
"Like this converted code wasn't unreadable enough.",
};
String result = "goto Lbl_"+content+";";
if (Math.random() > 0.9) {
result += " //" + Arrays.asList(gotosAreBad).get((int)(Math.random()*gotosAreBad.length));
}
return result;
}
//Prog
if (content.startsWith(new String(new char[]{0xED}))) {
String result = content.substring(1);
if (result.charAt(0) == '"') {
result = result.substring(1);
}
if (result.charAt(result.length()-1) == '"') {
result = result.substring(0, result.length()-1);
}
return "prog_"+parseProgName(result)+"();";
}
//If
if (content.startsWith(new String(new char[]{0xF7,0x00}))) {
incrementTabs();
return "if ((*" + parse(content.substring(2), CONV_TO_BCD) + ")[1]) {";
}
//Then
if (content.startsWith(new String(new char[]{0xF7,0x01}))) {
return parse(content.substring(2), WHOLE_INSTRUCTION);
}
//Else
if (content.startsWith(new String(new char[]{0xF7,0x02}))) {
decrementTabs();
String result = "\n" + tabs + "} else {\n";
incrementTabs();
return result + tabs + parse(content.substring(2), WHOLE_INSTRUCTION);
}
//IfEnd ; it is always a single instruction
if (content.startsWith(new String(new char[]{0xF7,0x03}))) {
decrementTabs();
if (content.length() > 2) {
error("Instruction begins by IfEnd but includes something else!");
}
return "\n" + tabs + "}";
}
//inline if (double arrow '=>')
matchResult = checkMatch(content, new String(new char[]{0x13}));
if (matchResult >= 0) {
incrementTabs();
String result = "if ((*" + parse(content.substring(0, matchResult), CONV_TO_BCD)
+ ")[1]) {\n" + tabs + parse(content.substring(matchResult+1), WHOLE_INSTRUCTION) + "\n";
decrementTabs();
return result + "\n" + tabs + "}";
}
//For, To, Step
if (content.startsWith(new String(new char[]{0xF7,0x04}))) {
//Stocks the position of the "To", no need to check for strings because there are no strings in a for
int toPosition = content.indexOf(new String(new char[]{0xF7, 0x05}));
//Stocks the position of the ->
int assignmentPosition = content.indexOf((char)0x0E);
//Checks for "Step"
int stepPosition = content.indexOf(new String(new char[]{0xF7, 0x06}));
String variable = content.substring(assignmentPosition+1, toPosition);
System.out.println("Parsing a For instruction. Position of To is "+toPosition+
", position of -> is "+assignmentPosition+", position of Step is "+stepPosition+
", variable is: "+variable);
//Check for empty for, which is replaced by Sleep()
if (instructions.get(instructionNumber+1).equals(new String(new char[]{0xF7, 0x07}))) {
System.out.println("Parsing empty for");
String result = "Sleep(";
if (stepPosition >= 0) {
result += "Sleep(" + handleIntConversion(content.substring(toPosition+2, stepPosition)) + "/"+
handleIntConversion(content.substring(stepPosition+2));
} else {
result += handleIntConversion(content.substring(toPosition+2));
}
instructionNumber++;
return result + ");";
}
incrementTabs();
String result = "for (";
//variable = beginning;
result += parse(content.substring(2, toPosition), WHOLE_INSTRUCTION) + " ";
//TODO: parse the step as an integer to know if it is <0 or >0
//also put the break condition in the for
if (stepPosition >= 0) {
//step < 0 && var >= limit || step > 0 && var <= limit; variable = variable + step) {
String step = content.substring(stepPosition+2);
System.out.println("Step = " + step);
String limit = parse(content.substring(toPosition+2, stepPosition), CONV_TO_BCD);
result += "(*" + parse(step+"<0"+(char)0x7F+(char)0xB0+variable+(char)0x12+limit+(char)0x7F+(char)0xB1+step+">0"+(char)0x7F+(char)0xB0+variable+(char)0x10+limit, CONV_TO_BCD)+")[1]" + "; "
+ parse(variable+(char)0x89+step+(char)0x0E+variable) + ") {";
//+ "\n" + tabs + "if ((*B2C_calcExp((unsigned char*)\""+step+"<0\\x7F\"\"\\xB0\"\""+variable+"<"+limit+"\\x7F\"\"\\xB1\"\""+step+">0\\x7F\"\"\\xB0\"\""+variable+">"+limit+"\"))[1]) break;";
} else {
//variable <= limit; variable = variable + 1) {"
result += "(*" + parse(variable + (char)0x10 + content.substring(toPosition+2), CONV_TO_BCD) + ")[1]; "
+ parse(variable + (char)0x89 + "1" + (char)0x0E + variable) + ") {";
}
return result;
}
//Next ; like IfEnd
if (content.startsWith(new String(new char[]{0xF7,0x07}))) {
if (content.length() > 2) {
error("Instruction begins by Next but includes something else!");
}
decrementTabs();
return "\n"+tabs+"}";
}
//While
if (content.startsWith(new String(new char[]{0xF7,0x08}))) {
incrementTabs();
return "while ((*" + parse(content.substring(2), CONV_TO_BCD) + ")[1]) {";
}
//WhileEnd
if (content.startsWith(new String(new char[]{0xF7,0x09}))) {
if (content.length() > 2) {
error("Instruction begins by WhileEnd but includes something else!");
}
decrementTabs();
return "\n" + tabs + "}";
}
//Do
if (content.startsWith(new String(new char[]{0xF7,0x0A}))) {
if (content.length() > 2) {
error("Instruction begins by Do but includes something else!");
}
incrementTabs();
return "do {";
}
//LpWhile
if (content.startsWith(new String(new char[]{0xF7,0x0B}))) {
decrementTabs();
return "\n" + tabs + "} while ((*" + parse(content.substring(2)) + ")[1]);";
}
//Return
if (content.startsWith(new String(new char[]{0xF7,0x0C}))) {
if (content.length() > 2) {
error("Instruction begins by Return but includes something else!");
}
return "return;";
}
//Break
if (content.startsWith(new String(new char[]{0xF7,0x0D}))) {
if (content.length() > 2) {
error("Instruction begins by Break but includes something else!");
}
return "break;";
}
//Stop
if (content.startsWith(new String(new char[]{0xF7,0x0E}))) {
if (content.length() > 2) {
error("Instruction begins by Stop but includes something else!");
}
return "B2C_stop();";
}
//Locate
if (content.startsWith(new String(new char[]{0xF7,0x10}))) {
Integer[] args = parseArgs(content);
return "locate(" + handleIntConversion(content.substring(2, args[0])) + ", "
+ handleIntConversion(content.substring(args[0]+1, args[1]))
+ "); Print("
+ parseStr(content.substring(args[1]+1), CONV_TO_CHARARRAY) + "); ML_display_vram();";
}
//ClrText
if (content.startsWith(new String(new char[]{0xF7,0x18}))) {
if (content.length() > 2) {
error("Instruction begins by ClrText but includes something else!");
}
return "ML_clear_vram();";
}
//ClrMat
if (content.startsWith(new String(new char[]{0xF9,0x1E}))) {
if (content.length() != 3) {
error("Invalid argument for ClrMat!");
}
return "B2C_clrMat(" + getChar(content.charAt(2)) + ");";
}
//End of starting functions. At this point the instruction is likely a mathematical operation, or a string,
//or a variable assignment. Note that it can have functions inside, like the factorial or nCr function.
//Check for assignment
matchResult = checkMatch(content, new String(new char[]{0x0E}));
if (matchResult >= 0) {
/*if (option != WHOLE_INSTRUCTION) {
error("Assignment isn't whole instruction?!");
}*/
//Check for the Dim assignment case; in this case it's not an assignment but a method calling
if (content.substring(matchResult+1).startsWith(new String(new char[]{0x7F, 0x46}))) {
//The assignment is followed by a Dim. Now check if it's to a List or a Mat
System.out.println("Parsing a Dim assignment.");
if (content.substring(matchResult+3).startsWith(new String(new char[]{0x7F, 0x40}))) { //followed by Mat
String result = "B2C_setDimMat(" + (content.charAt(matchResult+5)-'A') + ", ";
if (content.startsWith(new String(new char[]{0x7F, 0x51}))) {
result += handleIntConversion(content.substring(0, matchResult) + "[1]") + ", ";
result += handleIntConversion(content.substring(0, matchResult) + "[2]") + ");";
return result;
} else if (content.startsWith("{")) {
Integer[] commaPos = parseArgs(content.substring(1, matchResult));
if (commaPos.length != 1) {
error("List must consist of 2 numbers!");
}
result += content.substring(1, commaPos[0]+1) + ", ";
if (content.charAt(matchResult-1) != '}') {
content = content.substring(0, matchResult) + '}' + content.substring(matchResult);
matchResult++;
}
result += content.substring(commaPos[0]+2, matchResult-1) + ");";
return result;
} else {
error("Unknown mat assignment");
}
} else if (content.substring(matchResult+3).startsWith(new String(new char[]{0x7F, 0x51}))) { //followed by List
return "B2C_setDimList(" + handleIntConversion(content.substring(matchResult+5)) + ", "
+ handleIntConversion(content.substring(0, matchResult)) + ");";
} else {
error("Dim instruction is not followed by List or Mat!");
}
//Check for '~' operator
} else if (content.substring(matchResult+1).matches("[A-Z]~[A-Z]")) {
String assignment = parse(content.substring(0, matchResult), CONV_TO_BCD);
incrementTabs();
String result = "for (i = "+content.charAt(matchResult+1)+"; i <= "+content.charAt(matchResult+3)+"; i++) {\n"+tabs+"B2C_setAlphaVar(i, "+assignment+");\n";
decrementTabs();
return result + tabs + "}";
//Check for list assignment
} else if (content.substring(matchResult+1).startsWith(new String(new char[]{0x7F, 0x51}))) {
Integer[] check = parseBrackets(content.substring(matchResult+1));
if (check.length > 0) {
if (check.length == 2 && matchResult+1+check[1] == content.length()-1) {
content = content.substring(0, content.length()-1);
}
String result = "B2C_setListRow(" + handleIntConversion(
content.substring(matchResult+3, matchResult+1+check[0])) +
", " + handleIntConversion(
content.substring(matchResult+2+check[0], content.length())) +
", " + parse(content.substring(0, matchResult), CONV_TO_BCD) + ");";
return result;
}
//Check for Mat assignment
} else if (content.substring(matchResult+1).startsWith(new String(new char[]{0x7F, 0x40}))) {
//Account for possible unmatched bracket
if (content.charAt(content.length()-1) != ']') {
content += ']';
}
Integer[] check = parseArgs(content.substring(matchResult+5, content.length()-1));
if (check.length != 1) {
error("Mat instruction does not have one comma!");
}
String result = "B2C_setMatCase(" + getChar(content.charAt(matchResult+3)) + ", " +
handleIntConversion(content.substring(matchResult+5, matchResult+5+check[0])) + ", " +
handleIntConversion(content.substring(matchResult+5+check[0]+1, content.length()-1)) + ", " +
parse(content.substring(0, matchResult), CONV_TO_BCD) + ");";
return result;
/*if (check.length > 0) {
if (check.length == 2 && matchResult+1+check[1] == content.length()-1) {
content = content.substring(0, content.length()-1);
}
String result = "B2C_setMat(" + handleIntConversion(
content.substring(matchResult+3, matchResult+1+check[0])) +
", " + handleIntConversion(
content.substring(matchResult+2+check[0], content.length())) +
", " + parse(content.substring(0, matchResult), CONV_TO_BCD) + ");";
return result;
}*/
//Check for variable assignment
} else if (content.substring(matchResult+1).matches("[A-Z\\xCD\\xCE]")) {
String result = "B2C_setAlphaVar(" + getChar(content.charAt(matchResult+1))
+ ", " + parse(content.substring(0, matchResult), CONV_TO_BCD) + ")";
if (option == WHOLE_INSTRUCTION) {
result += ";";
}
return result;
//Check for Str assignment
} else if (content.substring(matchResult+1).startsWith(new String(new char[]{0xF9, 0x3F}))) {
System.out.println("Parsing Str assignment, matchresult = " + matchResult);
String result = "B2C_setStr(" + getStr(content.substring(matchResult+3))
+ ", " + parse(content.substring(0, matchResult)) + ", " + handleString(content.substring(0, matchResult)) + ");";
return result;
} else {
error("Unknown assignment!");
}
}
//Check for strings
if (startsWithStringFunction(content)) {
if (option == WHOLE_INSTRUCTION) {
error("B2C does not support standalone strings!");
} else {
return parseStr(content, option);
}
}
//at this point it is a mathematical operation
//stock the level of the parentheses
Integer[] parenthesesPos = parseBrackets(content);
//System.out.println(Arrays.toString(parenthesesPos));
//Mat
if (content.startsWith(new String(new char[]{0x7F,0x40}))) {
System.out.println("Parsing a matrix");
//Before parsing, we must check if the Mat instruction does not include dimensions
if (content.length() == 3) {
String str = getMat(content.charAt(2));
if (option == WHOLE_INSTRUCTION) {
str = "B2C_setMat(" + getChar((char)0xC0) + ", " + str + ");";
}
return str;
}
Integer[] check = parseBrackets(content);
Integer[] arg = parseArgs(content.substring(check[0]+1, check[1]));
if (check[1] == content.length()-1) {
if (arg.length != 1) {
error("matrix coordinates are fewer or more than two!");
} else {
return supportAns("&" + getMat(content.charAt(2)) + ".data["+getMat(content.charAt(2)) + ".height*("
/*+ handleIntConversion(parse(content.substring(check[0]+1, check[0]+1+arg[0]))) + ")+("
+ handleIntConversion(parse(content.substring(check[0]+2+arg[0],content.length()-1))) + ")]", option);*/
+ handleIntConversion(content.substring(check[0]+1, check[0]+1+arg[0])) + "-1)+("
+ handleIntConversion(content.substring(check[0]+2+arg[0],content.length()-1)) + "-1)]", option);
}
}
}
//Dim
if (content.startsWith(new String(new char[]{0x7F,0x46}))) {
//If it is Dim Mat
if (content.substring(2).startsWith(new String(new char[]{0x7F, 0x40}))) {
String result = "B2C_getDimMat(" + getChar(content.charAt(4)) + ")";
if (option == WHOLE_INSTRUCTION)
return "B2C_setList(LIST_ANS, " + result + ");";
return result;
//If it is Dim List
} else if (content.substring(2).startsWith(new String(new char[]{0x7F, 0x40}))) {
return supportAns("&list["+getList(content.substring(2)) + "].nbElements", option);
} else {
error("Dim instruction is not followed by List or Mat!");
}
}
//List
if (content.startsWith(new String(new char[]{0x7F,0x51}))) {
System.out.println("Parsing a list");
//Before parsing, we must check if the entire instruction is a List instruction
Integer[] check = parseBrackets(content);
if (check.length == 0) {
String result = getList(content.substring(2));
if (option == WHOLE_INSTRUCTION)
return "B2C_setList(LIST_ANS, " + result + ");";
return result;
}
if (check.length == 2 && check[1] == content.length()-1 || option == WHOLE_INSTRUCTION && check.length > 0) {
if (check.length == 2 && check[1] == content.length()-1) {
content = content.substring(0, content.length()-1);
}
return supportAns("list[" + getList(content.substring(2, check[0])) + "].data["
+ handleIntConversion(content.substring(check[0]+1, content.length())) + "]", option);
}
}
//searches for an operator
for (int h = Operators.maxPrecedence; h >= 0; h--) {
for (int j = content.length()-1; j >= 0; j--) {
for (int i = 0; i < Operators.operators[h].length; i++) {
//System.out.println("Checking for operator " + Operators.operators[h][i].getAsciiFunction() + "(i="+i+", j="+j+", h="+h+")");
if (content.substring(0, j).endsWith(Operators.operators[h][i].getCasioChar())) {
//test if the operator is not within parentheses
boolean isInParentheses = false;
for (int k = 0; k < parenthesesPos.length; k+=2) {
if (j-Operators.operators[h][i].getCasioChar().length() >= parenthesesPos[k] && j-Operators.operators[h][i].getCasioChar().length() <= parenthesesPos[k+1]) {
isInParentheses = true;
}
}
System.out.println("Parsing operator: " + Operators.operators[h][i].getAsciiFunction());
//If the operator is at the beginning of the string, it isn't a binary operator
if (!isInParentheses && (j != Operators.operators[h][i].getCasioChar().length() || Operators.operators[h][i].getNbOperands() == 1)) {
String str = "";
//System.out.println("Found the above operator");
str += Operators.operators[h][i].getAsciiFunction() + "(";
//Check for special of unary plus operator (which does nothing)
/*if (str.equals("B2C_unaryPlus(")) {
return parse(content.substring(1), option);
}*/
//If it is a mathematical operation
if (Operators.operators[h][i].isMathematicalFunction()) {
str += addBCDBuffer() + ", ";
}
if (Operators.operators[h][i].getNbOperands() != 1) {
str += parse(content.substring(0, j-Operators.operators[h][i].getCasioChar().length()), CONV_TO_BCD) + ", " + parse(content.substring(j), CONV_TO_BCD);
} else {
//If it is an unary operator, check if we can convert them as a constant
if (content.matches("[\\d\\x99\\x87\\.\\x86]+")) {
//System.out.println("convert to const");
Constants.add(content);
return supportAns("&"+Constants.getVarNotation(content), option);
} else {
str += parse(content.substring(j), CONV_TO_BCD);
}
}
str += ")";
return supportAns(str, option);
}/* else {
System.out.println("Operator was in parenthesis, ignoring it");
}*/
}
/*if (isMultibytePrefix(content.charAt(j))) {
j++;
}*/
}
}
}
//replace variables with their position in the var[] array; only do this if the string only contains the variable
if (content.matches("[A-Z\\xCD\\xCE\\xC0]")) {
return handleAlphaVar(content, option);
}
//Test if it is a number (note that it can be something like 2X, implicit multiplication)
if (content.matches("^[\\d\\x99\\x87\\.](.+)?")) {
int testForImplicitMultiplication = -1;
for (char i = '0'; i <= '9'; i++) {
if (testForImplicitMultiplication < content.lastIndexOf(i)) {
testForImplicitMultiplication = content.lastIndexOf(i);
}
}
if (testForImplicitMultiplication != content.length()-1) {
return parse(content.substring(0, testForImplicitMultiplication+1) + (char)0xA9 + content.substring(testForImplicitMultiplication+1, content.length()));
}
//At this point it is a number, add it to constants
Constants.add(content);
String result = "";
if (option == WHOLE_INSTRUCTION || option == CONV_TO_BCD) {
/*if (content.matches("\\d")) {
result += consts[Integer.valueOf(content)];
} else if (content.equals("10")) {
result += consts[10];
} else {
result = "B2C_convToBCD(\"" + content + "\")";
}*/
result += "&"+Constants.getVarNotation(content);
} else {
result = content;
}
if (option == WHOLE_INSTRUCTION) {
return supportAns(result, option);
}
return result;
}
//At this point it is a calculation, check for calculations functions
//Any function that returns a BCD value must be here! (GetKey, RanInt...)
//Don't forget to add the buffer!
//RanInt#(
if (content.startsWith(new String(new char[]{0x7F,0x87}))) {
if (content.charAt(content.length()-1) == ')') {
content = content.substring(0, content.length()-1);
}
Integer[] args = parseArgs(content.substring(2));
if (args.length != 1) {
error("RanInt# method doesn't have 2 arguments!");
}
args[0] += 2;
return supportAns("B2C_ranInt(" + addBCDBuffer() + ", " + parse(content.substring(2, args[0]), CONV_TO_BCD) + ","
+ parse(content.substring(args[0]+1), CONV_TO_BCD) + ")", option);
}
//Getkey
if (content.startsWith(new String(new char[]{0x7F,0x8F}))) {
if (content.length() > 2) {
error("Instruction begins by GetKey but includes something else!");
}
return supportAns("B2C_getkey(" + addBCDBuffer() + ")", option);
}
//Not
if (content.startsWith(new String(new char[]{0x7F,0xB3}))) {
return supportAns("B2C_not(" + parse(content.substring(1, CONV_TO_BCD) + ")"), option);
}
//Int
if (content.startsWith(new String(new char[]{0xA6}))) {
if (content.length() == 1) {
error("Int instruction is standalone!");
}
return supportAns("B2C_int(" + addBCDBuffer() + ", " + parse(content.substring(1), CONV_TO_BCD) + ")", option);
}
//Ran#
if (content.startsWith(new String(new char[]{0xC1}))) {
if (content.length() != 1) {
error("Ran# instruction includes something else!");
}
return supportAns("B2C_rand(" + addBCDBuffer() + ")", option);
}
//this only occurs if the entire string is within parentheses, such as "(2+3)"
if (parenthesesPos.length == 2 && parenthesesPos[0] == 0 && parenthesesPos[1] == content.length()-1) {
return "(" + parse(content.substring(1, content.length()-1)) + ")";
}
/*String result = "";
if (content.matches("\\d+")) {
//Parse numbers as a global variable (for example, 36 is replaced by a const BCDvar _36 which value is calculated at the beginning).
if (!Constants.consts.contains(Integer.parseInt(content))) {
Constants.add(content);
}
result += "&" + Constants.getVarNotation(content);
return result;
//Check if it is a lone variable
} else if (content.matches("[A-Z\\xCD\\xCE\\xC0]")) {
result += handleAlphaVar(content, option);
return result;*/
/*} else {
result += "B2C_calcExp((unsigned char*)" + parseStr("\"" + content + "\"") + ")";
return result;
}*/
//return supportAns(result, option);
//At this point in the code, the method must have already returned
//if it has detected at least one instruction it understands
error("function not recognized! " + printNonAscii(content));
return "===COMPILATION ERROR===";
}
/**
* This function is called to parse hardcoded lists (written like {1,2,3}).
* At the moment the only functions calling this method are:
* - Assignment operation on Dim Mat ({1,2}->Dim Mat M)
* - Assignment operation on List ({1,2}->List 3)
* - Multi/Super drawstat (Graph(X,Y)=({1,2},{3,4});
*/
public static String parseList(String content) {
String result = "B2C_newList(";
//Check if the list is hardcoded or not
if (content.startsWith(new String(new char[]{0x7F, 0x51}))) {
return parse(content);
}
if (content.charAt(0) != '{') {
error("Trying to parse hardcoded list but the list doesn't begin with a '{'!");
return "";
}
if (content.charAt(content.length()-1) == '}') {
content = content.substring(0, content.length()-1);
}
System.out.println("Parsing a hardcoded list: "+content);
//remove the leading '{' for easier argument parsing
content = content.substring(1);
ArrayList<Integer> args = new ArrayList<Integer>();
args.addAll(Arrays.asList(parseArgs(content.substring(0))));
args.add(content.length());
System.out.println(args.toString());
result += args.size() + ", ";
result += parse(content.substring(0, args.get(0)), CONV_TO_BCD) + ", ";
for (int i = 0; i < args.size()-1; i++) {
result += parse(content.substring(args.get(i)+1, args.get(i+1)), CONV_TO_BCD) + ", ";
}
//remove the last comma
return result.substring(0, result.length()-2) + ")";
}
/**
* This method checks for the presence of the string match in the string content.
* This can't be done with traditional methods because it must check if the match
* string is in the content string AND not in a string.
*
* Returns -1 if the value doesn't exist, or the beginning of the first occurence of the match.
*
* Examples:
*
* checkMatch("Locate 1,1,\"=> First option\"", "=>") will return -1,
* because there is a '=>' but inside a string.
*
* checkMatch("A>B => Locate 1,1,C", "=>") will return 4.
*/
public static int checkMatch(String content, String match) {
boolean positionIsString = false;
for (int i = 0; i < content.length(); i++) {
if (isMultibytePrefix(content.charAt(i))) {
i += 2;
if (i >= content.length()) {
break;
}
}
if (content.substring(i).startsWith(match) && !positionIsString) {
return i;
}
if (content.charAt(i) == '"') {
positionIsString = !positionIsString;
} else if (content.charAt(i) == '\\') {
i++;
}
}
return -1;
}
public static String parseProgName(String content) {
//The use of replaceAll is possible because you can't have multi byte characters in program names
String[] specialChars = {
"\\{", "\\}",
"\\[", "\\]",
"\\.",
"\"",
" ",
"\\xA9|\\*",
"\\xB9|\\/",
"\\x99|-",
"\\x89|\\+",
"\\xCD",
"\\xCE",
"'",
"~",
"\0"
};
String[] replacements = {
"lcurlybracket",
"rcurlybracket",
"lbracket",
"rbracket",
"dot",
"quote",
"space",
"mult",
"div",
"sub",
"add",
"radius",
"theta",
"apos",
"tilde",
""
};
for (int i = 0; i < specialChars.length; i++) {
content = content.replaceAll(specialChars[i], replacements[i]);
}
content = content.replaceAll("[^ -~]", "_"); //replace non-ASCII characters
return content;
}
/**
* This method parses a string. It is designed to parse things like:
* Str 1 + "test" + Str 2
*
* The main parse() method only calls this method in case of an argument that is always a string,
* for the functions Locate, Text and standalone strings.
*
*/
//TODO use buffers as str and place free() after
public static String parseStr(String content, int option) {
System.out.println("Parsing string: "+printNonAscii(content));
Integer[] parenthesesPos = parseBrackets(content);
if (startsWithStringFunction(content)) {
//Note that standalone strings aren't supported, there is no Str Ans; therefore, no ';'!
//Parse operators
//It is easy because the only operator is the '+'
for (int i = content.length()-1; i >= 0; i--) {
if (content.substring(0, i).endsWith(new String(new char[]{0x89}))) {
//test if the operator is not within parentheses
boolean isInParentheses = false;
for (int k = 0; k < parenthesesPos.length; k+=2) {
if (i-1 >= parenthesesPos[k] && i-1 <= parenthesesPos[k+1]) {
isInParentheses = true;
}
}
System.out.println("Parsing concatenation operator (+)");
if (!isInParentheses) {
//Parse as StrJoin(a,b)
String str = parse(new String(new char[]{0xF9,0x30}) + content.substring(0, i-1) + ","
+ content.substring(i) + ")", option);
if (option == CONV_TO_CHARARRAY) {
return "B2C_strToCharArray(" + str + ", " + handleString(str) + ")";
}
return str;
}
}
}
//Plain strings
if (content.startsWith("\"")) {
if (content.charAt(content.length()-1) != '"') {
content += '"';
}
content = "(unsigned char*)" + replaceNonAscii(content);
if (option == CONV_TO_CHARARRAY) {
return content;
}
return "B2C_charArrayToStr(" + content + ")";
}
//No returns from here; always assign the result to this string!
//This is to handle the conversion to char array/str.
String result = "";
//Str function
if (content.startsWith(new String(new char[]{0xF9, 0x3F}))) {
result = getStr(content.substring(2));
//All subsequent str functions have parenthesis, so check if there is the right number
} else if (parenthesesPos.length != 2) {
error("Str function does not have one level of parenthesis! ("+parenthesesPos.length+")");
}
//These string functions all have similar structures (arguments = str, int)
String[] strFunctions1 = {"strLeft", "strRight", "strShift", "strRotate"};
String[] strOpcodes1 = {new String(new char[]{0xF9,0x34}),
new String(new char[]{0xF9,0x35}),
new String(new char[]{0xF9,0x3C}),
new String(new char[]{0xF9,0x3D}),
};
for (int i = 0; i < strFunctions1.length; i++) {
if (content.startsWith(strOpcodes1[i])) {
Integer[] argPos = parseArgs(content.substring(2, parenthesesPos[1]));
if (argPos.length > 1) {
error(strFunctions1[i]+" does not contain 2 arguments!");
}
if (argPos.length == 0) {
result = "B2C_"+strFunctions1[i]+"(" + parse(content.substring(2, parenthesesPos[1])) + ", " + handleString(content.substring(2, parenthesesPos[1]))
+ ", 1)";
} else {
result = "B2C_"+strFunctions1[i]+"(" + parse(content.substring(2, argPos[0]+2)) + ", " + handleString(content.substring(2, argPos[0]+2))
+ ", " + handleIntConversion(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ")";
}
}
}
//These string functions all have similar structures (argument = str)
String[] strFunctions2 = {"strInv", "strUpr", "strLwr"};
String[] strOpcodes2 = {new String(new char[]{0xF9,0x3B}),
new String(new char[]{0xF9,0x39}),
new String(new char[]{0xF9,0x3A}),
};
for (int i = 0; i < strFunctions2.length; i++) {
if (content.startsWith(strOpcodes2[i])) {
result = "B2C_"+strFunctions2[i]+"(" + parse(content.substring(2, parenthesesPos[1])) + ", " + handleString(content.substring(2, parenthesesPos[1])) + ")";
}
}
//StrMid
if (content.startsWith(new String(new char[]{0xF9, 0x36}))) {
Integer[] argPos = parseArgs(content.substring(2, parenthesesPos[1]));
if (argPos.length == 1) {
//StrMid with only 2 arguments = StrLeft
result = "B2C_strMid2args(" + parse(content.substring(2, argPos[0]+2)) + ", " + handleString(content.substring(2, argPos[0]+2))
+ ", " + handleIntConversion(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ")";
} else if (argPos.length != 2) {
error("strMid does not contain 3 arguments!");
} else {
result = "B2C_strMid(" + parse(content.substring(2, argPos[0]+2)) + ", " + handleString(content.substring(2, argPos[0]+2)) + ", "
+ handleIntConversion(content.substring(argPos[0]+2+1, argPos[1]+2)) + ", " + handleIntConversion(content.substring(argPos[1]+2+1, parenthesesPos[1])) + ")";
}
}
//StrJoin
if (content.startsWith(new String(new char[]{0xF9, 0x30}))) {
Integer[] argPos = parseArgs(content.substring(2, parenthesesPos[1]));
if (argPos.length != 1) {
error("StrJoin function does not contain 2 arguments!");
}
result = "B2C_strJoin(" + parse(content.substring(2, argPos[0]+2)) + ", " +handleString(content.substring(2, argPos[0]+2)) + ", "
+ parse(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ", " +handleString(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ")";
}
if (option == CONV_TO_CHARARRAY) {
return "B2C_strToCharArray(" + result + ", " + handleString(content) + ")";
}
return result;
}
String result = "";
//StrLen
if (content.startsWith(new String(new char[]{0xF9, 0x31}))) {
result = "B2C_strLen(" + addBCDBuffer() + ", " + parse(content.substring(2, parenthesesPos[1])) + ", " + handleString(content.substring(2, parenthesesPos[1])) + ")";
}
//Both these functions have 2 strings as arguments and return a BCDvar
//StrCmp
else if (content.startsWith(new String(new char[]{0xF9,0x32}))) {
Integer[] argPos = parseArgs(content.substring(2, parenthesesPos[1]));
if (argPos.length != 1) {
error("StrCmp function does not contain 2 arguments!");
}
result = "B2C_strCmp(" + addBCDBuffer() + ", " + parse(content.substring(2, argPos[0]+2)) +", " + handleString(content.substring(2, argPos[0]+2)) + ", "
+ parse(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ", "+ handleString(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ")";
}
//StrSrc
else if (content.startsWith(new String(new char[]{0xF9,0x33}))) {
Integer[] argPos = parseArgs(content.substring(2, parenthesesPos[1]));
if (argPos.length > 2 || argPos.length < 1) {
error("StrSrc function does not contain 2 or 3 arguments!");
} else if (argPos.length == 1) {
result = "B2C_strSrc(" + addBCDBuffer() + ", " + parse(content.substring(2, argPos[0]+2)) +", " + handleString(content.substring(2, argPos[0]+2)) + ", "
+ parse(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ", " +handleString(content.substring(argPos[0]+2+1, parenthesesPos[1])) + ", 1)";
} else {
result = "B2C_strSrc(" + addBCDBuffer() + ", " + parse(content.substring(2, argPos[0]+2)) +", " + handleString(content.substring(2, argPos[0]+2)) + ", "
+ parse(content.substring(argPos[0]+2+1, argPos[1]+2)) +", " + handleString(content.substring(argPos[0]+2+1, argPos[1]+2))
+ ", " + handleIntConversion(content.substring(argPos[1]+2+1, parenthesesPos[1])) + ")";
}
} else {
result = handleAlphaVar(content, NO_OPTION);
}
return "B2C_convToStr(" + result + ")";
}
//Replace non-ASCII characters with \xXX, using string concatenation.
public static String replaceNonAscii(String content) {
for (int i = 0; i < content.length(); i++) {
if (content.charAt(i) < ' ' || content.charAt(i) > '~') {
String result = content.substring(0, i);
result += "\\x";
String str = Integer.toHexString(content.charAt(i));
if (str.length() > 2) {
error("Unicode character u" + str + "!");
} else {
result += str;
}
//if (i < content.length()-1)
result += "\"\"";
result += content.substring(i+1);
content = result;
}
}
return content;
}
/**
* This method parses comma-separated arguments. It is used to parse any function
* with those kind of arguments. It is needed because the arguments themselves may have
* commas (example: Locate 1,Mat M[A,B],Str 1).
*
* Note that it doesn't return the arguments themselves, but the position of the commas, that
* must be exploited by the parser.
*
*/
public static Integer[] parseArgs(String content) {
System.out.println("Parsing arguments: "+printNonAscii(content));
ArrayList<Integer> args = new ArrayList<Integer>();
int argsBuffer = 0;
boolean positionIsString = false;
for (int i = 0; i < content.length(); i++) {
if (content.charAt(i) == ',' && argsBuffer == 0 && !positionIsString) {
args.add(i);
}
if (startsWithParenthesis(content.substring(i)) && !positionIsString) {
argsBuffer++;
} else if ((content.charAt(i) == ')' || content.charAt(i) == ']' || content.charAt(i) == '}') && !positionIsString) {
argsBuffer--;
} else if (content.charAt(i) == '"') {
positionIsString = !positionIsString;
} else if (content.charAt(i) == '\\') {
i++;
}
if (isMultibytePrefix(content.charAt(i))) {
i++;
}
}
//System.out.println("Result:"+args.toString());
return args.toArray(new Integer[args.size()]);
}
/**
* This function returns the index of each first-level opening and closing brackets/parentheses.
* Example: the string "3*(4*(5+6))+(4*5)" will return {2, 10, 12, 16}.
* It accounts for unmatched brackets at the end, so "2->Mat M[1,3" will return the same as "2->Mat M[1,3]".
* When adding an opcode containing parenthesis, make sure to include it in this function.
*/
public static Integer[] parseBrackets(String content) {
ArrayList<Integer> bracketsPos = new ArrayList<Integer>();
int bracketsLevel = 0;
boolean currentPositionIsString = false;
for (int i = 0; i < content.length(); i++) {
if (!currentPositionIsString && startsWithParenthesis(content.substring(i))) {
bracketsLevel++;
if (bracketsLevel == 1) {
bracketsPos.add(i);
}
} else if ((content.charAt(i) == ')' || content.charAt(i) == ']' || content.charAt(i) == '}') && !currentPositionIsString) {
bracketsLevel--;
if (bracketsLevel == 0) {
bracketsPos.add(i);
} else if (bracketsLevel < 0) {
error("brackets level below 0!");
}
} else if (content.charAt(i) == '"') {
currentPositionIsString = !currentPositionIsString;
} else if (content.charAt(i) == '\\') {
i++;
} else if (isMultibytePrefix(content.charAt(i))) {
i++;
}
}
//This should take care of unmatched brackets at the end
while (bracketsLevel > 0) {
bracketsPos.add(content.length());
bracketsLevel--;
}
return bracketsPos.toArray(new Integer[bracketsPos.size()]);
}
/**
* Returns true if the given string starts with a parenthesis (or a bracket),
* but also with an opcode containing a parenthesis (RanInt#(, StrRotate(, etc).
*/
public static boolean startsWithParenthesis(String content) {
if ((content.charAt(0) == '(' || content.charAt(0) == '[' || content.charAt(0) == '{' ||
//Check for opcodes that contains parenthesis
content.startsWith(new String(new char[]{0x7F, 0x87})) || //RanInt#(
content.startsWith(new String(new char[]{0x7F, 0x47})) || //Fill(
content.startsWith(new String(new char[]{0xF9, 0x30})) || //StrJoin(
content.startsWith(new String(new char[]{0xF9, 0x31})) || //StrLen(
content.startsWith(new String(new char[]{0xF9, 0x32})) || //StrCmp(
content.startsWith(new String(new char[]{0xF9, 0x33})) || //StrSrc(
content.startsWith(new String(new char[]{0xF9, 0x34})) || //StrLeft(
content.startsWith(new String(new char[]{0xF9, 0x35})) || //StrRight(
content.startsWith(new String(new char[]{0xF9, 0x36})) || //StrMid(
content.startsWith(new String(new char[]{0xF9, 0x37})) || //Exp->Str(
content.startsWith(new String(new char[]{0xF9, 0x38})) || //Exp(
content.startsWith(new String(new char[]{0xF9, 0x39})) || //StrUpr(
content.startsWith(new String(new char[]{0xF9, 0x3A})) || //StrLwr(
content.startsWith(new String(new char[]{0xF9, 0x3B})) || //StrInv(
content.startsWith(new String(new char[]{0xF9, 0x3C})) || //StrShift(
content.startsWith(new String(new char[]{0xF9, 0x3D})) //StrRotate(
)) {
return true;
}
return false;
}
/* This method replaces some basic functions (listed below).
* Reason for this method is to avoid replacing functions in strings, which can't be done with replaceAll().
* The replacements are the following:
*
* 0x99 (-) and 0x87 (-) by '-'
* 0x89 (+) by '+'
* 0xA8 (^) by '^'
* 0xA9 (*) by '*'
* 0xB9 (/) by '/'
*
* It it not used at the moment.
*/
/*public static StringBuilder autoreplace(StringBuilder result) {
boolean positionIsString = false;
for (int i = 0; i < result.length(); i++) {
if (result.charAt(i) == (char)0x99) {
result.setCharAt(i, '-');
}
if (result.charAt(i) == (char)0x87 && !positionIsString) {
result.setCharAt(i, '-');
}
if (result.charAt(i) == (char)0x89) {
result.setCharAt(i, '+');
}
if (result.charAt(i) == (char)0xA8) {
result.setCharAt(i, '^');
}
if (result.charAt(i) == (char)0xA9) {
result.setCharAt(i, '*');
}
if (result.charAt(i) == (char)0xB9) {
result.setCharAt(i, '/');
}
if (result.charAt(i) == '"') {
positionIsString = !positionIsString;
} else if (result.charAt(i) == '\\') {
i++;
}
if (isMultibytePrefix(result.charAt(i))) {
i++;
}
}
return result;
}*/
public static boolean isMultibytePrefix(char prefix) {
if (prefix == (char)0xF7 ||
prefix == (char)0x7F ||
prefix == (char)0xF9 ||
prefix == (char)0xE5 ||
prefix == (char)0xE6 ||
prefix == (char)0xE7)
return true;
return false;
}
/**
* Returns true if the given string starts with a string function.
* A string function is defined as a function that returns a string.
* That means that Str, StrLwr, StrShift, '"', ... return true,
* but not StrLen, StrCmp or StrSrc as they return BCDvars!
*
* The function must be the function alone, if there is something else then
* it will return false.
*/
public static boolean startsWithStringFunction(String function) {
if (function.startsWith(new String(new char[]{0xF9, 0x30})) || //StrJoin(
function.startsWith(new String(new char[]{0xF9, 0x34})) || //StrLeft(
function.startsWith(new String(new char[]{0xF9, 0x35})) || //StrRight(
function.startsWith(new String(new char[]{0xF9, 0x36})) || //StrMid(
function.startsWith(new String(new char[]{0xF9, 0x39})) || //StrUpr(
function.startsWith(new String(new char[]{0xF9, 0x3A})) || //StrLwr(
function.startsWith(new String(new char[]{0xF9, 0x3B})) || //StrInv(
function.startsWith(new String(new char[]{0xF9, 0x3C})) || //StrShift(
function.startsWith(new String(new char[]{0xF9, 0x3D})) || //StrRotate(
function.startsWith(new String(new char[]{0xF9, 0x3F})) || //Str
function.charAt(0) == '"'
) {
return true;
}
return false;
}
public static String supportAns(String content, int option) {
if (option == WHOLE_INSTRUCTION)
return "B2C_setAlphaVar("+getChar((char)0xC0)+", " + content + ");";
return content;
}
public static String handleIntConversion(String content) {
//TODO: optimise some cases like "A+2" where you could convert A then add 2
//instead of calculating "A+2" in BCD
if (Constants.isNumber(content)) {
return Constants.getNumberNotation(content);
}
return "B2C_convToUInt(" + parse(content) + ")";
}
public static String handleAlphaVar(String content, int option) {
//Handle var such that var[char-'A'] gives the correct variable
if (content.matches("[A-Z\\xCD\\xCE\\xC0]")) {
String result = getAlphaVar(content);
return supportAns(result, option);
}
return parse(content, option);
}
/**
* Handle the isString param, required for any function taking a string as argument.
* If isString(content) returns true, this function returns "TRUE". Else it returns "FALSE".
*/
public static String handleString(String content) {
if (isString(content)) {
return "TRUE";
}
return "FALSE";
}
/**
* Returns true iff the content consists only of the Str function.
* "Str 1 + StrRotate(Str 2)" will return false.
* "Str 20" will return true.
*/
public static boolean isString(String content) {
if (content.startsWith(new String(new char[]{0xF9, 0x3F})) && content.length() <= 4) {
return true;
}
return false;
}
public static void incrementTabs() {
tabs += "\t";
}
public static void decrementTabs() {
tabs = tabs.substring(0, tabs.length()-1);
}
public static String getAlphaVar(char var) {return getAlphaVar(""+var);}
public static String getAlphaVar(String var) {
String result = "VAR_";
if (var.length() != 1) {
error("Unknown var!");
} else if (var.charAt(0) >= 'A' && var.charAt(0) <= 'Z') {
result += var.charAt(0);
} else if (var.charAt(0) == 0xCD) {
result += "RADIUS";
} else if (var.charAt(0) == 0xCE) {
result += "THETA";
} else if (var.charAt(0) == 0xC0) {
result += "ANS";
} else {
error("Unknown var!");
}
return result;
}
public static String getMat(char mat) {
String result = "MAT_";
if (mat >= 'A' && mat <= 'Z') {
result += mat;
} else if (mat == 0xC0) {
result += "ANS";
} else {
error("Unknown mat "+mat+"!");
}
return result;
}
public static String getChar(String char_) {
if (char_.length() != 1) {
error("Unknown char " + char_ + "!");
}
return getChar(char_.charAt(0));
}
public static String getChar(char char_) {
String result = null;
if (char_ >= 'A' && char_ <= 'Z') {
result = ""+char_;
} else if (char_ == 0xCD) {
result = "RADIUS";
} else if (char_ == 0xCE) {
result = "THETA";
} else if (char_ == 0xC0) {
result = "ANS";
} else {
error("Unknown char "+char_+"!");
}
return result;
}
public static String getList(String list) {
if (list.length() == 1 && list.charAt(0) == 0xC0) {
return "ANS";
} else {
return handleIntConversion(list);
}
}
public static String getStr(String str) {
if (str.length() > 2) {
error("Invalid str " + printNonAscii(str) + "!");
}
try {
int strno = Integer.valueOf(str);
if (strno < 1 || strno > 20) {
error("Invalid str number " + strno + "!");
}
} catch (NumberFormatException e) {
error("Invalid str " + str + "!");
}
return "STR_"+Integer.valueOf(str);
}
public static String addBCDBuffer() {return addBuffer(BCD_BUFFER);}
//public static String addStrBuffer() {return addBuffer(STR_BUFFER);}
public static String addMatBuffer() {return addBuffer(MAT_BUFFER);}
public static String addListBuffer() {return addBuffer(LIST_BUFFER);}
public static String addBuffer(int buffer) {
String result = "&" + bufferVars[buffer] + buffers[buffer];
buffers[buffer]++;
return result;
}
public static String printNonAscii(String content) {
String result = "";
for (int i = 0; i < content.length(); i++) {
if (content.charAt(i) >= 32 && content.charAt(i) < 127 && (i == 0 || i > 0 && !isMultibytePrefix(content.charAt(i-1)))) {
result += content.charAt(i) + " ";
} else {
String hex = Integer.toHexString(content.charAt(i));
result += "0x" + hex + " ";
if (hex.length() > 2) {
error("Unhandled unicode character u+" + hex);
}
}
}
return result;
}
public static void error(String error) {
System.out.println("\nERROR: "+error+"\n");
System.exit(0);
}
}