blob: 7f875fd8b977f966db72a022d1e8b40d5a042b87 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.r.core.source;
import static org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNode.END_UNCLOSED;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.text.CharacterScannerReader;
import org.eclipse.statet.ecommons.text.core.rules.BufferedDocumentScanner;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNode;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeScan;
import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeScanner;
/**
* This scanner recognizes the comments, platform specif., verbatim-like section
* (and other/usual Rd code).
*/
public class RPartitionNodeScanner implements TreePartitionNodeScanner {
public static final @Nullable TreePartitionNode findRRootNode(@Nullable TreePartitionNode node) {
TreePartitionNode rNode;
while (true) {
if (node == null) {
return null;
}
if (node.getType() instanceof RPartitionNodeType) {
rNode= node;
break;
}
node= node.getParent();
}
TreePartitionNode parentNode;
while ((parentNode= rNode.getParent()) != null
&& parentNode.getType() instanceof RPartitionNodeType) {
rNode= parentNode;
}
return rNode;
}
/**
* Enum of states of the scanner.
* Note: id is index in array of tokens
* 0-7 are reserved.
**/
protected static final int S_DEFAULT= 0;
protected static final int S_QUOTED_SYMBOL= 1;
protected static final int S_INFIX_OPERATOR= 2;
protected static final int S_STRING_S= 3;
protected static final int S_STRING_D= 4;
protected static final int S_COMMENT= 5;
protected static final int S_ROXYGEN= 6;
/** Enum of last significant characters read. */
protected static final byte LAST_OTHER= 0;
protected static final byte LAST_EOF= 1;
protected static final byte LAST_NEWLINE= 2;
private final boolean isRoxygenEnabled;
protected final CharacterScannerReader reader= new CharacterScannerReader(
new BufferedDocumentScanner(1024) );
private TreePartitionNodeScan scan;
private TreePartitionNode rootNode;
/** The current node */
private TreePartitionNode node;
/** The current node type */
private RPartitionNodeType type;
/** The last significant characters read. */
protected byte last;
public RPartitionNodeScanner() {
this.isRoxygenEnabled= true;
}
@Override
public int getRestartOffset(final TreePartitionNode node, final IDocument document,
final int offset) throws BadLocationException {
return offset;
}
@Override
public RPartitionNodeType getDefaultRootType() {
return RPartitionNodeType.DEFAULT_ROOT;
}
@Override
public void execute(final TreePartitionNodeScan scan) {
this.scan= scan;
this.rootNode= null;
this.node= null;
setRange(scan.getStartOffset(), scan.getEndOffset());
init();
assert (this.rootNode != null && this.node != null);
process();
}
protected final TreePartitionNodeScan getScan() {
return this.scan;
}
protected void setRange(final int startOffset, final int endOffset) {
this.reader.setRange(getScan().getDocument(), startOffset, endOffset - startOffset);
updateLast();
}
protected void init() {
final TreePartitionNode beginNode= getScan().getBeginNode();
if (beginNode.getType() instanceof RPartitionNodeType) {
initNode(beginNode, (RPartitionNodeType) beginNode.getType());
}
else {
this.node= beginNode;
addNode(getDefaultRootType(), getScan().getStartOffset());
this.rootNode= this.node;
}
}
protected final TreePartitionNode getRootNode() {
return this.rootNode;
}
private void updateLast() {
if (this.reader.getOffset() > 0) {
this.last= LAST_OTHER;
try {
final char c= getScan().getDocument().getChar(this.reader.getOffset() - 1);
switch (c) {
case '\r':
case '\n':
this.last= LAST_NEWLINE;
break;
default:
break;
}
}
catch (final BadLocationException e) {}
}
else {
this.last= LAST_NEWLINE;
}
}
protected final void initNode(final TreePartitionNode node, final RPartitionNodeType type) {
if (this.node != null) {
throw new IllegalStateException();
}
this.node= node;
this.type= type;
this.rootNode= findRRootNode(node);
}
protected final void addNode(final RPartitionNodeType type, final int offset) {
this.node= this.scan.add(type, this.node, offset, 0);
this.type= type;
}
protected final TreePartitionNode getNode() {
return this.node;
}
protected final void exitNode(final int offset, final int flags) {
this.scan.expand(this.node, offset, flags, true);
this.node= this.node.getParent();
this.type= (RPartitionNodeType) this.node.getType();
}
protected final void exitNode() {
this.node= this.node.getParent();
this.type= (RPartitionNodeType) this.node.getType();
}
protected final void exitNodesTo(final TreePartitionNode stopNode,
final int offset, final int flags) {
while (this.node != stopNode) {
exitNode(offset, flags);
}
}
private void process() {
while (true) {
switch (this.last) {
case LAST_EOF:
handleEOF(this.type);
return;
case LAST_NEWLINE:
handleNewLine(this.type);
break;
default:
break;
}
switch (this.type.getScannerState()) {
case S_DEFAULT:
processDefault();
continue;
case S_QUOTED_SYMBOL:
processQuotedSymbol();
continue;
case S_INFIX_OPERATOR:
processInfixOperator();
continue;
case S_STRING_S:
processStringS();
continue;
case S_STRING_D:
processStringD();
continue;
case S_COMMENT:
case S_ROXYGEN:
processComment();
continue;
default:
processExt(this.type);
continue;
}
}
}
protected void processDefault() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
this.last= LAST_EOF;
return;
case '\r':
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
this.last= LAST_NEWLINE;
return;
case '"':
addNode(RPartitionNodeType.STRING_D, this.reader.getOffset() - 1);
this.last= LAST_OTHER;
return;
case '\'':
addNode(RPartitionNodeType.STRING_S, this.reader.getOffset() - 1);
this.last= LAST_OTHER;
return;
case '`':
addNode(RPartitionNodeType.QUOTED_SYMBOL, this.reader.getOffset() - 1);
this.last= LAST_OTHER;
return;
case '#':
if (this.isRoxygenEnabled && this.reader.read('\'')) {
addNode(RPartitionNodeType.ROXYGEN, this.reader.getOffset() - 2);
this.last= LAST_OTHER;
return;
}
else {
addNode(RPartitionNodeType.COMMENT, this.reader.getOffset() - 1);
this.last= LAST_OTHER;
return;
}
case '%':
addNode(RPartitionNodeType.INFIX_OPERATOR, this.reader.getOffset() - 1);
this.last= LAST_OTHER;
return;
default:
continue LOOP;
}
}
}
protected void processInfixOperator() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
exitNode(this.reader.getOffset(), END_UNCLOSED);
this.last= LAST_EOF;
return;
case '\r':
exitNode(this.reader.getOffset() - 1, END_UNCLOSED);
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
exitNode(this.reader.getOffset() - 1, END_UNCLOSED);
this.last= LAST_NEWLINE;
return;
case '%':
exitNode(this.reader.getOffset(), 0);
this.last= LAST_OTHER;
return;
default:
continue LOOP;
}
}
}
private void processBackslash() {
this.last= LAST_OTHER;
switch (this.reader.read()) {
case ICharacterScanner.EOF:
return;
case '\r':
case '\n':
this.reader.unread();
return;
default:
return;
}
}
protected void processQuotedSymbol() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
exitNode(this.reader.getOffset(), END_UNCLOSED);
this.last= LAST_EOF;
return;
case '\r':
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
this.last= LAST_NEWLINE;
return;
case '\\':
processBackslash();
continue;
case '`':
exitNode(this.reader.getOffset(), 0);
this.last= LAST_OTHER;
return;
default:
continue LOOP;
}
}
}
protected void processStringD() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
exitNode(this.reader.getOffset(), END_UNCLOSED);
this.last= LAST_EOF;
return;
case '\r':
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
this.last= LAST_NEWLINE;
return;
case '\\':
processBackslash();
continue;
case '\"':
exitNode(this.reader.getOffset(), 0);
this.last= LAST_OTHER;
return;
default:
continue LOOP;
}
}
}
protected void processStringS() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
exitNode(this.reader.getOffset(), END_UNCLOSED);
this.last= LAST_EOF;
return;
case '\r':
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
this.last= LAST_NEWLINE;
return;
case '\\':
processBackslash();
continue;
case '\'':
exitNode(this.reader.getOffset(), 0);
this.last= LAST_OTHER;
return;
default:
continue LOOP;
}
}
}
protected void processComment() {
LOOP: while (true) {
switch (this.reader.read()) {
case ICharacterScanner.EOF:
exitNode(this.reader.getOffset(), 0);
this.last= LAST_EOF;
return;
case '\r':
exitNode(this.reader.getOffset() - 1, 0);
this.reader.read('\n');
this.last= LAST_NEWLINE;
return;
case '\n':
exitNode(this.reader.getOffset() - 1, 0);
this.last= LAST_NEWLINE;
return;
default:
continue LOOP;
}
}
}
protected void processExt(final RPartitionNodeType type) {
throw new IllegalStateException("state= " + type.getScannerState());
}
protected void handleNewLine(final RPartitionNodeType type) {
}
protected void handleEOF(final RPartitionNodeType type) {
final TreePartitionNode rootNode= getRootNode();
final int offset= this.reader.getOffset();
exitNodesTo(rootNode, offset, END_UNCLOSED);
this.scan.expand(rootNode, offset, END_UNCLOSED, true);
}
}