Skip to content

Commit 51a4487

Browse files
mario-bucevvkuncak
authored andcommitted
Support for cvc5 and upgrade to Scala 3.3
1 parent ca9c022 commit 51a4487

24 files changed

+659
-495
lines changed

build.sbt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ IntegrationTest / javaOptions ++= Seq("-Xss128M")
88

99
IntegrationTest / fork := true
1010

11-
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" % "test;it"
11+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % "test;it"
1212

1313

1414
IntegrationTest / logBuffered := false
@@ -18,8 +18,7 @@ Test / parallelExecution := true
1818
lazy val commonSettings = Seq(
1919
organization := "com.regblanc",
2020
name := "scala-smtlib",
21-
scalaVersion := "2.13.6",
22-
crossScalaVersions := Seq("3.0.2")
21+
scalaVersion := "3.3.0",
2322
)
2423

2524
lazy val root = (project in file(".")).

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=1.5.6
1+
sbt.version=1.7.3

src/it/scala/smtlib/it/SmtLibRunnerTests.scala

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,70 @@ import interpreters._
1818
*
1919
* Compare the result of running command by command with an interpreter against
2020
* running the corresponding executable directly on the .smt2 files.
21-
*
22-
* TODO: proper way to display warning when not all tests are run because of not found executables.
2321
*/
2422
class SmtLibRunnerTests extends AnyFunSuite with TestHelpers {
2523

2624
filesInResourceDir("regression/smtlib/solving/all", _.endsWith(".smt2")).foreach(file => {
25+
val z3Test = s"With Z3: SMTLIB benchmark ${file.getPath}"
2726
if(isZ3Available) {
28-
test("With Z3: SMTLIB benchmark: " + file.getPath) {
27+
test(z3Test) {
2928
compareWithInterpreter(executeZ3)(getZ3Interpreter, file)
3029
}
30+
} else {
31+
ignore(z3Test) {}
3132
}
33+
34+
val cvc4Test = s"With CVC4: SMTLIB benchmark ${file.getPath}"
3235
if(isCVC4Available) {
33-
test("With CVC4: SMTLIB benchmark: " + file.getPath) {
36+
test(cvc4Test) {
3437
compareWithInterpreter(executeCVC4)(getCVC4Interpreter, file)
3538
}
39+
} else {
40+
ignore(cvc4Test) {}
41+
}
42+
43+
val cvc5Test = s"With cvc5: SMTLIB benchmark ${file.getPath}"
44+
if (isCVC5Available) {
45+
test(cvc5Test) {
46+
compareWithInterpreter(executeCVC5)(getCVC5Interpreter, file)
47+
}
48+
} else {
49+
ignore(cvc5Test) {}
3650
}
3751
})
3852

39-
if(isZ3Available) {
40-
filesInResourceDir("regression/smtlib/solving/z3", _.endsWith(".smt2")).foreach(file => {
41-
test("With Z3: SMTLIB benchmark: " + file.getPath) {
53+
// Z3-specific
54+
filesInResourceDir("regression/smtlib/solving/z3", _.endsWith(".smt2")).foreach(file => {
55+
val z3Test = "With Z3: SMTLIB benchmark: " + file.getPath
56+
if (isZ3Available) {
57+
test(z3Test) {
4258
compareWithInterpreter(executeZ3)(getZ3Interpreter, file)
4359
}
44-
})
45-
}
60+
} else {
61+
ignore(z3Test) {}
62+
}
63+
})
4664

47-
if(isCVC4Available) {
48-
filesInResourceDir("regression/smtlib/solving/cvc4", _.endsWith(".smt2")).foreach(file => {
49-
test("With CVC4: SMTLIB benchmark: " + file.getPath) {
65+
// CVC-specifc
66+
filesInResourceDir("regression/smtlib/solving/cvc4", _.endsWith(".smt2")).foreach(file => {
67+
val cvc4Test = "With CVC4: SMTLIB benchmark: " + file.getPath
68+
if (isCVC4Available) {
69+
test(cvc4Test) {
5070
compareWithWant(getCVC4Interpreter, file, new File(file.getPath + ".want"))
5171
}
52-
})
53-
}
72+
} else {
73+
ignore(cvc4Test) {}
74+
}
5475

76+
val cvc5Test = "With cvc5: SMTLIB benchmark: " + file.getPath
77+
if (isCVC5Available) {
78+
test(cvc5Test) {
79+
compareWithWant(getCVC5Interpreter, file, new File(file.getPath + ".want"))
80+
}
81+
} else {
82+
ignore(cvc5Test) {}
83+
}
84+
})
5585

5686
def compareWithInterpreter(executable: (File) => (String => Unit) => Unit)
5787
(interpreter: Interpreter, file: File) = {
@@ -79,9 +109,5 @@ class SmtLibRunnerTests extends AnyFunSuite with TestHelpers {
79109
assert(expected.trim === res.replace('\n', ' ').trim)
80110
}
81111
assert(parser.parseCommand === null)
82-
intercept[smtlib.parser.Parser.UnexpectedEOFException] {
83-
// There shouldn't be anything left on the interpreter parser (the solver process).
84-
interpreter.parser.parseSExpr
85-
}
86112
}
87113
}

src/it/scala/smtlib/it/TestHelpers.scala

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ trait TestHelpers {
2525
private val all: String => Boolean = (s: String) => true
2626
private val resourceDirHard = "src/it/resources/"
2727

28-
def filesInResourceDir(dir : String, filter : String=>Boolean = all) : Iterable[File] = {
28+
def filesInResourceDir(dir : String, filter : String=>Boolean = all) : Iterable[File] = {
2929
val d = this.getClass.getClassLoader.getResource(dir)
3030

3131
val asFile = if(d == null || d.getProtocol != "file") {
32-
// We are in Eclipse. The only way we are saved is by hard-coding the path
32+
// We are in Eclipse. The only way we are saved is by hard-coding the path
3333
new File(resourceDirHard + dir)
3434
} else new File(d.toURI())
3535

@@ -39,17 +39,18 @@ trait TestHelpers {
3939
else
4040
files.filter(f => filter(f.getPath()))
4141
}
42-
42+
4343
def getZ3Interpreter: Interpreter = Z3Interpreter.buildDefault
4444
def getCVC4Interpreter: Interpreter = CVC4Interpreter.buildDefault
45+
def getCVC5Interpreter: Interpreter = CVC5Interpreter.buildDefault
4546

4647
def isZ3Available: Boolean = try {
4748
val _: String = "z3 -help".!!
4849
true
4950
} catch {
5051
case _: Exception => false
5152
}
52-
53+
5354
def isCVC4Available: Boolean = try {
5455
// Make sure to pass a dummy option to cvc4, otherwise it starts in interactive mode (and does not exit)
5556
val _: String = "cvc4 --version".!!
@@ -58,6 +59,14 @@ trait TestHelpers {
5859
case _: Exception => false
5960
}
6061

62+
def isCVC5Available: Boolean = try {
63+
// Ditto for cvc5
64+
val _: String = "cvc5 --version".!!
65+
true
66+
} catch {
67+
case _: Exception => false
68+
}
69+
6170
def executeZ3(file: File)(f: (String) => Unit): Unit = {
6271
Seq("z3", "-smt2", file.getPath) ! ProcessLogger(f, s => ())
6372
}
@@ -66,5 +75,9 @@ trait TestHelpers {
6675
Seq("cvc4", "--lang", "smt", file.getPath) ! ProcessLogger(f, s => ())
6776
}
6877

78+
def executeCVC5(file: File)(f: (String) => Unit): Unit = {
79+
Seq("cvc5", "--lang", "smt", file.getPath) ! ProcessLogger(f, s => ())
80+
}
81+
6982
}
7083

src/it/scala/smtlib/it/TheoriesBuilderTests.scala

Lines changed: 107 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,62 @@
11
package smtlib
22
package it
33

4-
import scala.sys.process._
5-
64
import org.scalatest.funsuite.AnyFunSuite
7-
8-
import java.io.File
9-
import java.io.FileReader
10-
11-
import interpreters._
12-
13-
import trees.Terms._
14-
import trees.Commands._
15-
import trees.CommandsResponses._
5+
import smtlib.trees.Commands._
6+
import smtlib.trees.CommandsResponses._
7+
import smtlib.trees.Terms._
168

179

1810
/** Checks that formula build with theories module are correctly handled by solvers */
1911
class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
2012

13+
def mkTest(formula: Term, expectedStatus: Status, prefix: String): Unit = mkTest(Nil, formula, expectedStatus, prefix)
2114

22-
def mkTest(formula: Term, expectedStatus: Status, prefix: String) = {
23-
24-
if(isZ3Available) {
25-
test(prefix + ": with Z3") {
26-
val z3Interpreter = getZ3Interpreter
27-
val assertion = Assert(formula)
28-
assert(z3Interpreter.eval(assertion) === Success)
29-
val res = z3Interpreter.eval(CheckSat())
15+
def mkTest(cmds: List[Command], formula: Term, expectedStatus: Status, prefix: String): Unit = {
16+
def runAndCheck(mkInterp: => Interpreter): Unit = {
17+
val interp = mkInterp
18+
for (cmd <- cmds) {
19+
val res = interp.eval(cmd)
3020
res match {
31-
case CheckSatStatus(status) => assert(status === expectedStatus)
32-
case res => assert(false, "expected a check sat status, but got: " + res)
21+
case Success => ()
22+
case other => fail(s"expected a success, but got: $other")
3323
}
3424
}
25+
val assertion = Assert(formula)
26+
assert(interp.eval(assertion) === Success)
27+
val res = interp.eval(CheckSat())
28+
res match {
29+
case CheckSatStatus(status) => assert(status === expectedStatus)
30+
case other => fail(s"expected a check sat status, but got: $other")
31+
}
3532
}
3633

37-
if(isCVC4Available) {
38-
test(prefix + ": with CVC4") {
39-
val cvc4Interpreter = getCVC4Interpreter
40-
val assertion = Assert(formula)
41-
assert(cvc4Interpreter.eval(assertion) === Success)
42-
val res = cvc4Interpreter.eval(CheckSat())
43-
res match {
44-
case CheckSatStatus(status) => assert(status === expectedStatus)
45-
case res => assert(false, "expected a check sat status, but got: " + res)
46-
}
34+
val z3Test = prefix + ": with Z3"
35+
if (isZ3Available) {
36+
test(z3Test) {
37+
runAndCheck(getZ3Interpreter)
38+
}
39+
} else {
40+
ignore(z3Test) {}
41+
}
42+
43+
val cvc4Test = prefix + ": with CVC4"
44+
if (isCVC4Available) {
45+
test(cvc4Test) {
46+
runAndCheck(getCVC4Interpreter)
4747
}
48+
} else {
49+
ignore(cvc4Test) {}
4850
}
4951

52+
val cvc5Test = prefix + ": with cvc5"
53+
if (isCVC5Available) {
54+
test(cvc5Test) {
55+
runAndCheck(getCVC5Interpreter)
56+
}
57+
} else {
58+
ignore(cvc5Test) {}
59+
}
5060
}
5161

5262

@@ -146,8 +156,8 @@ class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
146156

147157
{
148158
import theories.Core.Equals
149-
import theories.Ints.NumeralLit
150159
import theories.FixedSizeBitVectors._
160+
import theories.Ints.NumeralLit
151161
val theoryString = "Theory of Bit Vectors"
152162
var counter = 0
153163
def uniqueName(): String = {
@@ -214,4 +224,69 @@ class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
214224
val f20 = Equals(Mul(BitVectorConstant(1, 32), BitVectorConstant(2, 32), BitVectorConstant(3, 32), BitVectorConstant(4, 32)), BitVectorConstant(24, 32))
215225
mkTest(f20, SatStatus, uniqueName())
216226
}
227+
228+
{
229+
import theories.Core._
230+
import theories.Ints._
231+
val theoryADTs = "Theory of ADTs"
232+
var counter = 0
233+
234+
def uniqueName(): String = {
235+
counter += 1
236+
"%d - %s".format(counter, theoryADTs)
237+
}
238+
239+
val list = SSymbol("MyList")
240+
val listSort = Sort(Identifier(list))
241+
val nil = SSymbol("Nil")
242+
val cons = SSymbol("Cons")
243+
val head = SSymbol("head")
244+
val tail = SSymbol("tail")
245+
val listCtors = List(
246+
Constructor(nil, Seq.empty),
247+
Constructor(cons, Seq((head, IntSort()), (tail, listSort)))
248+
)
249+
250+
def consApp(hd: Term, tl: Term): Term =
251+
FunctionApplication(
252+
QualifiedIdentifier(Identifier(cons), Some(listSort)),
253+
Seq(hd, tl)
254+
)
255+
256+
def nilApp: Term =
257+
QualifiedIdentifier(Identifier(nil), Some(listSort))
258+
259+
val x = QualifiedIdentifier(Identifier(SSymbol("x")), Some(IntSort()))
260+
val y = QualifiedIdentifier(Identifier(SSymbol("y")), Some(IntSort()))
261+
val z = QualifiedIdentifier(Identifier(SSymbol("z")), Some(listSort))
262+
val a = QualifiedIdentifier(Identifier(SSymbol("a")), Some(IntSort()))
263+
val b = QualifiedIdentifier(Identifier(SSymbol("b")), Some(IntSort()))
264+
val c = QualifiedIdentifier(Identifier(SSymbol("c")), Some(IntSort()))
265+
266+
val ids = Seq(x, y, z, a, b, c)
267+
val idsDecls = ids.map(id => DeclareFun(id.id.symbol, Seq.empty, id.sort.get))
268+
269+
mkTest(
270+
List(DeclareDatatypes(List((list, listCtors)))) ++ idsDecls,
271+
Equals(
272+
consApp(x, consApp(y, z)),
273+
consApp(a, consApp(b, consApp(c, nilApp)))
274+
),
275+
SatStatus,
276+
uniqueName()
277+
)
278+
279+
mkTest(
280+
List(DeclareDatatypes(List((list, listCtors)))) ++ idsDecls,
281+
And(
282+
Equals(
283+
consApp(x, consApp(y, z)),
284+
consApp(a, consApp(b, consApp(c, nilApp)))
285+
),
286+
Not(Equals(z, consApp(c, nilApp)))
287+
),
288+
UnsatStatus,
289+
uniqueName()
290+
)
291+
}
217292
}

src/main/scala/smtlib/Interpreter.scala

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,25 @@ object Interpreter {
4040
import java.io.BufferedReader
4141
import java.io.File
4242

43-
def execute(script: Script)(implicit interpreter: Interpreter): Unit = {
43+
def execute(script: Script)(using interpreter: Interpreter): Unit = {
4444
for(cmd <- script.commands)
4545
interpreter.eval(cmd)
4646
}
4747

48-
def execute(scriptReader: Reader)(implicit interpreter: Interpreter): Unit = {
49-
val parser = new Parser(new lexer.Lexer(scriptReader))
50-
val cmd: Command = null
51-
do {
52-
val cmd = parser.parseCommand
53-
if(cmd != null)
54-
interpreter.eval(cmd)
55-
} while(cmd != null)
56-
}
48+
def execute(scriptReader: Reader)(using Interpreter): Unit =
49+
execute(new Parser(new lexer.Lexer(scriptReader)))
5750

58-
def execute(file: File)(implicit interpreter: Interpreter): Unit = {
59-
val parser = new Parser(new lexer.Lexer(new BufferedReader(new FileReader(file))))
60-
var cmd: Command = null
61-
do {
62-
cmd = parser.parseCommand
51+
def execute(file: File)(using Interpreter): Unit =
52+
execute(new Parser(new lexer.Lexer(new BufferedReader(new FileReader(file)))))
53+
54+
private def execute(parser: Parser)(using interpreter: Interpreter): Unit = {
55+
// do-while, Scala 3 style
56+
// See https://docs.scala-lang.org/scala3/reference/dropped-features/do-while.html
57+
while ({
58+
val cmd = parser.parseCommand
6359
if(cmd != null)
6460
interpreter.eval(cmd)
65-
} while(cmd != null)
61+
cmd != null
62+
}) () // This trailing () is important! If we remove it, it messes with the next statement
6663
}
67-
6864
}

0 commit comments

Comments
 (0)