Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parse error of system default /usr/share/nano/*.nanorc #1157

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions builtins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
258 changes: 210 additions & 48 deletions builtins/src/main/java/org/jline/builtins/SyntaxHighlighter.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ private AttributedStringBuilder _highlight(
return asb;
}

private static class HighlightRule {
static class HighlightRule {
public enum RuleType {
PATTERN,
START_END,
Expand Down Expand Up @@ -465,7 +465,7 @@ public String toString() {
}
}

private static class NanorcParser {
static class NanorcParser {
private static final String DEFAULT_SYNTAX = "default";
private final String name;
private final String target;
Expand Down Expand Up @@ -574,20 +574,7 @@ public void parse() throws IOException {
}

private String fixRegexes(String line) {
mattirn marked this conversation as resolved.
Show resolved Hide resolved
return line.replaceAll("\\\\<", "\\\\b")
.replaceAll("\\\\>", "\\\\b")
.replaceAll("\\[:alnum:]", "\\\\p{Alnum}")
.replaceAll("\\[:alpha:]", "\\\\p{Alpha}")
.replaceAll("\\[:blank:]", "\\\\p{Blank}")
.replaceAll("\\[:cntrl:]", "\\\\p{Cntrl}")
.replaceAll("\\[:digit:]", "\\\\p{Digit}")
.replaceAll("\\[:graph:]", "\\\\p{Graph}")
.replaceAll("\\[:lower:]", "\\\\p{Lower}")
.replaceAll("\\[:print:]", "\\\\p{Print}")
.replaceAll("\\[:punct:]", "\\\\p{Punct}")
.replaceAll("\\[:space:]", "\\\\s")
.replaceAll("\\[:upper:]", "\\\\p{Upper}")
.replaceAll("\\[:xdigit:]", "\\\\p{XDigit}");
return line;
}

private boolean addHighlightRule(List<String> parts, int idx, String tokenName) {
Expand Down Expand Up @@ -656,43 +643,50 @@ private void addHighlightRule(String reference, List<String> parts, boolean case
Styles.StyleCompiler sh = new Styles.StyleCompiler(spec, true);
AttributedStyle style = new StyleResolver(sh::getStyle).resolve("." + reference);

if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PATTERN) {
if (parts.size() == 2) {
highlightRules.get(tokenName).add(new HighlightRule(style, doPattern(".*", caseInsensitive)));
} else {
for (int i = 2; i < parts.size(); i++) {
highlightRules
.get(tokenName)
.add(new HighlightRule(style, doPattern(parts.get(i), caseInsensitive)));
try {
if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PATTERN) {
if (parts.size() == 2) {
highlightRules.get(tokenName).add(new HighlightRule(style, doPattern(".*", caseInsensitive)));
} else {
for (int i = 2; i < parts.size(); i++) {
highlightRules
.get(tokenName)
.add(new HighlightRule(style, doPattern(parts.get(i), caseInsensitive)));
}
}
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.START_END) {
String s = parts.get(2);
String e = parts.get(3);
highlightRules
.get(tokenName)
.add(new HighlightRule(
style,
doPattern(s.substring(7, s.length() - 1), caseInsensitive),
doPattern(e.substring(5, e.length() - 1), caseInsensitive)));
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_START_WITH) {
highlightRules
.get(tokenName)
.add(new HighlightRule(
HighlightRule.RuleType.PARSER_START_WITH,
style,
parts.get(2).substring(10)));
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_CONTINUE_AS) {
highlightRules
.get(tokenName)
.add(new HighlightRule(
HighlightRule.RuleType.PARSER_CONTINUE_AS,
style,
parts.get(2).substring(11)));
}
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.START_END) {
String s = parts.get(2);
String e = parts.get(3);
highlightRules
.get(tokenName)
.add(new HighlightRule(
style,
doPattern(s.substring(7, s.length() - 1), caseInsensitive),
doPattern(e.substring(5, e.length() - 1), caseInsensitive)));
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_START_WITH) {
highlightRules
.get(tokenName)
.add(new HighlightRule(
HighlightRule.RuleType.PARSER_START_WITH,
style,
parts.get(2).substring(10)));
} else if (HighlightRule.evalRuleType(parts) == HighlightRule.RuleType.PARSER_CONTINUE_AS) {
highlightRules
.get(tokenName)
.add(new HighlightRule(
HighlightRule.RuleType.PARSER_CONTINUE_AS,
style,
parts.get(2).substring(11)));
} catch (PatternSyntaxException e) {
Log.warn("Invalid highlight regex", reference, parts, e);
} catch (Exception e) {
Log.warn("Failure while handling highlight regex", reference, parts, e);
}
}

private Pattern doPattern(String regex, boolean caseInsensitive) {
regex = Parser.fixRegexes(regex);
return caseInsensitive ? Pattern.compile(regex, Pattern.CASE_INSENSITIVE) : Pattern.compile(regex);
}
}
Expand Down Expand Up @@ -796,7 +790,7 @@ public int getEnd() {
}
}

private static class Parser {
static class Parser {
private static final char escapeChar = '\\';
private String blockCommentTokenName;
private BlockCommentDelimiters blockCommentDelimiters;
Expand Down Expand Up @@ -980,5 +974,173 @@ private boolean isEscaped(final CharSequence buffer, final int pos) {
}
return isEscapeChar(buffer, pos - 1);
}

/**
* Perform Posix/Java regex fixups. This function parses the given regex and escapes according to these rules:
*
* <p>The first {@code ]} in a bracket expression does not need to be escaped in Posix,translate to {@code \]}.
*
* <p>Same as above for a negating bracket expression like {@code [^][]}, translate to {@code [^\]\[]}.
*
* <p>Any {@code [} in a bracket expression does not need to be escaped in Posix, translate to {@code \[}.
*
* <p>Any {@code ]} not in a bracket expression is valid in both Posix and Java, no translation.
*
* <p>A backslash before the closing bracket like {@code [.f\]} is not an escape of the closing bracket,
* the backslash needs to be escaped for Java, translate to {@code [.f\\]}.
*
* <p>Do not perform the above translations within an escape via {@code \}, except for {@code \<} and {@code \>} to {@code \b}.
*
* <p>Replace the Posix classes like {@code [:word:]} or {@code [:digit:]} to Java classes, inside and outside a bracket expression.
mattirn marked this conversation as resolved.
Show resolved Hide resolved
*
* @param posix Posix regex
* @return Java regex
*/
static String fixRegexes(String posix) {
int len = posix.length();
StringBuilder java = new StringBuilder();

boolean inBracketExpression = false;

int i = 0;
char next;
try {
for (; i < len; i++) {
char c = posix.charAt(i);

switch (c) {
case escapeChar:
next = posix.charAt(++i);
// Don't translate anything after the \ character escape
if (inBracketExpression && next == ']') {
inBracketExpression = false;
java.append("\\\\").append(next);
} else {
// Translate '\<' and '\>' to '\b'
if (next == '<' || next == '>') {
next = 'b';
}
java.append(c).append(next);
}
break;
case '[':
if (i == len - 1) {
throw new IllegalArgumentException("Lone [ at the end of (index " + i + "): " + posix);
}
// Handle "double bracket" Posix "classes" like [[:word:]] or [[:digit:]] and their
mattirn marked this conversation as resolved.
Show resolved Hide resolved
// negations
// starting with [-[:
if (posix.regionMatches(i, "[[:", 0, 3)) {
int afterClass = nextAfterClass(posix, i + 3);
if (posix.regionMatches(afterClass, ":]]", 0, 3)) {
String className = posix.substring(i + 3, afterClass);
java.append('[').append(replaceClass(className));
i = afterClass + 1;
inBracketExpression = true;
break;
} else if (posix.regionMatches(afterClass, ":]", 0, 2)) {
if (inBracketExpression) {
throw new IllegalArgumentException("Unclear bracket expression");
}
// Handles character patterns like [[:alpha:]_-]
String className = posix.substring(i + 3, afterClass);
java.append('[').append(replaceClass(className));
i = afterClass + 1;
inBracketExpression = true;
break;
} else {
throw new IllegalArgumentException("Invalid character class");
}
}
// Handle "single bracket" Posix "classes" like [:word:]
else if (posix.charAt(i + 1) == ':') {
int afterClass = nextAfterClass(posix, i + 2);
if (!posix.regionMatches(afterClass, ":]", 0, 2)) {
java.append("[:");
i++;
inBracketExpression = true;
} else {
String className = posix.substring(i + 2, afterClass);
java.append(replaceClass(className));
i = afterClass + 1;
}
break;
}
if (inBracketExpression) {
// Translate lone [ to \[
java.append('\\').append(c);
} else {
inBracketExpression = true;
java.append(c);
next = posix.charAt(i + 1);
if (next == ']') {
i++;
java.append("\\]");
} else if (next == '^' && posix.charAt(i + 2) == ']') {
i += 2;
java.append("^\\]");
}
}
break;
case ']':
inBracketExpression = false;
java.append(c);
break;
default:
java.append(c);
break;
}
}
} catch (Exception e) {
throw new IllegalArgumentException(
"Posix-to-Java regex translation failed around index " + i + " of: " + posix, e);
}

return java.toString();
}

private static String replaceClass(String className) {
switch (className) {
case "alnum":
return "\\p{Alnum}";
case "alpha":
return "\\p{Alpha}";
case "blank":
return "\\p{Blank}";
case "cntrl":
return "\\p{Cntrl}";
case "digit":
return "\\p{Digit}";
case "graph":
return "\\p{Graph}";
case "lower":
return "\\p{Lower}";
case "print":
return "\\p{Print}";
case "punct":
return "\\p{Punct}";
case "space":
return "\\s";
case "upper":
return "\\p{Upper}";
case "xdigit":
return "\\p{XDigit}";
}
throw new IllegalArgumentException("Unknown class '" + className + "'");
}

private static int nextAfterClass(String s, int idx) {
if (s.charAt(idx) == ':') {
idx++;
}
while (true) {
char c = s.charAt(idx);
if (!Character.isLetterOrDigit(c)) {
break;
}
idx++;
}
return idx;
}
}
}
Loading
Loading