Painless: =~ and ==~ operators

Adds support for the find operator (=~) and the match operator (==~)
to painless's regexes. Also whitelists most of the Matcher class and
documents regex support in painless.

The find operator (=~) returns a boolean that is the result of building
a matcher on the lhs with the Pattern on the RHS and calling `find` on
it. Use it like this:

```
if (ctx._source.last =~ /b/)
```

The match operator (==~) returns boolean like find but instead of calling
`find` on the Matcher it calls `matches`.

```
if (ctx._source.last ==~ /[^aeiou].*[aeiou]/)
```

Finally, if you want the actual matcher you do:

```
Matcher m = /[aeiou]/.matcher(ctx._source.last)
```
This commit is contained in:
Nik Everett 2016-06-13 21:42:37 -04:00
parent 3c9712794e
commit 8d3ef742db
17 changed files with 947 additions and 700 deletions

View file

@ -33,6 +33,8 @@ to `painless`.
* Shortcuts for list, map access using the dot `.` operator * Shortcuts for list, map access using the dot `.` operator
* Native support for regular expressions with `/pattern/`, `=~`, and `==~`
[[painless-examples]] [[painless-examples]]
[float] [float]
@ -199,6 +201,79 @@ POST hockey/player/1/_update
---------------------------------------------------------------- ----------------------------------------------------------------
// CONSOLE // CONSOLE
[float]
=== Regular expressions
Painless's native support for regular expressions has syntax constructs:
* `/pattern/`: Pattern literals create patterns. This is the only way to create
a pattern in painless.
* `=~`: The find operator return a `boolean`, `true` if a subsequence of the
text matches, `false` otherwise.
* `==~`: The match operator returns a `boolean`, `true` if the text matches,
`false` if it doesn't.
Using the find operator (`=~`) you can update all hockey players with "b" in
their last name:
[source,js]
----------------------------------------------------------------
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "if (ctx._source.last =~ /b/) {ctx._source.last += \"matched\"} else {ctx.op = 'noop'}"
}
}
----------------------------------------------------------------
// CONSOLE
Using the match operator (`==~`) you can update all the hockey players who's
names start with a consonant and end with a vowel:
[source,js]
----------------------------------------------------------------
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "if (ctx._source.last ==~ /[^aeiou].*[aeiou]/) {ctx._source.last += \"matched\"} else {ctx.op = 'noop'}"
}
}
----------------------------------------------------------------
// CONSOLE
Or you can use the `Pattern.matcher` directory to get a `Matcher` instance and
remove all of the vowels in all of their names:
[source,js]
----------------------------------------------------------------
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = /[aeiou]/.matcher(ctx._source.last).replaceAll('')"
}
}
----------------------------------------------------------------
// CONSOLE
Note: all of the `_update_by_query` examples above could really do with a
`query` to limit the data that they pull back. While you *could* use a
<<query-dsl-script-query>> it wouldn't be as efficient as using any other query
because script queries aren't able to use the inverted index to limit the
documents that they have to check.
The pattern syntax is just
http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html[Java regular expressions].
We intentionally don't allow scripts to call `Pattern.compile` to get a new
pattern on the fly because building a `Pattern` is (comparatively) slow.
Pattern literals (`/apattern/`) have fancy constant extraction so no matter
where they show up in the painless script they are built only when the script
is first used. It is fairly similar to how `String` literals work in Java.
[[painless-api]] [[painless-api]]
[float] [float]
== Painless API == Painless API

View file

@ -80,6 +80,8 @@ COND: '?';
COLON: ':'; COLON: ':';
REF: '::'; REF: '::';
ARROW: '->'; ARROW: '->';
FIND: '=~';
MATCH: '==~';
INCR: '++'; INCR: '++';
DECR: '--'; DECR: '--';

View file

@ -49,33 +49,35 @@ COND=48
COLON=49 COLON=49
REF=50 REF=50
ARROW=51 ARROW=51
INCR=52 FIND=52
DECR=53 MATCH=53
ASSIGN=54 INCR=54
AADD=55 DECR=55
ASUB=56 ASSIGN=56
AMUL=57 AADD=57
ADIV=58 ASUB=58
AREM=59 AMUL=59
AAND=60 ADIV=60
AXOR=61 AREM=61
AOR=62 AAND=62
ALSH=63 AXOR=63
ARSH=64 AOR=64
AUSH=65 ALSH=65
OCTAL=66 ARSH=66
HEX=67 AUSH=67
INTEGER=68 OCTAL=68
DECIMAL=69 HEX=69
STRING=70 INTEGER=70
REGEX=71 DECIMAL=71
TRUE=72 STRING=72
FALSE=73 REGEX=73
NULL=74 TRUE=74
TYPE=75 FALSE=75
ID=76 NULL=76
DOTINTEGER=77 TYPE=77
DOTID=78 ID=78
DOTINTEGER=79
DOTID=80
'{'=3 '{'=3
'}'=4 '}'=4
'['=5 '['=5
@ -125,20 +127,22 @@ DOTID=78
':'=49 ':'=49
'::'=50 '::'=50
'->'=51 '->'=51
'++'=52 '=~'=52
'--'=53 '==~'=53
'='=54 '++'=54
'+='=55 '--'=55
'-='=56 '='=56
'*='=57 '+='=57
'/='=58 '-='=58
'%='=59 '*='=59
'&='=60 '/='=60
'^='=61 '%='=61
'|='=62 '&='=62
'<<='=63 '^='=63
'>>='=64 '|='=64
'>>>='=65 '<<='=65
'true'=72 '>>='=66
'false'=73 '>>>='=67
'null'=74 'true'=74
'false'=75
'null'=76

View file

@ -113,6 +113,7 @@ expression returns [boolean s = true]
: u = unary[false] { $s = $u.s; } # single : u = unary[false] { $s = $u.s; } # single
| expression ( MUL | DIV | REM ) expression { $s = false; } # binary | expression ( MUL | DIV | REM ) expression { $s = false; } # binary
| expression ( ADD | SUB ) expression { $s = false; } # binary | expression ( ADD | SUB ) expression { $s = false; } # binary
| expression ( FIND | MATCH ) expression { $s = false; } # binary
| expression ( LSH | RSH | USH ) expression { $s = false; } # binary | expression ( LSH | RSH | USH ) expression { $s = false; } # binary
| expression ( LT | LTE | GT | GTE ) expression { $s = false; } # comp | expression ( LT | LTE | GT | GTE ) expression { $s = false; } # comp
| expression ( EQ | EQR | NE | NER ) expression { $s = false; } # comp | expression ( EQ | EQR | NE | NER ) expression { $s = false; } # comp

View file

@ -49,33 +49,35 @@ COND=48
COLON=49 COLON=49
REF=50 REF=50
ARROW=51 ARROW=51
INCR=52 FIND=52
DECR=53 MATCH=53
ASSIGN=54 INCR=54
AADD=55 DECR=55
ASUB=56 ASSIGN=56
AMUL=57 AADD=57
ADIV=58 ASUB=58
AREM=59 AMUL=59
AAND=60 ADIV=60
AXOR=61 AREM=61
AOR=62 AAND=62
ALSH=63 AXOR=63
ARSH=64 AOR=64
AUSH=65 ALSH=65
OCTAL=66 ARSH=66
HEX=67 AUSH=67
INTEGER=68 OCTAL=68
DECIMAL=69 HEX=69
STRING=70 INTEGER=70
REGEX=71 DECIMAL=71
TRUE=72 STRING=72
FALSE=73 REGEX=73
NULL=74 TRUE=74
TYPE=75 FALSE=75
ID=76 NULL=76
DOTINTEGER=77 TYPE=77
DOTID=78 ID=78
DOTINTEGER=79
DOTID=80
'{'=3 '{'=3
'}'=4 '}'=4
'['=5 '['=5
@ -125,20 +127,22 @@ DOTID=78
':'=49 ':'=49
'::'=50 '::'=50
'->'=51 '->'=51
'++'=52 '=~'=52
'--'=53 '==~'=53
'='=54 '++'=54
'+='=55 '--'=55
'-='=56 '='=56
'*='=57 '+='=57
'/='=58 '-='=58
'%='=59 '*='=59
'&='=60 '/='=60
'^='=61 '%='=61
'|='=62 '&='=62
'<<='=63 '^='=63
'>>='=64 '|='=64
'>>>='=65 '<<='=65
'true'=72 '>>='=66
'false'=73 '>>>='=67
'null'=74 'true'=74
'false'=75
'null'=76

View file

@ -87,6 +87,8 @@ public final class Definition {
public static final Type DEF_TYPE = getType("def"); public static final Type DEF_TYPE = getType("def");
public static final Type STRING_TYPE = getType("String"); public static final Type STRING_TYPE = getType("String");
public static final Type EXCEPTION_TYPE = getType("Exception"); public static final Type EXCEPTION_TYPE = getType("Exception");
public static final Type PATTERN_TYPE = getType("Pattern");
public static final Type MATCHER_TYPE = getType("Matcher");
public enum Sort { public enum Sort {
VOID( void.class , 0 , true , false , false , false ), VOID( void.class , 0 , true , false , false , false ),

View file

@ -386,5 +386,4 @@ public final class MethodWriter extends GeneratorAdapter {
public void visitEnd() { public void visitEnd() {
throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead"); throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead");
} }
} }

View file

@ -32,6 +32,8 @@ public enum Operation {
REM ( "%" ), REM ( "%" ),
ADD ( "+" ), ADD ( "+" ),
SUB ( "-" ), SUB ( "-" ),
FIND ( "=~" ),
MATCH ( "==~" ),
LSH ( "<<" ), LSH ( "<<" ),
RSH ( ">>" ), RSH ( ">>" ),
USH ( ">>>" ), USH ( ">>>" ),

View file

@ -35,6 +35,7 @@ import java.util.BitSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -73,12 +74,15 @@ public final class WriterConstants {
public final static Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); public final static Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
/** /**
* A Method instance for {@linkplain Pattern#compile}. This isn't looked up from Definition because we intentionally don't add it there * A Method instance for {@linkplain Pattern#compile}. This isn't available from Definition because we intentionally don't add it there
* so that the script can't create regexes without this syntax. Essentially, our static regex syntax has a monopoly on building regexes * so that the script can't create regexes without this syntax. Essentially, our static regex syntax has a monopoly on building regexes
* because it can do it statically. This is both faster and prevents the script from doing something super slow like building a regex * because it can do it statically. This is both faster and prevents the script from doing something super slow like building a regex
* per time it is run. * per time it is run.
*/ */
public final static Method PATTERN_COMPILE = getAsmMethod(Pattern.class, "compile", String.class); public final static Method PATTERN_COMPILE = getAsmMethod(Pattern.class, "compile", String.class);
public final static Method PATTERN_MATCHER = getAsmMethod(Matcher.class, "matcher", CharSequence.class);
public final static Method MATCHER_MATCHES = getAsmMethod(boolean.class, "matches");
public final static Method MATCHER_FIND = getAsmMethod(boolean.class, "find");
/** dynamic callsite bootstrap signature */ /** dynamic callsite bootstrap signature */
public final static MethodType DEF_BOOTSTRAP_TYPE = public final static MethodType DEF_BOOTSTRAP_TYPE =

View file

@ -26,10 +26,10 @@ class PainlessLexer extends Lexer {
BWNOT=26, MUL=27, DIV=28, REM=29, ADD=30, SUB=31, LSH=32, RSH=33, USH=34, BWNOT=26, MUL=27, DIV=28, REM=29, ADD=30, SUB=31, LSH=32, RSH=33, USH=34,
LT=35, LTE=36, GT=37, GTE=38, EQ=39, EQR=40, NE=41, NER=42, BWAND=43, LT=35, LTE=36, GT=37, GTE=38, EQ=39, EQR=40, NE=41, NER=42, BWAND=43,
XOR=44, BWOR=45, BOOLAND=46, BOOLOR=47, COND=48, COLON=49, REF=50, ARROW=51, XOR=44, BWOR=45, BOOLAND=46, BOOLOR=47, COND=48, COLON=49, REF=50, ARROW=51,
INCR=52, DECR=53, ASSIGN=54, AADD=55, ASUB=56, AMUL=57, ADIV=58, AREM=59, FIND=52, MATCH=53, INCR=54, DECR=55, ASSIGN=56, AADD=57, ASUB=58, AMUL=59,
AAND=60, AXOR=61, AOR=62, ALSH=63, ARSH=64, AUSH=65, OCTAL=66, HEX=67, ADIV=60, AREM=61, AAND=62, AXOR=63, AOR=64, ALSH=65, ARSH=66, AUSH=67,
INTEGER=68, DECIMAL=69, STRING=70, REGEX=71, TRUE=72, FALSE=73, NULL=74, OCTAL=68, HEX=69, INTEGER=70, DECIMAL=71, STRING=72, REGEX=73, TRUE=74,
TYPE=75, ID=76, DOTINTEGER=77, DOTID=78; FALSE=75, NULL=76, TYPE=77, ID=78, DOTINTEGER=79, DOTID=80;
public static final int AFTER_DOT = 1; public static final int AFTER_DOT = 1;
public static String[] modeNames = { public static String[] modeNames = {
"DEFAULT_MODE", "AFTER_DOT" "DEFAULT_MODE", "AFTER_DOT"
@ -41,10 +41,11 @@ class PainlessLexer extends Lexer {
"BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "THIS", "BOOLNOT", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "THIS", "BOOLNOT",
"BWNOT", "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT", "BWNOT", "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT",
"LTE", "GT", "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR", "LTE", "GT", "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR",
"BOOLAND", "BOOLOR", "COND", "COLON", "REF", "ARROW", "INCR", "DECR", "BOOLAND", "BOOLOR", "COND", "COLON", "REF", "ARROW", "FIND", "MATCH",
"ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", "AREM", "AAND",
"ALSH", "ARSH", "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL",
"REGEX", "TRUE", "FALSE", "NULL", "TYPE", "ID", "DOTINTEGER", "DOTID" "STRING", "REGEX", "TRUE", "FALSE", "NULL", "TYPE", "ID", "DOTINTEGER",
"DOTID"
}; };
private static final String[] _LITERAL_NAMES = { private static final String[] _LITERAL_NAMES = {
@ -53,9 +54,10 @@ class PainlessLexer extends Lexer {
"'return'", "'new'", "'try'", "'catch'", "'throw'", "'this'", "'!'", "'~'", "'return'", "'new'", "'try'", "'catch'", "'throw'", "'this'", "'!'", "'~'",
"'*'", "'/'", "'%'", "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='", "'*'", "'/'", "'%'", "'+'", "'-'", "'<<'", "'>>'", "'>>>'", "'<'", "'<='",
"'>'", "'>='", "'=='", "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'", "'>'", "'>='", "'=='", "'==='", "'!='", "'!=='", "'&'", "'^'", "'|'",
"'&&'", "'||'", "'?'", "':'", "'::'", "'->'", "'++'", "'--'", "'='", "'+='", "'&&'", "'||'", "'?'", "':'", "'::'", "'->'", "'=~'", "'==~'", "'++'",
"'-='", "'*='", "'/='", "'%='", "'&='", "'^='", "'|='", "'<<='", "'>>='", "'--'", "'='", "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='",
"'>>>='", null, null, null, null, null, null, "'true'", "'false'", "'null'" "'|='", "'<<='", "'>>='", "'>>>='", null, null, null, null, null, null,
"'true'", "'false'", "'null'"
}; };
private static final String[] _SYMBOLIC_NAMES = { private static final String[] _SYMBOLIC_NAMES = {
null, "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", "RP", null, "WS", "COMMENT", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "LP", "RP",
@ -63,10 +65,11 @@ class PainlessLexer extends Lexer {
"BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "THIS", "BOOLNOT", "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "THIS", "BOOLNOT",
"BWNOT", "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT", "BWNOT", "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT",
"LTE", "GT", "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR", "LTE", "GT", "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR",
"BOOLAND", "BOOLOR", "COND", "COLON", "REF", "ARROW", "INCR", "DECR", "BOOLAND", "BOOLOR", "COND", "COLON", "REF", "ARROW", "FIND", "MATCH",
"ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", "AREM", "AAND", "AXOR", "AOR", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", "AREM", "AAND",
"ALSH", "ARSH", "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL", "STRING", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", "HEX", "INTEGER", "DECIMAL",
"REGEX", "TRUE", "FALSE", "NULL", "TYPE", "ID", "DOTINTEGER", "DOTID" "STRING", "REGEX", "TRUE", "FALSE", "NULL", "TYPE", "ID", "DOTINTEGER",
"DOTID"
}; };
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
@ -127,9 +130,9 @@ class PainlessLexer extends Lexer {
switch (ruleIndex) { switch (ruleIndex) {
case 27: case 27:
return DIV_sempred((RuleContext)_localctx, predIndex); return DIV_sempred((RuleContext)_localctx, predIndex);
case 70: case 72:
return REGEX_sempred((RuleContext)_localctx, predIndex); return REGEX_sempred((RuleContext)_localctx, predIndex);
case 74: case 76:
return TYPE_sempred((RuleContext)_localctx, predIndex); return TYPE_sempred((RuleContext)_localctx, predIndex);
} }
return true; return true;
@ -157,7 +160,7 @@ class PainlessLexer extends Lexer {
} }
public static final String _serializedATN = public static final String _serializedATN =
"\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2P\u0228\b\1\b\1\4"+ "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2R\u0233\b\1\b\1\4"+
"\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n"+ "\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n"+
"\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ "\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+
"\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31"+ "\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31"+
@ -166,190 +169,193 @@ class PainlessLexer extends Lexer {
"+\4,\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64"+ "+\4,\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64"+
"\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:\4;\t;\4<\t<\4=\t"+ "\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:\4;\t;\4<\t<\4=\t"+
"=\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4"+ "=\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4"+
"I\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\3\2\6\2\u00a2\n\2\r\2\16\2\u00a3"+ "I\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4Q\tQ\3\2\6\2\u00a6\n\2"+
"\3\2\3\2\3\3\3\3\3\3\3\3\7\3\u00ac\n\3\f\3\16\3\u00af\13\3\3\3\3\3\3\3"+ "\r\2\16\2\u00a7\3\2\3\2\3\3\3\3\3\3\3\3\7\3\u00b0\n\3\f\3\16\3\u00b3\13"+
"\3\3\3\3\7\3\u00b6\n\3\f\3\16\3\u00b9\13\3\3\3\3\3\5\3\u00bd\n\3\3\3\3"+ "\3\3\3\3\3\3\3\3\3\3\3\7\3\u00ba\n\3\f\3\16\3\u00bd\13\3\3\3\3\3\5\3\u00c1"+
"\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3\n\3\n\3\13"+ "\n\3\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3"+
"\3\13\3\f\3\f\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3\17\3\17\3\17\3\17"+ "\n\3\n\3\13\3\13\3\f\3\f\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3\17\3\17"+
"\3\17\3\17\3\20\3\20\3\20\3\21\3\21\3\21\3\21\3\22\3\22\3\22\3\22\3\22"+ "\3\17\3\17\3\17\3\17\3\20\3\20\3\20\3\21\3\21\3\21\3\21\3\22\3\22\3\22"+
"\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24"+ "\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\23\3\24\3\24"+
"\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\27\3\27\3\27"+ "\3\24\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\27"+
"\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31\3\31\3\31"+ "\3\27\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31"+
"\3\32\3\32\3\33\3\33\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\37\3\37\3 \3"+ "\3\31\3\31\3\32\3\32\3\33\3\33\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\37"+
" \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3#\3$\3$\3%\3%\3%\3&\3&\3\'\3\'\3\'\3"+ "\3\37\3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3#\3$\3$\3%\3%\3%\3&\3&\3\'"+
"(\3(\3(\3)\3)\3)\3)\3*\3*\3*\3+\3+\3+\3+\3,\3,\3-\3-\3.\3.\3/\3/\3/\3"+ "\3\'\3\'\3(\3(\3(\3)\3)\3)\3)\3*\3*\3*\3+\3+\3+\3+\3,\3,\3-\3-\3.\3.\3"+
"\60\3\60\3\60\3\61\3\61\3\62\3\62\3\63\3\63\3\63\3\64\3\64\3\64\3\65\3"+ "/\3/\3/\3\60\3\60\3\60\3\61\3\61\3\62\3\62\3\63\3\63\3\63\3\64\3\64\3"+
"\65\3\65\3\66\3\66\3\66\3\67\3\67\38\38\38\39\39\39\3:\3:\3:\3;\3;\3;"+ "\64\3\65\3\65\3\65\3\66\3\66\3\66\3\66\3\67\3\67\3\67\38\38\38\39\39\3"+
"\3<\3<\3<\3=\3=\3=\3>\3>\3>\3?\3?\3?\3@\3@\3@\3@\3A\3A\3A\3A\3B\3B\3B"+ ":\3:\3:\3;\3;\3;\3<\3<\3<\3=\3=\3=\3>\3>\3>\3?\3?\3?\3@\3@\3@\3A\3A\3"+
"\3B\3B\3C\3C\6C\u018f\nC\rC\16C\u0190\3C\5C\u0194\nC\3D\3D\3D\6D\u0199"+ "A\3B\3B\3B\3B\3C\3C\3C\3C\3D\3D\3D\3D\3D\3E\3E\6E\u019a\nE\rE\16E\u019b"+
"\nD\rD\16D\u019a\3D\5D\u019e\nD\3E\3E\3E\7E\u01a3\nE\fE\16E\u01a6\13E"+ "\3E\5E\u019f\nE\3F\3F\3F\6F\u01a4\nF\rF\16F\u01a5\3F\5F\u01a9\nF\3G\3"+
"\5E\u01a8\nE\3E\5E\u01ab\nE\3F\3F\3F\7F\u01b0\nF\fF\16F\u01b3\13F\5F\u01b5"+ "G\3G\7G\u01ae\nG\fG\16G\u01b1\13G\5G\u01b3\nG\3G\5G\u01b6\nG\3H\3H\3H"+
"\nF\3F\3F\6F\u01b9\nF\rF\16F\u01ba\5F\u01bd\nF\3F\3F\5F\u01c1\nF\3F\6"+ "\7H\u01bb\nH\fH\16H\u01be\13H\5H\u01c0\nH\3H\3H\6H\u01c4\nH\rH\16H\u01c5"+
"F\u01c4\nF\rF\16F\u01c5\5F\u01c8\nF\3F\5F\u01cb\nF\3G\3G\3G\3G\3G\3G\7"+ "\5H\u01c8\nH\3H\3H\5H\u01cc\nH\3H\6H\u01cf\nH\rH\16H\u01d0\5H\u01d3\n"+
"G\u01d3\nG\fG\16G\u01d6\13G\3G\3G\3G\3G\3G\3G\3G\7G\u01df\nG\fG\16G\u01e2"+ "H\3H\5H\u01d6\nH\3I\3I\3I\3I\3I\3I\7I\u01de\nI\fI\16I\u01e1\13I\3I\3I"+
"\13G\3G\5G\u01e5\nG\3H\3H\3H\3H\6H\u01eb\nH\rH\16H\u01ec\3H\3H\3H\3I\3"+ "\3I\3I\3I\3I\3I\7I\u01ea\nI\fI\16I\u01ed\13I\3I\5I\u01f0\nI\3J\3J\3J\3"+
"I\3I\3I\3I\3J\3J\3J\3J\3J\3J\3K\3K\3K\3K\3K\3L\3L\3L\3L\7L\u0206\nL\f"+ "J\6J\u01f6\nJ\rJ\16J\u01f7\3J\3J\3J\3K\3K\3K\3K\3K\3L\3L\3L\3L\3L\3L\3"+
"L\16L\u0209\13L\3L\3L\3M\3M\7M\u020f\nM\fM\16M\u0212\13M\3N\3N\3N\7N\u0217"+ "M\3M\3M\3M\3M\3N\3N\3N\3N\7N\u0211\nN\fN\16N\u0214\13N\3N\3N\3O\3O\7O"+
"\nN\fN\16N\u021a\13N\5N\u021c\nN\3N\3N\3O\3O\7O\u0222\nO\fO\16O\u0225"+ "\u021a\nO\fO\16O\u021d\13O\3P\3P\3P\7P\u0222\nP\fP\16P\u0225\13P\5P\u0227"+
"\13O\3O\3O\6\u00ad\u00b7\u01d4\u01e0\2P\4\3\6\4\b\5\n\6\f\7\16\b\20\t"+ "\nP\3P\3P\3Q\3Q\7Q\u022d\nQ\fQ\16Q\u0230\13Q\3Q\3Q\6\u00b1\u00bb\u01df"+
"\22\n\24\13\26\f\30\r\32\16\34\17\36\20 \21\"\22$\23&\24(\25*\26,\27."+ "\u01eb\2R\4\3\6\4\b\5\n\6\f\7\16\b\20\t\22\n\24\13\26\f\30\r\32\16\34"+
"\30\60\31\62\32\64\33\66\348\35:\36<\37> @!B\"D#F$H%J&L\'N(P)R*T+V,X-"+ "\17\36\20 \21\"\22$\23&\24(\25*\26,\27.\30\60\31\62\32\64\33\66\348\35"+
"Z.\\/^\60`\61b\62d\63f\64h\65j\66l\67n8p9r:t;v<x=z>|?~@\u0080A\u0082B"+ ":\36<\37> @!B\"D#F$H%J&L\'N(P)R*T+V,X-Z.\\/^\60`\61b\62d\63f\64h\65j\66"+
"\u0084C\u0086D\u0088E\u008aF\u008cG\u008eH\u0090I\u0092J\u0094K\u0096"+ "l\67n8p9r:t;v<x=z>|?~@\u0080A\u0082B\u0084C\u0086D\u0088E\u008aF\u008c"+
"L\u0098M\u009aN\u009cO\u009eP\4\2\3\23\5\2\13\f\17\17\"\"\4\2\f\f\17\17"+ "G\u008eH\u0090I\u0092J\u0094K\u0096L\u0098M\u009aN\u009cO\u009eP\u00a0"+
"\3\2\629\4\2NNnn\4\2ZZzz\5\2\62;CHch\3\2\63;\3\2\62;\b\2FFHHNNffhhnn\4"+ "Q\u00a2R\4\2\3\23\5\2\13\f\17\17\"\"\4\2\f\f\17\17\3\2\629\4\2NNnn\4\2"+
"\2GGgg\4\2--//\4\2HHhh\4\2$$^^\4\2\f\f\61\61\3\2\f\f\5\2C\\aac|\6\2\62"+ "ZZzz\5\2\62;CHch\3\2\63;\3\2\62;\b\2FFHHNNffhhnn\4\2GGgg\4\2--//\4\2H"+
";C\\aac|\u0247\2\4\3\2\2\2\2\6\3\2\2\2\2\b\3\2\2\2\2\n\3\2\2\2\2\f\3\2"+ "Hhh\4\2$$^^\4\2\f\f\61\61\3\2\f\f\5\2C\\aac|\6\2\62;C\\aac|\u0252\2\4"+
"\2\2\2\16\3\2\2\2\2\20\3\2\2\2\2\22\3\2\2\2\2\24\3\2\2\2\2\26\3\2\2\2"+ "\3\2\2\2\2\6\3\2\2\2\2\b\3\2\2\2\2\n\3\2\2\2\2\f\3\2\2\2\2\16\3\2\2\2"+
"\2\30\3\2\2\2\2\32\3\2\2\2\2\34\3\2\2\2\2\36\3\2\2\2\2 \3\2\2\2\2\"\3"+ "\2\20\3\2\2\2\2\22\3\2\2\2\2\24\3\2\2\2\2\26\3\2\2\2\2\30\3\2\2\2\2\32"+
"\2\2\2\2$\3\2\2\2\2&\3\2\2\2\2(\3\2\2\2\2*\3\2\2\2\2,\3\2\2\2\2.\3\2\2"+ "\3\2\2\2\2\34\3\2\2\2\2\36\3\2\2\2\2 \3\2\2\2\2\"\3\2\2\2\2$\3\2\2\2\2"+
"\2\2\60\3\2\2\2\2\62\3\2\2\2\2\64\3\2\2\2\2\66\3\2\2\2\28\3\2\2\2\2:\3"+ "&\3\2\2\2\2(\3\2\2\2\2*\3\2\2\2\2,\3\2\2\2\2.\3\2\2\2\2\60\3\2\2\2\2\62"+
"\2\2\2\2<\3\2\2\2\2>\3\2\2\2\2@\3\2\2\2\2B\3\2\2\2\2D\3\2\2\2\2F\3\2\2"+ "\3\2\2\2\2\64\3\2\2\2\2\66\3\2\2\2\28\3\2\2\2\2:\3\2\2\2\2<\3\2\2\2\2"+
"\2\2H\3\2\2\2\2J\3\2\2\2\2L\3\2\2\2\2N\3\2\2\2\2P\3\2\2\2\2R\3\2\2\2\2"+ ">\3\2\2\2\2@\3\2\2\2\2B\3\2\2\2\2D\3\2\2\2\2F\3\2\2\2\2H\3\2\2\2\2J\3"+
"T\3\2\2\2\2V\3\2\2\2\2X\3\2\2\2\2Z\3\2\2\2\2\\\3\2\2\2\2^\3\2\2\2\2`\3"+ "\2\2\2\2L\3\2\2\2\2N\3\2\2\2\2P\3\2\2\2\2R\3\2\2\2\2T\3\2\2\2\2V\3\2\2"+
"\2\2\2\2b\3\2\2\2\2d\3\2\2\2\2f\3\2\2\2\2h\3\2\2\2\2j\3\2\2\2\2l\3\2\2"+ "\2\2X\3\2\2\2\2Z\3\2\2\2\2\\\3\2\2\2\2^\3\2\2\2\2`\3\2\2\2\2b\3\2\2\2"+
"\2\2n\3\2\2\2\2p\3\2\2\2\2r\3\2\2\2\2t\3\2\2\2\2v\3\2\2\2\2x\3\2\2\2\2"+ "\2d\3\2\2\2\2f\3\2\2\2\2h\3\2\2\2\2j\3\2\2\2\2l\3\2\2\2\2n\3\2\2\2\2p"+
"z\3\2\2\2\2|\3\2\2\2\2~\3\2\2\2\2\u0080\3\2\2\2\2\u0082\3\2\2\2\2\u0084"+ "\3\2\2\2\2r\3\2\2\2\2t\3\2\2\2\2v\3\2\2\2\2x\3\2\2\2\2z\3\2\2\2\2|\3\2"+
"\3\2\2\2\2\u0086\3\2\2\2\2\u0088\3\2\2\2\2\u008a\3\2\2\2\2\u008c\3\2\2"+ "\2\2\2~\3\2\2\2\2\u0080\3\2\2\2\2\u0082\3\2\2\2\2\u0084\3\2\2\2\2\u0086"+
"\2\2\u008e\3\2\2\2\2\u0090\3\2\2\2\2\u0092\3\2\2\2\2\u0094\3\2\2\2\2\u0096"+ "\3\2\2\2\2\u0088\3\2\2\2\2\u008a\3\2\2\2\2\u008c\3\2\2\2\2\u008e\3\2\2"+
"\3\2\2\2\2\u0098\3\2\2\2\2\u009a\3\2\2\2\3\u009c\3\2\2\2\3\u009e\3\2\2"+ "\2\2\u0090\3\2\2\2\2\u0092\3\2\2\2\2\u0094\3\2\2\2\2\u0096\3\2\2\2\2\u0098"+
"\2\4\u00a1\3\2\2\2\6\u00bc\3\2\2\2\b\u00c0\3\2\2\2\n\u00c2\3\2\2\2\f\u00c4"+ "\3\2\2\2\2\u009a\3\2\2\2\2\u009c\3\2\2\2\2\u009e\3\2\2\2\3\u00a0\3\2\2"+
"\3\2\2\2\16\u00c6\3\2\2\2\20\u00c8\3\2\2\2\22\u00ca\3\2\2\2\24\u00cc\3"+ "\2\3\u00a2\3\2\2\2\4\u00a5\3\2\2\2\6\u00c0\3\2\2\2\b\u00c4\3\2\2\2\n\u00c6"+
"\2\2\2\26\u00d0\3\2\2\2\30\u00d2\3\2\2\2\32\u00d4\3\2\2\2\34\u00d7\3\2"+ "\3\2\2\2\f\u00c8\3\2\2\2\16\u00ca\3\2\2\2\20\u00cc\3\2\2\2\22\u00ce\3"+
"\2\2\36\u00dc\3\2\2\2 \u00e2\3\2\2\2\"\u00e5\3\2\2\2$\u00e9\3\2\2\2&\u00f2"+ "\2\2\2\24\u00d0\3\2\2\2\26\u00d4\3\2\2\2\30\u00d6\3\2\2\2\32\u00d8\3\2"+
"\3\2\2\2(\u00f8\3\2\2\2*\u00ff\3\2\2\2,\u0103\3\2\2\2.\u0107\3\2\2\2\60"+ "\2\2\34\u00db\3\2\2\2\36\u00e0\3\2\2\2 \u00e6\3\2\2\2\"\u00e9\3\2\2\2"+
"\u010d\3\2\2\2\62\u0113\3\2\2\2\64\u0118\3\2\2\2\66\u011a\3\2\2\28\u011c"+ "$\u00ed\3\2\2\2&\u00f6\3\2\2\2(\u00fc\3\2\2\2*\u0103\3\2\2\2,\u0107\3"+
"\3\2\2\2:\u011e\3\2\2\2<\u0121\3\2\2\2>\u0123\3\2\2\2@\u0125\3\2\2\2B"+ "\2\2\2.\u010b\3\2\2\2\60\u0111\3\2\2\2\62\u0117\3\2\2\2\64\u011c\3\2\2"+
"\u0127\3\2\2\2D\u012a\3\2\2\2F\u012d\3\2\2\2H\u0131\3\2\2\2J\u0133\3\2"+ "\2\66\u011e\3\2\2\28\u0120\3\2\2\2:\u0122\3\2\2\2<\u0125\3\2\2\2>\u0127"+
"\2\2L\u0136\3\2\2\2N\u0138\3\2\2\2P\u013b\3\2\2\2R\u013e\3\2\2\2T\u0142"+ "\3\2\2\2@\u0129\3\2\2\2B\u012b\3\2\2\2D\u012e\3\2\2\2F\u0131\3\2\2\2H"+
"\3\2\2\2V\u0145\3\2\2\2X\u0149\3\2\2\2Z\u014b\3\2\2\2\\\u014d\3\2\2\2"+ "\u0135\3\2\2\2J\u0137\3\2\2\2L\u013a\3\2\2\2N\u013c\3\2\2\2P\u013f\3\2"+
"^\u014f\3\2\2\2`\u0152\3\2\2\2b\u0155\3\2\2\2d\u0157\3\2\2\2f\u0159\3"+ "\2\2R\u0142\3\2\2\2T\u0146\3\2\2\2V\u0149\3\2\2\2X\u014d\3\2\2\2Z\u014f"+
"\2\2\2h\u015c\3\2\2\2j\u015f\3\2\2\2l\u0162\3\2\2\2n\u0165\3\2\2\2p\u0167"+ "\3\2\2\2\\\u0151\3\2\2\2^\u0153\3\2\2\2`\u0156\3\2\2\2b\u0159\3\2\2\2"+
"\3\2\2\2r\u016a\3\2\2\2t\u016d\3\2\2\2v\u0170\3\2\2\2x\u0173\3\2\2\2z"+ "d\u015b\3\2\2\2f\u015d\3\2\2\2h\u0160\3\2\2\2j\u0163\3\2\2\2l\u0166\3"+
"\u0176\3\2\2\2|\u0179\3\2\2\2~\u017c\3\2\2\2\u0080\u017f\3\2\2\2\u0082"+ "\2\2\2n\u016a\3\2\2\2p\u016d\3\2\2\2r\u0170\3\2\2\2t\u0172\3\2\2\2v\u0175"+
"\u0183\3\2\2\2\u0084\u0187\3\2\2\2\u0086\u018c\3\2\2\2\u0088\u0195\3\2"+ "\3\2\2\2x\u0178\3\2\2\2z\u017b\3\2\2\2|\u017e\3\2\2\2~\u0181\3\2\2\2\u0080"+
"\2\2\u008a\u01a7\3\2\2\2\u008c\u01b4\3\2\2\2\u008e\u01e4\3\2\2\2\u0090"+ "\u0184\3\2\2\2\u0082\u0187\3\2\2\2\u0084\u018a\3\2\2\2\u0086\u018e\3\2"+
"\u01e6\3\2\2\2\u0092\u01f1\3\2\2\2\u0094\u01f6\3\2\2\2\u0096\u01fc\3\2"+ "\2\2\u0088\u0192\3\2\2\2\u008a\u0197\3\2\2\2\u008c\u01a0\3\2\2\2\u008e"+
"\2\2\u0098\u0201\3\2\2\2\u009a\u020c\3\2\2\2\u009c\u021b\3\2\2\2\u009e"+ "\u01b2\3\2\2\2\u0090\u01bf\3\2\2\2\u0092\u01ef\3\2\2\2\u0094\u01f1\3\2"+
"\u021f\3\2\2\2\u00a0\u00a2\t\2\2\2\u00a1\u00a0\3\2\2\2\u00a2\u00a3\3\2"+ "\2\2\u0096\u01fc\3\2\2\2\u0098\u0201\3\2\2\2\u009a\u0207\3\2\2\2\u009c"+
"\2\2\u00a3\u00a1\3\2\2\2\u00a3\u00a4\3\2\2\2\u00a4\u00a5\3\2\2\2\u00a5"+ "\u020c\3\2\2\2\u009e\u0217\3\2\2\2\u00a0\u0226\3\2\2\2\u00a2\u022a\3\2"+
"\u00a6\b\2\2\2\u00a6\5\3\2\2\2\u00a7\u00a8\7\61\2\2\u00a8\u00a9\7\61\2"+ "\2\2\u00a4\u00a6\t\2\2\2\u00a5\u00a4\3\2\2\2\u00a6\u00a7\3\2\2\2\u00a7"+
"\2\u00a9\u00ad\3\2\2\2\u00aa\u00ac\13\2\2\2\u00ab\u00aa\3\2\2\2\u00ac"+ "\u00a5\3\2\2\2\u00a7\u00a8\3\2\2\2\u00a8\u00a9\3\2\2\2\u00a9\u00aa\b\2"+
"\u00af\3\2\2\2\u00ad\u00ae\3\2\2\2\u00ad\u00ab\3\2\2\2\u00ae\u00b0\3\2"+ "\2\2\u00aa\5\3\2\2\2\u00ab\u00ac\7\61\2\2\u00ac\u00ad\7\61\2\2\u00ad\u00b1"+
"\2\2\u00af\u00ad\3\2\2\2\u00b0\u00bd\t\3\2\2\u00b1\u00b2\7\61\2\2\u00b2"+ "\3\2\2\2\u00ae\u00b0\13\2\2\2\u00af\u00ae\3\2\2\2\u00b0\u00b3\3\2\2\2"+
"\u00b3\7,\2\2\u00b3\u00b7\3\2\2\2\u00b4\u00b6\13\2\2\2\u00b5\u00b4\3\2"+ "\u00b1\u00b2\3\2\2\2\u00b1\u00af\3\2\2\2\u00b2\u00b4\3\2\2\2\u00b3\u00b1"+
"\2\2\u00b6\u00b9\3\2\2\2\u00b7\u00b8\3\2\2\2\u00b7\u00b5\3\2\2\2\u00b8"+ "\3\2\2\2\u00b4\u00c1\t\3\2\2\u00b5\u00b6\7\61\2\2\u00b6\u00b7\7,\2\2\u00b7"+
"\u00ba\3\2\2\2\u00b9\u00b7\3\2\2\2\u00ba\u00bb\7,\2\2\u00bb\u00bd\7\61"+ "\u00bb\3\2\2\2\u00b8\u00ba\13\2\2\2\u00b9\u00b8\3\2\2\2\u00ba\u00bd\3"+
"\2\2\u00bc\u00a7\3\2\2\2\u00bc\u00b1\3\2\2\2\u00bd\u00be\3\2\2\2\u00be"+ "\2\2\2\u00bb\u00bc\3\2\2\2\u00bb\u00b9\3\2\2\2\u00bc\u00be\3\2\2\2\u00bd"+
"\u00bf\b\3\2\2\u00bf\7\3\2\2\2\u00c0\u00c1\7}\2\2\u00c1\t\3\2\2\2\u00c2"+ "\u00bb\3\2\2\2\u00be\u00bf\7,\2\2\u00bf\u00c1\7\61\2\2\u00c0\u00ab\3\2"+
"\u00c3\7\177\2\2\u00c3\13\3\2\2\2\u00c4\u00c5\7]\2\2\u00c5\r\3\2\2\2\u00c6"+ "\2\2\u00c0\u00b5\3\2\2\2\u00c1\u00c2\3\2\2\2\u00c2\u00c3\b\3\2\2\u00c3"+
"\u00c7\7_\2\2\u00c7\17\3\2\2\2\u00c8\u00c9\7*\2\2\u00c9\21\3\2\2\2\u00ca"+ "\7\3\2\2\2\u00c4\u00c5\7}\2\2\u00c5\t\3\2\2\2\u00c6\u00c7\7\177\2\2\u00c7"+
"\u00cb\7+\2\2\u00cb\23\3\2\2\2\u00cc\u00cd\7\60\2\2\u00cd\u00ce\3\2\2"+ "\13\3\2\2\2\u00c8\u00c9\7]\2\2\u00c9\r\3\2\2\2\u00ca\u00cb\7_\2\2\u00cb"+
"\2\u00ce\u00cf\b\n\3\2\u00cf\25\3\2\2\2\u00d0\u00d1\7.\2\2\u00d1\27\3"+ "\17\3\2\2\2\u00cc\u00cd\7*\2\2\u00cd\21\3\2\2\2\u00ce\u00cf\7+\2\2\u00cf"+
"\2\2\2\u00d2\u00d3\7=\2\2\u00d3\31\3\2\2\2\u00d4\u00d5\7k\2\2\u00d5\u00d6"+ "\23\3\2\2\2\u00d0\u00d1\7\60\2\2\u00d1\u00d2\3\2\2\2\u00d2\u00d3\b\n\3"+
"\7h\2\2\u00d6\33\3\2\2\2\u00d7\u00d8\7g\2\2\u00d8\u00d9\7n\2\2\u00d9\u00da"+ "\2\u00d3\25\3\2\2\2\u00d4\u00d5\7.\2\2\u00d5\27\3\2\2\2\u00d6\u00d7\7"+
"\7u\2\2\u00da\u00db\7g\2\2\u00db\35\3\2\2\2\u00dc\u00dd\7y\2\2\u00dd\u00de"+ "=\2\2\u00d7\31\3\2\2\2\u00d8\u00d9\7k\2\2\u00d9\u00da\7h\2\2\u00da\33"+
"\7j\2\2\u00de\u00df\7k\2\2\u00df\u00e0\7n\2\2\u00e0\u00e1\7g\2\2\u00e1"+ "\3\2\2\2\u00db\u00dc\7g\2\2\u00dc\u00dd\7n\2\2\u00dd\u00de\7u\2\2\u00de"+
"\37\3\2\2\2\u00e2\u00e3\7f\2\2\u00e3\u00e4\7q\2\2\u00e4!\3\2\2\2\u00e5"+ "\u00df\7g\2\2\u00df\35\3\2\2\2\u00e0\u00e1\7y\2\2\u00e1\u00e2\7j\2\2\u00e2"+
"\u00e6\7h\2\2\u00e6\u00e7\7q\2\2\u00e7\u00e8\7t\2\2\u00e8#\3\2\2\2\u00e9"+ "\u00e3\7k\2\2\u00e3\u00e4\7n\2\2\u00e4\u00e5\7g\2\2\u00e5\37\3\2\2\2\u00e6"+
"\u00ea\7e\2\2\u00ea\u00eb\7q\2\2\u00eb\u00ec\7p\2\2\u00ec\u00ed\7v\2\2"+ "\u00e7\7f\2\2\u00e7\u00e8\7q\2\2\u00e8!\3\2\2\2\u00e9\u00ea\7h\2\2\u00ea"+
"\u00ed\u00ee\7k\2\2\u00ee\u00ef\7p\2\2\u00ef\u00f0\7w\2\2\u00f0\u00f1"+ "\u00eb\7q\2\2\u00eb\u00ec\7t\2\2\u00ec#\3\2\2\2\u00ed\u00ee\7e\2\2\u00ee"+
"\7g\2\2\u00f1%\3\2\2\2\u00f2\u00f3\7d\2\2\u00f3\u00f4\7t\2\2\u00f4\u00f5"+ "\u00ef\7q\2\2\u00ef\u00f0\7p\2\2\u00f0\u00f1\7v\2\2\u00f1\u00f2\7k\2\2"+
"\7g\2\2\u00f5\u00f6\7c\2\2\u00f6\u00f7\7m\2\2\u00f7\'\3\2\2\2\u00f8\u00f9"+ "\u00f2\u00f3\7p\2\2\u00f3\u00f4\7w\2\2\u00f4\u00f5\7g\2\2\u00f5%\3\2\2"+
"\7t\2\2\u00f9\u00fa\7g\2\2\u00fa\u00fb\7v\2\2\u00fb\u00fc\7w\2\2\u00fc"+ "\2\u00f6\u00f7\7d\2\2\u00f7\u00f8\7t\2\2\u00f8\u00f9\7g\2\2\u00f9\u00fa"+
"\u00fd\7t\2\2\u00fd\u00fe\7p\2\2\u00fe)\3\2\2\2\u00ff\u0100\7p\2\2\u0100"+ "\7c\2\2\u00fa\u00fb\7m\2\2\u00fb\'\3\2\2\2\u00fc\u00fd\7t\2\2\u00fd\u00fe"+
"\u0101\7g\2\2\u0101\u0102\7y\2\2\u0102+\3\2\2\2\u0103\u0104\7v\2\2\u0104"+ "\7g\2\2\u00fe\u00ff\7v\2\2\u00ff\u0100\7w\2\2\u0100\u0101\7t\2\2\u0101"+
"\u0105\7t\2\2\u0105\u0106\7{\2\2\u0106-\3\2\2\2\u0107\u0108\7e\2\2\u0108"+ "\u0102\7p\2\2\u0102)\3\2\2\2\u0103\u0104\7p\2\2\u0104\u0105\7g\2\2\u0105"+
"\u0109\7c\2\2\u0109\u010a\7v\2\2\u010a\u010b\7e\2\2\u010b\u010c\7j\2\2"+ "\u0106\7y\2\2\u0106+\3\2\2\2\u0107\u0108\7v\2\2\u0108\u0109\7t\2\2\u0109"+
"\u010c/\3\2\2\2\u010d\u010e\7v\2\2\u010e\u010f\7j\2\2\u010f\u0110\7t\2"+ "\u010a\7{\2\2\u010a-\3\2\2\2\u010b\u010c\7e\2\2\u010c\u010d\7c\2\2\u010d"+
"\2\u0110\u0111\7q\2\2\u0111\u0112\7y\2\2\u0112\61\3\2\2\2\u0113\u0114"+ "\u010e\7v\2\2\u010e\u010f\7e\2\2\u010f\u0110\7j\2\2\u0110/\3\2\2\2\u0111"+
"\7v\2\2\u0114\u0115\7j\2\2\u0115\u0116\7k\2\2\u0116\u0117\7u\2\2\u0117"+ "\u0112\7v\2\2\u0112\u0113\7j\2\2\u0113\u0114\7t\2\2\u0114\u0115\7q\2\2"+
"\63\3\2\2\2\u0118\u0119\7#\2\2\u0119\65\3\2\2\2\u011a\u011b\7\u0080\2"+ "\u0115\u0116\7y\2\2\u0116\61\3\2\2\2\u0117\u0118\7v\2\2\u0118\u0119\7"+
"\2\u011b\67\3\2\2\2\u011c\u011d\7,\2\2\u011d9\3\2\2\2\u011e\u011f\7\61"+ "j\2\2\u0119\u011a\7k\2\2\u011a\u011b\7u\2\2\u011b\63\3\2\2\2\u011c\u011d"+
"\2\2\u011f\u0120\6\35\2\2\u0120;\3\2\2\2\u0121\u0122\7\'\2\2\u0122=\3"+ "\7#\2\2\u011d\65\3\2\2\2\u011e\u011f\7\u0080\2\2\u011f\67\3\2\2\2\u0120"+
"\2\2\2\u0123\u0124\7-\2\2\u0124?\3\2\2\2\u0125\u0126\7/\2\2\u0126A\3\2"+ "\u0121\7,\2\2\u01219\3\2\2\2\u0122\u0123\7\61\2\2\u0123\u0124\6\35\2\2"+
"\2\2\u0127\u0128\7>\2\2\u0128\u0129\7>\2\2\u0129C\3\2\2\2\u012a\u012b"+ "\u0124;\3\2\2\2\u0125\u0126\7\'\2\2\u0126=\3\2\2\2\u0127\u0128\7-\2\2"+
"\7@\2\2\u012b\u012c\7@\2\2\u012cE\3\2\2\2\u012d\u012e\7@\2\2\u012e\u012f"+ "\u0128?\3\2\2\2\u0129\u012a\7/\2\2\u012aA\3\2\2\2\u012b\u012c\7>\2\2\u012c"+
"\7@\2\2\u012f\u0130\7@\2\2\u0130G\3\2\2\2\u0131\u0132\7>\2\2\u0132I\3"+ "\u012d\7>\2\2\u012dC\3\2\2\2\u012e\u012f\7@\2\2\u012f\u0130\7@\2\2\u0130"+
"\2\2\2\u0133\u0134\7>\2\2\u0134\u0135\7?\2\2\u0135K\3\2\2\2\u0136\u0137"+ "E\3\2\2\2\u0131\u0132\7@\2\2\u0132\u0133\7@\2\2\u0133\u0134\7@\2\2\u0134"+
"\7@\2\2\u0137M\3\2\2\2\u0138\u0139\7@\2\2\u0139\u013a\7?\2\2\u013aO\3"+ "G\3\2\2\2\u0135\u0136\7>\2\2\u0136I\3\2\2\2\u0137\u0138\7>\2\2\u0138\u0139"+
"\2\2\2\u013b\u013c\7?\2\2\u013c\u013d\7?\2\2\u013dQ\3\2\2\2\u013e\u013f"+ "\7?\2\2\u0139K\3\2\2\2\u013a\u013b\7@\2\2\u013bM\3\2\2\2\u013c\u013d\7"+
"\7?\2\2\u013f\u0140\7?\2\2\u0140\u0141\7?\2\2\u0141S\3\2\2\2\u0142\u0143"+ "@\2\2\u013d\u013e\7?\2\2\u013eO\3\2\2\2\u013f\u0140\7?\2\2\u0140\u0141"+
"\7#\2\2\u0143\u0144\7?\2\2\u0144U\3\2\2\2\u0145\u0146\7#\2\2\u0146\u0147"+ "\7?\2\2\u0141Q\3\2\2\2\u0142\u0143\7?\2\2\u0143\u0144\7?\2\2\u0144\u0145"+
"\7?\2\2\u0147\u0148\7?\2\2\u0148W\3\2\2\2\u0149\u014a\7(\2\2\u014aY\3"+ "\7?\2\2\u0145S\3\2\2\2\u0146\u0147\7#\2\2\u0147\u0148\7?\2\2\u0148U\3"+
"\2\2\2\u014b\u014c\7`\2\2\u014c[\3\2\2\2\u014d\u014e\7~\2\2\u014e]\3\2"+ "\2\2\2\u0149\u014a\7#\2\2\u014a\u014b\7?\2\2\u014b\u014c\7?\2\2\u014c"+
"\2\2\u014f\u0150\7(\2\2\u0150\u0151\7(\2\2\u0151_\3\2\2\2\u0152\u0153"+ "W\3\2\2\2\u014d\u014e\7(\2\2\u014eY\3\2\2\2\u014f\u0150\7`\2\2\u0150["+
"\7~\2\2\u0153\u0154\7~\2\2\u0154a\3\2\2\2\u0155\u0156\7A\2\2\u0156c\3"+ "\3\2\2\2\u0151\u0152\7~\2\2\u0152]\3\2\2\2\u0153\u0154\7(\2\2\u0154\u0155"+
"\2\2\2\u0157\u0158\7<\2\2\u0158e\3\2\2\2\u0159\u015a\7<\2\2\u015a\u015b"+ "\7(\2\2\u0155_\3\2\2\2\u0156\u0157\7~\2\2\u0157\u0158\7~\2\2\u0158a\3"+
"\7<\2\2\u015bg\3\2\2\2\u015c\u015d\7/\2\2\u015d\u015e\7@\2\2\u015ei\3"+ "\2\2\2\u0159\u015a\7A\2\2\u015ac\3\2\2\2\u015b\u015c\7<\2\2\u015ce\3\2"+
"\2\2\2\u015f\u0160\7-\2\2\u0160\u0161\7-\2\2\u0161k\3\2\2\2\u0162\u0163"+ "\2\2\u015d\u015e\7<\2\2\u015e\u015f\7<\2\2\u015fg\3\2\2\2\u0160\u0161"+
"\7/\2\2\u0163\u0164\7/\2\2\u0164m\3\2\2\2\u0165\u0166\7?\2\2\u0166o\3"+ "\7/\2\2\u0161\u0162\7@\2\2\u0162i\3\2\2\2\u0163\u0164\7?\2\2\u0164\u0165"+
"\2\2\2\u0167\u0168\7-\2\2\u0168\u0169\7?\2\2\u0169q\3\2\2\2\u016a\u016b"+ "\7\u0080\2\2\u0165k\3\2\2\2\u0166\u0167\7?\2\2\u0167\u0168\7?\2\2\u0168"+
"\7/\2\2\u016b\u016c\7?\2\2\u016cs\3\2\2\2\u016d\u016e\7,\2\2\u016e\u016f"+ "\u0169\7\u0080\2\2\u0169m\3\2\2\2\u016a\u016b\7-\2\2\u016b\u016c\7-\2"+
"\7?\2\2\u016fu\3\2\2\2\u0170\u0171\7\61\2\2\u0171\u0172\7?\2\2\u0172w"+ "\2\u016co\3\2\2\2\u016d\u016e\7/\2\2\u016e\u016f\7/\2\2\u016fq\3\2\2\2"+
"\3\2\2\2\u0173\u0174\7\'\2\2\u0174\u0175\7?\2\2\u0175y\3\2\2\2\u0176\u0177"+ "\u0170\u0171\7?\2\2\u0171s\3\2\2\2\u0172\u0173\7-\2\2\u0173\u0174\7?\2"+
"\7(\2\2\u0177\u0178\7?\2\2\u0178{\3\2\2\2\u0179\u017a\7`\2\2\u017a\u017b"+ "\2\u0174u\3\2\2\2\u0175\u0176\7/\2\2\u0176\u0177\7?\2\2\u0177w\3\2\2\2"+
"\7?\2\2\u017b}\3\2\2\2\u017c\u017d\7~\2\2\u017d\u017e\7?\2\2\u017e\177"+ "\u0178\u0179\7,\2\2\u0179\u017a\7?\2\2\u017ay\3\2\2\2\u017b\u017c\7\61"+
"\3\2\2\2\u017f\u0180\7>\2\2\u0180\u0181\7>\2\2\u0181\u0182\7?\2\2\u0182"+ "\2\2\u017c\u017d\7?\2\2\u017d{\3\2\2\2\u017e\u017f\7\'\2\2\u017f\u0180"+
"\u0081\3\2\2\2\u0183\u0184\7@\2\2\u0184\u0185\7@\2\2\u0185\u0186\7?\2"+ "\7?\2\2\u0180}\3\2\2\2\u0181\u0182\7(\2\2\u0182\u0183\7?\2\2\u0183\177"+
"\2\u0186\u0083\3\2\2\2\u0187\u0188\7@\2\2\u0188\u0189\7@\2\2\u0189\u018a"+ "\3\2\2\2\u0184\u0185\7`\2\2\u0185\u0186\7?\2\2\u0186\u0081\3\2\2\2\u0187"+
"\7@\2\2\u018a\u018b\7?\2\2\u018b\u0085\3\2\2\2\u018c\u018e\7\62\2\2\u018d"+ "\u0188\7~\2\2\u0188\u0189\7?\2\2\u0189\u0083\3\2\2\2\u018a\u018b\7>\2"+
"\u018f\t\4\2\2\u018e\u018d\3\2\2\2\u018f\u0190\3\2\2\2\u0190\u018e\3\2"+ "\2\u018b\u018c\7>\2\2\u018c\u018d\7?\2\2\u018d\u0085\3\2\2\2\u018e\u018f"+
"\2\2\u0190\u0191\3\2\2\2\u0191\u0193\3\2\2\2\u0192\u0194\t\5\2\2\u0193"+ "\7@\2\2\u018f\u0190\7@\2\2\u0190\u0191\7?\2\2\u0191\u0087\3\2\2\2\u0192"+
"\u0192\3\2\2\2\u0193\u0194\3\2\2\2\u0194\u0087\3\2\2\2\u0195\u0196\7\62"+ "\u0193\7@\2\2\u0193\u0194\7@\2\2\u0194\u0195\7@\2\2\u0195\u0196\7?\2\2"+
"\2\2\u0196\u0198\t\6\2\2\u0197\u0199\t\7\2\2\u0198\u0197\3\2\2\2\u0199"+ "\u0196\u0089\3\2\2\2\u0197\u0199\7\62\2\2\u0198\u019a\t\4\2\2\u0199\u0198"+
"\u019a\3\2\2\2\u019a\u0198\3\2\2\2\u019a\u019b\3\2\2\2\u019b\u019d\3\2"+ "\3\2\2\2\u019a\u019b\3\2\2\2\u019b\u0199\3\2\2\2\u019b\u019c\3\2\2\2\u019c"+
"\2\2\u019c\u019e\t\5\2\2\u019d\u019c\3\2\2\2\u019d\u019e\3\2\2\2\u019e"+ "\u019e\3\2\2\2\u019d\u019f\t\5\2\2\u019e\u019d\3\2\2\2\u019e\u019f\3\2"+
"\u0089\3\2\2\2\u019f\u01a8\7\62\2\2\u01a0\u01a4\t\b\2\2\u01a1\u01a3\t"+ "\2\2\u019f\u008b\3\2\2\2\u01a0\u01a1\7\62\2\2\u01a1\u01a3\t\6\2\2\u01a2"+
"\t\2\2\u01a2\u01a1\3\2\2\2\u01a3\u01a6\3\2\2\2\u01a4\u01a2\3\2\2\2\u01a4"+ "\u01a4\t\7\2\2\u01a3\u01a2\3\2\2\2\u01a4\u01a5\3\2\2\2\u01a5\u01a3\3\2"+
"\u01a5\3\2\2\2\u01a5\u01a8\3\2\2\2\u01a6\u01a4\3\2\2\2\u01a7\u019f\3\2"+ "\2\2\u01a5\u01a6\3\2\2\2\u01a6\u01a8\3\2\2\2\u01a7\u01a9\t\5\2\2\u01a8"+
"\2\2\u01a7\u01a0\3\2\2\2\u01a8\u01aa\3\2\2\2\u01a9\u01ab\t\n\2\2\u01aa"+ "\u01a7\3\2\2\2\u01a8\u01a9\3\2\2\2\u01a9\u008d\3\2\2\2\u01aa\u01b3\7\62"+
"\u01a9\3\2\2\2\u01aa\u01ab\3\2\2\2\u01ab\u008b\3\2\2\2\u01ac\u01b5\7\62"+ "\2\2\u01ab\u01af\t\b\2\2\u01ac\u01ae\t\t\2\2\u01ad\u01ac\3\2\2\2\u01ae"+
"\2\2\u01ad\u01b1\t\b\2\2\u01ae\u01b0\t\t\2\2\u01af\u01ae\3\2\2\2\u01b0"+ "\u01b1\3\2\2\2\u01af\u01ad\3\2\2\2\u01af\u01b0\3\2\2\2\u01b0\u01b3\3\2"+
"\u01b3\3\2\2\2\u01b1\u01af\3\2\2\2\u01b1\u01b2\3\2\2\2\u01b2\u01b5\3\2"+ "\2\2\u01b1\u01af\3\2\2\2\u01b2\u01aa\3\2\2\2\u01b2\u01ab\3\2\2\2\u01b3"+
"\2\2\u01b3\u01b1\3\2\2\2\u01b4\u01ac\3\2\2\2\u01b4\u01ad\3\2\2\2\u01b5"+ "\u01b5\3\2\2\2\u01b4\u01b6\t\n\2\2\u01b5\u01b4\3\2\2\2\u01b5\u01b6\3\2"+
"\u01bc\3\2\2\2\u01b6\u01b8\5\24\n\2\u01b7\u01b9\t\t\2\2\u01b8\u01b7\3"+ "\2\2\u01b6\u008f\3\2\2\2\u01b7\u01c0\7\62\2\2\u01b8\u01bc\t\b\2\2\u01b9"+
"\2\2\2\u01b9\u01ba\3\2\2\2\u01ba\u01b8\3\2\2\2\u01ba\u01bb\3\2\2\2\u01bb"+ "\u01bb\t\t\2\2\u01ba\u01b9\3\2\2\2\u01bb\u01be\3\2\2\2\u01bc\u01ba\3\2"+
"\u01bd\3\2\2\2\u01bc\u01b6\3\2\2\2\u01bc\u01bd\3\2\2\2\u01bd\u01c7\3\2"+ "\2\2\u01bc\u01bd\3\2\2\2\u01bd\u01c0\3\2\2\2\u01be\u01bc\3\2\2\2\u01bf"+
"\2\2\u01be\u01c0\t\13\2\2\u01bf\u01c1\t\f\2\2\u01c0\u01bf\3\2\2\2\u01c0"+ "\u01b7\3\2\2\2\u01bf\u01b8\3\2\2\2\u01c0\u01c7\3\2\2\2\u01c1\u01c3\5\24"+
"\u01c1\3\2\2\2\u01c1\u01c3\3\2\2\2\u01c2\u01c4\t\t\2\2\u01c3\u01c2\3\2"+ "\n\2\u01c2\u01c4\t\t\2\2\u01c3\u01c2\3\2\2\2\u01c4\u01c5\3\2\2\2\u01c5"+
"\2\2\u01c4\u01c5\3\2\2\2\u01c5\u01c3\3\2\2\2\u01c5\u01c6\3\2\2\2\u01c6"+ "\u01c3\3\2\2\2\u01c5\u01c6\3\2\2\2\u01c6\u01c8\3\2\2\2\u01c7\u01c1\3\2"+
"\u01c8\3\2\2\2\u01c7\u01be\3\2\2\2\u01c7\u01c8\3\2\2\2\u01c8\u01ca\3\2"+ "\2\2\u01c7\u01c8\3\2\2\2\u01c8\u01d2\3\2\2\2\u01c9\u01cb\t\13\2\2\u01ca"+
"\2\2\u01c9\u01cb\t\r\2\2\u01ca\u01c9\3\2\2\2\u01ca\u01cb\3\2\2\2\u01cb"+ "\u01cc\t\f\2\2\u01cb\u01ca\3\2\2\2\u01cb\u01cc\3\2\2\2\u01cc\u01ce\3\2"+
"\u008d\3\2\2\2\u01cc\u01d4\7$\2\2\u01cd\u01ce\7^\2\2\u01ce\u01d3\7$\2"+ "\2\2\u01cd\u01cf\t\t\2\2\u01ce\u01cd\3\2\2\2\u01cf\u01d0\3\2\2\2\u01d0"+
"\2\u01cf\u01d0\7^\2\2\u01d0\u01d3\7^\2\2\u01d1\u01d3\n\16\2\2\u01d2\u01cd"+ "\u01ce\3\2\2\2\u01d0\u01d1\3\2\2\2\u01d1\u01d3\3\2\2\2\u01d2\u01c9\3\2"+
"\3\2\2\2\u01d2\u01cf\3\2\2\2\u01d2\u01d1\3\2\2\2\u01d3\u01d6\3\2\2\2\u01d4"+ "\2\2\u01d2\u01d3\3\2\2\2\u01d3\u01d5\3\2\2\2\u01d4\u01d6\t\r\2\2\u01d5"+
"\u01d5\3\2\2\2\u01d4\u01d2\3\2\2\2\u01d5\u01d7\3\2\2\2\u01d6\u01d4\3\2"+ "\u01d4\3\2\2\2\u01d5\u01d6\3\2\2\2\u01d6\u0091\3\2\2\2\u01d7\u01df\7$"+
"\2\2\u01d7\u01e5\7$\2\2\u01d8\u01e0\7)\2\2\u01d9\u01da\7^\2\2\u01da\u01df"+ "\2\2\u01d8\u01d9\7^\2\2\u01d9\u01de\7$\2\2\u01da\u01db\7^\2\2\u01db\u01de"+
"\7)\2\2\u01db\u01dc\7^\2\2\u01dc\u01df\7^\2\2\u01dd\u01df\n\16\2\2\u01de"+ "\7^\2\2\u01dc\u01de\n\16\2\2\u01dd\u01d8\3\2\2\2\u01dd\u01da\3\2\2\2\u01dd"+
"\u01d9\3\2\2\2\u01de\u01db\3\2\2\2\u01de\u01dd\3\2\2\2\u01df\u01e2\3\2"+ "\u01dc\3\2\2\2\u01de\u01e1\3\2\2\2\u01df\u01e0\3\2\2\2\u01df\u01dd\3\2"+
"\2\2\u01e0\u01e1\3\2\2\2\u01e0\u01de\3\2\2\2\u01e1\u01e3\3\2\2\2\u01e2"+ "\2\2\u01e0\u01e2\3\2\2\2\u01e1\u01df\3\2\2\2\u01e2\u01f0\7$\2\2\u01e3"+
"\u01e0\3\2\2\2\u01e3\u01e5\7)\2\2\u01e4\u01cc\3\2\2\2\u01e4\u01d8\3\2"+ "\u01eb\7)\2\2\u01e4\u01e5\7^\2\2\u01e5\u01ea\7)\2\2\u01e6\u01e7\7^\2\2"+
"\2\2\u01e5\u008f\3\2\2\2\u01e6\u01ea\7\61\2\2\u01e7\u01eb\n\17\2\2\u01e8"+ "\u01e7\u01ea\7^\2\2\u01e8\u01ea\n\16\2\2\u01e9\u01e4\3\2\2\2\u01e9\u01e6"+
"\u01e9\7^\2\2\u01e9\u01eb\n\20\2\2\u01ea\u01e7\3\2\2\2\u01ea\u01e8\3\2"+ "\3\2\2\2\u01e9\u01e8\3\2\2\2\u01ea\u01ed\3\2\2\2\u01eb\u01ec\3\2\2\2\u01eb"+
"\2\2\u01eb\u01ec\3\2\2\2\u01ec\u01ea\3\2\2\2\u01ec\u01ed\3\2\2\2\u01ed"+ "\u01e9\3\2\2\2\u01ec\u01ee\3\2\2\2\u01ed\u01eb\3\2\2\2\u01ee\u01f0\7)"+
"\u01ee\3\2\2\2\u01ee\u01ef\7\61\2\2\u01ef\u01f0\6H\3\2\u01f0\u0091\3\2"+ "\2\2\u01ef\u01d7\3\2\2\2\u01ef\u01e3\3\2\2\2\u01f0\u0093\3\2\2\2\u01f1"+
"\2\2\u01f1\u01f2\7v\2\2\u01f2\u01f3\7t\2\2\u01f3\u01f4\7w\2\2\u01f4\u01f5"+ "\u01f5\7\61\2\2\u01f2\u01f6\n\17\2\2\u01f3\u01f4\7^\2\2\u01f4\u01f6\n"+
"\7g\2\2\u01f5\u0093\3\2\2\2\u01f6\u01f7\7h\2\2\u01f7\u01f8\7c\2\2\u01f8"+ "\20\2\2\u01f5\u01f2\3\2\2\2\u01f5\u01f3\3\2\2\2\u01f6\u01f7\3\2\2\2\u01f7"+
"\u01f9\7n\2\2\u01f9\u01fa\7u\2\2\u01fa\u01fb\7g\2\2\u01fb\u0095\3\2\2"+ "\u01f5\3\2\2\2\u01f7\u01f8\3\2\2\2\u01f8\u01f9\3\2\2\2\u01f9\u01fa\7\61"+
"\2\u01fc\u01fd\7p\2\2\u01fd\u01fe\7w\2\2\u01fe\u01ff\7n\2\2\u01ff\u0200"+ "\2\2\u01fa\u01fb\6J\3\2\u01fb\u0095\3\2\2\2\u01fc\u01fd\7v\2\2\u01fd\u01fe"+
"\7n\2\2\u0200\u0097\3\2\2\2\u0201\u0207\5\u009aM\2\u0202\u0203\5\24\n"+ "\7t\2\2\u01fe\u01ff\7w\2\2\u01ff\u0200\7g\2\2\u0200\u0097\3\2\2\2\u0201"+
"\2\u0203\u0204\5\u009aM\2\u0204\u0206\3\2\2\2\u0205\u0202\3\2\2\2\u0206"+ "\u0202\7h\2\2\u0202\u0203\7c\2\2\u0203\u0204\7n\2\2\u0204\u0205\7u\2\2"+
"\u0209\3\2\2\2\u0207\u0205\3\2\2\2\u0207\u0208\3\2\2\2\u0208\u020a\3\2"+ "\u0205\u0206\7g\2\2\u0206\u0099\3\2\2\2\u0207\u0208\7p\2\2\u0208\u0209"+
"\2\2\u0209\u0207\3\2\2\2\u020a\u020b\6L\4\2\u020b\u0099\3\2\2\2\u020c"+ "\7w\2\2\u0209\u020a\7n\2\2\u020a\u020b\7n\2\2\u020b\u009b\3\2\2\2\u020c"+
"\u0210\t\21\2\2\u020d\u020f\t\22\2\2\u020e\u020d\3\2\2\2\u020f\u0212\3"+ "\u0212\5\u009eO\2\u020d\u020e\5\24\n\2\u020e\u020f\5\u009eO\2\u020f\u0211"+
"\2\2\2\u0210\u020e\3\2\2\2\u0210\u0211\3\2\2\2\u0211\u009b\3\2\2\2\u0212"+ "\3\2\2\2\u0210\u020d\3\2\2\2\u0211\u0214\3\2\2\2\u0212\u0210\3\2\2\2\u0212"+
"\u0210\3\2\2\2\u0213\u021c\7\62\2\2\u0214\u0218\t\b\2\2\u0215\u0217\t"+ "\u0213\3\2\2\2\u0213\u0215\3\2\2\2\u0214\u0212\3\2\2\2\u0215\u0216\6N"+
"\t\2\2\u0216\u0215\3\2\2\2\u0217\u021a\3\2\2\2\u0218\u0216\3\2\2\2\u0218"+ "\4\2\u0216\u009d\3\2\2\2\u0217\u021b\t\21\2\2\u0218\u021a\t\22\2\2\u0219"+
"\u0219\3\2\2\2\u0219\u021c\3\2\2\2\u021a\u0218\3\2\2\2\u021b\u0213\3\2"+ "\u0218\3\2\2\2\u021a\u021d\3\2\2\2\u021b\u0219\3\2\2\2\u021b\u021c\3\2"+
"\2\2\u021b\u0214\3\2\2\2\u021c\u021d\3\2\2\2\u021d\u021e\bN\4\2\u021e"+ "\2\2\u021c\u009f\3\2\2\2\u021d\u021b\3\2\2\2\u021e\u0227\7\62\2\2\u021f"+
"\u009d\3\2\2\2\u021f\u0223\t\21\2\2\u0220\u0222\t\22\2\2\u0221\u0220\3"+ "\u0223\t\b\2\2\u0220\u0222\t\t\2\2\u0221\u0220\3\2\2\2\u0222\u0225\3\2"+
"\2\2\2\u0222\u0225\3\2\2\2\u0223\u0221\3\2\2\2\u0223\u0224\3\2\2\2\u0224"+ "\2\2\u0223\u0221\3\2\2\2\u0223\u0224\3\2\2\2\u0224\u0227\3\2\2\2\u0225"+
"\u0226\3\2\2\2\u0225\u0223\3\2\2\2\u0226\u0227\bO\4\2\u0227\u009f\3\2"+ "\u0223\3\2\2\2\u0226\u021e\3\2\2\2\u0226\u021f\3\2\2\2\u0227\u0228\3\2"+
"\2\2#\2\3\u00a3\u00ad\u00b7\u00bc\u0190\u0193\u019a\u019d\u01a4\u01a7"+ "\2\2\u0228\u0229\bP\4\2\u0229\u00a1\3\2\2\2\u022a\u022e\t\21\2\2\u022b"+
"\u01aa\u01b1\u01b4\u01ba\u01bc\u01c0\u01c5\u01c7\u01ca\u01d2\u01d4\u01de"+ "\u022d\t\22\2\2\u022c\u022b\3\2\2\2\u022d\u0230\3\2\2\2\u022e\u022c\3"+
"\u01e0\u01e4\u01ea\u01ec\u0207\u0210\u0218\u021b\u0223\5\b\2\2\4\3\2\4"+ "\2\2\2\u022e\u022f\3\2\2\2\u022f\u0231\3\2\2\2\u0230\u022e\3\2\2\2\u0231"+
"\2\2"; "\u0232\bQ\4\2\u0232\u00a3\3\2\2\2#\2\3\u00a7\u00b1\u00bb\u00c0\u019b\u019e"+
"\u01a5\u01a8\u01af\u01b2\u01b5\u01bc\u01bf\u01c5\u01c7\u01cb\u01d0\u01d2"+
"\u01d5\u01dd\u01df\u01e9\u01eb\u01ef\u01f5\u01f7\u0212\u021b\u0223\u0226"+
"\u022e\5\b\2\2\4\3\2\4\2\2";
public static final ATN _ATN = public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray()); new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static { static {

View file

@ -512,6 +512,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
operation = Operation.ADD; operation = Operation.ADD;
} else if (ctx.SUB() != null) { } else if (ctx.SUB() != null) {
operation = Operation.SUB; operation = Operation.SUB;
} else if (ctx.FIND() != null) {
operation = Operation.FIND;
} else if (ctx.MATCH() != null) {
operation = Operation.MATCH;
} else if (ctx.LSH() != null) { } else if (ctx.LSH() != null) {
operation = Operation.LSH; operation = Operation.LSH;
} else if (ctx.RSH() != null) { } else if (ctx.RSH() != null) {

View file

@ -26,6 +26,7 @@ import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
/** /**
@ -61,6 +62,10 @@ public final class EBinary extends AExpression {
analyzeAdd(locals); analyzeAdd(locals);
} else if (operation == Operation.SUB) { } else if (operation == Operation.SUB) {
analyzeSub(locals); analyzeSub(locals);
} else if (operation == Operation.FIND) {
analyzeRegexOp(locals);
} else if (operation == Operation.MATCH) {
analyzeRegexOp(locals);
} else if (operation == Operation.LSH) { } else if (operation == Operation.LSH) {
analyzeLSH(locals); analyzeLSH(locals);
} else if (operation == Operation.RSH) { } else if (operation == Operation.RSH) {
@ -320,6 +325,21 @@ public final class EBinary extends AExpression {
} }
} }
private void analyzeRegexOp(Locals variables) {
left.analyze(variables);
right.analyze(variables);
left.expected = Definition.STRING_TYPE;
right.expected = Definition.PATTERN_TYPE;
left = left.cast(variables);
right = right.cast(variables);
// It'd be nice to be able to do constant folding here but we can't because constants aren't flowing through EChain
promote = Definition.BOOLEAN_TYPE;
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeLSH(Locals variables) { private void analyzeLSH(Locals variables) {
left.analyze(variables); left.analyze(variables);
right.analyze(variables); right.analyze(variables);
@ -607,6 +627,12 @@ public final class EBinary extends AExpression {
if (!cat) { if (!cat) {
writer.writeToStrings(); writer.writeToStrings();
} }
} else if (operation == Operation.FIND) {
writeBuildMatcher(writer);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
} else if (operation == Operation.MATCH) {
writeBuildMatcher(writer);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
} else { } else {
left.write(writer); left.write(writer);
right.write(writer); right.write(writer);
@ -620,4 +646,10 @@ public final class EBinary extends AExpression {
writer.writeBranch(tru, fals); writer.writeBranch(tru, fals);
} }
private void writeBuildMatcher(MethodWriter writer) {
right.write(writer);
left.write(writer);
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
}
} }

View file

@ -34,8 +34,6 @@ import org.elasticsearch.painless.WriterConstants;
* Represents a regex constant. All regexes are constants. * Represents a regex constant. All regexes are constants.
*/ */
public final class LRegex extends ALink { public final class LRegex extends ALink {
private static final Definition.Type PATTERN_TYPE = Definition.getType("Pattern");
private final String pattern; private final String pattern;
private Constant constant; private Constant constant;
@ -60,8 +58,8 @@ public final class LRegex extends ALink {
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "].")); throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
} }
constant = locals.addConstant(location, PATTERN_TYPE, "regexAt$" + location.getOffset(), this::initializeConstant); constant = locals.addConstant(location, Definition.PATTERN_TYPE, "regexAt$" + location.getOffset(), this::initializeConstant);
after = PATTERN_TYPE; after = Definition.PATTERN_TYPE;
return this; return this;
} }
@ -74,7 +72,7 @@ public final class LRegex extends ALink {
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, PATTERN_TYPE.type); writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
} }
@Override @Override
@ -84,6 +82,6 @@ public final class LRegex extends ALink {
private void initializeConstant(MethodWriter writer) { private void initializeConstant(MethodWriter writer) {
writer.push(pattern); writer.push(pattern);
writer.invokeStatic(PATTERN_TYPE.type, WriterConstants.PATTERN_COMPILE); writer.invokeStatic(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_COMPILE);
} }
} }

View file

@ -29,5 +29,31 @@ class Pattern -> java.util.regex.Pattern extends Object {
} }
class Matcher -> java.util.regex.Matcher extends Object { class Matcher -> java.util.regex.Matcher extends Object {
int end()
int end(int)
boolean find()
boolean find(int)
String group()
String group(int)
String namedGroup/group(String)
int groupCount()
boolean hasAnchoringBounds()
boolean hasTransparentBounds()
boolean hitEnd()
boolean lookingAt()
boolean matches() boolean matches()
Pattern pattern()
String quoteReplacement(String)
Matcher region(int,int)
int regionEnd()
int regionStart()
String replaceAll(String)
String replaceFirst(String)
boolean requireEnd()
Matcher reset()
int start()
int start(int)
Matcher useAnchoringBounds(boolean)
Matcher usePattern(Pattern)
Matcher useTransparentBounds(boolean)
} }

View file

@ -19,53 +19,131 @@
package org.elasticsearch.painless; package org.elasticsearch.painless;
import java.util.regex.PatternSyntaxException;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.containsString;
public class RegexTests extends ScriptTestCase { public class RegexTests extends ScriptTestCase {
public void testPatternAfterReturn() { public void testPatternAfterReturn() {
assertEquals(true, exec("return /foo/.matcher(\"foo\").matches()")); assertEquals(true, exec("return 'foo' ==~ /foo/"));
assertEquals(false, exec("return /foo/.matcher(\"bar\").matches()")); assertEquals(false, exec("return 'bar' ==~ /foo/"));
} }
public void testSlashesEscapePattern() { public void testSlashesEscapePattern() {
assertEquals(true, exec("return /\\/\\//.matcher(\"//\").matches()")); assertEquals(true, exec("return '//' ==~ /\\/\\//"));
} }
public void testPatternAfterAssignment() { public void testPatternAfterAssignment() {
assertEquals(true, exec("def a = /foo/; return a.matcher(\"foo\").matches()")); assertEquals(true, exec("def a = /foo/; return 'foo' ==~ a"));
} }
public void testPatternInIfStement() { public void testPatternInIfStement() {
assertEquals(true, exec("if (/foo/.matcher(\"foo\").matches()) { return true } else { return false }")); assertEquals(true, exec("if (/foo/.matcher('foo').matches()) { return true } else { return false }"));
assertEquals(true, exec("if ('foo' ==~ /foo/) { return true } else { return false }"));
} }
public void testPatternAfterInfixBoolean() { public void testPatternAfterInfixBoolean() {
assertEquals(true, exec("return false || /foo/.matcher(\"foo\").matches()")); assertEquals(true, exec("return false || /foo/.matcher('foo').matches()"));
assertEquals(true, exec("return true && /foo/.matcher(\"foo\").matches()")); assertEquals(true, exec("return true && /foo/.matcher('foo').matches()"));
assertEquals(true, exec("return false || 'foo' ==~ /foo/"));
assertEquals(true, exec("return true && 'foo' ==~ /foo/"));
} }
public void testPatternAfterUnaryNotBoolean() { public void testPatternAfterUnaryNotBoolean() {
assertEquals(false, exec("return !/foo/.matcher(\"foo\").matches()")); assertEquals(false, exec("return !/foo/.matcher('foo').matches()"));
assertEquals(true, exec("return !/foo/.matcher(\"bar\").matches()")); assertEquals(true, exec("return !/foo/.matcher('bar').matches()"));
} }
public void testInTernaryCondition() { public void testInTernaryCondition() {
assertEquals(true, exec("return /foo/.matcher(\"foo\").matches() ? true : false")); assertEquals(true, exec("return /foo/.matcher('foo').matches() ? true : false"));
assertEquals(1, exec("def i = 0; i += /foo/.matcher(\"foo\").matches() ? 1 : 1; return i")); assertEquals(1, exec("def i = 0; i += /foo/.matcher('foo').matches() ? 1 : 1; return i"));
assertEquals(true, exec("return 'foo' ==~ /foo/ ? true : false"));
assertEquals(1, exec("def i = 0; i += 'foo' ==~ /foo/ ? 1 : 1; return i"));
} }
public void testInTernaryTrueArm() { public void testInTernaryTrueArm() {
assertEquals(true, exec("def i = true; return i ? /foo/.matcher(\"foo\").matches() : false")); assertEquals(true, exec("def i = true; return i ? /foo/.matcher('foo').matches() : false"));
assertEquals(true, exec("def i = true; return i ? 'foo' ==~ /foo/ : false"));
} }
public void testInTernaryFalseArm() { public void testInTernaryFalseArm() {
assertEquals(true, exec("def i = false; return i ? false : /foo/.matcher(\"foo\").matches()")); assertEquals(true, exec("def i = false; return i ? false : 'foo' ==~ /foo/"));
} }
public void testRegexInFunction() { public void testRegexInFunction() {
assertEquals(true, exec("boolean m(String s) {/foo/.matcher(s).matches()} m(\"foo\")")); assertEquals(true, exec("boolean m(String s) {/foo/.matcher(s).matches()} m('foo')"));
assertEquals(true, exec("boolean m(String s) {s ==~ /foo/} m('foo')"));
} }
public void testReturnRegexFromFunction() { public void testReturnRegexFromFunction() {
assertEquals(true, exec("Pattern m(boolean a) {a ? /foo/ : /bar/} m(true).matcher(\"foo\").matches()")); assertEquals(true, exec("Pattern m(boolean a) {a ? /foo/ : /bar/} m(true).matcher('foo').matches()"));
assertEquals(true, exec("Pattern m(boolean a) {a ? /foo/ : /bar/} 'foo' ==~ m(true)"));
assertEquals(false, exec("Pattern m(boolean a) {a ? /foo/ : /bar/} m(false).matcher('foo').matches()"));
assertEquals(false, exec("Pattern m(boolean a) {a ? /foo/ : /bar/} 'foo' ==~ m(false)"));
} }
public void testCallMatcherDirectly() {
assertEquals(true, exec("return /foo/.matcher('foo').matches()"));
assertEquals(false, exec("return /foo/.matcher('bar').matches()"));
}
public void testFindInIf() {
assertEquals(true, exec("if ('fooasdfbasdf' =~ /foo/) {return true} else {return false}"));
assertEquals(true, exec("if ('1fooasdfbasdf' =~ /foo/) {return true} else {return false}"));
assertEquals(false, exec("if ('1f11ooasdfbasdf' =~ /foo/) {return true} else {return false}"));
}
public void testFindCastToBoolean() {
assertEquals(true, exec("return (boolean)('fooasdfbasdf' =~ /foo/)"));
assertEquals(true, exec("return (boolean)('111fooasdfbasdf' =~ /foo/)"));
assertEquals(false, exec("return (boolean)('fo11oasdfbasdf' =~ /foo/)"));
}
public void testFindOrStringConcat() {
assertEquals(true, exec("return 'f' + 'o' + 'o' =~ /foo/"));
}
public void testFindOfDef() {
assertEquals(true, exec("def s = 'foo'; return s =~ /foo/"));
}
public void testFindOnInput() {
assertEquals(true, exec("return params.s =~ /foo/", singletonMap("s", "fooasdfdf")));
assertEquals(false, exec("return params.s =~ /foo/", singletonMap("s", "11f2ooasdfdf")));
}
public void testGroup() {
assertEquals("foo", exec("Matcher m = /foo/.matcher('foo'); m.find(); return m.group()"));
}
public void testNumberedGroup() {
assertEquals("o", exec("Matcher m = /(f)(o)o/.matcher('foo'); m.find(); return m.group(2)"));
}
public void testNamedGroup() {
assertEquals("o", exec("Matcher m = /(?<first>f)(?<second>o)o/.matcher('foo'); m.find(); return m.namedGroup('second')"));
}
public void testCantUsePatternCompile() {
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("Pattern.compile('aa')");
});
assertEquals("Unknown call [compile] with [1] arguments on type [Pattern].", e.getMessage());
}
public void testBadRegexPattern() {
PatternSyntaxException e = expectScriptThrows(PatternSyntaxException.class, () -> {
exec("/\\ujjjj/"); // Invalid unicode
});
assertThat(e.getMessage(), containsString("Illegal Unicode escape sequence near index 2"));
assertThat(e.getMessage(), containsString("\\ujjjj"));
}
public void testRegexAgainstNumber() {
ClassCastException e = expectScriptThrows(ClassCastException.class, () -> {
exec("12 ==~ /cat/");
});
assertEquals("Cannot cast from [int] to [String].", e.getMessage());
}
} }

View file

@ -22,7 +22,6 @@ package org.elasticsearch.painless;
import java.lang.invoke.WrongMethodTypeException; import java.lang.invoke.WrongMethodTypeException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.regex.PatternSyntaxException;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -214,21 +213,6 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
assertEquals("invalid sequence of tokens near ['}'].", e.getMessage()); assertEquals("invalid sequence of tokens near ['}'].", e.getMessage());
} }
public void testCantUsePatternCompile() {
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("Pattern.compile(\"aa\")");
});
assertEquals("Unknown call [compile] with [1] arguments on type [Pattern].", e.getMessage());
}
public void testBadRegexPattern() {
PatternSyntaxException e = expectScriptThrows(PatternSyntaxException.class, () -> {
exec("/\\ujjjj/"); // Invalid unicode
});
assertThat(e.getMessage(), containsString("Illegal Unicode escape sequence near index 2"));
assertThat(e.getMessage(), containsString("\\ujjjj"));
}
public void testBadBoxingCast() { public void testBadBoxingCast() {
expectScriptThrows(ClassCastException.class, () -> { expectScriptThrows(ClassCastException.class, () -> {
exec("BitSet bs = new BitSet(); bs.and(2);"); exec("BitSet bs = new BitSet(); bs.and(2);");