@@ -356,7 +356,7 @@ private AttributedStringBuilder _highlight(
356356 return asb ;
357357 }
358358
359- private static class HighlightRule {
359+ static class HighlightRule {
360360 public enum RuleType {
361361 PATTERN ,
362362 START_END ,
@@ -465,7 +465,7 @@ public String toString() {
465465 }
466466 }
467467
468- private static class NanorcParser {
468+ static class NanorcParser {
469469 private static final String DEFAULT_SYNTAX = "default" ;
470470 private final String name ;
471471 private final String target ;
@@ -497,7 +497,7 @@ public void parse() throws IOException {
497497 idx ++;
498498 line = line .trim ();
499499 if (!line .isEmpty () && !line .startsWith ("#" )) {
500- List <String > parts = RuleSplitter .split (fixRegexes ( line ) );
500+ List <String > parts = RuleSplitter .split (line );
501501 if (parts .get (0 ).equals ("syntax" )) {
502502 syntaxName = parts .get (1 );
503503 List <Pattern > filePatterns = new ArrayList <>();
@@ -547,7 +547,7 @@ public void parse() throws IOException {
547547 }
548548 for (String l : colorTheme .get (key ).split ("\\ \\ n" )) {
549549 idx ++;
550- addHighlightRule (RuleSplitter .split (fixRegexes ( l ) ), idx , key );
550+ addHighlightRule (RuleSplitter .split (l ), idx , key );
551551 }
552552 }
553553 } else {
@@ -560,7 +560,7 @@ public void parse() throws IOException {
560560 if (theme != null ) {
561561 for (String l : theme .split ("\\ \\ n" )) {
562562 idx ++;
563- addHighlightRule (RuleSplitter .split (fixRegexes ( l ) ), idx , TOKEN_NANORC );
563+ addHighlightRule (RuleSplitter .split (l ), idx , TOKEN_NANORC );
564564 }
565565 } else {
566566 Log .warn ("Unknown token type: " , key );
@@ -573,23 +573,6 @@ public void parse() throws IOException {
573573 }
574574 }
575575
576- private String fixRegexes (String line ) {
577- return line .replaceAll ("\\ \\ <" , "\\ \\ b" )
578- .replaceAll ("\\ \\ >" , "\\ \\ b" )
579- .replaceAll ("\\ [:alnum:]" , "\\ \\ p{Alnum}" )
580- .replaceAll ("\\ [:alpha:]" , "\\ \\ p{Alpha}" )
581- .replaceAll ("\\ [:blank:]" , "\\ \\ p{Blank}" )
582- .replaceAll ("\\ [:cntrl:]" , "\\ \\ p{Cntrl}" )
583- .replaceAll ("\\ [:digit:]" , "\\ \\ p{Digit}" )
584- .replaceAll ("\\ [:graph:]" , "\\ \\ p{Graph}" )
585- .replaceAll ("\\ [:lower:]" , "\\ \\ p{Lower}" )
586- .replaceAll ("\\ [:print:]" , "\\ \\ p{Print}" )
587- .replaceAll ("\\ [:punct:]" , "\\ \\ p{Punct}" )
588- .replaceAll ("\\ [:space:]" , "\\ \\ s" )
589- .replaceAll ("\\ [:upper:]" , "\\ \\ p{Upper}" )
590- .replaceAll ("\\ [:xdigit:]" , "\\ \\ p{XDigit}" );
591- }
592-
593576 private boolean addHighlightRule (List <String > parts , int idx , String tokenName ) {
594577 boolean out = true ;
595578 if (parts .get (0 ).equals ("color" )) {
@@ -656,43 +639,50 @@ private void addHighlightRule(String reference, List<String> parts, boolean case
656639 Styles .StyleCompiler sh = new Styles .StyleCompiler (spec , true );
657640 AttributedStyle style = new StyleResolver (sh ::getStyle ).resolve ("." + reference );
658641
659- if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PATTERN ) {
660- if (parts .size () == 2 ) {
661- highlightRules .get (tokenName ).add (new HighlightRule (style , doPattern (".*" , caseInsensitive )));
662- } else {
663- for (int i = 2 ; i < parts .size (); i ++) {
664- highlightRules
665- .get (tokenName )
666- .add (new HighlightRule (style , doPattern (parts .get (i ), caseInsensitive )));
642+ try {
643+ if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PATTERN ) {
644+ if (parts .size () == 2 ) {
645+ highlightRules .get (tokenName ).add (new HighlightRule (style , doPattern (".*" , caseInsensitive )));
646+ } else {
647+ for (int i = 2 ; i < parts .size (); i ++) {
648+ highlightRules
649+ .get (tokenName )
650+ .add (new HighlightRule (style , doPattern (parts .get (i ), caseInsensitive )));
651+ }
667652 }
653+ } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .START_END ) {
654+ String s = parts .get (2 );
655+ String e = parts .get (3 );
656+ highlightRules
657+ .get (tokenName )
658+ .add (new HighlightRule (
659+ style ,
660+ doPattern (s .substring (7 , s .length () - 1 ), caseInsensitive ),
661+ doPattern (e .substring (5 , e .length () - 1 ), caseInsensitive )));
662+ } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PARSER_START_WITH ) {
663+ highlightRules
664+ .get (tokenName )
665+ .add (new HighlightRule (
666+ HighlightRule .RuleType .PARSER_START_WITH ,
667+ style ,
668+ parts .get (2 ).substring (10 )));
669+ } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PARSER_CONTINUE_AS ) {
670+ highlightRules
671+ .get (tokenName )
672+ .add (new HighlightRule (
673+ HighlightRule .RuleType .PARSER_CONTINUE_AS ,
674+ style ,
675+ parts .get (2 ).substring (11 )));
668676 }
669- } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .START_END ) {
670- String s = parts .get (2 );
671- String e = parts .get (3 );
672- highlightRules
673- .get (tokenName )
674- .add (new HighlightRule (
675- style ,
676- doPattern (s .substring (7 , s .length () - 1 ), caseInsensitive ),
677- doPattern (e .substring (5 , e .length () - 1 ), caseInsensitive )));
678- } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PARSER_START_WITH ) {
679- highlightRules
680- .get (tokenName )
681- .add (new HighlightRule (
682- HighlightRule .RuleType .PARSER_START_WITH ,
683- style ,
684- parts .get (2 ).substring (10 )));
685- } else if (HighlightRule .evalRuleType (parts ) == HighlightRule .RuleType .PARSER_CONTINUE_AS ) {
686- highlightRules
687- .get (tokenName )
688- .add (new HighlightRule (
689- HighlightRule .RuleType .PARSER_CONTINUE_AS ,
690- style ,
691- parts .get (2 ).substring (11 )));
677+ } catch (PatternSyntaxException e ) {
678+ Log .warn ("Invalid highlight regex" , reference , parts , e );
679+ } catch (Exception e ) {
680+ Log .warn ("Failure while handling highlight regex" , reference , parts , e );
692681 }
693682 }
694683
695684 private Pattern doPattern (String regex , boolean caseInsensitive ) {
685+ regex = Parser .fixRegexes (regex );
696686 return caseInsensitive ? Pattern .compile (regex , Pattern .CASE_INSENSITIVE ) : Pattern .compile (regex );
697687 }
698688 }
@@ -796,7 +786,7 @@ public int getEnd() {
796786 }
797787 }
798788
799- private static class Parser {
789+ static class Parser {
800790 private static final char escapeChar = '\\' ;
801791 private String blockCommentTokenName ;
802792 private BlockCommentDelimiters blockCommentDelimiters ;
@@ -980,5 +970,148 @@ private boolean isEscaped(final CharSequence buffer, final int pos) {
980970 }
981971 return isEscapeChar (buffer , pos - 1 );
982972 }
973+
974+ /**
975+ * Perform Posix/Java regex fixups. This function parses the given regex and escapes according to these rules:
976+ *
977+ * <p>The first {@code ]} in a bracket expression does not need to be escaped in Posix,translate to {@code \]}.
978+ *
979+ * <p>Same as above for a negating bracket expression like {@code [^][]}, translate to {@code [^\]\[]}.
980+ *
981+ * <p>Any {@code [} in a bracket expression does not need to be escaped in Posix, translate to {@code \[}.
982+ *
983+ * <p>Any {@code ]} not in a bracket expression is valid in both Posix and Java, no translation.
984+ *
985+ * <p>A backslash before the closing bracket like {@code [.f\]} is not an escape of the closing bracket,
986+ * the backslash needs to be escaped for Java, translate to {@code [.f\\]}.
987+ *
988+ * <p>Do not perform the above translations within an escape via {@code \}, except for {@code \<} and {@code \>} to {@code \b}.
989+ *
990+ * <p>Replace the Posix classes like {@code [:space:]} or {@code [:digit:]} to Java classes, inside and outside a bracket expression.
991+ *
992+ * @param posix Posix regex
993+ * @return Java regex
994+ */
995+ static String fixRegexes (String posix ) {
996+ int len = posix .length ();
997+ StringBuilder java = new StringBuilder ();
998+
999+ boolean inBracketExpression = false ;
1000+
1001+ int i = 0 ;
1002+ char next ;
1003+ try {
1004+ for (; i < len ; i ++) {
1005+ char c = posix .charAt (i );
1006+
1007+ switch (c ) {
1008+ case escapeChar :
1009+ next = posix .charAt (++i );
1010+ // Don't translate anything after the \ character escape
1011+ if (inBracketExpression && next == ']' ) {
1012+ inBracketExpression = false ;
1013+ java .append ("\\ \\ " ).append (next );
1014+ } else {
1015+ // Translate '\<' and '\>' to '\b'
1016+ if (next == '<' || next == '>' ) {
1017+ next = 'b' ;
1018+ }
1019+ java .append (c ).append (next );
1020+ }
1021+ break ;
1022+ case '[' :
1023+ if (i == len - 1 ) {
1024+ throw new IllegalArgumentException ("Lone [ at the end of (index " + i + "): " + posix );
1025+ }
1026+ // Handle "single bracket" Posix "classes" like [:space:] or [:digit:]
1027+ if (posix .charAt (i + 1 ) == ':' ) {
1028+ int afterClass = nextAfterClass (posix , i + 2 );
1029+ if (!posix .regionMatches (afterClass , ":]" , 0 , 2 )) {
1030+ java .append ("[:" );
1031+ i ++;
1032+ inBracketExpression = true ;
1033+ } else {
1034+ String className = posix .substring (i + 2 , afterClass );
1035+ java .append (replaceClass (className ));
1036+ i = afterClass + 1 ;
1037+ }
1038+ break ;
1039+ }
1040+ if (inBracketExpression ) {
1041+ // Translate lone [ to \[
1042+ java .append ('\\' ).append (c );
1043+ } else {
1044+ inBracketExpression = true ;
1045+ java .append (c );
1046+ next = posix .charAt (i + 1 );
1047+ if (next == ']' ) {
1048+ i ++;
1049+ java .append ("\\ ]" );
1050+ } else if (next == '^' && posix .charAt (i + 2 ) == ']' ) {
1051+ i += 2 ;
1052+ java .append ("^\\ ]" );
1053+ }
1054+ }
1055+ break ;
1056+ case ']' :
1057+ inBracketExpression = false ;
1058+ java .append (c );
1059+ break ;
1060+ default :
1061+ java .append (c );
1062+ break ;
1063+ }
1064+ }
1065+ } catch (Exception e ) {
1066+ throw new IllegalArgumentException (
1067+ "Posix-to-Java regex translation failed around index " + i + " of: " + posix , e );
1068+ }
1069+
1070+ return java .toString ();
1071+ }
1072+
1073+ private static String replaceClass (String className ) {
1074+ switch (className ) {
1075+ case "alnum" :
1076+ return "\\ p{Alnum}" ;
1077+ case "alpha" :
1078+ return "\\ p{Alpha}" ;
1079+ case "blank" :
1080+ return "\\ p{Blank}" ;
1081+ case "cntrl" :
1082+ return "\\ p{Cntrl}" ;
1083+ case "digit" :
1084+ return "\\ p{Digit}" ;
1085+ case "graph" :
1086+ return "\\ p{Graph}" ;
1087+ case "lower" :
1088+ return "\\ p{Lower}" ;
1089+ case "print" :
1090+ return "\\ p{Print}" ;
1091+ case "punct" :
1092+ return "\\ p{Punct}" ;
1093+ case "space" :
1094+ return "\\ s" ;
1095+ case "upper" :
1096+ return "\\ p{Upper}" ;
1097+ case "xdigit" :
1098+ return "\\ p{XDigit}" ;
1099+ }
1100+ throw new IllegalArgumentException ("Unknown class '" + className + "'" );
1101+ }
1102+
1103+ private static int nextAfterClass (String s , int idx ) {
1104+ if (s .charAt (idx ) == ':' ) {
1105+ idx ++;
1106+ }
1107+ while (true ) {
1108+ char c = s .charAt (idx );
1109+ if (!Character .isLetterOrDigit (c )) {
1110+ break ;
1111+ }
1112+ idx ++;
1113+ }
1114+ return idx ;
1115+ }
9831116 }
9841117}
0 commit comments