Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for compilation to TLA+ #2891

Merged
merged 10 commits into from
May 6, 2024
1 change: 1 addition & 0 deletions .unreleased/bug-fixes/fold-let-extraction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a problem where folds produced by the Shai command `TLA` had an invalid form and could not be parsed back (#2891)
1 change: 1 addition & 0 deletions .unreleased/bug-fixes/sanitize-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a problem where bindings from exists and forall operators where not properly sanitized before printing (#2891)
1 change: 1 addition & 0 deletions .unreleased/bug-fixes/slice-to-replaceAt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a problem where translation from `slice` to `replaceAt` added an incorrect increment to the last argument (#2891)
1 change: 1 addition & 0 deletions .unreleased/features/compilation-type-annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TLA+ modules produced by the Shai command `TLA` now include type annotations (#2891)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.forsyte.apalache.shai.v1

import java.io.{PrintWriter, StringWriter}
import scala.util.Try
import com.typesafe.scalalogging.Logger
import io.grpc.Status
Expand All @@ -16,7 +17,8 @@ import at.forsyte.apalache.tla.bmcmt.config.CheckerModule
import at.forsyte.apalache.tla.passes.imp.ParserModule
import at.forsyte.apalache.tla.passes.typecheck.TypeCheckerModule
import at.forsyte.apalache.tla.lir.TlaModule
import at.forsyte.apalache.io.lir.PrettyWriter
import at.forsyte.apalache.io.annotations.PrettyWriterWithAnnotations
import at.forsyte.apalache.io.annotations.store._

/**
* Provides the [[CmdExecutorService]]
Expand Down Expand Up @@ -99,7 +101,32 @@ class CmdExecutorService(logger: Logger) extends ZioCmdExecutor.ZCmdExecutor[ZEn
}

private def tlaModuleToJsonString(module: TlaModule): ujson.Value = {
ujson.Str(PrettyWriter.writeAsString(module))
val annotationStore = createAnnotationStore()

val buf = new StringWriter()
val prettyWriter = new PrettyWriterWithAnnotations(annotationStore, new PrintWriter(buf))
val modules_to_extend = List("Integers", "Sequences", "FiniteSets", "TLC", "Apalache", "Variants")
prettyWriter.write(module, modules_to_extend)
val moduleString = buf.toString()

val modifiedModule = extractLetFromFolds(moduleString)
ujson.Str(modifiedModule)
}

// Apalache inlines fold operator arguments as LET .. IN expressions, but this
// is not valid for SANY. In order to produce a valid TLA+ module from Quint
// files, we transform expressions like:
// ```
// ApaFoldSet(LET __QUINT_LAMBDAn(a, b) == c IN __QUINT_LAMBDAn, init, set)
// ```
//
// into:
// ```
// LET __QUINT_LAMBDAn(a, b) == c IN ApaFoldSet(__QUINT_LAMBDAn, init, set)
// ```
private def extractLetFromFolds(module: String): String = {
val regex = """(?s)(ApaFold[\w]*\()\s*(LET\s.*?\sIN\s+)(__QUINT_LAMBDA)"""
return module.replaceAll(regex, "$2 $1$3")
}

// Allows us to handle invalid protobuf messages on the ZIO level, before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class PrettyWriter(
prettyWriteDoc(declToDoc(decl) <> line <> line)
}

def write(expr: TlaEx): Unit = prettyWriteDoc(exToDoc((0, 0), expr, sanatizeID))
def write(expr: TlaEx): Unit = prettyWriteDoc(exToDoc((0, 0), expr, sanitizeID))

def writeComment(commentStr: String): Unit = {
prettyWriteDoc(wrapWithComment(commentStr) <> line)
Expand All @@ -103,14 +103,14 @@ class PrettyWriter(
def writeFooter(): Unit = prettyWriteDoc(moduleTerminalDoc)

private def moduleNameDoc(name: String): Doc = {
val middle = s" MODULE ${sanatizeID(name)} "
val middle = s" MODULE ${sanitizeID(name)} "
val nDashes = math.max(5, (layout.textWidth - middle.length) / 2) // int div rounds down
s"${List.fill(nDashes)("-").mkString}$middle${List.fill(nDashes)("-").mkString}" <> line
}

private def moduleExtendsDoc(moduleNames: List[String]): Doc =
if (moduleNames.isEmpty) emptyDoc
else line <> text("EXTENDS") <> space <> hsep(moduleNames.map(n => text(sanatizeID(n))), comma) <> line
else line <> text("EXTENDS") <> space <> hsep(moduleNames.map(n => text(sanitizeID(n))), comma) <> line

private def moduleTerminalDoc: Doc =
s"${List.fill(layout.textWidth)("=").mkString}" <> line
Expand Down Expand Up @@ -209,7 +209,7 @@ class PrettyWriter(
val sign = PrettyWriter.bindingOps(op)
val doc =
group(
group(text(sign) <> space <> text(x.toString) <> space <>
group(text(sign) <> space <> text(sanitizeID(x.toString)) <> space <>
text(PrettyWriter.binaryOps(TlaSetOper.in)) <> softline <>
exToDoc(op.precedence, set, nameResolver) <> text(":")) <>
nest(line <> exToDoc(op.precedence, pred, nameResolver))
Expand Down Expand Up @@ -538,7 +538,7 @@ class PrettyWriter(
}))
}

def declToDoc(decl: TlaDecl, nameResolver: String => String = sanatizeID): Doc = {
def declToDoc(decl: TlaDecl, nameResolver: String => String = sanitizeID): Doc = {
val annotations = declAnnotator(layout)(decl)

decl match {
Expand Down Expand Up @@ -680,15 +680,15 @@ class PrettyWriter(
private val validIdentifierPrefix = """_*[a-zA-Z]""".r

// Sanitize an identifier to ensure it can be read by TLC
private def sanatizeID(s: String): String = {
private def sanitizeID(s: String): String = {
val s0 = if (validIdentifierPrefix.findPrefixOf(s).isDefined) s else "id" + s
invalidIdentifierParts.replaceAllIn(s0, "_")
}

private def parseableName(name: String): String = {
// An operator name may contain '!' if it comes from an instance. Replace '!' with "_i_".
// Additionally, the name may contain '$', which is produced during preprocessing. Replace '$' with "_si_".
sanatizeID(name.replaceAll("!", "_i_").replaceAll("\\$", "_si_"))
sanitizeID(name.replaceAll("!", "_i_").replaceAll("\\$", "_si_"))
}

def close(): Unit = writer.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ class Quint(quintOutput: QuintOutput) {
case "foldl" => ternaryApp(opName, (seq, init, op) => tla.foldSeq(op, init, seq))
case "nth" => binaryApp(opName, (seq, idx) => tla.app(seq, incrTla(idx)))
case "replaceAt" => ternaryApp(opName, (seq, idx, x) => tla.except(seq, incrTla(idx), x))
case "slice" => ternaryApp(opName, (seq, from, to) => tla.subseq(seq, incrTla(from), incrTla(to)))
case "slice" => ternaryApp(opName, (seq, from, to) => tla.subseq(seq, incrTla(from), to))
case "select" => MkTla.selectSeq(opName, typeConv.convert(types(id).typ))
case "range" =>
binaryApp(opName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,16 @@ class TestPrettyWriter extends AnyFunSuite with BeforeAndAfterEach {
assert(expected == stringWriter.toString)
}

test("an exists with a binding with invalid characters") {
val writer = new PrettyWriter(printWriter, layout40)
val expr = exists(name("a::x"), name("a::y"), name("a::z"))
writer.write(expr)
printWriter.flush()
// a multi-line vertical box always breaks from the previous line, as otherwise it is incredibly hard to indent
val expected = "\\E a_x \\in a_y: a_z"
assert(expected == stringWriter.toString)
}

test("nested \\EE and \\AA") {
val writer = new PrettyWriter(printWriter, layout10)
val ex1 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ class TestQuintEx extends AnyFunSuite {

test("can convert builtin slice operator application") {
assert(convert(Q.app("slice", Q.intList, Q._0, Q._1)(
QuintSeqT(QuintIntT()))) == "Sequences!SubSeq(<<1, 2, 3>>, 0 + 1, 1 + 1)")
QuintSeqT(QuintIntT()))) == "Sequences!SubSeq(<<1, 2, 3>>, 0 + 1, 1)")
}

test("can convert builtin select operator application") {
Expand Down
Loading