/******************************************************************************* * Copyright (c) 2002, 2016 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * QNX Software Systems - Initial API and implementation *******************************************************************************/ package org.eclipse.cdt.make.internal.ui.text.makefile; import org.eclipse.jface.text.rules.ICharacterScanner; import org.eclipse.jface.text.rules.IPredicateRule; import org.eclipse.jface.text.rules.IToken; class MacroDefinitionRule implements IPredicateRule { private static final int INIT_STATE = 0; private static final int VAR_STATE = 1; private static final int END_VAR_STATE = 2; private static final int EQUAL_STATE = 3; private static final int FINISH_STATE = 4; private static final int ERROR_STATE = 5; private IToken token; private StringBuilder buffer = new StringBuilder(); protected IToken defaultToken; public MacroDefinitionRule(IToken token, IToken defaultToken) { this.token = token; this.defaultToken = defaultToken; } @Override public IToken getSuccessToken() { return token; } @Override public IToken evaluate(ICharacterScanner scanner, boolean resume) { buffer.setLength(0); int state = INIT_STATE; if (resume) scanToBeginOfLine(scanner); for (int c = scanner.read(); c != ICharacterScanner.EOF; c = scanner.read()) { switch (state) { case INIT_STATE : if (c != '\n' && Character.isWhitespace((char) c)) { break; } if (isValidCharacter(c)) { state = VAR_STATE; } else { state = ERROR_STATE; } break; case VAR_STATE : if (isValidCharacter(c)) { break; } //$FALL-THROUGH$ case END_VAR_STATE : if (c != '\n' && Character.isWhitespace((char) c)) { state = END_VAR_STATE; } else if (c == ':' || c == '+' || c == '?') { state = EQUAL_STATE; } else if (c == '=') { state = FINISH_STATE; } else { state = ERROR_STATE; } break; case EQUAL_STATE : if (c == '=') { state = FINISH_STATE; } else { state = ERROR_STATE; } break; case FINISH_STATE : break; default : break; } if (state >= FINISH_STATE) { break; } buffer.append((char) c); } scanner.unread(); if (state == FINISH_STATE) { //scanToEndOfLine(scanner); return token; } if (defaultToken.isUndefined()) unreadBuffer(scanner); return defaultToken; } @Override public IToken evaluate(ICharacterScanner scanner) { return evaluate(scanner, false); } /** * Returns the characters in the buffer to the scanner. * * @param scanner the scanner to be used */ protected void unreadBuffer(ICharacterScanner scanner) { for (int i = buffer.length() - 1; i >= 0; i--) { scanner.unread(); } } @SuppressWarnings("unused") private void scanToEndOfLine(ICharacterScanner scanner) { int c; char[][] delimiters = scanner.getLegalLineDelimiters(); while ((c = scanner.read()) != ICharacterScanner.EOF) { // Check for end of line since it can be used to terminate the pattern. for (int i = 0; i < delimiters.length; i++) { if (c == delimiters[i][0] && sequenceDetected(scanner, delimiters[i])) { return; } } } } private void scanToBeginOfLine(ICharacterScanner scanner) { while(scanner.getColumn() != 0) { scanner.unread(); } } private boolean sequenceDetected(ICharacterScanner scanner, char[] sequence) { for (int i = 1; i < sequence.length; i++) { int c = scanner.read(); if (c == ICharacterScanner.EOF) { return true; } else if (c != sequence[i]) { // Non-matching character detected, rewind the scanner back to the start. for (; i > 0; i--) { scanner.unread(); } return false; } } return true; } protected boolean isValidCharacter(int c) { // From GNUMakefile manual: // A variable name may be any sequence of characters not containing �:�, �#�, �=�, or leading or trailing whitespace. // However, variable names containing characters other than letters, numbers, and underscores should be avoided, // as they may be given special meanings in the future, and with some shells they cannot be passed through the environment to a sub-make return !Character.isWhitespace(c) && c != ':' && c != '#' && c != '='; } }