X-Git-Url: http://secure.phpeclipse.com diff --git a/net.sourceforge.phpeclipse.ui/src/net/sourceforge/phpdt/internal/ui/text/JavaHeuristicScanner.java b/net.sourceforge.phpeclipse.ui/src/net/sourceforge/phpdt/internal/ui/text/JavaHeuristicScanner.java new file mode 100644 index 0000000..0ca33ab --- /dev/null +++ b/net.sourceforge.phpeclipse.ui/src/net/sourceforge/phpdt/internal/ui/text/JavaHeuristicScanner.java @@ -0,0 +1,1030 @@ +/******************************************************************************* + * Copyright (c) 2000, 2004 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package net.sourceforge.phpdt.internal.ui.text; + +import java.util.Arrays; + +import net.sourceforge.phpdt.internal.compiler.parser.Scanner; +import net.sourceforge.phpeclipse.phpeditor.php.PHPDocumentPartitioner; + +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; + +/** + * Utility methods for heuristic based Java manipulations in an incomplete Java + * source file. + * + *

+ * An instance holds some internal position in the document and is therefore not + * threadsafe. + *

+ * + * @since 3.0 + */ +public class JavaHeuristicScanner implements Symbols { + /** + * Returned by all methods when the requested position could not be found, + * or if a {@link BadLocationException} was thrown while scanning. + */ + public static final int NOT_FOUND = -1; + + /** + * Special bound parameter that means either -1 (backward scanning) or + * fDocument.getLength() (forward scanning). + */ + public static final int UNBOUND = -2; + + /* character constants */ + private static final char LBRACE = '{'; + + private static final char RBRACE = '}'; + + private static final char LPAREN = '('; + + private static final char RPAREN = ')'; + + private static final char SEMICOLON = ';'; + + private static final char COLON = ':'; + + private static final char COMMA = ','; + + private static final char LBRACKET = '['; + + private static final char RBRACKET = ']'; + + private static final char QUESTIONMARK = '?'; + + private static final char EQUAL = '='; + + /** + * Specifies the stop condition, upon which the scanXXX + * methods will decide whether to keep scanning or not. This interface may + * implemented by clients. + */ + public interface StopCondition { + /** + * Instructs the scanner to return the current position. + * + * @param ch + * the char at the current position + * @param position + * the current position + * @param forward + * the iteration direction + * @return true if the stop condition is met. + */ + boolean stop(char ch, int position, boolean forward); + } + + /** + * Stops upon a non-whitespace (as defined by + * {@link Character#isWhitespace(char)}) character. + */ + private static class NonWhitespace implements StopCondition { + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char) + */ + public boolean stop(char ch, int position, boolean forward) { + return !Character.isWhitespace(ch); + } + } + + /** + * Stops upon a non-whitespace character in the default partition. + * + * @see NonWhitespace + */ + private class NonWhitespaceDefaultPartition extends NonWhitespace { + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char) + */ + public boolean stop(char ch, int position, boolean forward) { + return super.stop(ch, position, true) + && isDefaultPartition(position); + } + } + + /** + * Stops upon a non-java identifier (as defined by + * {@link Scanner#isPHPIdentifierPart(char)}) character. + */ + private static class NonJavaIdentifierPart implements StopCondition { + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char) + */ + public boolean stop(char ch, int position, boolean forward) { + return !Scanner.isPHPIdentifierPart(ch); + } + } + + /** + * Stops upon a non-java identifier character in the default partition. + * + * @see NonJavaIdentifierPart + */ + private class NonJavaIdentifierPartDefaultPartition extends + NonJavaIdentifierPart { + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char) + */ + public boolean stop(char ch, int position, boolean forward) { + return super.stop(ch, position, true) + || !isDefaultPartition(position); + } + } + + /** + * Stops upon a character in the default partition that matches the given + * character list. + */ + private class CharacterMatch implements StopCondition { + private final char[] fChars; + + /** + * Creates a new instance. + * + * @param ch + * the single character to match + */ + public CharacterMatch(char ch) { + this(new char[] { ch }); + } + + /** + * Creates a new instance. + * + * @param chars + * the chars to match. + */ + public CharacterMatch(char[] chars) { + Assert.isNotNull(chars); + Assert.isTrue(chars.length > 0); + fChars = chars; + Arrays.sort(chars); + } + + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char, + * int) + */ + public boolean stop(char ch, int position, boolean forward) { + return Arrays.binarySearch(fChars, ch) >= 0 + && isDefaultPartition(position); + } + } + + /** + * Acts like character match, but skips all scopes introduced by + * parenthesis, brackets, and braces. + */ + protected class SkippingScopeMatch extends CharacterMatch { + private char fOpening, fClosing; + + private int fDepth = 0; + + /** + * Creates a new instance. + * + * @param ch + * the single character to match + */ + public SkippingScopeMatch(char ch) { + super(ch); + } + + /** + * Creates a new instance. + * + * @param chars + * the chars to match. + */ + public SkippingScopeMatch(char[] chars) { + super(chars); + } + + /* + * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char, + * int) + */ + public boolean stop(char ch, int position, boolean forward) { + + if (fDepth == 0 && super.stop(ch, position, true)) + return true; + else if (ch == fOpening) + fDepth++; + else if (ch == fClosing) { + fDepth--; + if (fDepth == 0) { + fOpening = 0; + fClosing = 0; + } + } else if (fDepth == 0) { + fDepth = 1; + if (forward) { + + switch (ch) { + case LBRACE: + fOpening = LBRACE; + fClosing = RBRACE; + break; + case LPAREN: + fOpening = LPAREN; + fClosing = RPAREN; + break; + case LBRACKET: + fOpening = LBRACKET; + fClosing = RBRACKET; + break; + } + + } else { + switch (ch) { + case RBRACE: + fOpening = RBRACE; + fClosing = LBRACE; + break; + case RPAREN: + fOpening = RPAREN; + fClosing = LPAREN; + break; + case RBRACKET: + fOpening = RBRACKET; + fClosing = LBRACKET; + break; + } + + } + } + + return false; + + } + + } + + /** The document being scanned. */ + private IDocument fDocument; + + /** The partitioning being used for scanning. */ + private String fPartitioning; + + /** The partition to scan in. */ + private String fPartition; + + /* internal scan state */ + + /** the most recently read character. */ + private char fChar; + + /** the most recently read position. */ + private int fPos; + + /* preset stop conditions */ + private final StopCondition fNonWSDefaultPart = new NonWhitespaceDefaultPartition(); + + private final static StopCondition fNonWS = new NonWhitespace(); + + private final StopCondition fNonIdent = new NonJavaIdentifierPartDefaultPartition(); + + /** + * Creates a new instance. + * + * @param document + * the document to scan + * @param partitioning + * the partitioning to use for scanning + * @param partition + * the partition to scan in + */ + public JavaHeuristicScanner(IDocument document, String partitioning, + String partition) { + Assert.isNotNull(document); + Assert.isNotNull(partitioning); + Assert.isNotNull(partition); + fDocument = document; + fPartitioning = partitioning; + fPartition = partition; + } + + /** + * Calls + * this(document, IJavaPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE). + * + * @param document + * the document to scan. + */ + public JavaHeuristicScanner(IDocument document) { +// this(document, IPHPPartitions.PHP_PARTITIONING, +// IDocument.DEFAULT_CONTENT_TYPE); + this(document, IPHPPartitions.PHP_PARTITIONING, + PHPDocumentPartitioner.PHP_SCRIPT_CODE); + } + + /** + * Returns the most recent internal scan position. + * + * @return the most recent internal scan position. + */ + public int getPosition() { + return fPos; + } + + /** + * Returns the next token in forward direction, starting at + * start, and not extending further than bound. + * The return value is one of the constants defined in {@link Symbols}. + * After a call, {@link #getPosition()} will return the position just after + * the scanned token (i.e. the next position that will be scanned). + * + * @param start + * the first character position in the document to consider + * @param bound + * the first position not to consider any more + * @return a constant from {@link Symbols} describing the next token + */ + public int nextToken(int start, int bound) { + int pos = scanForward(start, bound, fNonWSDefaultPart); + if (pos == NOT_FOUND) + return TokenEOF; + + fPos++; + + switch (fChar) { + case LBRACE: + return TokenLBRACE; + case RBRACE: + return TokenRBRACE; + case LBRACKET: + return TokenLBRACKET; + case RBRACKET: + return TokenRBRACKET; + case LPAREN: + return TokenLPAREN; + case RPAREN: + return TokenRPAREN; + case SEMICOLON: + return TokenSEMICOLON; + case COMMA: + return TokenCOMMA; + case QUESTIONMARK: + return TokenQUESTIONMARK; + case EQUAL: + try { + if (fDocument.getChar(fPos) == '>') { + fPos++; + return TokenOTHER; + } + } catch (BadLocationException e) { + } + return TokenEQUAL; + case '<': + try { + if (fDocument.get(fPos, 4).equalsIgnoreCase("?php")) { + fPos += 4; + return TokenEOF; + } else if (fDocument.getChar(fPos) == '?') { + fPos++; + return TokenEOF; + } + } catch (BadLocationException e) { + } + } + + // else + if (Scanner.isPHPIdentifierPart(fChar)) { + // assume an ident or keyword + int from = pos, to; + pos = scanForward(pos + 1, bound, fNonIdent); + if (pos == NOT_FOUND) + to = bound == UNBOUND ? fDocument.getLength() : bound; + else + to = pos; + + String identOrKeyword; + try { + identOrKeyword = fDocument.get(from, to - from); + } catch (BadLocationException e) { + return TokenEOF; + } + + return getToken(identOrKeyword); + + } else { + // operators, number literals etc + return TokenOTHER; + } + } + + /** + * Returns the next token in backward direction, starting at + * start, and not extending further than bound. + * The return value is one of the constants defined in {@link Symbols}. + * After a call, {@link #getPosition()} will return the position just before + * the scanned token starts (i.e. the next position that will be scanned). + * + * @param start + * the first character position in the document to consider + * @param bound + * the first position not to consider any more + * @return a constant from {@link Symbols} describing the previous token + */ + public int previousToken(int start, int bound) { + int pos = scanBackward(start, bound, fNonWSDefaultPart); + if (pos == NOT_FOUND) + return TokenEOF; + + fPos--; + + switch (fChar) { + case LBRACE: + return TokenLBRACE; + case RBRACE: + return TokenRBRACE; + case LBRACKET: + return TokenLBRACKET; + case RBRACKET: + return TokenRBRACKET; + case LPAREN: + return TokenLPAREN; + case RPAREN: + return TokenRPAREN; + case SEMICOLON: + return TokenSEMICOLON; + case COLON: + return TokenCOLON; + case COMMA: + return TokenCOMMA; + case QUESTIONMARK: + return TokenQUESTIONMARK; + case EQUAL: + return TokenEQUAL; + case '>': + try { + switch (fDocument.getChar(fPos)) { + case '=': + fPos--; + return TokenOTHER; + case '?': + fPos--; + return TokenEOF; + } + } catch (BadLocationException e) { + } + } + + // else + if (Scanner.isPHPIdentifierPart(fChar)) { + // assume an ident or keyword + int from, to = pos + 1; + pos = scanBackward(pos - 1, bound, fNonIdent); + if (pos == NOT_FOUND) + from = bound == UNBOUND ? 0 : bound + 1; + else + from = pos + 1; + + String identOrKeyword; + try { + identOrKeyword = fDocument.get(from, to - from); + } catch (BadLocationException e) { + return TokenEOF; + } + + return getToken(identOrKeyword); + + } else { + // operators, number literals etc + return TokenOTHER; + } + + } + + /** + * Returns one of the keyword constants or TokenIDENT for a + * scanned identifier. + * + * @param s + * a scanned identifier + * @return one of the constants defined in {@link Symbols} + */ + private int getToken(String s) { + Assert.isNotNull(s); + + switch (s.length()) { + case 2: + if ("if".equals(s)) //$NON-NLS-1$ + return TokenIF; + if ("do".equals(s)) //$NON-NLS-1$ + return TokenDO; + break; + case 3: + if ("for".equals(s)) //$NON-NLS-1$ + return TokenFOR; + if ("try".equals(s)) //$NON-NLS-1$ + return TokenTRY; + if ("new".equals(s)) //$NON-NLS-1$ + return TokenNEW; + break; + case 4: + if ("case".equals(s)) //$NON-NLS-1$ + return TokenCASE; + if ("else".equals(s)) //$NON-NLS-1$ + return TokenELSE; + if ("goto".equals(s)) //$NON-NLS-1$ + return TokenGOTO; + break; + case 5: + if ("break".equals(s)) //$NON-NLS-1$ + return TokenBREAK; + if ("catch".equals(s)) //$NON-NLS-1$ + return TokenCATCH; + if ("while".equals(s)) //$NON-NLS-1$ + return TokenWHILE; + break; + case 6: + if ("return".equals(s)) //$NON-NLS-1$ + return TokenRETURN; + if ("static".equals(s)) //$NON-NLS-1$ + return TokenSTATIC; + if ("switch".equals(s)) //$NON-NLS-1$ + return TokenSWITCH; + break; + case 7: + if ("default".equals(s)) //$NON-NLS-1$ + return TokenDEFAULT; + if ("finally".equals(s)) //$NON-NLS-1$ + return TokenFINALLY; + break; + case 12: + if ("synchronized".equals(s)) //$NON-NLS-1$ + return TokenSYNCHRONIZED; + break; + } + return TokenIDENT; + } + + /** + * Returns the position of the closing peer character (forward search). Any + * scopes introduced by opening peers are skipped. All peers accounted for + * must reside in the default partition. + * + *

+ * Note that start must not point to the opening peer, but to + * the first character being searched. + *

+ * + * @param start + * the start position + * @param openingPeer + * the opening peer character (e.g. '{') + * @param closingPeer + * the closing peer character (e.g. '}') + * @return the matching peer character position, or NOT_FOUND + */ + public int findClosingPeer(int start, final char openingPeer, + final char closingPeer) { + Assert.isNotNull(fDocument); + Assert.isTrue(start >= 0); + + try { + int depth = 1; + start -= 1; + while (true) { + start = scanForward(start + 1, UNBOUND, new CharacterMatch( + new char[] { openingPeer, closingPeer })); + if (start == NOT_FOUND) + return NOT_FOUND; + + if (fDocument.getChar(start) == openingPeer) + depth++; + else + depth--; + + if (depth == 0) + return start; + } + + } catch (BadLocationException e) { + return NOT_FOUND; + } + } + + /** + * Returns the position of the opening peer character (backward search). Any + * scopes introduced by closing peers are skipped. All peers accounted for + * must reside in the default partition. + * + *

+ * Note that start must not point to the closing peer, but to + * the first character being searched. + *

+ * + * @param start + * the start position + * @param openingPeer + * the opening peer character (e.g. '{') + * @param closingPeer + * the closing peer character (e.g. '}') + * @return the matching peer character position, or NOT_FOUND + */ + public int findOpeningPeer(int start, char openingPeer, char closingPeer) { + Assert.isTrue(start < fDocument.getLength()); + + try { + int depth = 1; + start += 1; + while (true) { + start = scanBackward(start - 1, UNBOUND, new CharacterMatch( + new char[] { openingPeer, closingPeer })); + if (start == NOT_FOUND) + return NOT_FOUND; + + if (fDocument.getChar(start) == closingPeer) + depth++; + else + depth--; + + if (depth == 0) + return start; + } + + } catch (BadLocationException e) { + return NOT_FOUND; + } + } + + /** + * Computes the surrounding block around offset. The search + * is started at the beginning of offset, i.e. an opening + * brace at offset will not be part of the surrounding block, + * but a closing brace will. + * + * @param offset + * the offset for which the surrounding block is computed + * @return a region describing the surrounding block, or null + * if none can be found + */ + public IRegion findSurroundingBlock(int offset) { + if (offset < 1 || offset >= fDocument.getLength()) + return null; + + int begin = findOpeningPeer(offset - 1, LBRACE, RBRACE); + int end = findClosingPeer(offset, LBRACE, RBRACE); + if (begin == NOT_FOUND || end == NOT_FOUND) + return null; + return new Region(begin, end + 1 - begin); + } + + /** + * Finds the smallest position in fDocument such that the + * position is >= position and < bound + * and Character.isWhitespace(fDocument.getChar(pos)) + * evaluates to false and the position is in the default + * partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound > position, + * or UNBOUND + * @return the smallest position of a non-whitespace character in [position, + * bound) that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int findNonWhitespaceForward(int position, int bound) { + return scanForward(position, bound, fNonWSDefaultPart); + } + + /** + * Finds the smallest position in fDocument such that the + * position is >= position and < bound + * and Character.isWhitespace(fDocument.getChar(pos)) + * evaluates to false. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound > position, + * or UNBOUND + * @return the smallest position of a non-whitespace character in [position, + * bound), or NOT_FOUND if none can + * be found + */ + public int findNonWhitespaceForwardInAnyPartition(int position, int bound) { + return scanForward(position, bound, fNonWS); + } + + /** + * Finds the highest position in fDocument such that the + * position is <= position and > bound + * and Character.isWhitespace(fDocument.getChar(pos)) + * evaluates to false and the position is in the default + * partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound < position, + * or UNBOUND + * @return the highest position of a non-whitespace character in (bound, + * position] that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int findNonWhitespaceBackward(int position, int bound) { + return scanBackward(position, bound, fNonWSDefaultPart); + } + + /** + * Finds the lowest position p in fDocument + * such that start <= p < bound and + * condition.stop(fDocument.getChar(p), p) evaluates to + * true. + * + * @param start + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound > start, + * or UNBOUND + * @param condition + * the StopCondition to check + * @return the lowest position in [start, + * bound) for which condition holds, + * or NOT_FOUND if none can be found + */ + public int scanForward(int start, int bound, StopCondition condition) { + Assert.isTrue(start >= 0); + + if (bound == UNBOUND) + bound = fDocument.getLength(); + + Assert.isTrue(bound <= fDocument.getLength()); + + try { + fPos = start; + while (fPos < bound) { + + fChar = fDocument.getChar(fPos); + // omit closing tag + if (fChar == '?') { + if (fPos < fDocument.getLength() - 1) { + if (fDocument.get(fPos - 1, 2).equalsIgnoreCase("?>")) { + fPos++; + return NOT_FOUND; + } + } + } + if (condition.stop(fChar, fPos, true)) + return fPos; + + fPos++; + } + } catch (BadLocationException e) { + } + return NOT_FOUND; + } + + /** + * Finds the lowest position in fDocument such that the + * position is >= position and < bound + * and fDocument.getChar(position) == ch evaluates to + * true and the position is in the default partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound > position, + * or UNBOUND + * @param ch + * the char to search for + * @return the lowest position of ch in (bound, + * position] that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int scanForward(int position, int bound, char ch) { + return scanForward(position, bound, new CharacterMatch(ch)); + } + + /** + * Finds the lowest position in fDocument such that the + * position is >= position and < bound + * and fDocument.getChar(position) == ch evaluates to + * true for at least one ch in chars and the + * position is in the default partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound > position, + * or UNBOUND + * @param chars + * an array of char to search for + * @return the lowest position of a non-whitespace character in [position, + * bound) that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int scanForward(int position, int bound, char[] chars) { + return scanForward(position, bound, new CharacterMatch(chars)); + } + + /** + * Finds the highest position p in fDocument + * such that bound < p <= + * start and + * condition.stop(fDocument.getChar(p), p) evaluates to + * true. + * + * @param start + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound < start, + * or UNBOUND + * @param condition + * the StopCondition to check + * @return the highest position in (bound, + * start for which condition holds, or + * NOT_FOUND if none can be found + */ + public int scanBackward(int start, int bound, StopCondition condition) { + if (bound == UNBOUND) + bound = -1; + + Assert.isTrue(bound >= -1); + Assert.isTrue(start < fDocument.getLength()); + + try { + fPos = start; + while (fPos > bound) { + + fChar = fDocument.getChar(fPos); + // omit opening tag + if (fChar == 'p' || fChar == 'P') { + if (fPos >= 4) { + if (fDocument.get(fPos - 4, 5).equalsIgnoreCase("= 1) { + if (fDocument.get(fPos - 1, 2).equalsIgnoreCase("fDocument such that the + * position is <= position and > bound + * and fDocument.getChar(position) == ch evaluates to + * true for at least one ch in chars and the + * position is in the default partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound < position, + * or UNBOUND + * @param ch + * the char to search for + * @return the highest position of one element in chars in (bound, + * position] that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int scanBackward(int position, int bound, char ch) { + return scanBackward(position, bound, new CharacterMatch(ch)); + } + + /** + * Finds the highest position in fDocument such that the + * position is <= position and > bound + * and fDocument.getChar(position) == ch evaluates to + * true for at least one ch in chars and the + * position is in the default partition. + * + * @param position + * the first character position in fDocument to be + * considered + * @param bound + * the first position in fDocument to not consider + * any more, with bound < position, + * or UNBOUND + * @param chars + * an array of char to search for + * @return the highest position of one element in chars in (bound, + * position] that resides in a Java partition, or + * NOT_FOUND if none can be found + */ + public int scanBackward(int position, int bound, char[] chars) { + return scanBackward(position, bound, new CharacterMatch(chars)); + } + + /** + * Checks whether position resides in a default (Java) + * partition of fDocument. + * + * @param position + * the position to be checked + * @return true if position is in the default + * partition of fDocument, false + * otherwise + */ + public boolean isDefaultPartition(int position) { + Assert.isTrue(position >= 0); + Assert.isTrue(position <= fDocument.getLength()); + + try { + ITypedRegion region = TextUtilities.getPartition(fDocument, + fPartitioning, position, false); + return region.getType().equals(fPartition); + + } catch (BadLocationException e) { + } + + return false; + } + + /** + * Checks if the line seems to be an open condition not followed by a block + * (i.e. an if, while, or for statement with just one following statement, + * see example below). + * + *
+	 * if (condition)
+	 * 	doStuff();
+	 * 
+ * + *

+ * Algorithm: if the last non-WS, non-Comment code on the line is an if + * (condition), while (condition), for( expression), do, else, and there is + * no statement after that + *

+ * + * @param position + * the insert position of the new character + * @param bound + * the lowest position to consider + * @return true if the code is a conditional statement or + * loop without a block, false otherwise + */ + public boolean isBracelessBlockStart(int position, int bound) { + if (position < 1) + return false; + + switch (previousToken(position, bound)) { + case TokenDO: + case TokenELSE: + return true; + case TokenRPAREN: + position = findOpeningPeer(fPos, LPAREN, RPAREN); + if (position > 0) { + switch (previousToken(position - 1, bound)) { + case TokenIF: + case TokenFOR: + case TokenWHILE: + return true; + } + } + } + + return false; + } +}