11package info .codesaway .bex .matching ;
22
33import static info .codesaway .bex .BEXPairs .bexPair ;
4+ import static info .codesaway .bex .BEXSide .LEFT ;
5+ import static info .codesaway .bex .BEXSide .RIGHT ;
46import static info .codesaway .bex .matching .BEXMatchingUtilities .currentChar ;
5- import static info .codesaway .bex .matching .BEXMatchingUtilities .hasCaseInsensitiveText ;
67import static info .codesaway .bex .matching .BEXMatchingUtilities .hasText ;
78import static info .codesaway .bex .matching .BEXMatchingUtilities .isWordCharacter ;
9+ import static info .codesaway .bex .matching .BEXMatchingUtilities .previousChar ;
10+ import static java .util .stream .Collectors .toList ;
811
912import java .util .Arrays ;
1013import java .util .Collections ;
1114import java .util .List ;
1215import java .util .Optional ;
1316import java .util .Set ;
17+ import java .util .function .BiPredicate ;
1418import java .util .function .Function ;
1519
1620import info .codesaway .bex .BEXPair ;
21+ import info .codesaway .bex .BEXSide ;
1722import info .codesaway .bex .ImmutableIntRangeMap ;
1823
1924public enum BEXMatchingLanguage implements MatchingLanguage {
@@ -24,30 +29,37 @@ public enum BEXMatchingLanguage implements MatchingLanguage {
2429 * SQL Matching language
2530 * @since 0.11
2631 */
27- SQL (BEXMatchingUtilities ::parseSQLTextStates , true , bexPair ("BEGIN" , "END" )),
32+ SQL (BEXMatchingUtilities ::parseSQLTextStates , "@" , true , bexPair ("BEGIN" , "END" )),
2833
2934 // End of enum
3035 ;
3136
3237 private final Function <CharSequence , ImmutableIntRangeMap <MatchingStateOption >> parseFunction ;
38+ private final String specialWordCharacters ;
3339 private final boolean hasCaseInsensitiveDelimiters ;
34- private final List <BEXPair <String >> delimiters ;
40+ private final List <Optional < BEXPair <String > >> delimiters ;
3541
3642 private BEXMatchingLanguage (
3743 final Function <CharSequence , ImmutableIntRangeMap <MatchingStateOption >> parseFunction ) {
3844 this .parseFunction = parseFunction ;
45+ this .specialWordCharacters = "" ;
3946 this .hasCaseInsensitiveDelimiters = false ;
4047 this .delimiters = Collections .emptyList ();
4148 }
4249
4350 @ SafeVarargs
4451 private BEXMatchingLanguage (
4552 final Function <CharSequence , ImmutableIntRangeMap <MatchingStateOption >> parseFunction ,
53+ final String specialWordCharacters ,
4654 final boolean hasCaseInsensitiveDelimiters ,
4755 final BEXPair <String >... delimiters ) {
4856 this .parseFunction = parseFunction ;
57+ this .specialWordCharacters = specialWordCharacters ;
4958 this .hasCaseInsensitiveDelimiters = hasCaseInsensitiveDelimiters ;
50- this .delimiters = Arrays .asList (delimiters );
59+ this .delimiters = Arrays .stream (delimiters )
60+ // Wrap in Optional, so don't have to create new Optional objects for each matching
61+ .map (Optional ::of )
62+ .collect (toList ());
5163 }
5264
5365 @ Override
@@ -59,19 +71,10 @@ public ImmutableIntRangeMap<MatchingStateOption> parse(final CharSequence text)
5971 public Optional <BEXPair <String >> findStartDelimiter (final CharSequence text , final int index ,
6072 final Set <MatchingLanguageSetting > settings ) {
6173
62- for (BEXPair <String > delimiter : this .delimiters ) {
63- String s = delimiter .getLeft ();
74+ Optional <BEXPair <String >> delimiter = this .findDelimiter (LEFT , text , index );
6475
65- if (this .hasCaseInsensitiveDelimiters ) {
66- if (hasCaseInsensitiveText (text , index , s )
67- && !isWordCharacter (currentChar (text , index + s .length ()))) {
68- return Optional .of (delimiter );
69- }
70- } else {
71- if (hasText (text , index , s ) && !isWordCharacter (currentChar (text , index + s .length ()))) {
72- return Optional .of (delimiter );
73- }
74- }
76+ if (delimiter .isPresent ()) {
77+ return delimiter ;
7578 }
7679
7780 return MatchingLanguage .super .findStartDelimiter (text , index , settings );
@@ -82,30 +85,41 @@ public MatchingDelimiterState findEndDelimiter(final BEXPair<String> lastDelimit
8285 final int index ,
8386 final Set <MatchingLanguageSetting > settings ) {
8487
85- for (BEXPair <String > delimiter : this .delimiters ) {
86- String s = delimiter .getRight ();
88+ Optional <BEXPair <String >> delimiter = this .findDelimiter (RIGHT , text , index );
8789
88- if (this .hasCaseInsensitiveDelimiters ) {
89- if (hasCaseInsensitiveText (text , index , s )
90- && !isWordCharacter (currentChar (text , index + s .length ()))) {
91- MatchingDelimiterResult result = lastDelimiter != null
92- && s .equalsIgnoreCase (lastDelimiter .getRight ())
93- ? MatchingDelimiterResult .FOUND
94- : MatchingDelimiterResult .MISMATCHED ;
90+ if (delimiter .isPresent ()) {
91+ String s = delimiter .get ().getRight ();
9592
96- return new MatchingDelimiterState (result , s );
97- }
98- } else {
99- if (hasText (text , index , s ) && !isWordCharacter (currentChar (text , index + s .length ()))) {
100- MatchingDelimiterResult result = lastDelimiter != null && s .equals (lastDelimiter .getRight ())
101- ? MatchingDelimiterResult .FOUND
102- : MatchingDelimiterResult .MISMATCHED ;
93+ BiPredicate <String , String > equals = this .hasCaseInsensitiveDelimiters
94+ ? String ::equalsIgnoreCase
95+ : String ::equals ;
96+
97+ MatchingDelimiterResult result = lastDelimiter != null && equals .test (s , lastDelimiter .getRight ())
98+ ? MatchingDelimiterResult .FOUND
99+ : MatchingDelimiterResult .MISMATCHED ;
100+
101+ return new MatchingDelimiterState (result , s );
102+ }
103103
104- return new MatchingDelimiterState (result , s );
104+ return MatchingLanguage .super .findEndDelimiter (lastDelimiter , text , index , settings );
105+ }
106+
107+ private Optional <BEXPair <String >> findDelimiter (final BEXSide side , final CharSequence text , final int index ) {
108+ if (!this .delimiters .isEmpty () && !this .isPartOfWord (previousChar (text , index ))) {
109+ for (Optional <BEXPair <String >> delimiter : this .delimiters ) {
110+ String s = delimiter .get ().get (side );
111+ char nextChar = currentChar (text , index + s .length ());
112+
113+ if (hasText (text , index , s , this .hasCaseInsensitiveDelimiters ) && !this .isPartOfWord (nextChar )) {
114+ return delimiter ;
105115 }
106116 }
107117 }
108118
109- return MatchingLanguage .super .findEndDelimiter (lastDelimiter , text , index , settings );
119+ return Optional .empty ();
120+ }
121+
122+ private boolean isPartOfWord (final char c ) {
123+ return isWordCharacter (c ) || this .specialWordCharacters .indexOf (c ) != -1 ;
110124 }
111125}
0 commit comments