34 public RsqlNode
parse(
final String input) {
35 if (input ==
null || input.isBlank()) {
38 final var state =
new State(input);
39 final var node = parseOr(state);
42 throw new IllegalArgumentException(
"invalid rsql near position " + state.pos);
47 private RsqlNode parseOr(
final State state) {
48 final var nodes =
new ArrayList<RsqlNode>();
49 nodes.add(parseAnd(state));
51 while (state.peek(
',')) {
53 nodes.add(parseAnd(state));
56 return nodes.size() == 1 ? nodes.get(0) :
new RsqlNode.Or(nodes);
59 private RsqlNode parseAnd(
final State state) {
60 final var nodes =
new ArrayList<RsqlNode>();
61 nodes.add(parseTerm(state));
63 while (state.peek(
';')) {
65 nodes.add(parseTerm(state));
68 return nodes.size() == 1 ? nodes.get(0) :
new RsqlNode.And(nodes);
71 private RsqlNode parseTerm(
final State state) {
73 if (state.peek(
'(')) {
75 final var node = parseOr(state);
80 return parseComparison(state);
83 private RsqlNode parseComparison(
final State state) {
84 final var selector = parseSelector(state);
85 final var op = parseOperator(state);
86 final List<String> args;
87 if (op == RsqlOperator.IN || op == RsqlOperator.OUT) {
88 args = parseListArguments(state);
90 args = List.of(parseValue(state));
93 throw new IllegalArgumentException(
"operator requires arguments");
95 return new RsqlNode.Comp(selector, op, args);
98 private String parseSelector(
final State state) {
100 final int start = state.pos;
101 while (!state.eof()) {
102 final char c = state.ch();
103 if (Character.isLetterOrDigit(c) || c ==
'_' || c ==
'-') {
109 if (state.pos == start) {
110 throw new IllegalArgumentException(
"missing selector at position " + start);
112 return state.input.substring(start, state.pos);
115 private RsqlOperator parseOperator(
final State state) {
117 if (state.match(
"==")) {
118 return RsqlOperator.EQ;
120 if (state.match(
"!=")) {
121 return RsqlOperator.NEQ;
123 if (state.match(
"=in=")) {
124 return RsqlOperator.IN;
126 if (state.match(
"=out=")) {
127 return RsqlOperator.OUT;
129 if (state.match(
"=like=")) {
130 return RsqlOperator.LIKE;
132 throw new IllegalArgumentException(
"unsupported operator at position " + state.pos);
135 private List<String> parseListArguments(
final State state) {
138 final var args =
new ArrayList<String>();
141 if (state.peek(
')')) {
145 args.add(parseValue(state));
147 if (state.peek(
',')) {
157 private String parseValue(
final State state) {
159 if (state.peek(
'"')) {
160 return parseQuoted(state);
162 final int start = state.pos;
163 while (!state.eof()) {
164 final char c = state.ch();
165 if (c ==
';' || c ==
',' || c ==
')') {
168 if (Character.isWhitespace(c)) {
173 if (state.pos == start) {
174 throw new IllegalArgumentException(
"missing value at position " + start);
176 return state.input.substring(start, state.pos);
179 private String parseQuoted(
final State state) {
181 final var out =
new StringBuilder();
182 while (!state.eof()) {
183 final char c = state.read();
186 throw new IllegalArgumentException(
"unterminated escape sequence");
188 out.append(state.read());
192 return out.toString();
196 throw new IllegalArgumentException(
"unterminated quoted value");
199 private static final class State {
200 private final String input;
203 private State(
final String input) {
208 private boolean eof() {
209 return pos >= input.length();
213 return input.charAt(pos);
216 private char read() {
217 return input.charAt(pos++);
220 private boolean peek(
final char c) {
221 return !eof() && ch() == c;
224 private void expect(
final char c) {
226 throw new IllegalArgumentException(
"expected '" + c +
"' at position " + pos);
231 private boolean match(
final String s) {
232 if (input.regionMatches(pos, s, 0, s.length())) {
239 private void skipWs() {
240 while (!eof() && Character.isWhitespace(ch())) {