| /*=============================================================================# |
| # 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.ui.text.r; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.rules.ICharacterScanner; |
| import org.eclipse.jface.text.rules.IPartitionTokenScanner; |
| import org.eclipse.jface.text.rules.IToken; |
| import org.eclipse.jface.text.rules.Token; |
| |
| import org.eclipse.statet.ecommons.text.IPartitionScannerConfigExt; |
| import org.eclipse.statet.ecommons.text.core.rules.BufferedDocumentScanner; |
| |
| import org.eclipse.statet.internal.r.ui.RUIPlugin; |
| import org.eclipse.statet.r.core.source.IRDocumentConstants; |
| |
| |
| /** |
| * This scanner recognizes the comments, platform specif., verbatim-like section |
| * (and other/usual Rd code). |
| */ |
| public class RFastPartitionScanner implements IPartitionTokenScanner, IPartitionScannerConfigExt { |
| |
| |
| /** |
| * 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 = 3; |
| protected static final int S_COMMENT = 4; |
| protected static final int S_ROXYGEN = 5; |
| |
| protected final static IToken T_DEFAULT = new Token(null); |
| protected final static IToken T_QUOTED_SYMBOL = new Token(IRDocumentConstants.R_QUOTED_SYMBOL_CONTENT_TYPE); |
| protected final static IToken T_INFIX = new Token(IRDocumentConstants.R_INFIX_OPERATOR_CONTENT_TYPE); |
| protected final static IToken T_STRING = new Token(IRDocumentConstants.R_STRING_CONTENT_TYPE); |
| protected final static IToken T_COMMENT = new Token(IRDocumentConstants.R_COMMENT_CONTENT_TYPE); |
| protected final static IToken T_ROXYGEN = new Token(IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE); |
| |
| |
| /** Enum of last significant characters read. */ |
| protected static final int LAST_OTHER = 0; |
| protected static final int LAST_BACKSLASH = 1; |
| protected static final int LAST_NEWLINE = 2; |
| |
| |
| /** The scanner. */ |
| private final BufferedDocumentScanner fScanner = new BufferedDocumentScanner(1000); // faster implementation |
| |
| private IDocument fDocument; |
| |
| private final boolean fIsRoxygenEnabled; |
| |
| private IToken fToken; |
| /** The offset of the last returned token. */ |
| private int fTokenOffset; |
| /** The length of the last returned token. */ |
| private int fTokenLength; |
| |
| private int fStartPartitionState = S_DEFAULT; |
| /** The current state of the scanner. */ |
| private int fState; |
| /** The last significant characters read. */ |
| protected int fLast; |
| /** The amount of characters already read on first call to nextToken(). */ |
| private int fPrefixLength; |
| |
| private char fEndChar; |
| |
| private final IToken[] fTokens; |
| |
| |
| public RFastPartitionScanner() { |
| final Map<Integer, IToken> list= new HashMap<>(); |
| initTokens(list); |
| final int count = maxState(list.keySet())+1; |
| fTokens = new IToken[count]; |
| for (int i = 0; i < count; i++) { |
| fTokens[i] = list.get(i); |
| } |
| fIsRoxygenEnabled = true; |
| } |
| |
| private int maxState(final Set<Integer> states) { |
| int max = 0; |
| final Iterator<Integer> iter = states.iterator(); |
| while (iter.hasNext()) { |
| final int state = iter.next().intValue(); |
| if (state > max) { |
| max = state; |
| } |
| } |
| return max; |
| } |
| |
| protected void initTokens(final Map<Integer, IToken> states) { |
| states.put(S_DEFAULT, T_DEFAULT); |
| states.put(S_QUOTED_SYMBOL, T_QUOTED_SYMBOL); |
| states.put(S_INFIX_OPERATOR, T_INFIX); |
| states.put(S_STRING, T_STRING); |
| states.put(S_COMMENT, T_COMMENT); |
| states.put(S_ROXYGEN, T_ROXYGEN); |
| } |
| |
| /** |
| * Sets explicitly the partition type on position 0. |
| * |
| * @param contentType |
| */ |
| @Override |
| public void setStartPartitionType(final String contentType) { |
| fStartPartitionState = getState(contentType); |
| } |
| |
| @Override |
| public void setRange(final IDocument document, final int offset, final int length) { |
| setPartialRange(document, offset, length, null, -1); |
| } |
| |
| @Override |
| public void setPartialRange(final IDocument document, final int offset, final int length, final String contentType, int partitionOffset) { |
| if (partitionOffset < 0) { |
| partitionOffset = offset; |
| } |
| fDocument = document; |
| fScanner.setRange(document, offset, length); |
| fTokenOffset = partitionOffset; |
| fTokenLength = 0; |
| fPrefixLength = offset - partitionOffset; |
| fLast = LAST_OTHER; |
| if (offset > 0) { |
| try { |
| final char c = document.getChar(offset-1); |
| switch (c) { |
| case '\r': |
| case '\n': |
| fLast = LAST_NEWLINE; |
| break; |
| } |
| } |
| catch (final BadLocationException e) { |
| RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "Error occured when detect last char.", e); //$NON-NLS-1$ |
| } |
| fState = (fPrefixLength == 0) ? S_DEFAULT : getState(contentType); |
| } |
| else { |
| fLast = LAST_NEWLINE; |
| fState = fStartPartitionState; |
| } |
| if (fPrefixLength > 0) { |
| switch (fState) { |
| case S_QUOTED_SYMBOL: |
| fEndChar = '`'; |
| break; |
| case S_STRING: |
| try { |
| fEndChar = document.getChar(fTokenOffset); |
| } |
| catch (final BadLocationException e) { |
| RUIPlugin.logError(RUIPlugin.INTERNAL_ERROR, "Error occured when detecting start char.", e); //$NON-NLS-1$ |
| } |
| break; |
| } |
| } |
| } |
| |
| |
| @Override |
| public IToken nextToken() { |
| fToken = null; |
| fTokenOffset += fTokenLength; |
| fTokenLength = fPrefixLength; |
| |
| CHECK_NEXT: while (fToken == null) { |
| final int c = fScanner.read(); |
| |
| // characters |
| if (c == ICharacterScanner.EOF) { |
| fPrefixLength = 0; |
| if (fTokenLength > 0) { |
| handleEOF(fState); |
| fToken = fTokens[fState]; |
| break CHECK_NEXT; |
| } |
| fToken = Token.EOF; |
| break CHECK_NEXT; |
| } |
| |
| fTokenLength++; |
| handleChar(fState, c); |
| continue CHECK_NEXT; |
| } |
| return fToken; |
| } |
| |
| protected void handleChar(final int state, final int c) { |
| switch (state) { |
| case S_DEFAULT: |
| fLast = LAST_OTHER; |
| switch (c) { |
| case '\r': |
| case '\n': |
| fLast = LAST_NEWLINE; |
| return; |
| case '"': |
| fEndChar = '"'; |
| newState(S_STRING, 1); |
| return; |
| case '\'': |
| fEndChar = '\''; |
| newState(S_STRING, 1); |
| return; |
| case '`': |
| fEndChar = '`'; |
| newState(S_QUOTED_SYMBOL, 1); |
| return; |
| case '#': |
| if (fIsRoxygenEnabled && readChar('\'')) { |
| newState(S_ROXYGEN, 2); |
| return; |
| } |
| else { |
| newState(S_COMMENT, 1); |
| return; |
| } |
| case '%': |
| newState(S_INFIX_OPERATOR, 1); |
| return; |
| default: // Standard |
| return; |
| } |
| |
| case S_INFIX_OPERATOR: |
| if (c == '%') { |
| newState(S_DEFAULT, 0); |
| return; |
| } |
| if (c == '\r' || c == '\n') { |
| fLast = LAST_NEWLINE; |
| newState(S_DEFAULT, 1); |
| return; |
| } |
| return; |
| |
| case S_QUOTED_SYMBOL: |
| case S_STRING: |
| // Escaped? |
| if (fLast == LAST_BACKSLASH) { |
| fLast = LAST_OTHER; |
| return; |
| } |
| // Backslash? |
| if (c == '\\') { |
| fLast = LAST_BACKSLASH; |
| return; |
| } |
| if (c == '\r' || c == '\n') { |
| fLast = LAST_NEWLINE; |
| return; |
| } |
| fLast = LAST_OTHER; |
| // String Ende? |
| if (c == fEndChar) { |
| newState(S_DEFAULT, 0); |
| return; |
| } |
| // Standard |
| return; |
| |
| case S_COMMENT: |
| case S_ROXYGEN: |
| if (c == '\r' || c == '\n') { |
| fLast = LAST_NEWLINE; |
| newState(S_DEFAULT, 1); |
| return; |
| } |
| return; |
| |
| default: |
| handleExtState(state, c); |
| return; |
| } |
| } |
| |
| protected void handleEOF(final int state) { |
| } |
| |
| protected void handleExtState(final int state, final int c) { |
| if (c == '\r' || c == '\n') { |
| fLast = LAST_NEWLINE; |
| return; |
| } |
| } |
| |
| protected final void newState(final int newState, final int prefixLength) { |
| if (fTokenLength-prefixLength > 0) { |
| fToken = fTokens[fState]; |
| fState = newState; |
| fTokenLength -= prefixLength; |
| fPrefixLength = prefixLength; |
| return; |
| } |
| // assert (fTokenLength == 0); |
| fState = newState; |
| fTokenLength = prefixLength; |
| fPrefixLength = 0; |
| } |
| |
| protected final int getState(final String contentType) { |
| if (contentType == null) { |
| return S_DEFAULT; |
| } |
| if (contentType == IRDocumentConstants.R_DEFAULT_CONTENT_TYPE) { |
| return S_DEFAULT; |
| } |
| if (contentType == IRDocumentConstants.R_STRING_CONTENT_TYPE) { |
| return S_STRING; |
| } |
| if (contentType == IRDocumentConstants.R_COMMENT_CONTENT_TYPE) { |
| return S_COMMENT; |
| } |
| if (contentType == IRDocumentConstants.R_ROXYGEN_CONTENT_TYPE) { |
| return S_ROXYGEN; |
| } |
| if (contentType == IRDocumentConstants.R_INFIX_OPERATOR_CONTENT_TYPE) { |
| return S_INFIX_OPERATOR; |
| } |
| if (contentType == IRDocumentConstants.R_QUOTED_SYMBOL_CONTENT_TYPE) { |
| return S_QUOTED_SYMBOL; |
| } |
| return getExtState(contentType); |
| } |
| |
| protected int getExtState(final String contentType) { |
| return S_DEFAULT; |
| } |
| |
| |
| @Override |
| public int getTokenLength() { |
| return fTokenLength; |
| } |
| |
| @Override |
| public int getTokenOffset() { |
| return fTokenOffset; |
| } |
| |
| |
| protected final boolean readChar(final char c1) { |
| final int c = fScanner.read(); |
| if (c == c1) { |
| fTokenLength ++; |
| return true; |
| } |
| if (c >= 0) { |
| fScanner.unread(); |
| } |
| return false; |
| } |
| |
| protected final IDocument getDocument() { |
| return fDocument; |
| } |
| |
| } |