package org.eclipse.dltk.ui.text.heredoc; import org.eclipse.jface.text.rules.ICharacterScanner; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.Token; /** * Abstract implementation of a rule that can be used to detect heredoc * partitions. * * <p> * A note on tokens returned from this rule... * </p> * * Heredoc partitions are unique because they have an identifier/terminator and * span multiple lines but may have characters that follow the heredoc * identifier but are not considered to be part of the identifier itself, ie: * </p> * * <pre> * <<EOF . "hello world"; * heredoc body * EOF * </pre> * * <p> * requiring the terminator to be preserved to allow the partitioner to know * when the partition has ended. To achieve this, the identifier/terminator is * appended to the partition types returned by this rule so future rule * evaluations against existing partitions can terminate correctly. * </p> * * <p> * Therefore, the success tokens returned from this rule will never be comprised * of just the partition type, which is why the <code>HereDocEnabled*</code> * classes must be used in conjunction with this rule. * </p> * * @see HereDocEnabledPartitioner * @see HereDocEnabledPartitionScanner * @see HereDocEnabledPresentationReconciler */ public abstract class HereDocPartitionRule { private String fPartition; private String fStart; /** * Creates a new heredoc partition rule. * * @param start * heredoc start sequence * @param token * success token */ public HereDocPartitionRule(String start, IToken token) { this.fStart = start; // we only care about the content type... this.fPartition = (String) token.getData(); } /** * Evalute the rule for a possible heredoc partition. * * @param scanner * heredoc character scanner * * @return heredoc token */ public IToken evaluate(HereDocEnabledPartitionScanner scanner) { StringBuffer buffer = new StringBuffer(); int c; while ((c = scanner.read()) != ICharacterScanner.EOF) { buffer.append((char) c); if (buffer.length() == fStart.length()) { break; } } if (buffer.toString().equals((fStart))) { return detectIdentifier(scanner); } unwind(scanner, buffer.length()); return Token.UNDEFINED; } /** * Evaluate the rule when a known heredoc partition has been seen. * * @param scanner * heredoc character scanner * * @param partition * known heredoc partition containing terminator * * @return heredoc token */ public IToken evaluate(HereDocEnabledPartitionScanner scanner, String partition) { boolean readChar = false; StringBuffer buffer = new StringBuffer(); String terminator = HereDocUtils.getTerminator(partition); int c; while ((c = scanner.read()) != ICharacterScanner.EOF) { readChar = true; if (!isNewline(scanner, c)) { buffer.append((char) c); continue; } if (buffer.toString().equals(terminator)) { break; } buffer.setLength(0); } // we only saw 'EOF' if (!readChar) { scanner.unread(); return Token.UNDEFINED; } return createTerminator(terminator); } /** * Create a token representing the heredoc identifier partition * * @param identifier * heredoc identifier * * @return token */ protected final IToken createIdentifier(String identifier) { return new Token(HereDocUtils.createIdentifier(fPartition, identifier)); } /** * Create a token representing the heredoc terminator partition * * @param terminator * heredoc terminator * * @return token */ protected final Token createTerminator(String terminator) { return new Token(HereDocUtils.createTerminator(fPartition, terminator)); } /** * Extract the heredoc identifier. * * <p> * The input string will not contain whatever start sequence was specified * in the constructor. If the passed string does not contain a valid heredoc * identifier, <code>null</code> may be returned. * </p> * * @param str * string containing potential heredoc identifier * * @return heredoc identifier or <code>null</code> if no valid identifer * exists */ protected abstract String extractIdentifier(String str); /** * Is the specified character a newline character. */ protected final boolean isNewline(ICharacterScanner scanner, int c) { char[][] lineDelimiters = scanner.getLegalLineDelimiters(); for (int i = 0; i < lineDelimiters.length; i++) { if (c == lineDelimiters[i][0]) { return true; } } return false; } /** * Parse the heredoc identifier to extract the heredoc terminator. * * <p> * Some dynamic languages allow the heredoc identifier to be quoted and/or * escaped, however those characters are not considered to be part of the * terminator and need to be removed from the string. * </p> * * @param identifier * heredoc identifier * * @return heredoc terminator */ protected abstract String parseIdentifier(String identifier); /** * Unwind the scanner a specified length. */ protected final void unwind(ICharacterScanner scanner, int length) { for (; length > 0; length--) { scanner.unread(); } } private IToken detectIdentifier(ICharacterScanner scanner) { int c; StringBuffer buffer = new StringBuffer(); /* * rather then attempt to detect the heredoc identifier char by char, we * buffer everything in up until the newline character and then hand-off * to the sub-class to first extract the identifier so the scanner can * be unwound for the chars not consumed and then we reparse the * identifier to extract the heredoc terminator, as the identifier may * be quoted, etc if the language supports it. */ while ((c = scanner.read()) != ICharacterScanner.EOF) { if (isNewline(scanner, c)) { break; } buffer.append((char) c); } // unread the '\n' or EOF scanner.unread(); String identifier = extractIdentifier(buffer.toString()); if (identifier == null) { unwind(scanner, buffer.length()); return Token.UNDEFINED; } unwind(scanner, buffer.length() - identifier.length()); identifier = parseIdentifier(identifier); return createIdentifier(identifier); } }