blob: 13a21285a9934cf6278b7c8fb7b7a7c1e5b7d35c [file] [log] [blame]
/*******************************************************************************
* 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 != '=';
}
}