Merge pull request #4044 from oowekyala:text-utils-javacc
[core] Text documents escapes #4044
This commit is contained in:
92 files changed
+1918
-1016
No files matched your search
+33
-3
@@ -59,8 +59,8 @@
|
||||
<property name="base-class-name" value="Abstract${lang-name}Node" />
|
||||
|
||||
<!-- This will be moved to impl package when all language modules have been ported -->
|
||||
<property name="base-tokenmgr" value="${ast-api-package}.AbstractTokenManager"/>
|
||||
<property name="charstream-itf" value="${ast-api-package}.CharStream"/>
|
||||
<property name="base-tokenmgr" value="${ast-impl-package}.AbstractTokenManager"/>
|
||||
<property name="charstream-itf" value="${ast-impl-package}.CharStream"/>
|
||||
|
||||
|
||||
<property name="tokenmgr-name" value="${parser-name}TokenManager" />
|
||||
@@ -284,6 +284,25 @@
|
||||
<substitution expression="protected Token jjFillToken() {return input_stream.getTokenDocument().createToken(jjmatchedKind, input_stream, jjstrLiteralImages[jjmatchedKind]);}" />
|
||||
</replaceregexp>
|
||||
|
||||
<!-- Renamed CharStream methods -->
|
||||
<replaceregexp flags="g">
|
||||
<file name="${tokenmgr-file}" />
|
||||
<regexp pattern="image.append\(input_stream.GetSuffix\(" />
|
||||
<substitution expression="input_stream.appendSuffix(image,(" />
|
||||
</replaceregexp>
|
||||
|
||||
<replaceregexp flags="g">
|
||||
<file name="${tokenmgr-file}" />
|
||||
<regexp pattern="input_stream.GetImage" />
|
||||
<substitution expression="input_stream.getTokenImage" />
|
||||
</replaceregexp>
|
||||
|
||||
<replaceregexp flags="g">
|
||||
<file name="${tokenmgr-file}" />
|
||||
<regexp pattern="input_stream.BeginToken" />
|
||||
<substitution expression="input_stream.markTokenStart" />
|
||||
</replaceregexp>
|
||||
|
||||
<!-- This is used to allow for tokens to be immutable. The lexical actions
|
||||
return the new token instead of mutating it. -->
|
||||
<replaceregexp flags="sg">
|
||||
@@ -396,7 +415,7 @@ public final class ${token-constants-name} \{${line.separator}
|
||||
* be used as a basis for a CPD Tokenizer.
|
||||
*/
|
||||
@net.sourceforge.pmd.annotation.InternalApi
|
||||
public static net.sourceforge.pmd.lang.TokenManager<%%%API_PACK%%%.impl.javacc.JavaccToken> newTokenManager(%%%API_PACK%%%.CharStream cs) {
|
||||
public static net.sourceforge.pmd.lang.TokenManager<%%%API_PACK%%%.impl.javacc.JavaccToken> newTokenManager(%%%API_PACK%%%.impl.javacc.CharStream cs) {
|
||||
return new %%%TOKEN_MGR_NAME%%%(cs);
|
||||
}
|
||||
|
||||
@@ -405,6 +424,17 @@ public final class ${token-constants-name} \{${line.separator}
|
||||
<fileset file="${token-constants-file}" />
|
||||
</replace>
|
||||
|
||||
<replace>
|
||||
<replacetoken>};</replacetoken>
|
||||
<replacevalue><![CDATA[
|
||||
};
|
||||
|
||||
/** Nams of the tokens, each index corresponds to a kind. See also {@link #describe(int)}. */
|
||||
public static final java.util.List<String> TOKEN_NAMES = java.util.Collections.unmodifiableList(java.util.Arrays.asList(tokenImage));
|
||||
]]> </replacevalue>
|
||||
<fileset file="${token-constants-file}" />
|
||||
</replace>
|
||||
|
||||
<replaceregexp>
|
||||
<regexp pattern="%%%TOKEN_MGR_NAME%%%" />
|
||||
<substitution expression="${tokenmgr-name}" />
|
||||
|
||||
@@ -37,9 +37,7 @@ public final class ApexParser implements Parser {
|
||||
|
||||
final Compilation astRoot = CompilerService.INSTANCE.parseApex(task.getTextDocument());
|
||||
|
||||
if (astRoot == null) {
|
||||
throw new ParseException("Couldn't parse the source - there is not root node - Syntax Error??");
|
||||
}
|
||||
assert astRoot != null : "Normally replaced by Compilation.INVALID";
|
||||
|
||||
String property = task.getProperties().getProperty(MULTIFILE_DIRECTORY);
|
||||
ApexMultifileAnalysis analysisHandler = ApexMultifileAnalysis.getAnalysisInstance(property);
|
||||
@@ -48,7 +46,7 @@ public final class ApexParser implements Parser {
|
||||
final ApexTreeBuilder treeBuilder = new ApexTreeBuilder(task);
|
||||
return treeBuilder.buildTree(astRoot, analysisHandler);
|
||||
} catch (apex.jorje.services.exception.ParseException e) {
|
||||
throw new ParseException(e);
|
||||
throw new ParseException(e).setFileName(task.getFileDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,29 +13,6 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<inherited>true</inherited>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-sources</id>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<target>
|
||||
<ant antfile="src/main/ant/alljavacc.xml">
|
||||
<property name="target" value="${project.build.directory}/generated-sources/javacc" />
|
||||
<property name="javacc.jar" value="${javacc.jar}" />
|
||||
</ant>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
<project name="pmd" default="alljavacc" basedir="../../">
|
||||
|
||||
<property name="javacc-home.path" value="target/lib" />
|
||||
|
||||
<property name="tmp-package" value="net.sourceforge.pmd.lang.ast.dummy" />
|
||||
|
||||
<property name="tmp-package.dir" value="${target}/net/sourceforge/pmd/lang/ast/dummy" />
|
||||
|
||||
<property name="base-ast-package" value="net.sourceforge.pmd.lang.ast" />
|
||||
<property name="base-ast-package.dir" value="${target}/net/sourceforge/pmd/lang/ast" />
|
||||
|
||||
<property name="target-package" value="${base-ast-package}.impl.javacc" />
|
||||
<property name="target-package.dir" value="${base-ast-package.dir}/impl/javacc" />
|
||||
|
||||
|
||||
<target name="alljavacc"
|
||||
description="Generates all JavaCC aspects within PMD"
|
||||
depends="checkUpToDate,init,dummyjjtree,cleanup" />
|
||||
|
||||
<target name="checkUpToDate">
|
||||
<uptodate property="javaccBuildNotRequired" targetfile="${target}/last-generated-timestamp">
|
||||
<srcfiles dir="etc/grammar" includes="*.jj*"/>
|
||||
</uptodate>
|
||||
<echo message="up to date check: javaccBuildNotRequired=${javaccBuildNotRequired}"/>
|
||||
</target>
|
||||
|
||||
<target name="init" unless="javaccBuildNotRequired">
|
||||
<mkdir dir="${javacc-home.path}" />
|
||||
<copy file="${javacc.jar}" tofile="${javacc-home.path}/javacc.jar" />
|
||||
|
||||
<mkdir dir="${target}"/>
|
||||
<touch file="${target}/last-generated-timestamp"/>
|
||||
</target>
|
||||
|
||||
<target name="cleanup">
|
||||
<delete dir="${javacc-home.path}" />
|
||||
</target>
|
||||
|
||||
<target name="dummyjjtree" description="Generates the reusable JavaCC aspects" unless="javaccBuildNotRequired">
|
||||
|
||||
<delete dir="${tmp-package.dir}" />
|
||||
<mkdir dir="${tmp-package.dir}" />
|
||||
<echo>Using JavaCC home: ${javacc-home.path}</echo>
|
||||
<java fork="true"
|
||||
classname="jjtree"
|
||||
classpath="${javacc-home.path}/javacc.jar">
|
||||
<sysproperty key="file.encoding" value="UTF-8" />
|
||||
<arg value="-OUTPUT_DIRECTORY:${tmp-package.dir}" />
|
||||
<arg value="etc/grammar/dummy.jjt" />
|
||||
</java>
|
||||
|
||||
<!-- Generate CharStream interface -->
|
||||
<java fork="true"
|
||||
classname="javacc"
|
||||
classpath="${javacc-home.path}/javacc.jar">
|
||||
<sysproperty key="file.encoding" value="UTF-8" />
|
||||
<arg value="-USER_CHAR_STREAM:true" />
|
||||
<arg value="-OUTPUT_DIRECTORY:${tmp-package.dir}" />
|
||||
<arg value="${tmp-package.dir}/dummy.jj" />
|
||||
</java>
|
||||
|
||||
<replace file="${tmp-package.dir}/CharStream.java"
|
||||
token="interface"
|
||||
value="@Deprecated @net.sourceforge.pmd.annotation.InternalApi interface" />
|
||||
|
||||
<!-- Generate ASCII w/ Unicode Escapes CharStream implementation -->
|
||||
<javacc usercharstream="false"
|
||||
unicodeinput="true"
|
||||
javaunicodeescape="true"
|
||||
static="false"
|
||||
target="${tmp-package.dir}/dummy.jj"
|
||||
outputdirectory="${tmp-package.dir}"
|
||||
javacchome="${javacc-home.path}" />
|
||||
|
||||
<replace file="${tmp-package.dir}/JavaCharStream.java"
|
||||
token="${tmp-package}"
|
||||
value="${target-package}">
|
||||
<fileset dir="${tmp-package.dir}">
|
||||
</fileset>
|
||||
</replace>
|
||||
|
||||
|
||||
<!-- Patch JavaCharStream -->
|
||||
|
||||
<antcall target="patch-char-stream">
|
||||
<param name="cs.prefix" value="Java" />
|
||||
</antcall>
|
||||
|
||||
<delete dir="${tmp-package.dir}" />
|
||||
|
||||
</target>
|
||||
|
||||
|
||||
<target name="patch-char-stream">
|
||||
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="${cs.prefix}CharStream"
|
||||
value="${cs.prefix}CharStreamBase"/>
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="class ${cs.prefix}CharStreamBase"
|
||||
value="abstract class ${cs.prefix}CharStreamBase implements ${base-ast-package}.CharStream" />
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="/** Read a character. */"
|
||||
value="protected boolean doEscape() { return true; }" />
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="if ((buffer[bufpos] = c = ReadByte()) == '\\')"
|
||||
value="if ((buffer[bufpos] = c = ReadByte()) == '\\' && doEscape())" />
|
||||
<!-- This is in ExpandBuf, the exception this may throw is OutOfMemoryError, which is stupidly erased by javacc -->
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="throw new Error(t.getMessage())"
|
||||
value="throw t" />
|
||||
<!-- Set tab size of JavaCC to 1 -->
|
||||
<replace file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
token="int tabSize = 8;"
|
||||
value="int tabSize = 1;" />
|
||||
|
||||
<replaceregexp file="${tmp-package.dir}/${cs.prefix}CharStream.java">
|
||||
<regexp pattern='throw new Error\("Invalid escape character at line " \+ line \+\s+" column " \+ column \+ "."\);' />
|
||||
<substitution expression='throw new ${base-ast-package}.TokenMgrError(line, column, null, "Invalid unicode escape", null);' />
|
||||
</replaceregexp>
|
||||
|
||||
<move overwrite="true"
|
||||
file="${tmp-package.dir}/${cs.prefix}CharStream.java"
|
||||
tofile="${target-package.dir}/${cs.prefix}CharStreamBase.java" />
|
||||
|
||||
|
||||
</target>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -13,10 +13,10 @@ import net.sourceforge.pmd.cpd.Tokens;
|
||||
import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter;
|
||||
import net.sourceforge.pmd.cpd.token.TokenFilter;
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.lang.ast.TokenMgrError;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.CharStreamFactory;
|
||||
import net.sourceforge.pmd.lang.ast.FileAnalysisException;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.CharStream;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument.TokenDocumentBehavior;
|
||||
import net.sourceforge.pmd.lang.document.CpdCompat;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
@@ -24,11 +24,11 @@ public abstract class JavaCCTokenizer implements Tokenizer {
|
||||
|
||||
@SuppressWarnings("PMD.CloseResource")
|
||||
protected TokenManager<JavaccToken> getLexerForSource(TextDocument sourceCode) throws IOException {
|
||||
return makeLexerImpl(makeCharStream(sourceCode));
|
||||
return makeLexerImpl(CharStream.create(sourceCode, tokenBehavior()));
|
||||
}
|
||||
|
||||
protected CharStream makeCharStream(TextDocument sourceCode) {
|
||||
return CharStreamFactory.simpleCharStream(sourceCode);
|
||||
protected TokenDocumentBehavior tokenBehavior() {
|
||||
return TokenDocumentBehavior.DEFAULT;
|
||||
}
|
||||
|
||||
protected abstract TokenManager<JavaccToken> makeLexerImpl(CharStream sourceCode);
|
||||
@@ -55,7 +55,7 @@ public abstract class JavaCCTokenizer implements Tokenizer {
|
||||
tokenEntries.add(processToken(tokenEntries, currentToken));
|
||||
currentToken = tokenFilter.getNextToken();
|
||||
}
|
||||
} catch (TokenMgrError e) {
|
||||
} catch (FileAnalysisException e) {
|
||||
throw e.setFileName(sourceCode.getFileName());
|
||||
} finally {
|
||||
tokenEntries.add(TokenEntry.getEOF());
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument;
|
||||
|
||||
/**
|
||||
* PMD flavour of character streams used by JavaCC parsers.
|
||||
*
|
||||
* TODO for when all JavaCC languages are aligned:
|
||||
* * rename methods to match decent naming conventions
|
||||
* * move to impl.javacc package
|
||||
*/
|
||||
public interface CharStream {
|
||||
|
||||
/**
|
||||
* Returns the next character from the input. After a {@link #backup(int)},
|
||||
* some of the already read chars must be spit out again.
|
||||
*
|
||||
* @return The next character
|
||||
*
|
||||
* @throws IOException If the underlying char stream throws
|
||||
*/
|
||||
char readChar() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Calls {@link #readChar()} and returns its value, marking its position
|
||||
* as the beginning of the next token. All characters must remain in
|
||||
* the buffer between two successive calls to this method to implement
|
||||
* backup correctly.
|
||||
*/
|
||||
char BeginToken() throws IOException; // SUPPRESS CHECKSTYLE we'll rename it later
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string made up of characters from the token mark up to
|
||||
* to the current buffer position.
|
||||
*/
|
||||
String GetImage(); // SUPPRESS CHECKSTYLE we'll rename it later
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of characters that make up the suffix of length 'len' for
|
||||
* the current token. This is used to build up the matched string
|
||||
* for use in actions in the case of MORE. A simple and inefficient
|
||||
* implementation of this is as follows :
|
||||
*
|
||||
* <pre>{@code
|
||||
* String t = tokenImage();
|
||||
* return t.substring(t.length() - len, t.length()).toCharArray();
|
||||
* }</pre>
|
||||
*
|
||||
* @param len Length of the returned array
|
||||
*
|
||||
* @return The suffix
|
||||
*
|
||||
* @throws IndexOutOfBoundsException If len is greater than the length of the
|
||||
* current token
|
||||
*/
|
||||
char[] GetSuffix(int len); // SUPPRESS CHECKSTYLE we'll rename it later
|
||||
|
||||
|
||||
/**
|
||||
* Pushes a given number of already read chars into the buffer.
|
||||
* Subsequent calls to {@link #readChar()} will read those characters
|
||||
* before proceeding to read the underlying char stream.
|
||||
*
|
||||
* <p>A lexer calls this method if it has already read some characters,
|
||||
* but cannot use them to match a (longer) token. So, they will
|
||||
* be used again as the prefix of the next token.
|
||||
*
|
||||
* @throws AssertionError If the requested amount is greater than the
|
||||
* number of read chars
|
||||
*/
|
||||
void backup(int amount);
|
||||
|
||||
@Deprecated
|
||||
int getBeginColumn();
|
||||
|
||||
@Deprecated
|
||||
int getBeginLine();
|
||||
|
||||
|
||||
/** Returns the column number of the last character for the current token. */
|
||||
int getEndColumn();
|
||||
|
||||
|
||||
/** Returns the line number of the last character for current token. */
|
||||
int getEndLine();
|
||||
|
||||
// These methods are added by PMD
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token document for the tokens being built. Having it
|
||||
* here is the most convenient place for the time being.
|
||||
*/
|
||||
default JavaccTokenDocument getTokenDocument() {
|
||||
return null; // for VelocityCharStream
|
||||
}
|
||||
|
||||
|
||||
/** Returns the start offset of the current token (in the original source), inclusive. */
|
||||
default int getStartOffset() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/** Returns the end offset of the current token (in the original source), exclusive. */
|
||||
default int getEndOffset() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.TextFile;
|
||||
@@ -39,7 +40,7 @@ public class FileAnalysisException extends RuntimeException {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
FileAnalysisException setFileName(String filename) {
|
||||
public FileAnalysisException setFileName(String filename) {
|
||||
this.filename = Objects.requireNonNull(filename);
|
||||
return this;
|
||||
}
|
||||
@@ -55,6 +56,22 @@ public class FileAnalysisException extends RuntimeException {
|
||||
return filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return errorKind() + StringUtils.uncapitalize(positionToString()) + ": " + super.getMessage();
|
||||
}
|
||||
|
||||
protected String errorKind() {
|
||||
return "Error";
|
||||
}
|
||||
|
||||
protected String positionToString() {
|
||||
if (hasFileName()) {
|
||||
return " in file '" + getFileName() + "'";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps the cause into an analysis exception. If it is itself an analysis
|
||||
|
||||
@@ -6,8 +6,11 @@ package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.internal.util.IteratorUtil;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.reporting.Reportable;
|
||||
|
||||
@@ -37,18 +40,37 @@ public interface GenericToken<T extends GenericToken<T>> extends Comparable<T>,
|
||||
T getPreviousComment();
|
||||
|
||||
/**
|
||||
* Returns the token's text.
|
||||
* Returns the token's text as a string.
|
||||
*/
|
||||
default String getImage() {
|
||||
return getImageCs().toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the image as a {@link CharSequence}.
|
||||
* Returns the text of the token as a char sequence.
|
||||
* This should be preferred when you can use eg {@link StringUtils}
|
||||
* to do some processing, without having to create a string.
|
||||
*/
|
||||
CharSequence getImageCs();
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the image of this token equals
|
||||
* the given charsequence. This does not create a
|
||||
* string.
|
||||
*
|
||||
* @param charSeq A character sequence
|
||||
*/
|
||||
default boolean imageEquals(CharSequence charSeq) {
|
||||
CharSequence imageCs = getImageCs();
|
||||
if (imageCs instanceof Chars) {
|
||||
return ((Chars) imageCs).contentEquals(charSeq);
|
||||
}
|
||||
return StringUtils.equals(imageCs, charSeq);
|
||||
}
|
||||
|
||||
|
||||
/** Returns a text region with the coordinates of this token. */
|
||||
TextRegion getRegion();
|
||||
|
||||
@@ -58,6 +80,7 @@ public interface GenericToken<T extends GenericToken<T>> extends Comparable<T>,
|
||||
*/
|
||||
boolean isEof();
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this token is implicit, ie was inserted artificially
|
||||
* and has a zero-length image.
|
||||
|
||||
@@ -132,7 +132,7 @@ public interface Node extends Reportable {
|
||||
|
||||
|
||||
// Those are kept here because they're handled specially as XPath
|
||||
// attributes
|
||||
// attributes, for now
|
||||
|
||||
@Override
|
||||
default int getBeginLine() {
|
||||
|
||||
@@ -40,11 +40,6 @@ public class ParseException extends FileAnalysisException {
|
||||
this.currentToken = null;
|
||||
}
|
||||
|
||||
public ParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.currentToken = null;
|
||||
}
|
||||
|
||||
public ParseException(String message, JavaccToken token) {
|
||||
super(message);
|
||||
this.currentToken = token;
|
||||
@@ -59,6 +54,11 @@ public class ParseException extends FileAnalysisException {
|
||||
currentToken = currentTokenVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String errorKind() {
|
||||
return "Parse exception";
|
||||
}
|
||||
|
||||
/**
|
||||
* It uses "currentToken" and "expectedTokenSequences" to generate a parse
|
||||
* error message and returns it. If this object has been created
|
||||
|
||||
@@ -46,21 +46,28 @@ public interface Parser {
|
||||
private final SemanticErrorReporter reporter;
|
||||
private final ClassLoader auxclasspathClassLoader;
|
||||
|
||||
private final PropertySource propertySource;
|
||||
private final ParserTaskProperties propertySource;
|
||||
|
||||
public ParserTask(TextDocument textDoc, SemanticErrorReporter reporter, ClassLoader auxclasspathClassLoader) {
|
||||
this.textDoc = Objects.requireNonNull(textDoc, "Text document was null");
|
||||
this.reporter = Objects.requireNonNull(reporter, "reporter was null");
|
||||
this.auxclasspathClassLoader = Objects.requireNonNull(auxclasspathClassLoader, "auxclasspathClassLoader was null");
|
||||
|
||||
this.propertySource = new ParserTaskProperties();
|
||||
propertySource.definePropertyDescriptor(COMMENT_MARKER);
|
||||
this(textDoc, reporter, new ParserTaskProperties(), auxclasspathClassLoader);
|
||||
}
|
||||
|
||||
public ParserTask(TextDocument textDoc, SemanticErrorReporter reporter) {
|
||||
this(textDoc, reporter, Parser.class.getClassLoader());
|
||||
}
|
||||
|
||||
private ParserTask(TextDocument textDoc,
|
||||
SemanticErrorReporter reporter,
|
||||
ParserTaskProperties source,
|
||||
ClassLoader auxclasspathClassLoader) {
|
||||
this.textDoc = Objects.requireNonNull(textDoc, "Text document was null");
|
||||
this.reporter = Objects.requireNonNull(reporter, "reporter was null");
|
||||
this.auxclasspathClassLoader = Objects.requireNonNull(auxclasspathClassLoader, "auxclasspathClassLoader was null");
|
||||
|
||||
this.propertySource = new ParserTaskProperties(source);
|
||||
}
|
||||
|
||||
|
||||
public static final PropertyDescriptor<String> COMMENT_MARKER =
|
||||
PropertyFactory.stringProperty("suppressionCommentMarker")
|
||||
.desc("deprecated! NOPMD")
|
||||
@@ -117,9 +124,33 @@ public interface Parser {
|
||||
return getProperties().getProperty(COMMENT_MARKER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the text document with another.
|
||||
*/
|
||||
public ParserTask withTextDocument(TextDocument doc) {
|
||||
return new ParserTask(doc, this.reporter, this.propertySource, this.auxclasspathClassLoader);
|
||||
}
|
||||
|
||||
|
||||
private static final class ParserTaskProperties extends AbstractPropertySource {
|
||||
|
||||
ParserTaskProperties() {
|
||||
definePropertyDescriptor(COMMENT_MARKER);
|
||||
}
|
||||
|
||||
ParserTaskProperties(ParserTaskProperties toCopy) {
|
||||
for (PropertyDescriptor<?> prop : toCopy.getPropertyDescriptors()) {
|
||||
definePropertyDescriptor(prop);
|
||||
}
|
||||
toCopy.getOverriddenPropertyDescriptors().forEach(
|
||||
prop -> copyProperty(prop, toCopy, this)
|
||||
);
|
||||
}
|
||||
|
||||
static <T> void copyProperty(PropertyDescriptor<T> prop, PropertySource source, PropertySource target) {
|
||||
target.setProperty(prop, source.getProperty(prop));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPropertySourceType() {
|
||||
return "ParserOptions";
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.NoAttribute;
|
||||
|
||||
@@ -17,23 +19,39 @@ public interface TextAvailableNode extends Node {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the exact region of text delimiting
|
||||
* the node in the underlying text document. Note
|
||||
* that {@link #getReportLocation()} does not need
|
||||
* to match this region. {@link #getReportLocation()}
|
||||
* can be scoped down to a specific token, eg the
|
||||
* class identifier.
|
||||
* Returns the exact region of text delimiting the node in the underlying
|
||||
* text document. Note that {@link #getReportLocation()} does not need
|
||||
* to match this region. {@link #getReportLocation()} can be scoped down
|
||||
* to a specific token, eg the class identifier. This region uses
|
||||
* the translated coordinate system, ie the coordinate system of
|
||||
* {@link #getTextDocument()}.
|
||||
*/
|
||||
@Override
|
||||
TextRegion getTextRegion();
|
||||
|
||||
/**
|
||||
* Returns the original source code underlying this node. In
|
||||
* particular, for a {@link RootNode}, returns the whole text
|
||||
* of the file.
|
||||
* Returns the original source code underlying this node, before
|
||||
* any escapes have been translated. In particular, for a {@link RootNode},
|
||||
* returns the whole text of the file.
|
||||
*
|
||||
* @see TextDocument#sliceOriginalText(TextRegion)
|
||||
*/
|
||||
@NoAttribute
|
||||
CharSequence getText();
|
||||
default Chars getOriginalText() {
|
||||
return getTextDocument().sliceOriginalText(getTextRegion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source code underlying this node, after any escapes
|
||||
* have been translated. In particular, for a {@link RootNode}, returns
|
||||
* the whole text of the file.
|
||||
*
|
||||
* @see TextDocument#sliceTranslatedText(TextRegion)
|
||||
*/
|
||||
@NoAttribute
|
||||
default Chars getText() {
|
||||
return getTextDocument().sliceTranslatedText(getTextRegion());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -53,12 +53,14 @@ public final class TokenMgrError extends FileAnalysisException {
|
||||
return column;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String positionToString() {
|
||||
return super.positionToString() + " at line " + line + ", column " + column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String leader = hasFileName() ? "Lexical error in file " + getFileName() : "Lexical error";
|
||||
return leader + " at line " + line + ", column " + column + ". Encountered: " + super.getMessage();
|
||||
protected String errorKind() {
|
||||
return "Lexical error";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-6
@@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.impl.AbstractNode;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
@@ -48,11 +47,6 @@ public abstract class AbstractJjtreeNode<B extends AbstractJjtreeNode<B, N>, N e
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Chars getText() {
|
||||
return getTextDocument().sliceText(getTextRegion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final TextRegion getTextRegion() {
|
||||
return TextRegion.fromBothOffsets(getFirstToken().getStartOffset(),
|
||||
|
||||
+1
-4
@@ -2,19 +2,16 @@
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast;
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
|
||||
/**
|
||||
* A base class for the token managers generated by JavaCC.
|
||||
*
|
||||
* TODO move to impl.javacc package
|
||||
*/
|
||||
public abstract class AbstractTokenManager implements TokenManager<JavaccToken> {
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import static java.lang.Integer.min;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
/**
|
||||
* A base class for readers that handle escapes starting with a backslash.
|
||||
*/
|
||||
public abstract class BackslashEscapeTranslator extends EscapeTranslator {
|
||||
|
||||
private static final char BACKSLASH = '\\';
|
||||
|
||||
/**
|
||||
* An offset until which we read backslashes and decided they were not
|
||||
* an escape. The read procedure may cut off in the middle of the escape,
|
||||
* and turn an even num of backslashes into an odd one, so until we crossed
|
||||
* this offset, backslashes are not treated specially.
|
||||
*/
|
||||
private int savedNotEscapeSpecialEnd = Integer.MAX_VALUE;
|
||||
|
||||
|
||||
public BackslashEscapeTranslator(TextDocument builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int gobbleMaxWithoutEscape(final int maxOff) throws MalformedSourceException {
|
||||
int off = this.bufpos;
|
||||
boolean seenBackslash = true;
|
||||
int notEscapeEnd = this.savedNotEscapeSpecialEnd;
|
||||
while (off < maxOff) {
|
||||
seenBackslash = input.charAt(off) == BACKSLASH && notEscapeEnd >= off;
|
||||
if (seenBackslash) {
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
|
||||
if (!seenBackslash || off == maxOff) {
|
||||
this.bufpos = off;
|
||||
return off;
|
||||
}
|
||||
|
||||
return handleBackslash(maxOff, off);
|
||||
}
|
||||
|
||||
protected abstract int handleBackslash(int maxOff, int firstBackslashOff) throws MalformedSourceException;
|
||||
|
||||
@Override
|
||||
protected int recordEscape(int startOffsetInclusive, int endOffsetExclusive, Chars translation) {
|
||||
this.savedNotEscapeSpecialEnd = Integer.MAX_VALUE;
|
||||
return super.recordEscape(startOffsetInclusive, endOffsetExclusive, translation);
|
||||
}
|
||||
|
||||
protected int abortEscape(int off, int maxOff) {
|
||||
// not an escape sequence
|
||||
int min = min(maxOff, off);
|
||||
// save the number of backslashes that are part of the escape,
|
||||
// might have been cut in half by the maxReadahead
|
||||
this.savedNotEscapeSpecialEnd = min < off ? off : Integer.MAX_VALUE;
|
||||
this.bufpos = min;
|
||||
return min;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument.TokenDocumentBehavior;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
|
||||
/**
|
||||
* PMD flavour of character streams used by JavaCC parsers.
|
||||
*/
|
||||
public final class CharStream {
|
||||
|
||||
private final JavaccTokenDocument tokenDoc;
|
||||
private final TextDocument textDoc;
|
||||
private final Chars chars;
|
||||
private final boolean useMarkSuffix;
|
||||
private int curOffset;
|
||||
private int markOffset;
|
||||
|
||||
private CharStream(JavaccTokenDocument tokenDoc) {
|
||||
this.tokenDoc = tokenDoc;
|
||||
this.textDoc = tokenDoc.getTextDocument();
|
||||
this.chars = textDoc.getText();
|
||||
this.useMarkSuffix = tokenDoc.useMarkSuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new char stream for the given document. This may create
|
||||
* a new {@link TextDocument} view over the original, which reflects
|
||||
* its character escapes.
|
||||
*/
|
||||
public static CharStream create(TextDocument doc, TokenDocumentBehavior behavior) throws MalformedSourceException {
|
||||
TextDocument translated = behavior.translate(doc);
|
||||
return new CharStream(new JavaccTokenDocument(translated, behavior));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next character from the input. After a {@link #backup(int)},
|
||||
* some of the already read chars must be spit out again.
|
||||
*
|
||||
* @return The next character
|
||||
*
|
||||
* @throws EOFException Upon EOF
|
||||
*/
|
||||
public char readChar() throws EOFException {
|
||||
if (curOffset == chars.length()) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return chars.charAt(curOffset++);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls {@link #readChar()} and returns its value, marking its position
|
||||
* as the beginning of the next token. All characters must remain in
|
||||
* the buffer between two successive calls to this method to implement
|
||||
* backup correctly.
|
||||
*/
|
||||
public char markTokenStart() throws EOFException {
|
||||
markOffset = curOffset;
|
||||
return readChar();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string made up of characters from the token mark up to
|
||||
* to the current buffer position.
|
||||
*/
|
||||
public String getTokenImage() {
|
||||
return getTokenImageCs().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string made up of characters from the token mark up to
|
||||
* to the current buffer position.
|
||||
*/
|
||||
public Chars getTokenImageCs() {
|
||||
assert markOffset >= 0;
|
||||
return chars.slice(markOffset, markLen());
|
||||
}
|
||||
|
||||
private int markLen() {
|
||||
return curOffset - markOffset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends the suffix of length 'len' of the current token to the given
|
||||
* string builder. This is used to build up the matched string
|
||||
* for use in actions in the case of MORE.
|
||||
*
|
||||
* @param len Length of the returned array
|
||||
*
|
||||
* @throws IndexOutOfBoundsException If len is greater than the length of the current token
|
||||
*/
|
||||
public void appendSuffix(StringBuilder sb, int len) {
|
||||
if (useMarkSuffix) {
|
||||
assert len <= markLen() : "Suffix is greater than the mark length? " + len + " > " + markLen();
|
||||
chars.appendChars(sb, curOffset - len, len);
|
||||
} // otherwise dead code, kept because Javacc's argument expressions do side effects
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pushes a given number of already read chars into the buffer.
|
||||
* Subsequent calls to {@link #readChar()} will read those characters
|
||||
* before proceeding to read the underlying char stream.
|
||||
*
|
||||
* <p>A lexer calls this method if it has already read some characters,
|
||||
* but cannot use them to match a (longer) token. So, they will
|
||||
* be used again as the prefix of the next token.
|
||||
*
|
||||
* @throws AssertionError If the requested amount is greater than the
|
||||
* length of the mark
|
||||
*/
|
||||
public void backup(int amount) {
|
||||
if (amount > markLen()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
curOffset -= amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column number of the last character for the current token.
|
||||
* This is only used for parse exceptions and is very inefficient.
|
||||
*/
|
||||
public int getEndColumn() {
|
||||
return endLocation().getEndColumn();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the line number of the last character for current token.
|
||||
* This is only used for parse exceptions and is very inefficient.
|
||||
*/
|
||||
public int getEndLine() {
|
||||
return endLocation().getEndLine();
|
||||
}
|
||||
|
||||
|
||||
private FileLocation endLocation() {
|
||||
return textDoc.toLocation(TextRegion.caretAt(getEndOffset()));
|
||||
}
|
||||
|
||||
|
||||
/** Returns the start offset of the current token (in the translated source), inclusive. */
|
||||
public int getStartOffset() {
|
||||
return markOffset;
|
||||
}
|
||||
|
||||
|
||||
/** Returns the end offset of the current token (in the translated source), exclusive. */
|
||||
public int getEndOffset() {
|
||||
return curOffset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token document for the tokens being built. Having it
|
||||
* here is the most convenient place for the time being.
|
||||
*/
|
||||
public JavaccTokenDocument getTokenDocument() {
|
||||
return tokenDoc;
|
||||
}
|
||||
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
public final class CharStreamFactory {
|
||||
|
||||
private CharStreamFactory() {
|
||||
// util class
|
||||
}
|
||||
|
||||
/**
|
||||
* A char stream that doesn't perform any escape translation.
|
||||
*/
|
||||
public static CharStream simpleCharStream(TextDocument input) {
|
||||
return simpleCharStream(input, JavaccTokenDocument::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* A char stream that doesn't perform any escape translation.
|
||||
*/
|
||||
public static CharStream simpleCharStream(TextDocument input,
|
||||
Function<? super TextDocument, ? extends JavaccTokenDocument> documentMaker) {
|
||||
JavaccTokenDocument document = documentMaker.apply(input);
|
||||
return new SimpleCharStream(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* A char stream that translates java unicode sequences.
|
||||
*/
|
||||
public static CharStream javaCharStream(TextDocument input) {
|
||||
return javaCharStream(input, JavaccTokenDocument::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* A char stream that translates java unicode sequences.
|
||||
*/
|
||||
public static CharStream javaCharStream(TextDocument input, Function<? super TextDocument, ? extends JavaccTokenDocument> documentMaker) {
|
||||
JavaccTokenDocument document = documentMaker.apply(input);
|
||||
return new JavaCharStream(document);
|
||||
}
|
||||
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import static java.lang.Integer.min;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.AssertionUtil;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.FragmentedDocBuilder;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
/**
|
||||
* An object that can translate an input document into an output document,
|
||||
* typically by replacing escape sequences with the character they represent.
|
||||
*
|
||||
* <p>This is an abstract class because the default implementation does not
|
||||
* perform any escape processing. Subclasses refine this behavior.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
public abstract class EscapeTranslator {
|
||||
// Note that this can easily be turned into a java.io.Reader with
|
||||
// efficient block IO, optimized for the common case where there are
|
||||
// few or no escapes. This is part of the history of this file, but
|
||||
// was removed for simplicity.
|
||||
|
||||
/**
|
||||
* Source characters. When there is an escape, eg \ u00a0, the
|
||||
* first backslash is replaced with the translated value of the
|
||||
* escape. The bufpos is updated so that we read the next char
|
||||
* after the escape.
|
||||
*/
|
||||
protected Chars input;
|
||||
/** Position of the next char to read in the input. */
|
||||
protected int bufpos;
|
||||
/** Keep track of adjustments to make to the offsets, caused by unicode escapes. */
|
||||
final FragmentedDocBuilder builder;
|
||||
|
||||
private Chars curEscape;
|
||||
private int offInEscape;
|
||||
|
||||
/**
|
||||
* Create a translator that will read from the given document.
|
||||
*
|
||||
* @param original Original document
|
||||
*
|
||||
* @throws NullPointerException If the parameter is null
|
||||
*/
|
||||
public EscapeTranslator(TextDocument original) {
|
||||
AssertionUtil.requireParamNotNull("builder", original);
|
||||
this.input = original.getText();
|
||||
this.bufpos = 0;
|
||||
this.builder = new FragmentedDocBuilder(original);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate all the input in the buffer. This consumes this object.
|
||||
*
|
||||
* @return The translated text document. If there is no escape, returns the original text
|
||||
*
|
||||
* @throws IllegalStateException If this method is called more than once on the same object
|
||||
* @throws MalformedSourceException If there are invalid escapes in the source
|
||||
*/
|
||||
public TextDocument translateDocument() throws MalformedSourceException {
|
||||
ensureOpen();
|
||||
try {
|
||||
return translateImpl();
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private TextDocument translateImpl() {
|
||||
if (this.bufpos == input.length()) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
final int len = input.length(); // remove Integer.MAX_VALUE
|
||||
|
||||
int readChars = 0;
|
||||
while (readChars < len && (this.bufpos < input.length() || curEscape != null)) {
|
||||
if (curEscape != null) {
|
||||
int toRead = min(len - readChars, curEscape.length() - offInEscape);
|
||||
|
||||
readChars += toRead;
|
||||
offInEscape += toRead;
|
||||
|
||||
if (curEscape.length() == offInEscape) {
|
||||
curEscape = null;
|
||||
continue;
|
||||
} else {
|
||||
break; // len cut us off, we'll retry next time
|
||||
}
|
||||
}
|
||||
|
||||
int bpos = this.bufpos;
|
||||
int nextJump = gobbleMaxWithoutEscape(min(input.length(), bpos + len - readChars));
|
||||
int newlyReadChars = nextJump - bpos;
|
||||
|
||||
assert newlyReadChars >= 0 && (readChars + newlyReadChars) <= len;
|
||||
|
||||
if (newlyReadChars == 0 && nextJump == input.length()) {
|
||||
// eof
|
||||
break;
|
||||
}
|
||||
readChars += newlyReadChars;
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max offset, EXclusive, up to which we can cut the input
|
||||
* array from the bufpos to dump it into the output array.
|
||||
*
|
||||
* @param maxOff Max offset up to which to read ahead
|
||||
*/
|
||||
protected int gobbleMaxWithoutEscape(int maxOff) throws MalformedSourceException {
|
||||
this.bufpos = maxOff;
|
||||
return maxOff;
|
||||
}
|
||||
|
||||
protected int recordEscape(final int startOffsetInclusive, int endOffsetExclusive, Chars translation) {
|
||||
assert endOffsetExclusive > startOffsetInclusive && startOffsetInclusive >= 0;
|
||||
this.builder.recordDelta(startOffsetInclusive, endOffsetExclusive, translation);
|
||||
this.bufpos = endOffsetExclusive;
|
||||
this.curEscape = translation;
|
||||
this.offInEscape = 0;
|
||||
return startOffsetInclusive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing a translator does not close the underlying document, it just
|
||||
* clears the intermediary state.
|
||||
*/
|
||||
private void close() {
|
||||
this.bufpos = -1;
|
||||
this.input = null;
|
||||
}
|
||||
|
||||
|
||||
/** Check to make sure that the stream has not been closed */
|
||||
protected final void ensureOpen() {
|
||||
if (input == null) {
|
||||
throw new IllegalStateException("Closed");
|
||||
}
|
||||
}
|
||||
|
||||
protected FileLocation locationAt(int indexInInput) {
|
||||
return builder.toLocation(indexInInput);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
|
||||
/**
|
||||
* This stream buffers the whole file in memory before parsing,
|
||||
* and track start/end offsets of tokens. This allows building {@link JavaccToken}.
|
||||
* The buffer is assumed to be composed of only ASCII characters,
|
||||
* and the stream unescapes Unicode escapes. The {@link #getTokenDocument() token document}
|
||||
* stores the original file with escapes and all.
|
||||
*/
|
||||
public class JavaCharStream extends JavaCharStreamBase {
|
||||
|
||||
// full text with nothing escaped and all
|
||||
private final Chars fullText;
|
||||
private final JavaccTokenDocument document;
|
||||
|
||||
private int[] startOffsets;
|
||||
|
||||
public JavaCharStream(JavaccTokenDocument document) {
|
||||
super(document.getTextDocument().newReader());
|
||||
this.fullText = document.getFullText();
|
||||
this.document = document;
|
||||
this.startOffsets = new int[bufsize];
|
||||
maxNextCharInd = fullText.length();
|
||||
|
||||
nextCharBuf = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void ExpandBuff(boolean wrapAround) {
|
||||
int[] newStartOffsets = new int[bufsize + 2048];
|
||||
|
||||
if (wrapAround) {
|
||||
System.arraycopy(startOffsets, tokenBegin, newStartOffsets, 0, bufsize - tokenBegin);
|
||||
System.arraycopy(startOffsets, 0, newStartOffsets, bufsize - tokenBegin, bufpos);
|
||||
startOffsets = newStartOffsets;
|
||||
} else {
|
||||
System.arraycopy(startOffsets, tokenBegin, newStartOffsets, 0, bufsize - tokenBegin);
|
||||
startOffsets = newStartOffsets;
|
||||
}
|
||||
|
||||
super.ExpandBuff(wrapAround);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void UpdateLineColumn(char c) {
|
||||
startOffsets[bufpos] = nextCharInd;
|
||||
super.UpdateLineColumn(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartOffset() {
|
||||
return startOffsets[tokenBegin];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndOffset() {
|
||||
if (isAtEof()) {
|
||||
return fullText.length();
|
||||
} else {
|
||||
return startOffsets[bufpos] + 1; // + 1 for exclusive
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaccTokenDocument getTokenDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String GetImage() {
|
||||
if (bufpos >= tokenBegin) {
|
||||
return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
|
||||
} else {
|
||||
return new String(buffer, tokenBegin, bufsize - tokenBegin)
|
||||
+ new String(buffer, 0, bufpos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected char ReadByte() throws IOException {
|
||||
++nextCharInd;
|
||||
|
||||
if (isAtEof()) {
|
||||
if (bufpos != 0) {
|
||||
--bufpos;
|
||||
if (bufpos < 0) {
|
||||
bufpos += bufsize;
|
||||
}
|
||||
} else {
|
||||
bufline[bufpos] = line;
|
||||
bufcolumn[bufpos] = column;
|
||||
startOffsets[bufpos] = fullText.length();
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return fullText.charAt(nextCharInd);
|
||||
}
|
||||
|
||||
private boolean isAtEof() {
|
||||
return nextCharInd >= fullText.length();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void FillBuff() {
|
||||
throw new IllegalStateException("Buffer shouldn't be refilled");
|
||||
}
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
/**
|
||||
* An implementation of {@link EscapeTranslator} that translates Java
|
||||
* unicode escapes.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
public final class JavaEscapeTranslator extends BackslashEscapeTranslator {
|
||||
|
||||
public JavaEscapeTranslator(TextDocument input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int handleBackslash(final int maxOff, final int firstBackslashOff) throws MalformedSourceException {
|
||||
int off = firstBackslashOff;
|
||||
while (off < input.length() && input.charAt(off) == '\\') {
|
||||
off++;
|
||||
}
|
||||
|
||||
int bslashCount = off - firstBackslashOff;
|
||||
// is there an escape at offset firstBslashOff?
|
||||
if ((bslashCount & 1) == 1 // odd number of backslashes
|
||||
&& off < input.length() && input.charAt(off) == 'u') { // at least one 'u'
|
||||
// this is enough to expect an escape or throw an exception
|
||||
while (off < input.length() && input.charAt(off) == 'u') {
|
||||
// consume all the 'u's
|
||||
off++;
|
||||
}
|
||||
Chars value = escapeValue(firstBackslashOff, off - 1);
|
||||
int endOffset = off + 4; // + 4 hex digits
|
||||
return recordEscape(firstBackslashOff, endOffset, value);
|
||||
} else {
|
||||
return abortEscape(off, maxOff);
|
||||
}
|
||||
}
|
||||
|
||||
private Chars escapeValue(int posOfFirstBackSlash, final int offOfTheU) throws MalformedSourceException {
|
||||
int off = offOfTheU;
|
||||
try {
|
||||
char c = (char)
|
||||
( hexVal(input.charAt(++off)) << 12 // SUPPRESS CHECKSTYLE paren pad
|
||||
| hexVal(input.charAt(++off)) << 8
|
||||
| hexVal(input.charAt(++off)) << 4
|
||||
| hexVal(input.charAt(++off))
|
||||
);
|
||||
|
||||
return Chars.wrap(Character.toString(c));
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
// cut off u and 4 digits
|
||||
String escape = input.substring(offOfTheU, Math.min(input.length(), offOfTheU + 5));
|
||||
throw new MalformedSourceException("Invalid unicode escape \\" + escape, e, locationAt(posOfFirstBackSlash));
|
||||
}
|
||||
}
|
||||
|
||||
private static int hexVal(char c) {
|
||||
switch (c) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return c - '0';
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
return c - ('A' - 10);
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return c - ('a' - 10);
|
||||
default:
|
||||
throw new NumberFormatException("Character '" + c + "' is not a valid hexadecimal digit");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
|
||||
@@ -76,6 +76,19 @@ public class JavaccToken implements GenericToken<JavaccToken> {
|
||||
public JavaccToken specialToken;
|
||||
|
||||
|
||||
// common constructor, with a CharSequence parameter
|
||||
JavaccToken(int kind, CharSequence image, int startInclusive, int endExclusive, JavaccTokenDocument document) {
|
||||
assert document != null : "Null document";
|
||||
assert image instanceof String || image instanceof Chars : "Null image";
|
||||
assert TextRegion.isValidRegion(startInclusive, endExclusive, document.getTextDocument());
|
||||
|
||||
this.kind = kind;
|
||||
this.image = image;
|
||||
this.startOffset = startInclusive;
|
||||
this.endOffset = endExclusive;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new token of the specified kind.
|
||||
*
|
||||
@@ -85,19 +98,15 @@ public class JavaccToken implements GenericToken<JavaccToken> {
|
||||
* @param endExclusive End of the token in the text file (before translating escapes)
|
||||
* @param document Document owning the token
|
||||
*/
|
||||
public JavaccToken(int kind,
|
||||
CharSequence image,
|
||||
int startInclusive,
|
||||
int endExclusive,
|
||||
JavaccTokenDocument document) {
|
||||
assert document != null : "Null document";
|
||||
assert TextRegion.isValidRegion(startInclusive, endExclusive, document.getTextDocument());
|
||||
public JavaccToken(int kind, Chars image, int startInclusive, int endExclusive, JavaccTokenDocument document) {
|
||||
this(kind, (CharSequence) image, startInclusive, endExclusive, document);
|
||||
}
|
||||
|
||||
this.kind = kind;
|
||||
this.image = image;
|
||||
this.startOffset = startInclusive;
|
||||
this.endOffset = endExclusive;
|
||||
this.document = document;
|
||||
/**
|
||||
* Constructor with a {@link String} image (see {@link #JavaccToken(int, Chars, int, int, JavaccTokenDocument) the other ctor}).
|
||||
*/
|
||||
public JavaccToken(int kind, String image, int startInclusive, int endExclusive, JavaccTokenDocument document) {
|
||||
this(kind, (CharSequence) image, startInclusive, endExclusive, document);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,12 +137,18 @@ public class JavaccToken implements GenericToken<JavaccToken> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getImageCs() {
|
||||
return image;
|
||||
public Chars getImageCs() {
|
||||
// wrap it: it's zero cost (images are either Chars or String) and Chars has a nice API
|
||||
return Chars.wrap(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextRegion getRegion() {
|
||||
public String getImage() {
|
||||
return image.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final TextRegion getRegion() {
|
||||
return TextRegion.fromBothOffsets(startOffset, endOffset);
|
||||
}
|
||||
|
||||
@@ -171,24 +186,13 @@ public class JavaccToken implements GenericToken<JavaccToken> {
|
||||
public JavaccToken replaceImage(CharStream charStream) {
|
||||
return new JavaccToken(
|
||||
this.kind,
|
||||
charStream.GetImage(),
|
||||
charStream.getTokenImageCs(),
|
||||
this.startOffset,
|
||||
charStream.getEndOffset(),
|
||||
this.document
|
||||
);
|
||||
}
|
||||
|
||||
public JavaccToken withImage(String image) {
|
||||
return new JavaccToken(
|
||||
this.kind,
|
||||
image,
|
||||
this.startOffset,
|
||||
this.endOffset,
|
||||
this.document
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new token with the given kind, and all other parameters
|
||||
|
||||
+124
-55
@@ -4,23 +4,137 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.cpd.internal.JavaCCTokenizer;
|
||||
import net.sourceforge.pmd.lang.ast.impl.TokenDocument;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
/**
|
||||
* Token document for Javacc implementations. This is a helper object
|
||||
* for generated token managers.
|
||||
* for generated token managers. Note: the extension point is a custom
|
||||
* implementation of {@link TokenDocumentBehavior}, see {@link JjtreeParserAdapter#tokenBehavior()},
|
||||
* {@link JavaCCTokenizer#tokenBehavior()}
|
||||
*/
|
||||
public class JavaccTokenDocument extends TokenDocument<JavaccToken> {
|
||||
public final class JavaccTokenDocument extends TokenDocument<JavaccToken> {
|
||||
|
||||
private final TokenDocumentBehavior behavior;
|
||||
|
||||
private JavaccToken first;
|
||||
|
||||
public JavaccTokenDocument(TextDocument textDocument) {
|
||||
public JavaccTokenDocument(TextDocument textDocument, TokenDocumentBehavior behavior) {
|
||||
super(textDocument);
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable configuration of a token document.
|
||||
*/
|
||||
public static class TokenDocumentBehavior {
|
||||
|
||||
public static final TokenDocumentBehavior DEFAULT = new TokenDocumentBehavior(Collections.emptyList());
|
||||
private final List<String> tokenNames;
|
||||
|
||||
public TokenDocumentBehavior(List<String> tokenNames) {
|
||||
this.tokenNames = tokenNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the lexer should accumulate the image of MORE
|
||||
* tokens into the StringBuilder jjimage. This is useless in our
|
||||
* current implementations, because the image of tokens can be cut
|
||||
* out using text coordinates, so doesn't need to be put into a separate string.
|
||||
* The default returns false, which makes {@link CharStream#appendSuffix(StringBuilder, int)} a noop.
|
||||
*/
|
||||
public boolean useMarkSuffix() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the escapes of the source document. The default implementation
|
||||
* does not perform any escaping.
|
||||
*
|
||||
* @param text Source doc
|
||||
*
|
||||
* @see EscapeTranslator
|
||||
*
|
||||
* TODO move that to LanguageVersionHandler once #3919 (Merge CPD and PMD language) is implemented
|
||||
*/
|
||||
public TextDocument translate(TextDocument text) throws MalformedSourceException {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string that describes the token kind.
|
||||
*
|
||||
* @param kind Kind of token
|
||||
*
|
||||
* @return A descriptive string
|
||||
*/
|
||||
public final @NonNull String describeKind(int kind) {
|
||||
if (kind == JavaccToken.IMPLICIT_TOKEN) {
|
||||
return "<implicit token>";
|
||||
}
|
||||
String impl = describeKindImpl(kind);
|
||||
if (impl != null) {
|
||||
return impl;
|
||||
}
|
||||
return "<token of kind " + kind + ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the given kind. If this returns a non-null value, then
|
||||
* that's what {@link #describeKind(int)} will use. Otherwise a default
|
||||
* implementation is used.
|
||||
*
|
||||
* <p>An implementation typically uses the JavaCC-generated array
|
||||
* named {@code <parser name>Constants.tokenImage}. Remember to
|
||||
* check the bounds of the array.
|
||||
*
|
||||
* @param kind Kind of token
|
||||
*
|
||||
* @return A descriptive string, or null to use default
|
||||
*/
|
||||
protected @Nullable String describeKindImpl(int kind) {
|
||||
if (kind >= 0 && kind < tokenNames.size()) {
|
||||
return tokenNames.get(kind);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new token with the given kind. This is called back to
|
||||
* by JavaCC-generated token managers (jjFillToken). Note that a
|
||||
* created token is not guaranteed to end up in the final token chain.
|
||||
*
|
||||
* @param kind Kind of the token
|
||||
* @param cs Char stream of the file. This can be used to get text
|
||||
* coordinates and the image
|
||||
* @param image Shared instance of the image token. If this is non-null,
|
||||
* then no call to {@link CharStream#getTokenImage()} should be
|
||||
* issued.
|
||||
*
|
||||
* @return A new token
|
||||
*/
|
||||
public JavaccToken createToken(JavaccTokenDocument self, int kind, CharStream cs, @Nullable String image) {
|
||||
return new JavaccToken(
|
||||
kind,
|
||||
image == null ? cs.getTokenImageCs() : image,
|
||||
cs.getStartOffset(),
|
||||
cs.getEndOffset(),
|
||||
self
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
boolean useMarkSuffix() {
|
||||
return behavior.useMarkSuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,62 +166,17 @@ public class JavaccTokenDocument extends TokenDocument<JavaccToken> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that describes the token kind.
|
||||
*
|
||||
* @param kind Kind of token
|
||||
*
|
||||
* @return A descriptive string
|
||||
* @see TokenDocumentBehavior#describeKind(int)
|
||||
*/
|
||||
public final @NonNull String describeKind(int kind) {
|
||||
if (kind == JavaccToken.IMPLICIT_TOKEN) {
|
||||
return "<implicit token>";
|
||||
}
|
||||
String impl = describeKindImpl(kind);
|
||||
if (impl != null) {
|
||||
return impl;
|
||||
}
|
||||
return "<token of kind " + kind + ">";
|
||||
public @NonNull String describeKind(int kind) {
|
||||
return behavior.describeKind(kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the given kind. If this returns a non-null value, then
|
||||
* that's what {@link #describeKind(int)} will use. Otherwise a default
|
||||
* implementation is used.
|
||||
*
|
||||
* <p>An implementation typically uses the JavaCC-generated array
|
||||
* named {@code <parser name>Constants.tokenImage}. Remember to
|
||||
* check the bounds of the array.
|
||||
*
|
||||
* @param kind Kind of token
|
||||
*
|
||||
* @return A descriptive string, or null to use default
|
||||
*/
|
||||
protected @Nullable String describeKindImpl(int kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new token with the given kind. This is called back to
|
||||
* by JavaCC-generated token managers (jjFillToken). Note that a
|
||||
* created token is not guaranteed to end up in the final token chain.
|
||||
*
|
||||
* @param kind Kind of the token
|
||||
* @param cs Char stream of the file. This can be used to get text
|
||||
* coordinates and the image
|
||||
* @param image Shared instance of the image token. If this is non-null,
|
||||
* then no call to {@link CharStream#GetImage()} should be
|
||||
* issued.
|
||||
*
|
||||
* @return A new token
|
||||
* @see TokenDocumentBehavior#createToken(JavaccTokenDocument, int, CharStream, String)
|
||||
*/
|
||||
public JavaccToken createToken(int kind, CharStream cs, @Nullable String image) {
|
||||
return new JavaccToken(
|
||||
kind,
|
||||
image == null ? cs.GetImage() : image,
|
||||
cs.getStartOffset(),
|
||||
cs.getEndOffset(),
|
||||
this
|
||||
);
|
||||
return behavior.createToken(this, kind, cs, image);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.TextAvailableNode;
|
||||
import net.sourceforge.pmd.lang.ast.impl.GenericNode;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.reporting.Reportable;
|
||||
|
||||
/**
|
||||
@@ -19,12 +17,6 @@ import net.sourceforge.pmd.reporting.Reportable;
|
||||
*/
|
||||
public interface JjtreeNode<N extends JjtreeNode<N>> extends GenericNode<N>, TextAvailableNode, Reportable {
|
||||
|
||||
@Override
|
||||
Chars getText();
|
||||
|
||||
@Override
|
||||
TextRegion getTextRegion();
|
||||
|
||||
|
||||
// todo token accessors should most likely be protected in PMD 7.
|
||||
|
||||
|
||||
+10
-13
@@ -4,12 +4,10 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.CharStream;
|
||||
import net.sourceforge.pmd.lang.ast.FileAnalysisException;
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.lang.ast.RootNode;
|
||||
import net.sourceforge.pmd.lang.ast.TokenMgrError;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
|
||||
/**
|
||||
* Base implementation of the {@link Parser} interface for JavaCC language
|
||||
@@ -24,20 +22,19 @@ public abstract class JjtreeParserAdapter<R extends RootNode> implements Parser
|
||||
// inheritance only
|
||||
}
|
||||
|
||||
protected abstract JavaccTokenDocument newDocumentImpl(TextDocument textDocument);
|
||||
|
||||
protected CharStream newCharStream(JavaccTokenDocument tokenDocument) {
|
||||
return new SimpleCharStream(tokenDocument);
|
||||
}
|
||||
protected abstract JavaccTokenDocument.TokenDocumentBehavior tokenBehavior();
|
||||
|
||||
@Override
|
||||
public R parse(ParserTask task) throws ParseException {
|
||||
JavaccTokenDocument doc = newDocumentImpl(task.getTextDocument());
|
||||
CharStream charStream = newCharStream(doc);
|
||||
|
||||
public final R parse(ParserTask task) throws ParseException {
|
||||
try {
|
||||
// First read the source file and interpret escapes
|
||||
CharStream charStream = CharStream.create(task.getTextDocument(), tokenBehavior());
|
||||
// We replace the text document, so that it reflects escapes properly
|
||||
// Escapes are processed by CharStream#create
|
||||
task = task.withTextDocument(charStream.getTokenDocument().getTextDocument());
|
||||
// Finally, do the parsing
|
||||
return parseImpl(charStream, task);
|
||||
} catch (TokenMgrError tme) {
|
||||
} catch (FileAnalysisException tme) {
|
||||
throw tme.setFileName(task.getFileDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.FileAnalysisException;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
|
||||
/**
|
||||
* A {@link FileAnalysisException} thrown when the source format is invalid,
|
||||
* for example if some unicode escapes cannot be translated.
|
||||
*/
|
||||
public class MalformedSourceException extends FileAnalysisException {
|
||||
|
||||
private final FileLocation location;
|
||||
|
||||
public MalformedSourceException(String message, Throwable cause, FileLocation fileLocation) {
|
||||
super(message, cause);
|
||||
this.location = Objects.requireNonNull(fileLocation);
|
||||
setFileName(fileLocation.getFileName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String positionToString() {
|
||||
return super.positionToString() + " at " + location.startPosToString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String errorKind() {
|
||||
return "Source format error";
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.ast.impl.javacc;
|
||||
|
||||
/**
|
||||
* A char stream that does not perform unicode escaping.
|
||||
*/
|
||||
public class SimpleCharStream extends JavaCharStream {
|
||||
|
||||
public SimpleCharStream(JavaccTokenDocument document) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doEscape() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.document;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Base class for documents that apply a transform to their output offsets.
|
||||
* This includes translated documents, and slices (subdocument views).
|
||||
*/
|
||||
abstract class BaseMappedDocument implements TextDocument {
|
||||
|
||||
protected final TextDocument base;
|
||||
|
||||
BaseMappedDocument(TextDocument base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCheckSum() {
|
||||
return base.getCheckSum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathId() {
|
||||
return base.getPathId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return base.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chars sliceOriginalText(TextRegion region) {
|
||||
return base.sliceOriginalText(inputRegion(region));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileLocation toLocation(TextRegion region) {
|
||||
return base.toLocation(inputRegion(region));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextRegion createLineRange(int startLineInclusive, int endLineInclusive) {
|
||||
// see the doc, lines do not need to be translated
|
||||
return base.createLineRange(startLineInclusive, endLineInclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextPos2d lineColumnAtOffset(int offset, boolean inclusive) {
|
||||
return base.lineColumnAtOffset(inputOffset(offset, inclusive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a region given in the coordinate system of this
|
||||
* document, to the coordinate system of the base document.
|
||||
* This works as if creating a new region with both start and end
|
||||
* offsets translated through {@link #inputOffset(int, boolean)}. The
|
||||
* returned region may have a different length.
|
||||
*
|
||||
* @param outputRegion Output region
|
||||
*
|
||||
* @return Input region
|
||||
*/
|
||||
protected @NonNull TextRegion inputRegion(TextRegion outputRegion) {
|
||||
return TextRegion.fromBothOffsets(inputOffset(outputRegion.getStartOffset(), true),
|
||||
inputOffset(outputRegion.getEndOffset(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input offset for the given output offset. This maps
|
||||
* back an offset in the coordinate system of this document, to the
|
||||
* coordinate system of the base document. This includes the
|
||||
* length of any unicode escapes.
|
||||
*
|
||||
* <pre>
|
||||
* input: "a\u00a0b" (original document)
|
||||
* translated: "a b" (this document)
|
||||
*
|
||||
* translateOffset(0) = 0
|
||||
* translateOffset(1) = 1
|
||||
* translateOffset(2) = 7 // includes the length of the escape
|
||||
* </pre>
|
||||
*
|
||||
* @param outOffset Output offset
|
||||
* @param inclusive Whether the offset is to be interpreted as the index of a character (true),
|
||||
* or the position after a character (false)
|
||||
*
|
||||
* @return Input offset
|
||||
*/
|
||||
protected final int inputOffset(int outOffset, boolean inclusive) {
|
||||
if (outOffset < 0 || outOffset > getLength()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return localOffsetTransform(outOffset, inclusive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output offset to input offset.
|
||||
*/
|
||||
protected abstract int localOffsetTransform(int outOffset, boolean inclusive);
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
base.close();
|
||||
}
|
||||
}
|
||||
@@ -581,6 +581,16 @@ public final class Chars implements CharSequence {
|
||||
return StreamSupport.stream(lines().spliterator(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new stringbuilder containing the whole contents of this
|
||||
* char sequence.
|
||||
*/
|
||||
public StringBuilder toStringBuilder() {
|
||||
StringBuilder sb = new StringBuilder(length());
|
||||
appendChars(sb);
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new reader for the whole contents of this char sequence.
|
||||
|
||||
@@ -138,7 +138,7 @@ public final class FileLocation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new location from the given parameters.
|
||||
* Creates a new location for a range of text.
|
||||
*
|
||||
* @throws IllegalArgumentException If the file name is null
|
||||
* @throws IllegalArgumentException If any of the line/col parameters are strictly less than 1
|
||||
@@ -155,6 +155,21 @@ public final class FileLocation {
|
||||
end.getColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new location that starts and ends at the same position.
|
||||
*
|
||||
* @param fileName File name
|
||||
* @param line Line number
|
||||
* @param column Column number
|
||||
*
|
||||
* @return A new location
|
||||
*
|
||||
* @throws IllegalArgumentException See {@link #range(String, int, int, int, int)}
|
||||
*/
|
||||
public static FileLocation caret(String fileName, int line, int column) {
|
||||
return new FileLocation(fileName, line, column, line, column);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
Loaded 30 of 92 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user