| /*=============================================================================# |
| # 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); |
| } |
| |
| } |