/** * Author: Georg Hofferek <georg.hofferek@iaik.tugraz.at> */ package at.iaik.suraq.parser; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import at.iaik.suraq.exceptions.InvalidParametersException; import at.iaik.suraq.exceptions.NotATokenListException; import at.iaik.suraq.exceptions.ParseError; import at.iaik.suraq.sexp.SExpression; import at.iaik.suraq.sexp.SExpressionConstants; import at.iaik.suraq.sexp.Token; import at.iaik.suraq.smtlib.formula.AndFormula; import at.iaik.suraq.smtlib.formula.ArrayVariable; import at.iaik.suraq.smtlib.formula.DomainVariable; import at.iaik.suraq.smtlib.formula.Formula; import at.iaik.suraq.smtlib.formula.PropositionalFunctionMacro; import at.iaik.suraq.smtlib.formula.PropositionalVariable; import at.iaik.suraq.smtlib.formula.Term; import at.iaik.suraq.smtlib.formula.TermFunctionMacro; import at.iaik.suraq.smtlib.formula.UninterpretedFunction; /** * @author Georg Hofferek <georg.hofferek@iaik.tugraz.at> * */ public class LogicParser extends SMTLibParser implements Serializable { /** * */ private static final long serialVersionUID = 1L; /** * The formula that results from parsing. */ protected Formula mainFormula = null; /** * * Constructs a new <code>FormulaParser</code>. * * @param root * the root expression to parse. */ public LogicParser(SExpression root) { rootExpr = root; } /** * * Constructs a new <code>LogicParser</code>, initialized with the given * variable and function names. It can then be used to parse formulas by * calling the <code>parseFormulaBody</code> method. * * @param propVars * @param domainVars * @param functions */ public LogicParser(Collection<? extends PropositionalVariable> propVars, Collection<? extends DomainVariable> domainVars, Collection<? extends UninterpretedFunction> functions) { super(); this.boolVariables.addAll(propVars); this.domainVariables.addAll(domainVars); this.functions.addAll(functions); } // /** // * // * @return a (deep) copy of the root expression of this parser. // */ // public SExpression getRootExpr() { // return rootExpr.deepCopy(); // } /** * Parses the root s-expression into a formula, which can then be retrieved * using <code>getFormula</code>. * * @see at.iaik.suraq.parser.Parser#parse() */ @Override public void parse() throws ParseError { checkLogic(); for (int count = 1; count < rootExpr.getChildren().size(); count++) { SExpression expression = rootExpr.getChildren().get(count); if (expression instanceof Token) throw new ParseError(expression.getLineNumber(), expression.getColumnNumber(), expression.toString(), "Unexpected Token."); if (expression.getChildren().size() == 0) throw new ParseError(expression.getLineNumber(), expression.getColumnNumber(), expression.toString(), "Unexpected empty expression."); assert (expression.getChildren().size() >= 1); // at this point, we expect a declare-fun, a define-fun, or an // assert if (!(expression.getChildren().get(0) instanceof Token)) throw new ParseError(expression.getLineNumber(), expression.getColumnNumber(), expression.toString(), "Expected 'declare-fun', 'define-fun', or 'assert' expression."); assert (expression.getChildren().get(0) instanceof Token); Token token = (Token) expression.getChildren().get(0); if (token.equals(SExpressionConstants.DECLARE_FUN)) { handleDeclareFun(expression); continue; } if (token.equals(SExpressionConstants.DEFINE_FUN)) { handleDefineFun(expression); continue; } if (token.equals(SExpressionConstants.ASSERT)) { handleAssert(expression); continue; } // we got something unexpected, if we reach this point. throw new ParseError(expression.getLineNumber(), expression.getColumnNumber(), expression.toString(), "Expected 'declare-fun', 'define-fun', or 'assert' expression."); } parsingSuccessfull = true; rootExpr = null; // Allow this to be garbage collected } /** * Handles an assert expression. I.e., if <code>mainFormula</code> is still * <code>null</code>, it will be initialized to the result of parsing this * assert statement's body. If <code>mainFormula</code> already is non- * <code>null</code>, a conjunction of its current value an the parsed body * will be made. * * @param expression * the assert expression to parse. */ private void handleAssert(SExpression expression) throws ParseError { assert (expression.getChildren().get(0) instanceof Token); assert (expression.getChildren().get(0) .equals(SExpressionConstants.ASSERT)); if (expression.getChildren().size() != 2) throw new ParseError(expression, "Expected exactly one argument for 'assert'."); Formula body = parseFormulaBody(expression.getChildren().get(1)); if (mainFormula == null) mainFormula = body; else { List<Formula> list = new ArrayList<Formula>(); list.add(mainFormula); list.add(body); mainFormula = AndFormula.generate(list); } } /** * Handles a <code>define-fun</code> expression. * * @param expression * the <code>define-fun</code> expression. */ private void handleDefineFun(SExpression expression) throws ParseError { assert (expression.getChildren().get(0) instanceof Token); assert (expression.getChildren().get(0) .equals(SExpressionConstants.DEFINE_FUN)); if (expression.getChildren().size() != 5) throw new ParseError(expression, "Expected 5 subexpressions in define-fun expression!"); assert (expression.getChildren().size() == 5); if (!(expression.getChildren().get(1) instanceof Token)) throw new ParseError(expression, "The first argument of define-fun must be a token!"); assert (expression.getChildren().get(1) instanceof Token); Token name = (Token) expression.getChildren().get(1); if (name.toString().endsWith("NNF")) throw new ParseError(name, "Names of function macros may not end with 'NNF'."); SExpression type = expression.getChildren().get(3); SExpression params = expression.getChildren().get(2); List<Token> paramsList = new ArrayList<Token>(); Map<Token, SExpression> paramMap; try { paramMap = parseDefineFunParams(params, paramsList); } catch (InvalidParametersException exc) { throw new RuntimeException( "Unexpected situation while parsing macro parameters", exc); } if (type.equals(SExpressionConstants.BOOL_TYPE)) { // Handle Bool macro Formula body; this.currentLocals = paramMap; try { body = parseFormulaBody(expression.getChildren().get(4)); } finally { this.currentLocals = null; } PropositionalFunctionMacro macro; try { macro = PropositionalFunctionMacro.create(name, paramsList, paramMap, body); } catch (InvalidParametersException exc) { throw new RuntimeException( "Unexpected situation while parsing macro parameters", exc); } if (macros.containsKey(name)) throw new ParseError(name, "Duplicate macro definition: " + name.toString()); else macros.put(name, macro); } else if (type.equals(SExpressionConstants.VALUE_TYPE) || type.equals(SExpressionConstants.ARRAY_TYPE)) { // Handle Term macro Term body; this.currentLocals = paramMap; try { body = parseTerm(expression.getChildren().get(4)); } finally { this.currentLocals = null; } TermFunctionMacro macro; try { macro = TermFunctionMacro.create(name, paramsList, paramMap, body); } catch (InvalidParametersException exc) { throw new RuntimeException( "Unexpected situation while parsing macro parameters", exc); } if (macros.containsKey(name)) throw new ParseError(name, "Duplicate macro definition: " + name.toString()); else macros.put(name, macro); } else { // Only Bool, Value, and (Array Value Value) macros are allowed throw new ParseError(type, "Unsupported type: " + type.toString()); } } /** * Parses the parameters of a define-fun macro. * * @param params * the parameters to to check. * @param paramsList * an (empty) list to which the parameter names are added in * order. * @return a <code>Map</code> of parameter names (<code>Token</code>s) to * types (<code>SExpression</code>s). * @throws ParseError * if the parameters are invalid. * @throws InvalidParametersException * if the given <code>paramsList</code> is non-empty or * <code>null</code>; */ private Map<Token, SExpression> parseDefineFunParams(SExpression params, List<Token> paramsList) throws ParseError, InvalidParametersException { if (paramsList == null) throw new InvalidParametersException("paramsList is null"); if (paramsList.size() != 0) throw new InvalidParametersException("paramsList is non-empty"); Map<Token, SExpression> paramMap = new HashMap<Token, SExpression>(); for (SExpression paramMapping : params.getChildren()) { if (paramMapping.getChildren().size() != 2) throw new ParseError(paramMapping, "Illegal parameter declaration: " + paramMapping.toString()); SExpression paramName = paramMapping.getChildren().get(0); if (!(paramName instanceof Token)) throw new ParseError(paramName, "Illegal parameter declaration: " + paramName.toString()); SExpression paramType = paramMapping.getChildren().get(1); if (paramType.equals(SExpressionConstants.BOOL_TYPE) || paramType.equals(SExpressionConstants.VALUE_TYPE) || paramType.equals(SExpressionConstants.ARRAY_TYPE)) { paramMap.put((Token) paramName, paramType); paramsList.add((Token) paramName); continue; } else { throw new ParseError(paramType, "Unsupported parameter type: " + paramType.toString()); } } return paramMap; } /** * Handles a <code>declare-fun</code> expression. * * @param expression * the <code>declare-fun</code> expression. */ private void handleDeclareFun(SExpression expression) throws ParseError { assert (expression.getChildren().get(0) instanceof Token); assert (((Token) expression.getChildren().get(0)) .equalsString("declare-fun")); if (expression.getChildren().size() < 4 || expression.getChildren().size() > 5) throw new ParseError(expression, "Expected 4 or 5 subexpressions in declare-fun expression!"); assert (expression.getChildren().size() == 4 || expression .getChildren().size() == 5); if (!(expression.getChildren().get(1) instanceof Token)) throw new ParseError(expression, "The first argument of declare-fun must be a token!"); boolean noDependence = false; if (expression.getChildren().size() == 5) { if (!expression.getChildren().get(4) .equals(SExpressionConstants.NO_DEPENDENCE)) throw new ParseError(expression.getChildren().get(4), "Expected either '' or ':no_dependence' as fourth parameter of declare-fun."); else noDependence = true; } assert (expression.getChildren().get(1) instanceof Token); Token name = (Token) expression.getChildren().get(1); SExpression type = expression.getChildren().get(3); SExpression params = expression.getChildren().get(2); List<Token> param_list; try { param_list = params.toTokenList(); } catch (NotATokenListException exc) { throw new ParseError(params, "Error in parsing argument list of declare-fun!", exc); } if (param_list.size() == 0) handleVariable(name, type, noDependence); else handleFunction(name, param_list, type); } /** * Handles the declaration of a new uninterpreted function. Only functions * of the form <code>(Value+) -> Value</code> are supported. * * @param name * the name of the function * @param param_list * the parameter list * @param type * the return type. */ private void handleFunction(Token name, List<Token> param_list, SExpression type) throws ParseError { for (Token token : param_list) { if (!(token.equals(SExpressionConstants.VALUE_TYPE))) throw new ParseError(token, "Unsupported function argument: " + token.toString()); } if (!(type.equals(SExpressionConstants.VALUE_TYPE) || type .equals(SExpressionConstants.BOOL_TYPE))) throw new ParseError(type, "Unsupported function type: " + type.toString()); assert (type instanceof Token); if (!functions.add(UninterpretedFunction.create(name, param_list.size(), (Token) type))) { throw new ParseError(name, "Duplicate function definition: " + name.toString()); } } /** * Handles declarations of new variables (and constants). They must be of * one of the following types: Control, Bool, Value, (Array Value Value). * * @param name * the name of the variable. * @param type * the s-expression determining the type of the variable. * @param noDependence * <code>true</code> if this is a variable on which control logic * may <em>not</em> depend. */ private void handleVariable(Token name, SExpression type, boolean noDependence) throws ParseError { if (checkNameExists(name)) { throw new ParseError(name, "Name already used: " + name.toString()); // After this check the exceptions below should actually never // be thrown. } if (type.equals(SExpressionConstants.CONTROL_TYPE)) { if (!controlVariables.add(PropositionalVariable.create(name))) { throw new ParseError(name, "Duplicate variable definition: " + name.toString()); } } else if (type.equals(SExpressionConstants.BOOL_TYPE)) { if (!boolVariables.add(PropositionalVariable.create(name))) { throw new ParseError(name, "Duplicate variable definition: " + name.toString()); } } else if (type.equals(SExpressionConstants.VALUE_TYPE)) { if (!domainVariables.add(DomainVariable.create(name))) { throw new ParseError(name, "Duplicate variable definition: " + name.toString()); } } else if (type.equals(SExpressionConstants.ARRAY_TYPE)) { if (!arrayVariables.add(ArrayVariable.create(name))) { throw new ParseError(name, "Duplicate variable definition: " + name.toString()); } } else { throw new ParseError(type, "Unsupported variable type: " + type.toString()); } if (noDependence) { noDependenceVariables.add(name); } } /** * Checks whether the given name already exists (as an identifier of any * other type). * * @param name * the name to check. * @return <code>true</code> if something with this name already exists, * false otherwise. */ private boolean checkNameExists(Token name) { Set<Token> names = new HashSet<Token>(); for (PropositionalVariable variable : boolVariables) names.add(Token.generate(variable.getVarName())); for (PropositionalVariable variable : controlVariables) names.add(Token.generate(variable.getVarName())); for (DomainVariable variable : domainVariables) names.add(Token.generate(variable.getVarName())); for (ArrayVariable variable : arrayVariables) names.add(Token.generate(variable.getVarName())); for (UninterpretedFunction function : functions) names.add(function.getName()); names.addAll(macros.keySet()); return names.contains(names); } /** * Check if the first child of the root expression is * <code>(set-logic Suraq)</code> * * @throws ParseError * if the first child of the root expression is not * <code>(set-logic Suraq)</code> */ private void checkLogic() throws ParseError { assert (rootExpr.getChildren() != null); if (rootExpr.getChildren().size() < 1) throw new ParseError("Empty input"); SExpression child = rootExpr.getChildren().get(0); if (!child.equals(SExpressionConstants.SET_LOGIC_SURAQ)) throw new ParseError(child.getLineNumber(), child.getColumnNumber(), child.toString(), "Expected '(set-logic Suraq)'."); } /** * Returns the formula that resulted from parsing, or <code>null</code> if * parsing was not successful. * * @return the formula that resulted from parsing, or <code>null</code> if * parsing was not successful. */ public Formula getMainFormula() { if (!wasParsingSuccessfull()) return null; return mainFormula; } }