Skip to content

Commit 3fd6f5e

Browse files
committed
[CALCITE-7639] Extract SqlParserPos.adjacent helper for right-shift token check
Address review feedback: factor the '>>' token-adjacency check used by the right shift operator into a reusable SqlParserPos.adjacent(SqlParserPos) method, rather than open-coding the line/column comparison in the parser. * SqlParserPos: add adjacent(SqlParserPos), which reports whether two positions are immediately adjacent (one ends exactly where the other begins, with no characters in between). * Parser.jj: add a plain pos(Token) helper and use pos(getToken(1)).adjacent(pos(getToken(2))) in the BIT_RIGHT_SHIFT lookahead. pos is a plain method (not JAVACODE) so it can be called from generated lookahead code, which does not declare throws ParseException. * SqlParserPosTest: add a unit test for SqlParserPos.adjacent.
1 parent 479d691 commit 3fd6f5e

3 files changed

Lines changed: 106 additions & 2 deletions

File tree

core/src/main/codegen/templates/Parser.jj

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,24 @@ public class ${parser.class} extends SqlAbstractParserImpl
279279
Span.of(table, extendList).pos(), table, extendList);
280280
}
281281

282+
/**
283+
* Returns the parser position of the given token.
284+
*
285+
* <p>Declared as a plain method (rather than JAVACODE) so it can be called
286+
* from generated lookahead code, which does not declare
287+
* {@code throws ParseException}.
288+
*
289+
* @param token token whose position to return
290+
* @return parser position spanning the given token
291+
*/
292+
private static SqlParserPos pos(Token token) {
293+
return new SqlParserPos(
294+
token.beginLine,
295+
token.beginColumn,
296+
token.endLine,
297+
token.endColumn);
298+
}
299+
282300
/** Adds a warning that a token such as "HOURS" was used,
283301
* whereas the SQL standard only allows "HOUR".
284302
*
@@ -8479,8 +8497,7 @@ SqlBinaryOperator BinaryRowOperator() :
84798497
// so "a > > b" is not treated as a right shift; otherwise we fall through to
84808498
// the single ">" (greater-than) alternative below.
84818499
| LOOKAHEAD({ getToken(1).kind == GT && getToken(2).kind == GT
8482-
&& getToken(1).endLine == getToken(2).beginLine
8483-
&& getToken(1).endColumn + 1 == getToken(2).beginColumn })
8500+
&& pos(getToken(1)).adjacent(pos(getToken(2))) })
84848501
<GT> <GT> { return SqlStdOperatorTable.BIT_RIGHT_SHIFT; }
84858502
| <GT> { return SqlStdOperatorTable.GREATER_THAN; }
84868503
| <LT> { return SqlStdOperatorTable.LESS_THAN; }

core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,34 @@ public boolean startsAt(SqlParserPos pos) {
264264
&& columnNumber == pos.columnNumber;
265265
}
266266

267+
/**
268+
* Returns whether this position is immediately adjacent to another position;
269+
* that is, one position ends exactly where the other begins, with no
270+
* characters (such as whitespace) in between.
271+
*
272+
* <p>For example, the two {@code >} characters in {@code >>} are adjacent,
273+
* whereas those in {@code > >} are not.
274+
*
275+
* @param pos position to compare against
276+
* @return whether this position and {@code pos} are immediately adjacent
277+
*/
278+
public boolean adjacent(SqlParserPos pos) {
279+
return endsImmediatelyBefore(pos) || pos.endsImmediatelyBefore(this);
280+
}
281+
282+
/**
283+
* Returns whether this position ends exactly one column before another
284+
* position begins, on the same line.
285+
*
286+
* @param pos position that may immediately follow this one
287+
* @return whether this position ends exactly one column before {@code pos}
288+
* begins, on the same line
289+
*/
290+
private boolean endsImmediatelyBefore(SqlParserPos pos) {
291+
return endLineNumber == pos.lineNumber
292+
&& endColumnNumber + 1 == pos.columnNumber;
293+
}
294+
267295
/** Parser position for an identifier segment that is quoted. */
268296
private static class QuotedParserPos extends SqlParserPos {
269297
QuotedParserPos(int startLineNumber, int startColumnNumber,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.sql.parser;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.hamcrest.CoreMatchers.is;
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
24+
/**
25+
* Tests for {@link SqlParserPos}.
26+
*/
27+
public class SqlParserPosTest {
28+
/** Tests {@link SqlParserPos#adjacent(SqlParserPos)}. */
29+
@Test void testAdjacent() {
30+
// Two single-character positions in consecutive columns are adjacent,
31+
// like the two '>' of a '>>' token.
32+
final SqlParserPos col1 = new SqlParserPos(1, 1);
33+
final SqlParserPos col2 = new SqlParserPos(1, 2);
34+
assertThat(col1.adjacent(col2), is(true));
35+
36+
// Adjacency is symmetric.
37+
assertThat(col2.adjacent(col1), is(true));
38+
39+
// A gap between the positions (e.g. whitespace, like '> >') is not
40+
// adjacent.
41+
final SqlParserPos col3 = new SqlParserPos(1, 3);
42+
assertThat(col1.adjacent(col3), is(false));
43+
assertThat(col3.adjacent(col1), is(false));
44+
45+
// A position is not adjacent to itself.
46+
assertThat(col1.adjacent(col1), is(false));
47+
48+
// A multi-column position is adjacent to the position that starts one
49+
// column after it ends.
50+
final SqlParserPos cols1To2 = new SqlParserPos(1, 1, 1, 2);
51+
assertThat(cols1To2.adjacent(col3), is(true));
52+
assertThat(cols1To2.adjacent(col2), is(false));
53+
54+
// Positions on different lines are never adjacent.
55+
final SqlParserPos line2 = new SqlParserPos(2, 1);
56+
assertThat(new SqlParserPos(1, 5).adjacent(line2), is(false));
57+
assertThat(line2.adjacent(new SqlParserPos(1, 5)), is(false));
58+
}
59+
}

0 commit comments

Comments
 (0)