Inheritance: MonaExpr
        private MSOFormula<BDD> ConvertPredApp(MonaPredApp predApp, MapStack<string, MonaParam> locals)
        {
            var predDef = predMap[predApp.symbol.text];
            var predDecl = (MonaPredDecl)globals[predApp.symbol.text];

            int k = predDecl.parameters.Count;
            if (k != predApp.NrOfSubexprs)
                throw new ArgumentException("invalid call of " + predDecl.name);

            if (k == 0)
                return predDef;

            var newVars = new Variable[k];
            Dictionary<string, Variable> substitution = new Dictionary<string, Variable>();

            var argPreds = new MSOFormula<BDD>[k];
            var argVars = new Variable[k];

            for (int i = 0; i < k; i++)
            {
                if (predDecl.parameters[i].kind != MonaParamKind.var1 && predDecl.parameters[i].kind != MonaParamKind.var2)
                    throw new NotImplementedException("parameter kind " + predDecl.parameters[i].kind.ToString());

                MSOFormula<BDD> argPreds_i;
                Variable argVars_i;
                if (predDecl.parameters[i].kind == MonaParamKind.var1)
                {
                    argVars_i = ConvertTerm1(predApp[i], locals, out argPreds_i);
                    if (argPreds_i == null)
                    {
                        var tmp1 = MkNewVar1();
                        argPreds_i = new MSOEq<BDD>(tmp1, argVars_i);
                        argVars_i = tmp1;
                    }
                }
                else
                {
                    argVars_i = ConvertTerm2(predApp[i], locals, out argPreds_i);
                    if (argPreds_i == null)
                    {
                        var tmp2 = MkNewVar2();
                        argPreds_i = new MSOEq<BDD>(tmp2, argVars_i);
                        argVars_i = tmp2;
                    }
                }
                argPreds[i] = argPreds_i;
                argVars[i] = argVars_i;
                substitution[predDecl.parameters[i].Token.text] = argVars_i;
            }

            MSOFormula<BDD> psi = predDef.SubstituteVariables(substitution);
            for (int i = k - 1; i >= 0; i--)
                psi = new MSOExists<BDD>(argVars[i], argPreds[i] & psi);

            return psi;
        }
        private MSOFormula<BDD> ConvertFormula(MonaExpr expr, MapStack<string,MonaParam> locals)
        {
            switch (expr.symbol.Kind)
            {
                case Tokens.TRUE:
                    return new MSOTrue<BDD>();
                case Tokens.FALSE:
                    return new MSOFalse<BDD>();
                case Tokens.NOT:
                    return new MSONot<BDD>(ConvertFormula(expr[0], locals));
                case Tokens.AND:
                    return new MSOAnd<BDD>(ConvertFormula(expr[0], locals), ConvertFormula(expr[1], locals));
                case Tokens.OR:
                    return new MSOOr<BDD>(ConvertFormula(expr[0], locals), ConvertFormula(expr[1], locals));
                case Tokens.IMPLIES:
                    return new MSOImplies<BDD>(ConvertFormula(expr[0], locals), ConvertFormula(expr[1], locals));
                case Tokens.EQUIV:
                    return new MSOEquiv<BDD>(ConvertFormula(expr[0], locals), ConvertFormula(expr[1], locals));
                case Tokens.EQ:
                    return ConvertEq(expr[0], expr[1], locals);
                case Tokens.NE:
                    return new MSONot<BDD>(ConvertEq(expr[0], expr[1], locals));
                case Tokens.LT:
                    return ConvertLt(expr[0], expr[1], locals);
                case Tokens.GT:
                    return ConvertLt(expr[1], expr[0], locals);
                case Tokens.LE:
                    return ConvertLe(expr[0], expr[1], locals);
                case Tokens.GE:
                    return ConvertLe(expr[1], expr[0], locals);
                case Tokens.SUBSET:
                    return ConvertSubset(expr[1], expr[0], locals);
                case Tokens.IN:
                    return ConvertIn(expr[0], expr[1], locals);
                case Tokens.NOTIN:
                    return new MSONot<BDD>(ConvertIn(expr[0], expr[1], locals));
                case Tokens.EMPTY:
                    return ConvertIsEmpty(expr[0], locals);
                case Tokens.EX1:
                case Tokens.EX2:
                    {
                        MonaQFormula phi = (MonaQFormula)expr;
                        if ((phi.universes != null && phi.universes.Count > 1) ||
                            phi.vars.Exists(vw => vw.where != null))
                            throw new NotImplementedException(expr.ToString());
                        MSOFormula<BDD> psi = ConvertFormula(phi.formula, locals.Push(phi.varmap));
                        foreach (var vw in phi.vars)
                            psi = new MSOExists<BDD>(new Variable(vw.name, phi.varmap[vw.name].type == MonaExprType.INT), psi);
                        return psi;
                    }
                case Tokens.ALL1:
                case Tokens.ALL2:
                    {
                        MonaQFormula phi = (MonaQFormula)expr;
                        if ((phi.universes != null && phi.universes.Count > 1) ||
                            phi.vars.Exists(vw => vw.where != null))
                            throw new NotImplementedException(expr.ToString());
                        MSOFormula<BDD> psi = ConvertFormula(phi.formula, locals.Push(phi.varmap));
                        foreach (var vw in phi.vars)
                            psi = new MSOForall<BDD>(new Variable(vw.name, phi.varmap[vw.name].type == MonaExprType.INT), psi);
                        return psi;
                    }
                case Tokens.NAME:
                    {
                        var name = expr as MonaName;
                        if (name != null)
                        {
                            //must be a nullary predicate application (var0 is not supported)
                            var tmpPredApp = new MonaPredApp(name.symbol, Cons<MonaExpr>.Empty);
                            return ConvertPredApp(tmpPredApp, locals);
                        }

                        var predApp = expr as MonaPredApp;
                        if (predApp != null)
                        {
                            return ConvertPredApp(predApp, locals);
                        }

                        throw new NotImplementedException(expr.ToString());
                    }
                default:
                    throw new NotImplementedException(expr.ToString());
            }
        }