blob: 9f9ec1547f34185518ea246498c359c4f0c6a9ba [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2014 Xored Software Inc 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:
* Xored Software Inc - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.rcptt.core.ecl.scanner;
import org.eclipse.rcptt.core.ecl.scanner.EclToken.Type;
public class EclScanner {
private final String source;
private int p = 0;
private final char EOF = '\uFFFF';
private boolean allowEmptyVar = false;
private boolean allowEmptyOption = false;
public EclScanner(String source) {
this(source, false, false);
}
public EclScanner(String source, boolean allowEmptyVar, boolean allowEmptyOption) {
this.source = source;
this.allowEmptyVar = allowEmptyVar;
this.allowEmptyOption = allowEmptyOption;
}
private char c0;
private StringBuilder valueBuilder = new StringBuilder();
private EclToken.Type type;
private Object value;
private String message;
public EclToken next() {
type = Type.Invalid;
value = null;
message = null;
valueBuilder.setLength(0);
int start = p;
peek();
switch (c0) {
case EOF:
eof();
break;
case ' ':
case '\t':
spacing();
break;
case '\n':
case '\r':
linebreak();
break;
case '/':
comment();
break;
case '|':
single(Type.Pipe);
break;
case ';':
single(Type.Semicolon);
break;
case '+':
single(Type.Plus);
break;
case '[':
single(Type.SquareOpen);
break;
case ']':
single(Type.SquareClose);
break;
case '{':
single(Type.CurlyOpen);
break;
case '}':
single(Type.CurlyClose);
break;
case '"':
// case '\'':
string();
break;
case '-':
option();
break;
case '$':
variable();
break;
default:
if (EclCharClasses.isDigit(c0))
number();
else if (EclCharClasses.isIdentifierStart(c0))
identifier();
else
eat();
}
EclToken token = new EclToken();
token.type = message == null ? type : Type.Invalid;
token.begin = start;
token.end = p;
token.text = type != Type.Eof ? source.substring(start, p) : null;
token.value = value;
token.message = message;
return token;
}
// --
private void eof() {
type = Type.Eof;
eat();
}
private void single(Type type) {
this.type = type;
eat();
}
private void spacing() {
type = Type.Spacing;
do {
eat();
} while (c0 == ' ' || c0 == '\t');
}
private void linebreak() {
type = Type.Linebreak;
if (c0 == '\n') {
eat();
if (c0 == '\r')
eat();
} else if (c0 == '\r') {
eat();
if (c0 == '\n')
eat();
}
}
private void comment() {
eat();
if (c0 == '/')
slComment();
else if (c0 == '*')
mlComment();
}
private void slComment() {
type = Type.SlComment;
eat();
while (c0 != '\n' && c0 != '\r' && c0 != EOF)
valueBuilder.append(eat());
value = valueBuilder.toString();
}
private void mlComment() {
type = Type.MlComment;
eat();
boolean skipNextAsterisk = true;
while (true) {
if (c0 == EOF) {
message = "Unterminated multiline comment.";
return;
} else if (c0 == '*') {
eat();
if (c0 == '/') {
eat();
break;
}
else {
if (skipNextAsterisk) {
skipNextAsterisk = false;
} else {
valueBuilder.append('*');
}
}
} else {
if (c0 == '\n' || c0 == '\r') {
// Starting from new line we skips next *
skipNextAsterisk = true;
} else if (c0 != ' ' && c0 != '\t' && skipNextAsterisk == true) {
// If detected non space char before skipping -> we don't skip next *
skipNextAsterisk = false;
}
valueBuilder.append(eat());
}
}
value = valueBuilder.toString();
}
private void number() {
type = Type.Number;
do {
valueBuilder.append(eat());
} while (EclCharClasses.isDigit(c0));
String v = valueBuilder.toString();
try {
long l = Long.parseLong(v);
value = l;
} catch (NumberFormatException e) {
value = v;
}
}
private void string() {
type = Type.String;
char openQuote = eat();
while (true) {
if (c0 == EOF) {
message = "Unterminated string literal.";
return;
} else if (c0 == '\\') {
eat();
if (c0 == EOF) {
message = "Unterminated string literal.";
return;
}
switch (c0) {
case 'b':
valueBuilder.append('\b');
break;
case 't':
valueBuilder.append('\t');
break;
case 'n':
valueBuilder.append('\n');
break;
case 'f':
valueBuilder.append('\f');
break;
case 'r':
valueBuilder.append('\r');
break;
case '\"':
valueBuilder.append('\"');
break;
case '\'':
valueBuilder.append('\'');
break;
case '\\':
valueBuilder.append('\\');
break;
default:
message = "Unknown string literal escape sequence.";
break;
}
eat();
} else if (c0 == openQuote) {
eat();
break;
} else
valueBuilder.append(eat());
}
value = valueBuilder.toString();
}
private void identifier() {
type = Type.Identifier;
scanIdentifier();
value = valueBuilder.toString();
}
private void option() {
type = Type.Option;
eat();
if (c0 == '-')
eat();
if (scanIdentifier())
value = valueBuilder.toString();
else {
if (allowEmptyOption) {
value = "";
} else {
message = "Invalid option name.";
}
}
}
private void variable() {
type = Type.Variable;
eat();
if (scanIdentifier())
value = valueBuilder.toString();
else {
if (allowEmptyVar) {
value = "";
} else {
message = "Invalid variable name.";
}
}
}
// --
private boolean scanIdentifier() {
if (!EclCharClasses.isIdentifierStart(c0))
return false;
do {
valueBuilder.append(eat());
} while (EclCharClasses.isIdentifier(c0));
return true;
}
// --
private void peek() {
c0 = p < source.length() ? source.charAt(p) : EOF;
}
private char eat() {
char c1 = c0;
++p;
peek();
return c1;
}
}