Logic \uXXXX escape support

This commit is contained in:
Anuken
2026-09-08 10:10:48 -04:00
parent cf8b1c6b34
commit 12157feeca
4 changed files with 86 additions and 3 deletions
+5 -1
View File
@@ -81,7 +81,7 @@ public class LAssembler{
}
}
/** Decodes \n, \" and \\ escape sequences in a string literal's contents (quotes already stripped). */
/** Decodes \n, \", \\ and uXXXX escape sequences in a string literal's contents (quotes already stripped). */
static String unescape(String s){
if(s.indexOf('\\') == -1) return s;
@@ -98,6 +98,10 @@ public class LAssembler{
out.append(next);
i ++;
continue;
}else if(next == 'u' && i + 5 < s.length()){
out.append((char)Integer.parseInt(s.substring(i + 2, i + 6), 16));
i += 5;
continue;
}
}
out.append(c);
+11
View File
@@ -56,6 +56,17 @@ public class LParser{
continue;
}
//uXXXX: validate 4 hex digits
if(c == '\\' && pos + 1 < chars.length && chars[pos + 1] == 'u'){
if(pos + 5 >= chars.length) error("Invalid \\u escape; expected 4 hex digits.");
for(int j = pos + 2; j <= pos + 5; j++){
if(Character.digit(chars[j], 16) == -1) error("Invalid \\u escape; expected 4 hex digits.");
}
utflen += 3; //decoded char may take up to 3 bytes in modified UTF-8
pos += 5; //consume u and the 4 hex digits
continue;
}
if(c == '\n'){
error("Missing closing quote \" before end of line.");
}else if(c == '"'){
+13 -1
View File
@@ -99,13 +99,17 @@ public abstract class LStatement{
//escape characters that would otherwise break out of the string or corrupt it.
for(int i = 1; i < value.length() - 1; i++){
char c = value.charAt(i);
//exception: allow escape sequences in strings: \n, \", \\
//exception: allow escape sequences in strings: \n, \", \\, uXXXX
if(c == '\\' && i + 1 < value.length() - 1){
char next = value.charAt(i + 1);
if(next == '"' || next == '\\' || next == 'n'){
res.append(c).append(next);
i ++; //consumed the escape target too
continue;
}else if(next == 'u' && i + 5 < value.length() - 1 && isHex(value, i + 2)){
res.append(value, i, i + 6);
i += 5; //consumed u and the 4 hex digits too
continue;
}
}
switch(c){
@@ -136,6 +140,14 @@ public abstract class LStatement{
return value;
}
/** True if the 4 characters at value[from..from+3] are all hex digits. */
private static boolean isHex(String value, int from){
for(int i = from; i < from + 4; i++){
if(Character.digit(value.charAt(i), 16) == -1) return false;
}
return true;
}
protected static boolean logicLocalization(){
return Core.settings.getBool("logiclocalization", true);
}
+57 -1
View File
@@ -83,6 +83,21 @@ public class LogicTests{
"escaped backslash sitting right before the closing quote",
"set result \"end\\\\\"",
"end\\"
),
Arguments.of(
"\\u0041 decodes to the corresponding unicode character",
"set result \"\\u0041\"",
"A"
),
Arguments.of(
"\\uf8ff decodes to the corresponding unicode character",
"set result \"\\uf8ff\\uf8ff\"",
""
),
Arguments.of(
"\\u escapes combined with other escapes in one string",
"set result \"\\u0041\\n\\u0042\\\\end\"",
"A\nB\\end"
)
);
}
@@ -140,7 +155,32 @@ public class LogicTests{
Arguments.of("a raw '#' in an unquoted value gets neutralized so it can't start a comment", "a#b", "a_b"),
Arguments.of("a lone newline character is invalid on its own, same as a lone space", "\n", "invalid"),
Arguments.of("a lone tab character is invalid on its own, same as a lone space", "\t", "invalid"),
Arguments.of("a lone '#' character is invalid on its own, same as a lone space", "#", "invalid")
Arguments.of("a lone '#' character is invalid on its own, same as a lone space", "#", "invalid"),
Arguments.of(
"a well-formed \\uXXXX escape inside a quoted value passes through untouched",
"\"\\u0041\"",
"\"\\u0041\""
),
Arguments.of(
"\\uXXXX combined with other escapes inside a quoted value",
"\"\\u0041\\n\\u0042\\\\end\"",
"\"\\u0041\\n\\u0042\\\\end\""
),
Arguments.of(
"a malformed \\u escape with too few digits still escapes the backslash instead of throwing",
"\"\\u12\"",
"\"\\\\u12\""
),
Arguments.of(
"a malformed \\u escape with non-hex digits still escapes the backslash instead of throwing",
"\"\\u12zz\"",
"\"\\\\u12zz\""
),
Arguments.of(
"a \\u escape truncated by the closing quote still escapes the backslash instead of throwing",
"\"a\\u123\"",
"\"a\\\\u123\""
)
);
}
@@ -232,6 +272,22 @@ public class LogicTests{
Arguments.of(
"a string that never closes before the line ends, with trailing content after it",
"set result \"asd\nset bar 5"
),
Arguments.of(
"a \\u escape with non-hex digits is rejected",
"set result \"\\u12zz\""
),
Arguments.of(
"a \\u escape with non-hex digits is rejected",
"set result \"\\u1h23\""
),
Arguments.of(
"a \\u escape with non-hex digits is rejected",
"set result \"\\uhh23\""
),
Arguments.of(
"a \\u escape truncated by the closing quote is rejected",
"set result \"\\u12\""
)
);
}