/*******************************************************************************
* Copyright (c) 2007 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.el.core.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.jboss.tools.common.el.internal.core.parser.rule.BasicStates;
/**
*
* @author V. Kabanovich
*
*/
public class Tokenizer {
public static int LITERAL = -10;
private Map<Integer, ITokenDescription> tokenDescriptions = new HashMap<Integer, ITokenDescription>();
private Map<Integer, List<IRule>> rules = new HashMap<Integer, List<IRule>>();
private String sourceString;
private int index = 0;
private LexicalToken start;
private LexicalToken last;
private int state;
private Properties context = new Properties();
List<SyntaxError> errors = new ArrayList<SyntaxError>();
public Tokenizer() {}
/**
* Prepares this tokenizer to be reused with the same token descriptions and rules.
*
*/
public void dispose() {
sourceString = null;
index = 0;
start = null;
last = null;
context.clear();
errors.clear();
}
public void setTokenDescriptions(ITokenDescription[] ds) {
tokenDescriptions.clear();
for (int i = 0; i < ds.length; i++) {
int type = ds[i].getType();
if(tokenDescriptions.containsKey(type)) {
throw new IllegalArgumentException("Token type " + type + " is not unique."); //$NON-NLS-1$ //$NON-NLS-2$
}
tokenDescriptions.put(type, ds[i]);
}
}
public ITokenDescription getTokenDescription(int id) {
return tokenDescriptions.get(id);
}
public void setRules(IRule[] rules) {
this.rules.clear();
for (int i = 0; i < rules.length; i++) {
int[] ss = rules[i].getStartStates();
for (int j = 0; j < ss.length; j++) {
List<IRule> rs = this.rules.get(ss[j]);
if(rs == null) {
rs = new ArrayList<IRule>();
this.rules.put(ss[j], rs);
}
rs.add(rules[i]);
}
}
}
public LexicalToken parse(String sourceString) {
return parse(sourceString, 0, sourceString.length());
}
public LexicalToken parse(String sourceString, int initialOffset, int length) {
this.sourceString = sourceString;
errors.clear();
index = initialOffset;
start = new LexicalToken(initialOffset, 0, "", -1000); //$NON-NLS-1$
last = start;
state = BasicStates.STATE_EXPECTING_EL;
int lastIndex = initialOffset + length;
if(lastIndex > sourceString.length()) lastIndex = sourceString.length();
while(index < lastIndex) {
boolean done = false;
List<IRule> rs = rules.get(state);
for (IRule rule : rs) {
int[] ts = rule.getTokenTypes(state);
for (int j = 0; !done && j < ts.length; j++) {
ITokenDescription td = tokenDescriptions.get(ts[j]);
if (td != null && td.isStart(this, index)) {
td.read(this, index);
state = rule.getFinalState(state, ts[j]);
done = true;
}
}
}
if(!done) {
if(state == BasicStates.STATE_EXPECTING_EL || state == BasicStates.STATE_ERROR) {
char ch = readNextChar();
if(ch == '\0') break;
} else {
SyntaxError error = new SyntaxError(index, state);
String problem = null;
for (IRule rule : rs) {
problem = rule.getProblem(state, this);
if(problem != null) break;
}
error.setProblem(problem);
errors.add(error);
state = BasicStates.STATE_ERROR;
}
}
}
LexicalToken result = start.getNextToken();
if(result != null) {
result.makeItFirst();
}
if(last != null && last.getStart() + last.getLength() < sourceString.length()
&& last.getType() != LITERAL) {
int lastEnd = last.getStart() + last.getLength();
LexicalToken t = new LexicalToken(lastEnd, length, getCharSequence(lastEnd, sourceString.length()), LITERAL);
last.setNextToken(t);
last = t;
}
return result;
}
private static List<SyntaxError> EMPTY = new ArrayList<SyntaxError>();
/**
* Copies errors in order to reuse this object.
* @return
*/
public List<SyntaxError> getErrors() {
if(errors.size() == 0) return EMPTY;
List<SyntaxError> copy = new ArrayList<SyntaxError>();
copy.addAll(errors);
return copy;
}
public void addSyntaxError(SyntaxError error) {
errors.add(error);
}
public void addToken(int type, int start, int end) {
if(end < 0) return;
int lastEnd = last.getStart() + last.getLength();
if(start > lastEnd) {
int length = start - lastEnd;
LexicalToken t = new LexicalToken(lastEnd, length, getCharSequence(lastEnd, start), LITERAL);
last.setNextToken(t);
last = t;
index = start;
}
LexicalToken t = new LexicalToken(start, end - start, getCharSequence(start, end), type);
last.setNextToken(t);
last = t;
index = end;
}
public Properties getContext() {
return context;
}
private CharSequence getCharSequence(int start, int end) {
String text = sourceString.substring(start, end);
return text.subSequence(0, text.length());
}
public char readNextChar() {
char c = '\0';
if (index < sourceString.length()) {
c = sourceString.charAt(index);
}
index++;
return c;
}
public char lookUpChar(int i) {
return (i < 0 || sourceString.length() <= i) ? '\0' : sourceString.charAt(i);
}
/*
* returns the character to the document
*/
public void releaseChar() {
if (index > 0) {
index--;
}
}
public boolean startsWith(String s) {
int l = index + s.length();
if(l > sourceString.length()) return false;
for (int i = index; i < l; i++) {
if(lookUpChar(i) != s.charAt(i - index)) {
return false;
}
}
return true;
}
public LexicalToken getLastToken() {
return last;
}
public int getState() {
return state;
}
public int getCurrentIndex() {
return index;
}
}