Logic parser improvements for quote/escape handling + unit tests

This commit is contained in:
Anuken
2026-09-08 01:35:36 -04:00
parent 9ca00b5935
commit 04452e6ba8
6 changed files with 302 additions and 21 deletions
+6 -9
View File
@@ -56,22 +56,19 @@ public class LAssembler{
return new LParser(text, privileged).parse();
}
/** @return a variable by name.
* This may be a constant variable referring to a number or object. */
/**
* @return a variable by name. This may be a constant variable referring to a number or object.
* @param symbol the string literal, numeric literal, or variable name. Leading or trailing spaces are not allowed.
* */
public LVar var(String symbol){
LVar constVar = Vars.logicVars.get(symbol, privileged);
if(constVar != null) return constVar;
symbol = symbol.trim();
//string case
if(!symbol.isEmpty() && symbol.charAt(0) == '\"' && symbol.charAt(symbol.length() - 1) == '\"'){
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n"));
if(symbol.length() > 1 && symbol.charAt(0) == '\"' && symbol.charAt(symbol.length() - 1) == '\"'){
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1));
}
//remove spaces for non-strings
symbol = symbol.replace(' ', '_');
//use a positive invalid number if number might be negative, else use a negative invalid number
double value = parseDouble(symbol);
+43 -4
View File
@@ -25,6 +25,11 @@ public class LParser{
LParser(String text, boolean privileged){
this.privileged = privileged;
this.chars = text.toCharArray();
//normalize CRLF and lone-CR line endings to LF in place; avoids extra allocations, and an extra \n is harmless
for(int i = 0; i < chars.length; i++){
if(chars[i] == '\r') chars[i] = '\n';
}
}
void comment(){
@@ -39,15 +44,44 @@ public class LParser{
String string(){
int from = pos;
int utflen = 0;
//only allocated if an escape sequence is found
StringBuilder escaped = null;
while(++pos < chars.length){
char c = chars[pos];
//handle \n and \" escape sequences (also \\ so a literal backslash can be written)
if(c == '\\' && pos + 1 < chars.length){
char translated = switch(chars[pos + 1]){
case 'n' -> '\n';
case '"' -> '"';
case '\\' -> '\\';
default -> 0;
};
if(translated != 0){
if(escaped == null){
//copy everything read so far (without the surrounding quote)
escaped = new StringBuilder();
escaped.append(chars, from + 1, pos - from - 1);
}
escaped.append(translated);
utflen += translated <= 0x7F ? 1 : translated <= 0x7FF ? 2 : 3;
pos ++; //consume the escaped character too
continue;
}
//not a recognized escape sequence; fall through and treat the backslash as a literal character (TODO: isn't this an invalid escape?)
}
if(c == '\n'){
error("Missing closing quote \" before end of line.");
}else if(c == '"'){
break;
}
if(escaped != null) escaped.append(c);
// See ByteBufferOutput.writeUTF()
utflen += c != 0 && c <= 0x7F ? 1 : c <= 0x7FF ? 2 : 3;
}
@@ -55,7 +89,13 @@ public class LParser{
if(pos >= chars.length || chars[pos] != '"') error("Missing closing quote \" before end of file.");
if(utflen > 65535) error("String value too long.");
return new String(chars, from, ++pos - from);
pos ++; //move past the closing quote
if(escaped != null){
return "\"" + escaped + "\"";
}
return new String(chars, from, pos - from);
}
String token(){
@@ -63,7 +103,7 @@ public class LParser{
while(pos < chars.length){
char c = chars[pos];
if(c == '\n' || c == ' ' || c == '#' || c == '\t' || c == ';') break;
if(c == '\n' || c == ' ' || c == '#' || c == '\t' || c == ';' || c == '"') break;
pos ++;
}
@@ -191,7 +231,6 @@ public class LParser{
while(pos < chars.length && line < LExecutor.maxInstructions){
switch(chars[pos]){
case '\n', ';', ' ' -> pos ++; //skip newlines and spaces
case '\r' -> pos += 2; //skip the newline after the \r
default -> statement();
}
}
@@ -217,4 +256,4 @@ public class LParser{
}
}
}
}
+9 -6
View File
@@ -83,7 +83,8 @@ public abstract class LStatement{
tooltip(label, text);
}
protected String sanitize(String value){
/** Sanitizes variable input strings from text fields into values that will not break logic parsing. */
public static String sanitize(String value){
if(value.length() == 0){
return "";
}else if(value.length() == 1){
@@ -94,12 +95,14 @@ public abstract class LStatement{
StringBuilder res = new StringBuilder(value.length());
if(value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"'){
res.append('\"');
//strip out extra quotes
//escape characters that would otherwise break out of the string or corrupt it
for(int i = 1; i < value.length() - 1; i++){
if(value.charAt(i) == '"'){
res.append('\'');
}else{
res.append(value.charAt(i));
char c = value.charAt(i);
switch(c){
case '"' -> res.append("\\\"");
case '\\' -> res.append("\\\\");
case '\n' -> res.append("\\n");
default -> res.append(c);
}
}
res.append('\"');
+1 -1
View File
@@ -138,7 +138,7 @@ public class LogicDialog extends BaseDialog{
t.button("@load.clipboard", Icon.download, style, () -> {
dialog.hide();
try{
canvas.load(Core.app.getClipboardText().replace("\r\n", "\n"));
canvas.load(Core.app.getClipboardText());
}catch(Throwable e){
ui.showException(e);
}