This document specifies the input formats accepted by the solver. The solver supports two formats:
- Custom Format: Human-readable text format (recommended for manual testing)
- SMT-LIB 2.0: Standard format for compatibility with benchmarks (QF_UF logic)
Both formats accept conjunctions of literals representing constraints in the union of three theories:
- T_E: Theory of Equality with uninterpreted functions
- T_cons: Theory of Non-empty Possibly Cyclic Lists
- T_A: Theory of Arrays without Extensionality
Input Methods:
- Text file (command-line argument)
- Standard input (stdin)
- Pipe or redirection
Output:
- SAT (with optional model/equivalence classes)
- UNSAT (with optional conflict explanation)
Format: term = term
Examples:
a = b
f(x) = g(y)
cons(a, b) = x
select(arr, i) = v
Format: term != term
Examples:
a != b
car(x) != a
select(arr, i) != 0
Format: atom(term) or !atom(term)
The atom predicate is a unary predicate from the T_cons theory (Bradley & Manna Section 9.4).
By Axiom 7: ∀x,y. ¬atom(cons(x,y)) - all cons-constructed values are non-atomic.
Examples:
atom(x)
!atom(cons(a, b))
atom(car(z))
Terms are built from:
- Variables: lowercase identifiers (a-z, A-Z, 0-9, underscore)
- Constants: lowercase identifiers (same as variables - distinction is semantic, not syntactic)
- Function Applications:
functionName(arg1, arg2, ...)
Literal ::= Equality | Disequality | Atom | NegAtom
Equality ::= Term '=' Term
Disequality ::= Term '!=' Term
Atom ::= 'atom' '(' Term ')'
NegAtom ::= '!' 'atom' '(' Term ')'
Term ::= Variable
| FunctionApp
Variable ::= Identifier
FunctionApp ::= Identifier '(' Term (',' Term)* ')'
Identifier ::= [a-zA-Z_][a-zA-Z0-9_]*
Whitespace ::= [ \t\n\r]+
Comment ::= '#' [^\n]* | '//' [^\n]*Note: The parser treats all simple identifiers as variables. Function applications require parentheses.
These symbols have special meaning in their respective theories:
T_cons (Lists):
cons(x, y)- construct list with head x and tail ycar(x)- get head of list xcdr(x)- get tail of list x
T_A (Arrays):
select(a, i)- read array a at index istore(a, i, v)- write value v to array a at index i
Notes:
consmust have exactly 2 argumentscarandcdrmust have exactly 1 argumentselectmust have exactly 2 argumentsstoremust have exactly 3 arguments- All other functions are uninterpreted (theory T_E)
# Comments start with # or //
# One literal per line
literal1
literal2
literal3
...
Literals can be separated by newlines or semicolons:
a = b ; b = c ; c != a
- Whitespace (spaces, tabs, newlines) is ignored except inside identifiers
- Use whitespace freely for readability
# Simple transitivity violation
a = b
b = c
c != a
Expected: UNSAT
# Congruence example
f(a) = f(b)
a != b
Expected: UNSAT (if f is injective, but we can't prove it without more constraints) Actually: SAT (f could map both a and b to same value despite a != b)
# Congruence that is UNSAT
f(a) = g(b)
a = b
g(a) != f(a)
Expected: UNSAT (by congruence and transitivity)
# car/cdr axiom example
x = cons(a, b)
car(x) != a
Expected: UNSAT (violates car(cons(a,b)) = a)
# Valid list constraint
x = cons(a, b)
car(x) = a
cdr(x) = b
Expected: SAT (consistent with axioms)
# Cyclic list
x = cons(a, x)
car(x) = a
Expected: SAT (cyclic lists are allowed)
# Basic array axiom
select(store(a, i, v), i) != v
Expected: UNSAT (violates read-over-write axiom)
# Array write different index
i != j
select(store(a, i, v), j) != select(a, j)
Expected: UNSAT (if i != j, then store doesn't affect index j)
# Multiple stores
a1 = store(a, i, v1)
a2 = store(a1, j, v2)
i != j
select(a2, i) != v1
Expected: UNSAT
# Combining lists and arrays
x = cons(a, b)
y = car(x)
z = select(arr, y)
z != select(arr, a)
Expected: UNSAT (because car(x) = a by axiom)
# Complex mixed example
arr1 = store(arr, i, v)
x = cons(i, j)
i2 = car(x)
v2 = select(arr1, i2)
v2 != v
Expected: UNSAT
Function application has highest precedence:
f(a) = g(b) # Parsed as: (f(a)) = (g(b))
The parser should provide clear error messages for:
- Syntax Errors:
a = # Missing right-hand side
f(a, ) = b # Missing argument
a b = c # Missing operator
- Arity Errors:
cons(a) = b # cons requires 2 arguments
car(a, b) = c # car requires 1 argument
select(a) = b # select requires 2 arguments
store(a, i) = b # store requires 3 arguments
- Invalid Characters:
a @ b = c # '@' is not valid
a = $b # '$' is not valid
-
Unused Variables:
- Variable appears in only one literal
- May indicate typo
-
Tautologies:
a = a(always true, can be ignored)
-
Trivial Conflicts:
a = banda != bin input (immediate UNSAT)
For compatibility with SMT-LIB benchmarks:
(set-logic QF-UF)
(declare-fun a () U)
(declare-fun b () U)
(declare-fun f (U) U)
(assert (= a b))
(assert (= (f a) (f b)))
(check-sat)Note: Supporting SMT-LIB is optional but useful for testing against standard benchmarks.
{
"literals": [
{"type": "eq", "left": "a", "right": "b"},
{"type": "neq", "left": {"func": "f", "args": ["a"]}, "right": {"func": "g", "args": ["b"]}}
]
}Note: JSON format is optional, mainly for integration with other tools.
java -jar solver.jar < input.txt
java -jar solver.jar input.txtecho "a = b; b = c; c != a" | java -jar solver.jarjava -jar solver.jar
> a = b
> b = c
> c != a
> :solve
UNSAT
> :quit| Token Type | Pattern | Example |
|---|---|---|
| IDENTIFIER | [a-zA-Z_][a-zA-Z0-9_]* |
a, f, var_1 |
| EQUALS | = |
= |
| NOT_EQUALS | != or ≠ |
!= |
| LPAREN | ( |
( |
| RPAREN | ) |
) |
| COMMA | , |
, |
| SEMICOLON | ; |
; |
| COMMENT | #[^\n]* or //[^\n]* |
# comment |
| WHITESPACE | [ \t\n\r]+ |
(ignored) |
| EOF | End of input |
public class Lexer {
private String input;
private int position;
public Token nextToken() {
skipWhitespaceAndComments();
if (isAtEnd()) return new Token(TokenType.EOF);
char c = peek();
// Check for multi-char operators
if (c == '!' && peekNext() == '=') {
advance(); advance();
return new Token(TokenType.NOT_EQUALS, "!=");
}
if (c == '=') {
advance();
return new Token(TokenType.EQUALS, "=");
}
// ... handle other tokens ...
if (isAlpha(c)) {
return identifier();
}
throw new ParseException("Unexpected character: " + c);
}
}public class Parser {
private Lexer lexer;
private Token currentToken;
// Parse: Literal = Term ('=' | '!=') Term
public Literal parseLiteral() {
Term left = parseTerm();
if (match(TokenType.EQUALS)) {
Term right = parseTerm();
return new EqualityLiteral(left, right);
} else if (match(TokenType.NOT_EQUALS)) {
Term right = parseTerm();
return new DisequalityLiteral(left, right);
}
throw new ParseException("Expected '=' or '!='");
}
// Parse: Term = Identifier [ '(' [Term (',' Term)*] ')' ]
public Term parseTerm() {
if (!match(TokenType.IDENTIFIER)) {
throw new ParseException("Expected identifier");
}
String id = currentToken.getValue();
advance();
if (match(TokenType.LPAREN)) {
// Function application
List<Term> args = parseArguments();
expect(TokenType.RPAREN);
return factory.createFunctionApp(id, args);
} else {
// Variable or constant
return factory.createVariable(id);
}
}
}- Simple equality:
a = b - Simple disequality:
a != b - Function application:
f(a, b) = g(c) - Nested functions:
f(g(a)) = h(b) - Lists:
cons(a, b) = x - Arrays:
select(store(a, i, v), i) = v - Multiple literals:
a = b; b = c - With comments:
# comment\na = b - Extra whitespace:
a = b
- Missing operator:
a b - Missing operand:
a = - Unmatched parens:
f(a = b - Invalid characters:
a @ b - Empty input: `` (should be accepted as empty set)