diff --git a/src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java b/src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java index 983f99711d..3a1ad53294 100644 --- a/src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java +++ b/src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java @@ -71,22 +71,20 @@ public StringBuilder appendTo(StringBuilder builder) { return builder; } + /** + * The nearest enclosing object in the tree, or null when there is none. A node that carries + * only a token range, for an object no grammar rule opens a tree node for, has no parent. + */ public ASTNodeAccess getParent() { - Node parent = (Node) node.jjtGetParent(); - while (parent.jjtGetValue() == null) { - parent = (Node) parent.jjtGetParent(); - } - - return ASTNodeAccess.class.cast(parent.jjtGetValue()); + return getParent(ASTNodeAccess.class); } public T getParent(Class clazz) { - Node parent = (Node) node.jjtGetParent(); - while (parent.jjtGetValue() == null || !clazz.isInstance(parent.jjtGetValue())) { + Node parent = node == null ? null : (Node) node.jjtGetParent(); + while (parent != null && !clazz.isInstance(parent.jjtGetValue())) { parent = (Node) parent.jjtGetParent(); } - - return clazz.cast(parent.jjtGetValue()); + return parent == null ? null : clazz.cast(parent.jjtGetValue()); } @Override diff --git a/src/main/java/net/sf/jsqlparser/parser/RangeNode.java b/src/main/java/net/sf/jsqlparser/parser/RangeNode.java new file mode 100644 index 0000000000..9b025a2ebc --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/parser/RangeNode.java @@ -0,0 +1,71 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +/** + * A node outside the parse tree that records the tokens an object spans. It serves an object no + * grammar rule opens a tree node for, or one whose text runs past its tree node. In that second + * case it stands in for the tree node, answering its parent, children and id, with the wider range. + */ +final class RangeNode extends Node { + private final Node treeNode; + + RangeNode(Token first, Token last, Node treeNode) { + super(CCJSqlParserTreeConstants.JJTEXPRESSION); + this.treeNode = treeNode; + jjtSetFirstToken(first); + jjtSetLastToken(last); + } + + @Override + public Node jjtGetParent() { + return treeNode == null ? null : treeNode.jjtGetParent(); + } + + @Override + public Node[] jjtGetChildren() { + return treeNode == null ? super.jjtGetChildren() : treeNode.jjtGetChildren(); + } + + @Override + public Node jjtGetChild(int i) { + return treeNode == null ? super.jjtGetChild(i) : treeNode.jjtGetChild(i); + } + + @Override + public int jjtGetNumChildren() { + return treeNode == null ? super.jjtGetNumChildren() : treeNode.jjtGetNumChildren(); + } + + @Override + public Object childrenAccept(CCJSqlParserVisitor visitor, Object data) { + return treeNode == null ? super.childrenAccept(visitor, data) + : treeNode.childrenAccept(visitor, data); + } + + @Override + public int getId() { + return treeNode == null ? super.getId() : treeNode.getId(); + } + + @Override + public String toString() { + return treeNode == null ? super.toString() : treeNode.toString(); + } + + @Override + public void dump(String prefix) { + if (treeNode == null) { + super.dump(prefix); + } else { + treeNode.dump(prefix); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java index 7530045e57..5b6c5e8259 100644 --- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java +++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java @@ -48,6 +48,9 @@ public class SimpleCharStream { protected boolean trackLineColumn = true; protected int totalCharsRead = 0; protected int absoluteTokenBegin = 0; + // line, column and absolute position of each match begun since the last positionSpecialTokens() + private int[] matchBegins = new int[24]; + private int matchBeginCount = 0; int bufsize; int available; int tokenBegin; @@ -107,6 +110,38 @@ public final int getAbsoluteTokenBegin() { return absoluteTokenBegin; } + /** + * Gives the comments in front of {@code token} the positions where their matches began, from + * the begins recorded since the last call, and forgets those. Comments come through no token + * action, so this is how they get a position. The comments chain backwards from the token while + * the begins are recorded forwards, so one backwards pass pairs them. + */ + public final void positionSpecialTokens(Token token) { + int i = matchBeginCount - 3; + for (Token comment = token.specialToken; comment != null; comment = comment.specialToken) { + while (i >= 0 && (matchBegins[i] != comment.beginLine + || matchBegins[i + 1] != comment.beginColumn)) { + i -= 3; + } + if (i < 0) { + break; + } + comment.absoluteBegin = matchBegins[i + 2]; + comment.absoluteEnd = comment.absoluteBegin + comment.image.length(); + i -= 3; + } + matchBeginCount = 0; + } + + private void recordMatchBegin() { + if (matchBeginCount + 3 > matchBegins.length) { + matchBegins = java.util.Arrays.copyOf(matchBegins, matchBegins.length * 2); + } + matchBegins[matchBeginCount++] = getBeginLine(); + matchBegins[matchBeginCount++] = getBeginColumn(); + matchBegins[matchBeginCount++] = absoluteTokenBegin; + } + protected void ExpandBuff(boolean wrapAround) { final int newSize = bufsize * 2; char[] newbuffer = new char[newSize]; @@ -200,6 +235,7 @@ public final char BeginToken() throws java.io.IOException { tokenBegin = bufpos; absoluteTokenBegin = totalCharsRead; + recordMatchBegin(); if (c == '#' && featureConfiguration != null && featureConfiguration.getAsBoolean(Feature.allowHashLineComments)) { @@ -441,6 +477,7 @@ public final void backup(int amount) { * Reinitialise. */ public void ReInit(Provider dstream, int startline, int startcolumn, int buffersize) { + matchBeginCount = 0; inputStream = dstream; line = startline; column = startcolumn - 1; diff --git a/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java b/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java index 1e5a742982..58a294bb5f 100644 --- a/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import java.util.function.Consumer; @@ -22,7 +23,7 @@ * "https://cloud.google.com/bigquery/docs/reference/standard-sql/debugging-statements">Debugging * statements */ -public class AssertStatement implements Statement { +public class AssertStatement extends ASTNodeAccessImpl implements Statement { private Expression expression; private StringValue description; diff --git a/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java index 7ad71dd546..84726280d1 100644 --- a/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.statement.select.PlainSelect; import java.util.List; @@ -19,7 +20,7 @@ * * @see ATTACH */ -public class AttachStatement implements Statement { +public class AttachStatement extends ASTNodeAccessImpl implements Statement { private String databasePath; private String alias; private boolean usingDatabaseKeyword; diff --git a/src/main/java/net/sf/jsqlparser/statement/Block.java b/src/main/java/net/sf/jsqlparser/statement/Block.java index f609e58262..a4e822f01b 100644 --- a/src/main/java/net/sf/jsqlparser/statement/Block.java +++ b/src/main/java/net/sf/jsqlparser/statement/Block.java @@ -10,8 +10,9 @@ package net.sf.jsqlparser.statement; import java.util.function.Consumer; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; -public class Block implements Statement { +public class Block extends ASTNodeAccessImpl implements Statement { private boolean hasSemicolonAfterEnd = false; private Statements statements; diff --git a/src/main/java/net/sf/jsqlparser/statement/Commit.java b/src/main/java/net/sf/jsqlparser/statement/Commit.java index 20df0a111c..37205308fc 100644 --- a/src/main/java/net/sf/jsqlparser/statement/Commit.java +++ b/src/main/java/net/sf/jsqlparser/statement/Commit.java @@ -9,7 +9,9 @@ */ package net.sf.jsqlparser.statement; -public class Commit implements Statement { +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +public class Commit extends ASTNodeAccessImpl implements Statement { private TransactionKeyword keyword; private TransactionChain chain; diff --git a/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java index a5ae350c64..829410b280 100644 --- a/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java @@ -9,11 +9,13 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + /** * DuckDB's {@code CONNECT target [AS alias]}, which routes the session's queries to a remote * database. */ -public class ConnectStatement implements Statement { +public class ConnectStatement extends ASTNodeAccessImpl implements Statement { private String target; private String alias; diff --git a/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java index 0e05b58636..2697677dbb 100644 --- a/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.statement; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -25,7 +26,7 @@ * * @see COPY */ -public class CopyStatement implements Statement { +public class CopyStatement extends ASTNodeAccessImpl implements Statement { private Table table; private ExpressionList columns; private Select select; diff --git a/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java b/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java index d3f75a1278..b1978dc4a6 100644 --- a/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java @@ -15,13 +15,14 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.statement.create.function.FunctionReturnType; import net.sf.jsqlparser.statement.create.table.TableElement; /** * A base for the declaration of function like statements */ -public abstract class CreateFunctionalStatement implements Statement { +public abstract class CreateFunctionalStatement extends ASTNodeAccessImpl implements Statement { private String kind; private boolean orReplace = false; diff --git a/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java index 25d375e38b..58b0fe34cd 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java @@ -9,10 +9,12 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + /** * {@code DEALLOCATE [PREPARE] name}, which drops a statement prepared by {@link PrepareStatement}. */ -public class DeallocateStatement implements Statement { +public class DeallocateStatement extends ASTNodeAccessImpl implements Statement { private String name; private boolean usingPrepareKeyword; diff --git a/src/main/java/net/sf/jsqlparser/statement/DeclareStatement.java b/src/main/java/net/sf/jsqlparser/statement/DeclareStatement.java index 27537bc5ff..c189280b54 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DeclareStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DeclareStatement.java @@ -18,10 +18,11 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.statement.create.table.ColDataType; import net.sf.jsqlparser.statement.create.table.ColumnDefinition; -public final class DeclareStatement implements Statement { +public final class DeclareStatement extends ASTNodeAccessImpl implements Statement { private UserVariable userVariable = null; private DeclareType declareType = DeclareType.TYPE; diff --git a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java index 054b648436..2efedba2d1 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java @@ -9,10 +9,11 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.Select; -public class DescribeStatement implements Statement { +public class DescribeStatement extends ASTNodeAccessImpl implements Statement { private Table table; private Select select; diff --git a/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java index 80fe5780a9..d8bd25f810 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java @@ -9,12 +9,14 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + /** * DuckDB's {@code DETACH [DATABASE] [IF EXISTS] name}, the counterpart of {@link AttachStatement}. * * @see DETACH */ -public class DetachStatement implements Statement { +public class DetachStatement extends ASTNodeAccessImpl implements Statement { private String databaseName; private boolean usingDatabaseKeyword; private boolean ifExists; diff --git a/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java index fdbe6f4e71..1231963f67 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java @@ -9,10 +9,12 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + /** * DuckDB's {@code DISCONNECT [name]}, the counterpart of {@link ConnectStatement}. */ -public class DisconnectStatement implements Statement { +public class DisconnectStatement extends ASTNodeAccessImpl implements Statement { private String name; public DisconnectStatement() {} diff --git a/src/main/java/net/sf/jsqlparser/statement/DoStatement.java b/src/main/java/net/sf/jsqlparser/statement/DoStatement.java index c595ea1a32..4f21c91f6a 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DoStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DoStatement.java @@ -11,9 +11,10 @@ import java.util.function.Consumer; import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; /** A PostgreSQL anonymous routine. The language-specific body remains a string literal. */ -public class DoStatement implements Statement { +public class DoStatement extends ASTNodeAccessImpl implements Statement { private StringValue code; private String language; private boolean languageBeforeCode; diff --git a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java index 5442ce4c5e..22db6859f9 100644 --- a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java @@ -14,12 +14,13 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Table; /** * An {@code EXPLAIN} statement */ -public class ExplainStatement implements Statement { +public class ExplainStatement extends ASTNodeAccessImpl implements Statement { private String keyword; private Statement statement; private List