com.oracle.truffle.api.source.SourceSection Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.source;
import java.util.Arrays;
import com.oracle.truffle.api.nodes.Node;
/**
* Description of contiguous section of text within a {@link Source} of program code; supports
* multiple modes of access to the text and its location. A special
* {@link #createUnavailable(java.lang.String, java.lang.String) null value} should be used for code
* that is not available from source, e.g language builtins.
*
* Equality of instances is defined in terms of equivalent locations: the same start and length in
* equal source code instances.
*
*
* @see Source#createSection(String, int, int, int, int)
* @see Source#createSection(String, int, int, int)
* @see Source#createSection(String, int, int)
* @see Source#createSection(String, int)
* @see #createUnavailable
* @since 0.8 or earlier
*/
public final class SourceSection {
static final String[] EMTPY_TAGS = new String[0];
private final Source source;
private final String identifier;
private final int startLine;
private final int startColumn;
private final int charIndex;
private final int charLength;
private final String kind;
private final String[] tags;
/**
* Creates a new object representing a contiguous text section within the source code of a guest
* language program's text.
*
* The starting location of the section is specified using two different coordinate:
*
* - (row, column): rows and columns are 1-based, so the first character in a source
* file is at position {@code (1,1)}.
* - character index: 0-based offset of the character from the beginning of the source,
* so the first character in a file is at index {@code 0}.
*
* The newline that terminates each line counts as a single character for the purpose of
* a character index. The (row,column) coordinates of a newline character should never appear in
* a text section.
*
* Equality of instances is defined in terms of equivalent locations: the same start and length
* in equal source code instances.
*
* @param source object representing the complete source program that contains this section
* @param identifier an identifier used when printing the section
* @param startLine the 1-based number of the start line of the section
* @param startColumn the 1-based number of the start column of the section
* @param charIndex the 0-based index of the first character of the section
* @param charLength the length of the section in number of characters
* @param tags the assigned tags for the source section
*/
SourceSection(String kind, Source source, String identifier, int startLine, int startColumn, int charIndex, int charLength, String[] tags) {
this.kind = kind;
this.source = source;
this.identifier = identifier;
this.startLine = startLine;
this.startColumn = startColumn;
this.charIndex = charIndex;
this.charLength = charLength;
this.tags = tags;
assert tagsAreNonNullAndInterned(tags) : "All tags set for a source section must be interned and non-null.";
}
@SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ")
private static boolean tagsAreNonNullAndInterned(String[] tags) {
for (int i = 0; i < tags.length; i++) {
if (tags[i] == null) {
return false;
}
if (tags[i].intern() != tags[i]) {
return false;
}
}
return true;
}
/**
* @deprecated tags are now determined by {@link Node#isTaggedWith(Class)}
* @since 0.12
*/
@Deprecated
@SuppressFBWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
public boolean hasTag(String tag) {
assert tag.intern() == tag;
for (int i = 0; i < tags.length; i++) {
if (tags[i] == tag) {
return true;
}
}
return false;
}
/**
* Representation of the source program that contains this section.
*
* @return the source object
* @since 0.8 or earlier
*/
public Source getSource() {
return source;
}
/**
* Returns 1-based line number of the first character in this section (inclusive).
*
* @return the starting line number
* @since 0.8 or earlier
*/
public int getStartLine() {
return startLine;
}
/**
* Gets a representation of the first line of the section, suitable for a hash key.
*
* @return first line of the section
* @since 0.8 or earlier
*/
public LineLocation getLineLocation() {
return source.createLineLocation(startLine);
}
/**
* Returns the 1-based column number of the first character in this section (inclusive).
*
* @return the starting column number
* @since 0.8 or earlier
*/
public int getStartColumn() {
return startColumn;
}
/**
* Returns 1-based line number of the last character in this section (inclusive).
*
* @return the starting line number
* @since 0.8 or earlier
*/
public int getEndLine() {
return source.getLineNumber(charIndex + charLength - 1);
}
/**
* Returns the 1-based column number of the last character in this section (inclusive).
*
* @return the starting column number
* @since 0.8 or earlier
*/
public int getEndColumn() {
return source.getColumnNumber(charIndex + charLength - 1);
}
/**
* Returns the 0-based index of the first character in this section.
*
* @return the starting character index
* @since 0.8 or earlier
*/
public int getCharIndex() {
return charIndex;
}
/**
* Returns the length of this section in characters.
*
* @return the number of characters in the section
* @since 0.8 or earlier
*/
public int getCharLength() {
return charLength;
}
/**
* Returns the index of the text position immediately following the last character in the
* section.
*
* @return the end position of the section
* @since 0.8 or earlier
*/
public int getCharEndIndex() {
return charIndex + charLength;
}
/**
* Returns terse text describing this source section, typically used for printing the section.
*
* @return the identifier of the section
* @since 0.8 or earlier
*/
public String getIdentifier() {
return identifier;
}
/**
* Returns the source code fragment described by this section.
*
* @return the code as a string, or {@code ""} if the SourceSection was created
* using {@link #createUnavailable}.
* @since 0.8 or earlier
*/
public String getCode() {
return source == null ? "" : source.getCode(charIndex, charLength);
}
/**
* Returns a short description of the source section, using just the file name, rather than its
* full path.
*
* @return a short description of the source section formatted as {@code :}.
* @since 0.8 or earlier
*/
public String getShortDescription() {
if (source == null) {
return kind + ": " + identifier;
}
return String.format("%s:%d", source.getShortName(), startLine);
}
/**
* Returns an implementation-defined string representation of this source section to be used for
* debugging purposes only.
*
* @see #getCode()
* @see #getShortDescription()
* @since 0.8 or earlier
*/
@Override
public String toString() {
if (source == null) {
return kind + ": " + identifier;
} else {
return "source=" + source.getShortName() + " pos=" + charIndex + " len=" + charLength + " line=" + startLine + " col=" + startColumn +
(identifier != null ? " identifier=" + identifier : "") + " code=" + getCode().replaceAll("\\n", "\\\\n");
}
}
/**
* @deprecated tags are now determined by {@link Node#isTaggedWith(Class)}
* @since 0.12
*/
@Deprecated
public SourceSection withTags(@SuppressWarnings("hiding") String... tags) {
if (sameTags(tags)) {
// optimize copying of tags if tags are unchanged
return this;
}
return new SourceSection(kind, source, identifier, startLine, startColumn, charIndex, charLength, tags);
}
@SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ")
private boolean sameTags(String... t) {
if (t.length == tags.length) {
for (int i = 0; i < tags.length; i++) {
if (t[i] != tags[i]) {
return false;
}
}
return true;
}
return false;
}
/** @since 0.8 or earlier */
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + charIndex;
result = prime * result + charLength;
result = prime * result + ((identifier == null) ? 0 : identifier.hashCode());
result = prime * result + ((source == null) ? 0 : source.hashCode());
result = prime * result + startColumn;
result = prime * result + startLine;
result = prime * result + Arrays.hashCode(tags);
return result;
}
/** @since 0.8 or earlier */
@Override
@SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ")
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof SourceSection)) {
return false;
}
SourceSection other = (SourceSection) obj;
if (charIndex != other.charIndex) {
return false;
}
if (charLength != other.charLength) {
return false;
}
if (identifier == null) {
if (other.identifier != null) {
return false;
}
} else if (!identifier.equals(other.identifier)) {
return false;
}
if (source == null) {
if (other.source != null) {
return false;
}
} else if (!source.equals(other.source)) {
return false;
}
if (startColumn != other.startColumn) {
return false;
}
if (startLine != other.startLine) {
return false;
}
String[] otherTags = other.tags;
if (tags.length != otherTags.length) {
return false;
}
for (int i = 0; i < tags.length; i++) {
if (tags[i] != otherTags[i]) {
return false;
}
}
return true;
}
/**
* Placeholder for source that is unavailable, e.g. for language builtins. The
* SourceSection
created by this method returns null
when queried for
* a {@link #getSource()} - regular source sections created via one of
* {@link Source#createSection(java.lang.String, int) Source.createSection} methods have a non-
* null
source.
*
* @param kind the general category, e.g. "JS builtin"
* @param name specific name for this section
* @return source section which is mostly empty
* @since 0.8 or earlier
*/
public static SourceSection createUnavailable(String kind, String name) {
return new SourceSection(kind, null, name == null ? "" : name, -1, -1, -1, -1, EMTPY_TAGS);
}
}