/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.analyze.model;
import java.util.ArrayList;
import com.cburch.logisim.util.StringGetter;
public class Parser {
//
// parsing code
//
private static class Context {
int level;
Expression current;
Token cause;
Context(Expression current, int level, Token cause) {
this.level = level;
this.current = current;
this.cause = cause;
}
}
private static class Token {
int type;
int offset;
int length;
String text;
Token(int type, int offset, int length, String text) {
this.type = type;
this.offset = offset;
this.length = length;
this.text = text;
}
Token(int type, int offset, String text) {
this(type, offset, text.length(), text);
}
ParserException error(StringGetter message) {
return new ParserException(message, offset, length);
}
}
private static boolean okCharacter(char c) {
return Character.isWhitespace(c) || Character.isJavaIdentifierStart(c)
|| "()01~^+!&|".indexOf(c) >= 0;
}
private static Expression parse(ArrayList<Token> tokens)
throws ParserException {
ArrayList<Context> stack = new ArrayList<Context>();
Expression current = null;
for (int i = 0; i < tokens.size(); i++) {
Token t = tokens.get(i);
if (t.type == TOKEN_IDENT || t.type == TOKEN_CONST) {
Expression here;
if (t.type == TOKEN_IDENT) {
here = Expressions.variable(t.text);
} else {
here = Expressions.constant(Integer.parseInt(t.text, 16));
}
while (i + 1 < tokens.size()
&& tokens.get(i + 1).type == TOKEN_NOT_POSTFIX) {
here = Expressions.not(here);
i++;
}
while (peekLevel(stack) == Expression.NOT_LEVEL) {
here = Expressions.not(here);
pop(stack);
}
current = Expressions.and(current, here);
if (peekLevel(stack) == Expression.AND_LEVEL) {
Context top = pop(stack);
current = Expressions.and(top.current, current);
}
} else if (t.type == TOKEN_NOT) {
if (current != null) {
push(stack,
current,
Expression.AND_LEVEL,
new Token(TOKEN_AND, t.offset, Strings
.get("implicitAndOperator")));
}
push(stack, null, Expression.NOT_LEVEL, t);
current = null;
} else if (t.type == TOKEN_NOT_POSTFIX) {
throw t.error(Strings.getter("unexpectedApostrophe"));
} else if (t.type == TOKEN_LPAREN) {
if (current != null) {
push(stack,
current,
Expression.AND_LEVEL,
new Token(TOKEN_AND, t.offset, 0, Strings
.get("implicitAndOperator")));
}
push(stack, null, -2, t);
current = null;
} else if (t.type == TOKEN_RPAREN) {
current = popTo(stack, -1, current);
// there had better be a LPAREN atop the stack now.
if (stack.isEmpty()) {
throw t.error(Strings.getter("lparenMissingError"));
}
pop(stack);
while (i + 1 < tokens.size()
&& tokens.get(i + 1).type == TOKEN_NOT_POSTFIX) {
current = Expressions.not(current);
i++;
}
current = popTo(stack, Expression.AND_LEVEL, current);
} else {
if (current == null) {
throw t.error(Strings.getter("missingLeftOperandError",
t.text));
}
int level = 0;
switch (t.type) {
case TOKEN_AND:
level = Expression.AND_LEVEL;
break;
case TOKEN_OR:
level = Expression.OR_LEVEL;
break;
case TOKEN_XOR:
level = Expression.XOR_LEVEL;
break;
}
push(stack, popTo(stack, level, current), level, t);
current = null;
}
}
current = popTo(stack, -1, current);
if (!stack.isEmpty()) {
Context top = pop(stack);
throw top.cause.error(Strings.getter("rparenMissingError"));
}
return current;
}
public static Expression parse(String in, AnalyzerModel model)
throws ParserException {
ArrayList<Token> tokens = toTokens(in, false);
if (tokens.size() == 0)
return null;
for (Token token : tokens) {
if (token.type == TOKEN_ERROR) {
throw token.error(Strings.getter("invalidCharacterError",
token.text));
} else if (token.type == TOKEN_IDENT) {
int index = model.getInputs().indexOf(token.text);
if (index < 0) {
// ok; but maybe this is an operator
String opText = token.text.toUpperCase();
if (opText.equals("NOT")) {
token.type = TOKEN_NOT;
} else if (opText.equals("AND")) {
token.type = TOKEN_AND;
} else if (opText.equals("XOR")) {
token.type = TOKEN_XOR;
} else if (opText.equals("OR")) {
token.type = TOKEN_OR;
} else {
throw token.error(Strings.getter("badVariableName",
token.text));
}
}
}
}
return parse(tokens);
}
private static int peekLevel(ArrayList<Context> stack) {
if (stack.isEmpty())
return -3;
Context context = stack.get(stack.size() - 1);
return context.level;
}
private static Context pop(ArrayList<Context> stack) {
return stack.remove(stack.size() - 1);
}
private static Expression popTo(ArrayList<Context> stack, int level,
Expression current) throws ParserException {
while (!stack.isEmpty() && peekLevel(stack) >= level) {
Context top = pop(stack);
if (current == null)
throw top.cause.error(Strings.getter(
"missingRightOperandError", top.cause.text));
switch (top.level) {
case Expression.AND_LEVEL:
current = Expressions.and(top.current, current);
break;
case Expression.OR_LEVEL:
current = Expressions.or(top.current, current);
break;
case Expression.XOR_LEVEL:
current = Expressions.xor(top.current, current);
break;
case Expression.NOT_LEVEL:
current = Expressions.not(current);
break;
}
}
return current;
}
private static void push(ArrayList<Context> stack, Expression expr,
int level, Token cause) {
stack.add(new Context(expr, level, cause));
}
/**
* I wrote this without thinking, and then realized that this is quite
* complicated because of removing operators. I haven't bothered to do it
* correctly; instead, it just regenerates a string from the raw expression.
* static String removeVariable(String in, String variable) { StringBuilder
* ret = new StringBuilder(); ArrayList tokens = toTokens(in, true); Token
* lastWhite = null; for (int i = 0, n = tokens.size(); i < n; i++) { Token
* token = (Token) tokens.get(i); if (token.type == TOKEN_IDENT &&
* token.text.equals(variable)) { ; // just ignore it } else if (token.type
* == TOKEN_WHITE) { if (lastWhite != null) { if (lastWhite.text.length() >=
* token.text.length()) { ; // don't repeat shorter whitespace } else {
* ret.replace(ret.length() - lastWhite.text.length(), ret.length(),
* token.text); lastWhite = token; } } else { lastWhite = token;
* ret.append(token.text); } } else { lastWhite = null;
* ret.append(token.text); } } return ret.toString(); }
*/
static String replaceVariable(String in, String oldName, String newName) {
StringBuilder ret = new StringBuilder();
ArrayList<Token> tokens = toTokens(in, true);
for (Token token : tokens) {
if (token.type == TOKEN_IDENT && token.text.equals(oldName)) {
ret.append(newName);
} else {
ret.append(token.text);
}
}
return ret.toString();
}
private static ArrayList<Token> toTokens(String in, boolean includeWhite) {
ArrayList<Token> tokens = new ArrayList<Token>();
// Guarantee that we will stop just after reading whitespace,
// not in the middle of a token.
in = in + " ";
int pos = 0;
while (true) {
int whiteStart = pos;
while (pos < in.length() && Character.isWhitespace(in.charAt(pos)))
pos++;
if (includeWhite && pos != whiteStart) {
tokens.add(new Token(TOKEN_WHITE, whiteStart, in.substring(
whiteStart, pos)));
}
if (pos == in.length())
return tokens;
int start = pos;
char startChar = in.charAt(pos);
pos++;
if (Character.isJavaIdentifierStart(startChar)) {
while (Character.isJavaIdentifierPart(in.charAt(pos)))
pos++;
tokens.add(new Token(TOKEN_IDENT, start, in.substring(start,
pos)));
} else {
switch (startChar) {
case '(':
tokens.add(new Token(TOKEN_LPAREN, start, "("));
break;
case ')':
tokens.add(new Token(TOKEN_RPAREN, start, ")"));
break;
case '0':
case '1':
tokens.add(new Token(TOKEN_CONST, start, "" + startChar));
break;
case '~':
tokens.add(new Token(TOKEN_NOT, start, "~"));
break;
case '\'':
tokens.add(new Token(TOKEN_NOT_POSTFIX, start, "'"));
break;
case '^':
tokens.add(new Token(TOKEN_XOR, start, "^"));
break;
case '+':
tokens.add(new Token(TOKEN_OR, start, "+"));
break;
case '!':
tokens.add(new Token(TOKEN_NOT, start, "!"));
break;
case '&':
if (in.charAt(pos) == '&')
pos++;
tokens.add(new Token(TOKEN_AND, start, in.substring(start,
pos)));
break;
case '|':
if (in.charAt(pos) == '|')
pos++;
tokens.add(new Token(TOKEN_OR, start, in.substring(start,
pos)));
break;
default:
while (!okCharacter(in.charAt(pos)))
pos++;
String errorText = in.substring(start, pos);
tokens.add(new Token(TOKEN_ERROR, start, errorText));
}
}
}
}
//
// tokenizing code
//
private static final int TOKEN_AND = 0;
private static final int TOKEN_OR = 1;
private static final int TOKEN_XOR = 2;
private static final int TOKEN_NOT = 3;
private static final int TOKEN_NOT_POSTFIX = 4;
private static final int TOKEN_LPAREN = 5;
private static final int TOKEN_RPAREN = 6;
private static final int TOKEN_IDENT = 7;
private static final int TOKEN_CONST = 8;
private static final int TOKEN_WHITE = 9;
private static final int TOKEN_ERROR = 10;
private Parser() {
}
}