Compiling TLA⁺ expressions into Go expressions.
The output is a single Go.Expression, never a statement prelude: everything that needs
statements to express — the quantifiers' search loops, IF/CASE's laziness, EXCEPT's record
update — is wrapped in an immediately-applied Go.Expression.funcLit. That keeps this function
callable from any expression position (a branch's guard, an argument, another expression's
sub-term) without every caller having to thread a list of statements to emit first. funcLit
exists in Core.Go.Syntax precisely because these forms cannot be compiled without it.
Recurring conventions, all forced by the runtime library's own types:
- Everything of TLA⁺ type
Boolistlaplus.Bool, not Go'sbool.Boolis a defined type overbool, so Go's own&&/||/!still apply to it directly and their results staytlaplus.Bool; what does not apply is using one as anifcondition or handing one to a runtime predicate, both of which want a realbool. Those sites convert withbool(e), and anything producing a Gobool(SetIn,Eq,SetEq) converts back withtlaplus.Bool(e). - Literals go through the constructors, never composite literals.
tlaplus.MkInt(1)rather thanInt(1)(the arbitrary-precision representation is a struct),MkSet/MkSeqrather thanSet[τ]{…}/Seq[τ]{…}(a set literal may be unsorted or repeat an element; a sequence is 1-indexed with slot 0 unused). The one exception is the empty literal, which has nothing to infer a type parameter from and so is writtentlaplus.Set[τ]{}— trivially sorted, duplicate-free, and forSeqthe nil slice is already the valid empty sequence. - Types are read off the AST, never re-derived. Go's
funcliterals have mandatory signatures, so{e : x ∈ S},[x ∈ S ↦ e],IF/CASEand a recordEXCEPTall have to write out a type TLA⁺ never spells. The checker records each of them (Elaborator/Expressions.lean); this pass only reads them off the node it is compiling.f[e]'s head type is recorded for a different reason — it is a three-way dispatch, between a function application, a sequence index and a tuple projection, not a type to emit. - Operator applications dispatch on the head's
Origin.\in,=and friends are not Go functions with those names —x \in Sreverses its arguments intoSetIn(o, S, x)and=picks between the element dictionary'sEq,SetEqandSeqEqby operand type — so a builtin head is matched at the application site rather than compiled to a name and applied. A bare builtin reference (one passed around rather than applied) has no Go counterpart at all and is rejected. - Every comparison is a dictionary call, and the dictionary comes from the type.
Ord[T]is a struct of two functions, not an interface, so nothing is a method on the value being compared:x = yat typeτis⟦τ⟧ᴼʳᵈ.Eq(x, y), and every runtime operation that compares (MkSet,SetIn,SetUnion,FnApply, …) takes the dictionary as its first argument. Which dictionary aSetwas built with is not recorded in the set, so every operation on it must be handed the same one — guaranteed here by deriving both from the sameTyp, viaordDict. The operations that never compare (SetFilter,Choose,Cardinality, and all ofSequencesexcept equality) take none.
Deliberately not handled here, since they are not expression forms: operator and function
definitions (including MkRecFn for recursive ones), which live in Network2Go.Definition,
and the renaming of user-chosen names that collide after
capitalization.
bool(e) — Go's conversion out of the runtime's Bool, needed wherever a real bool is
required: an if condition, and every predicate the runtime library takes.
Cancels against tlaBool rather than nesting inside it. The two meet constantly — every runtime
predicate answers in Go's bool, gets wrapped so that the TLA⁺ expression has a TLA⁺ type, and is
then unwrapped again by whatever consumes it as a condition — and bool(tlaplus.Bool(e)) is
merely e, so a compiled guard reads as one comparison instead of three nested calls.
Equations
- One or more equations did not get rendered due to their size.
- Network2Go.goBool x✝ = (Go.Expression.var "bool").call [x✝]
Instances For
Compiles a checked TLA⁺ expression into the Go expression that computes it.
func(x τ) bool { return bool(P) } — the callback shape every runtime set operation takes.
SetFilter/Choose are declared over a Go bool predicate rather than a tlaplus.Bool one, so
the body converts.
\A x \in S : P and \E x \in S : P: delegates the search of S for the first
counterexample/witness to the runtime, the two quantifiers sharing one implementation
(SetForall/SetExists) the same way compilePredicate's callback shape is shared by every
other set operation here (SetFilter, Choose, SetMap, …). S's domain expression is
evaluated exactly once either way, since it is passed as an argument rather than inlined.
One ![e] = v / !.x = v override of an EXCEPT, following the path down and rebuilding on the
way back up. τ is the type of what base computes.
A function override is FnOverload, which keeps the fresh map header Insert returns so that the
override stays scoped to the overloaded copy. Records and tuples have no such helper — Go has no
functional update for a struct — so they go through a literal taking the struct by value: the
parameter is already a copy, so assigning into it cannot reach the original.
func(r T) T { r.X = …; return r }(base) — the struct update shared by record and tuple
overrides. Taking r by value is the whole trick: Go copies a struct argument, so the assignment
cannot be seen by whoever still holds base.
A builtin operator, applied. τ is the operator's own type, already specialized by
the checker, which is where the operand type = dispatches on comes from.
Every case producing a truth value converts back into tlaplus.Bool: the runtime's predicates
answer in Go's bool, but a TLA⁺ expression of type Bool must be one of the newtypes, since
everything in a specification has to satisfy Eq/Ord.
Equations
- One or more equations did not get rendered due to their size.
- Network2Go.compileIntrinsic pos "=" τ [x, y] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "=" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "/=" τ [x, y] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "/=" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "/\\" τ [x, y] = pure (Go.Expression.binary Go.BinaryOperator.and x y)
- Network2Go.compileIntrinsic pos "\\/" τ [x, y] = pure (Go.Expression.binary Go.BinaryOperator.or x y)
- Network2Go.compileIntrinsic pos "\\neg" τ [x] = pure (Go.Expression.unary Go.UnaryOperator.not x)
- Network2Go.compileIntrinsic pos "=>" τ [x, y] = pure (Go.Expression.binary Go.BinaryOperator.or (Go.Expression.unary Go.UnaryOperator.not x) y)
- Network2Go.compileIntrinsic pos "<=>" τ [x, y] = pure (Network2Go.tlaBool✝ (((Network2Go.tlaplusVar "BoolOrd").field "Eq").call [x, y]))
- Network2Go.compileIntrinsic pos "\\in" τ [x, s] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\in" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "\\notin" τ [x, s] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\notin" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "\\subseteq" τ [s, t] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\subseteq" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "\\cup" τ [s, t] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\cup" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "\\cap" τ [s, t] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\cap" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "\\" τ [s, t] = throw (N2GError.internalInvariantViolated pos (toString "'" ++ toString "\\" ++ toString "' has a non-operator type"))
- Network2Go.compileIntrinsic pos "DOMAIN" τ [f] = pure (Network2Go.tlaplusCall "Domain" [f])
- Network2Go.compileIntrinsic pos "StrToSeq" τ [s] = pure (Network2Go.tlaplusCall "StrToSeq" [s])
- Network2Go.compileIntrinsic pos name τ args = Network2Go.wrongArity✝ pos name args.length
Instances For
A reference to a builtin operator that carries no arguments — a value exported by a standard module, not something to apply.
Equations
- One or more equations did not get rendered due to their size.
- Network2Go.compileBuiltinVar pos "Naturals" "Nat" τ = pure (Network2Go.tlaplusCall "NatSet" [])
- Network2Go.compileBuiltinVar pos "Integers" "Int" τ = pure (Network2Go.tlaplusCall "IntSet" [])
- Network2Go.compileBuiltinVar pos "Bags" "EmptyBag" ρ.bag = do let __do_lift ← Network2Go.compileTyp ρ pure (Go.Expression.sliceLit (Network2Go.tlaplusTyp "Bag" [__do_lift]) [])
- Network2Go.compileBuiltinVar pos "Bags" name τ = throw (N2GError.unsupported pos (toString "Bags!" ++ toString name) "the Bags module has no runtime representation")
Instances For
A builtin operator from a standard module, applied. Naturals's comparisons all go through
tlaplus.IntOrd — Lt is one of the dictionary's two primitive fields, Gt/Le/Ge are methods
the runtime derives from it once. Arithmetic is not a comparison and takes no dictionary.
Instances For
compileExpr for a term straight from an earlier pass — its binders' bodies still carry de
Bruijn .bound indices. outer names any binders enclosing e that are not nodes within it: an
operator's or function's parameters, or a multicast filter's recipient, in source order. Every
caller outside this file goes through here so that compileExpr itself only ever meets .free
occurrences.
Equations
Instances For
compileExcept for a path and right-hand side straight from an earlier pass — opens every de
Bruijn index in the index expressions and the right-hand side against its binder hint first.
Equations
- One or more equations did not get rendered due to their size.