The Strata Core Language

The Strata Core Language Definition

Table of Contents

  1. 1. Introduction
  2. 2. Lambda
  3. 3. Imperative
  4. 4. Metadata

1. Introduction🔗

Strata aims to provide a foundation for representing the semantics of programs, specifications, protocols, architectures, and other aspects of large-scale distributed systems and their components. It achieves this through languages of two types. The first type, consisting of the single Strata Core language, provides a central hub that can serve as a connection point between multiple types of input artifact and multiple types of analysis, reducing the cost of implementing N analyses for M languages from N*M to N+M.

The second type consists of numerous Strata dialects. The Dialect Definition Mechanism, described here, provides a way to define the syntax and a simple type system for a dialect. At the moment, dialects do not directly have semantics (though we may add a mechanism for defining their semantics in the future) but instead are defined by translation to or from Strata Core. Said another way, each of these dialects is a different concrete way to write Strata programs, but all of these dialects are ultimately represented internally using the same Core language.

Dialects are used to describe both the initial artifacts being analyzed by Strata and more low-level representations of those artifacts used to communicate with external reasoning tools such as model checkers or SMT solvers. In both situations, Strata uses dialects as a mechanism for communicating with external tools (either language front ends or generic automated reasoning tools like SMT solvers).

The following "hourglass" diagram illustrates how various existing (blue) or potential (gray) input dialects could be translated into Strata Core and then into the input language for various back end tools. Solid lines indicate translation paths that exist (though experimentally in the connection between Strata Core and CBMC), and dotted lines indicate translations that illustrate the sorts of use cases we expect Strata to support but that haven't yet been implemented.

Strata hourglass diagram

The Strata Core language is constructed using a few building blocks that can be combined in different ways. This allows concrete dialects to systematically use different combinations that still share the majority of their implementation. In Lean (and in principle in most other source languages that could be used to process Strata programs), the type system can enforce various structural constraints, ensuring that only expected language constructs show up. The Strata Core language itself consists of an imperative statement type parameterized by an expression type, with various more fine-grained adjustments of other parameters.

The two fundamental building blocks of Strata Core are a representation of functional programs (Lambda), and a representation of imperative programs (Imperative). The Lambda language is parameterized by a type system and a set of built-in types and functions. The Imperative language is then parameterized by the type of expressions it allows in conditions, assignments, and so on. Currently, those expressions will almost always be some instantiation of Lambda. Both Core building blocks are parameterized by a metadata type, which by default is instantiated with a map from keys to structured values that can contain expressions (typically from Lambda).

The remainder of this document describes the current abstract syntax and semantics of Lambda and Imperative in detail, with direct reference to the Lean source code that defines these languages. We do not consider the Core language set in stone. It may evolve over time, particularly to add new fundamental constructs, and this document will be updated as it does. We intend for Strata Core to be close to a superset of B3, but it may at times make different choices to support its goal of being useful for a wide range of analyses, rather than being optimized for deductive verification. In particular, Strata aims to make it possible to encode most input artifacts without the need for axioms.

2. Lambda🔗

The Lambda language is a standard but generic implementation of the lambda calculus. It is parameterized by a type for metadata and the type of types (which may be Unit, to describe the untyped lambda calculus). It includes the standard constructs for constants, free and bound variables, abstractions, and applications. In addition, it includes a special type of constant, an operator, to represent built-in functions. It extends the standard lambda calculus by allowing quantifiers (since a key use of the language is to write logical predicates) and includes a constructor for if-then-else to allow it to have lazy semantics.

Although Lambda can be parameterized by an arbitrary type system, the Strata code base includes a formalization of a polymorphic Hindley-Milner type system and an implementation of an inference algorithm over the type LTy (described below). This allows universal quantification over types and the use of arbitrary named type constructors (as well as special support for bit vector types, to allow them to be parameterized by size).

2.1. Syntax🔗

The syntax of lambda expressions is provided by the LExpr type.

🔗inductive type
Lambda.LExpr (T : LExprParamsT) : Type
Lambda.LExpr (T : LExprParamsT) : Type

Lambda expressions with quantifiers.

Like Lean's own expressions, we use the locally nameless representation for this abstract syntax. See this paper for details.

We leave placeholders for type annotations only for constants (.const), operations (.op), binders (.abs, .quant), and free variables (.fvar).

LExpr is parameterized by LExprParamsT, which includes arbitrary metadata, user-allowed type annotations (optional), and special metadata to attach to Identifiers. Type inference adds any missing type annotations.

Constructors

const {T : LExprParamsT} (m : T.base.Metadata)
  (c : LConst) : LExpr T

A constant (in the sense of literals).

op {T : LExprParamsT} (m : T.base.Metadata)
  (o : Identifier T.base.IDMeta) (ty : Option T.TypeType) :
  LExpr T

A built-in operation, referred to by name.

bvar {T : LExprParamsT} (m : T.base.Metadata)
  (deBruijnIndex : Nat) : LExpr T

A bound variable, in de Bruijn form.

fvar {T : LExprParamsT} (m : T.base.Metadata)
  (name : Identifier T.base.IDMeta)
  (ty : Option T.TypeType) : LExpr T

A free variable, with an optional type annotation.

abs {T : LExprParamsT} (m : T.base.Metadata)
  (ty : Option T.TypeType) (e : LExpr T) : LExpr T

An abstraction, where ty the is (optional) type of bound variable.

quant {T : LExprParamsT} (m : T.base.Metadata)
  (k : QuantifierKind) (ty : Option T.TypeType)
  (trigger e : LExpr T) : LExpr T

A quantified expression, where k indicates whether it is universally or existentially quantified, ty is the type of bound variable, and trigger is a trigger pattern (primarily for use with SMT).

app {T : LExprParamsT} (m : T.base.Metadata)
  (fn e : LExpr T) : LExpr T

A function application.

ite {T : LExprParamsT} (m : T.base.Metadata)
  (c t e : LExpr T) : LExpr T

A conditional expression. This is a constructor rather than a built-in operation because it occurs so frequently.

eq {T : LExprParamsT} (m : T.base.Metadata)
  (e1 e2 : LExpr T) : LExpr T

An equality expression. This is a constructor rather than a built-in operation because it occurs so frequently.

Identifiers in lambda expressions, using the Identifier type, can be annotated with metadata.

🔗structure
Lambda.Identifier (IDMeta : Type) : Type
Lambda.Identifier (IDMeta : Type) : Type

Identifiers with a name and additional metadata

Constructor

Lambda.Identifier.mk

Fields

name : String

A unique name.

metadata : IDMeta

Any additional metadata that it would be useful to attach to an identifier.

Specific constructors exist for constants of various scalar types, including booleans, bit vectors, integers, reals, and strings.

🔗inductive type
Lambda.LConst : Type
Lambda.LConst : Type

Lambda constants.

Constants are integers, strings, reals, bitvectors of a fixed length, or booleans.

Constructors

intConst (i : Int) : LConst

An unbounded integer constant.

strConst (s : String) : LConst

A string constant, using Lean's String type for a sequence of Unicode code points encoded with UTF-8.

realConst (r : Rat) : LConst

A real constant, represented as a rational number.

bitvecConst (n : Nat) (b : BitVec n) : LConst

A bit vector constant, represented using Lean's BitVec type.

boolConst (b : Bool) : LConst

A Boolean constant.

The LExpr type can be parameterized by the type used to represent normal metadata and the type used to represent identifier metadata, as well as the type of types.

🔗structure
Lambda.LExprParams : Type 1
Lambda.LExprParams : Type 1

Expected interface for pure expressions that can be used to specialize the Imperative dialect.

Constructor

Lambda.LExprParams.mk

Fields

Metadata : Type

The type of metadata allowed on expressions.

IDMeta : Type

The type of metadata allowed on identifiers.

🔗structure
Lambda.LExprParamsT : Type 1
Lambda.LExprParamsT : Type 1

Extended LExprParams that includes TypeType parameter.

Constructor

Lambda.LExprParamsT.mk

Fields

base : LExprParams

The base parameters, with the types for expression and identifier metadata.

TypeType : Type

The type of types used to annotate expressions.

2.2. Type System🔗

Although LExpr can be parameterized by an arbitrary type system, Strata currently implements one, based on the types LMonoTy and LTy.

The first, LMonoTy, represents monomorphic types. It's a separate type because some contexts allow only monomorphic types.

🔗inductive type
Lambda.LMonoTy : Type
Lambda.LMonoTy : Type

Monomorphic types in Lambda. Note that all free type variables (.ftvar) are implicitly universally quantified.

Constructors

ftvar (name : TyIdentifier) : LMonoTy

A type variable.

tcons (name : String) (args : List LMonoTy) : LMonoTy

A type constructor.

bitvec (size : Nat) : LMonoTy

A bit vector type. This is a special case so that it can be parameterized by a size.

Type variables in LMonoTy use the TyIdentifier type.

🔗def
Lambda.TyIdentifier : Type
Lambda.TyIdentifier : Type

Type identifiers. For now, these are just strings.

The LTy type allows monomorphic types to be wrapped in universal type quantifiers that bind these type variables, creating polymorphic types.

🔗inductive type
Lambda.LTy : Type
Lambda.LTy : Type

Polymorphic type schemes in Lambda.

Constructors

forAll (vars : List TyIdentifier) (ty : LMonoTy) : LTy

A type containing universally quantified type variables.

An expression LExpr parameterized by LTy is well-typed according to the HasType relation. This relation depends on two types of context.

The first of these, LContext, contains information that is typically constant during expression type checking, but may be extended during statement type checking (e.g., when local function declarations add new functions to the factory). This includes information about built-in functions, using the Factory type, and built-in types, using the TypeFactory type. Built-in functions optionally include concrete evaluation functions, which can be used in the operational semantics described below.

🔗structure
Lambda.LContext (T : LExprParams) : Type
Lambda.LContext (T : LExprParams) : Type

Context data for type checking: a factory of user-specified functions and data structures for ensuring unique names of types and functions.

This context is typically constant during expression type checking, but may be extended during statement type checking when local function declarations (funcDecl) add new functions to the factory.

Invariant: all functions defined in TypeFactory.genFactory for datatypes should be in functions.

Constructor

Lambda.LContext.mk

Fields

functions : Factory T

Descriptions of all built-in functions.

datatypes : TypeFactory

Descriptions of all built-in datatypes.

knownTypes : KnownTypes

A list of known built-in types.

idents : Identifiers T.IDMeta

The set of identifiers that have been seen or generated so far.

The second context includes two pieces of data that change throughout the type checking process: a map from free variables in expressions to types, and a list of type aliases including the name and definition of each alias.

🔗structure
Lambda.TContext (IDMeta : Type) : Type
Lambda.TContext (IDMeta : Type) : Type

A type context describing the types of free variables and the mappings of type aliases.

Constructor

Lambda.TContext.mk

Fields

types : Maps (Identifier IDMeta) LTy

A map from free variables in expressions (i.e., LExpr.fvars) to their type schemes. This is essentially a stack to account for variable scopes.

aliases : List TypeAlias

A map from type synonym names to their corresponding type definitions. We expect these type definitions to not be aliases themselves, to avoid any cycles in the map (see TEnv.addTypeAlias).

Given these two pieces of context, the HasType relation describes the valid type of each expression form.

🔗inductive predicate
Lambda.LExpr.HasType {T : LExprParams} [DecidableEq T.IDMeta] (C : LContext T) : TContext T.IDMeta LExpr T.mono LTy Prop
Lambda.LExpr.HasType {T : LExprParams} [DecidableEq T.IDMeta] (C : LContext T) : TContext T.IDMeta LExpr T.mono LTy Prop

Typing relation for LExprs with respect to LTy.

The typing relation is parameterized by two contexts. An LContext contains known types and functions while a TContext associates free variables with their types.

Constructors

tbool_const {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (b : Bool) :
  C.knownTypes.containsName "bool" = true 
    LExpr.HasType C Γ (LExpr.boolConst m b)
      (LTy.forAll [] LMonoTy.bool)

A boolean constant has type .bool if bool is a known type in this context.

tint_const {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (n : Int) :
  C.knownTypes.containsName "int" = true 
    LExpr.HasType C Γ (LExpr.intConst m n)
      (LTy.forAll [] LMonoTy.int)

An integer constant has type .int if int is a known type in this context.

treal_const {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (r : Rat) :
  C.knownTypes.containsName "real" = true 
    LExpr.HasType C Γ (LExpr.realConst m r)
      (LTy.forAll [] LMonoTy.real)

A real constant has type .real if real is a known type in this context.

tstr_const {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (s : String) :
  C.knownTypes.containsName "string" = true 
    LExpr.HasType C Γ (LExpr.strConst m s)
      (LTy.forAll [] LMonoTy.string)

A string constant has type .string if string is a known type in this context.

tbitvec_const {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (n : Nat) (b : BitVec n) :
  C.knownTypes.containsName "bitvec" = true 
    LExpr.HasType C Γ (LExpr.bitvecConst m n b)
      (LTy.forAll [] (LMonoTy.bitvec n))

A bit vector constant of size n has type .bitvec n if bitvec is a known type in this context.

tvar {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata)
  (x : Identifier T.mono.base.IDMeta) (ty : LTy) :
  Γ.types.find? x = some ty 
    LExpr.HasType C Γ (LExpr.fvar m x none) ty

An un-annotated variable has the type recorded for it in Γ, if any.

tvar_annotated {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata)
  (x : Identifier T.mono.base.IDMeta) (ty_o : LTy)
  (ty_s : LMonoTy) (tys : List LMonoTy) :
  Γ.types.find? x = some ty_o 
    tys.length = ty_o.boundVars.length 
      LExpr.LTy.openFull ty_o tys = ty_s 
        LExpr.HasType C Γ (LExpr.fvar m x (some ty_s))
          (LTy.forAll [] ty_s)

An annotated free variable has its claimed type ty_s if ty_s is an instantiation of the type ty_o recorded for it in Γ.

tabs {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (x : IdentT LMonoTy T.IDMeta)
  (x_ty : LTy)
  (e : LExpr { base := T, TypeType := LMonoTy })
  (e_ty : LTy) (o : Option LMonoTy) :
  LExpr.fresh x e 
     (hx : x_ty.isMonoType = true)
      (he : e_ty.isMonoType = true),
      LExpr.HasType C
          { types := Γ.types.insert x.fst x_ty,
            aliases := Γ.aliases }
          (LExpr.varOpen 0 x e) e_ty 
        o = none  o = some (x_ty.toMonoType hx) 
          LExpr.HasType C Γ (LExpr.abs m o e)
            (LTy.forAll []
              (LMonoTy.tcons "arrow"
                [x_ty.toMonoType hx, e_ty.toMonoType he]))

An abstraction λ x.e has type x_ty e_ty if the claimed type of x is x_ty or None and if e has type e_ty when Γ is extended with the binding (x x_ty).

tapp {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (e1 e2 : LExpr T.mono)
  (t1 t2 : LTy) (h1 : t1.isMonoType = true)
  (h2 : t2.isMonoType = true) :
  LExpr.HasType C Γ e1
      (LTy.forAll []
        (LMonoTy.tcons "arrow"
          [t2.toMonoType h2, t1.toMonoType h1])) 
    LExpr.HasType C Γ e2 t2 
      LExpr.HasType C Γ (LExpr.app m e1 e2) t1

An application e₁e₂ has type t1 if e₁ has type t2 t1 and e₂ has type t2.

tinst {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (e : LExpr T.mono) (ty e_ty : LTy) (x : TyIdentifier)
  (x_ty : LMonoTy) :
  LExpr.HasType C Γ e ty 
    e_ty = LExpr.LTy.open x x_ty ty 
      LExpr.HasType C Γ e e_ty

If expression e has type ty and ty is more general than e_ty, then e has type e_ty (i.e. we can instantiate ty with e_ty).

tgen {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (e : LExpr T.mono) (a : TyIdentifier) (ty : LTy) :
  LExpr.HasType C Γ e ty 
    TContext.isFresh a Γ 
      LExpr.HasType C Γ e (LExpr.LTy.close a ty)

If e has type ty, it also has type a. ty as long as a is fresh. For instance, (·ftvar "a") (.ftvar "a") (or a a) can be generalized to (.btvar 0) (.btvar 0) (or a. a a), assuming a is not in the context.

tif {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (c e1 e2 : LExpr T.mono)
  (ty : LTy) :
  LExpr.HasType C Γ c (LTy.forAll [] LMonoTy.bool) 
    LExpr.HasType C Γ e1 ty 
      LExpr.HasType C Γ e2 ty 
        LExpr.HasType C Γ (LExpr.ite m c e1 e2) ty

If e1 and e2 have the same type ty, and c has type .bool, then .ite c e1 e2 has type ty.

teq {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (e1 e2 : LExpr T.mono)
  (ty : LTy) :
  LExpr.HasType C Γ e1 ty 
    LExpr.HasType C Γ e2 ty 
      LExpr.HasType C Γ (LExpr.eq m e1 e2)
        (LTy.forAll [] LMonoTy.bool)

If e1 and e2 have the same type ty, then .eq e1 e2 has type .bool.

tquant {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (k : QuantifierKind)
  (tr : LExpr { base := T, TypeType := LMonoTy })
  (tr_ty : LTy) (x : IdentT LMonoTy T.IDMeta) (x_ty : LTy)
  (e : LExpr { base := T, TypeType := LMonoTy })
  (o : Option LMonoTy) :
  LExpr.fresh x e 
     (hx : x_ty.isMonoType = true),
      LExpr.HasType C
          { types := Γ.types.insert x.fst x_ty,
            aliases := Γ.aliases }
          (LExpr.varOpen 0 x e)
          (LTy.forAll [] LMonoTy.bool) 
        LExpr.HasType C
            { types := Γ.types.insert x.fst x_ty,
              aliases := Γ.aliases }
            (LExpr.varOpen 0 x tr) tr_ty 
          o = none  o = some (x_ty.toMonoType hx) 
            LExpr.HasType C Γ (LExpr.quant m k o tr e)
              (LTy.forAll [] LMonoTy.bool)

A quantifier / {x: tr}.e has type bool if the claimed type of x is x_ty or None, and if, when Γ is extended with the binding (x x_ty), e has type bool and tr is well-typed.

top {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (f : LFunc T)
  (op : T.Identifier) (ty : LTy) :
  Array.find? (fun fn => fn.name == op) C.functions =
      some f 
    f.type = Except.ok ty 
      LExpr.HasType C Γ (LExpr.op m op none) ty

An un-annotated operator has the type recorded for it in C.functions, if any.

top_annotated {T : LExprParams} [DecidableEq T.IDMeta]
  {C : LContext T} (Γ : TContext T.IDMeta)
  (m : T.mono.base.Metadata) (f : LFunc T)
  (op : T.Identifier) (ty_o : LTy) (ty_s : LMonoTy)
  (tys : List LMonoTy) :
  Array.find? (fun fn => fn.name == op) C.functions =
      some f 
    f.type = Except.ok ty_o 
      tys.length = ty_o.boundVars.length 
        LExpr.LTy.openFull ty_o tys = ty_s 
          LExpr.HasType C Γ (LExpr.op m op (some ty_s))
            (LTy.forAll [] ty_s)

Similarly to free variables, an annotated operator has its claimed type ty_s if ty_s is an instantiation of the type ty_o recorded for it in C.functions.

2.3. Operational Semantics🔗

The semantics of the LExpr type are specified in a standard way using the small-step inductive relation Lambda.Step. This relation is parameterized by a Factory, which describes built-in functions via an optional body and/or evaluation function.

🔗inductive predicate
Lambda.Step {Tbase : LExprParams} [DecidableEq Tbase.IDMeta] (F : Factory Tbase) (rf : Env Tbase) : LExpr Tbase.mono LExpr Tbase.mono Prop
Lambda.Step {Tbase : LExprParams} [DecidableEq Tbase.IDMeta] (F : Factory Tbase) (rf : Env Tbase) : LExpr Tbase.mono LExpr Tbase.mono Prop

A small-step semantics for LExpr.

Currently only defined for expressions paremeterized by LMonoTy, but it will be expanded to an arbitrary type in the future.

The order of constructors matter because the constructor tactic will rely on it.

This small-step definitions faithfully follows the behavior of LExpr.eval, except that:

  1. This inductive definition may get stuck early when there is no assignment to a free variable available.

  2. This semantics does not describe how metadata must change, because metadata must not affect evaluation semantics. Different concrete evaluators like LExpr.eval can have different strategy for updating metadata.

Constructors

expand_fvar {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  {m : Tbase.mono.base.Metadata}
  {ty : Option Tbase.mono.TypeType} (x : Tbase.Identifier)
  (e : LExpr Tbase.mono) :
  rf x = some e  Step F rf (LExpr.fvar m x ty) e

A free variable. Stuck if fvar does not exist in FreeVarMap.

beta {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  {m1 m2 : Tbase.mono.base.Metadata}
  {ty : Option Tbase.mono.TypeType}
  (e1 v2 eres : LExpr Tbase.mono) :
  LExpr.isCanonicalValue F v2 = true 
    eres = LExpr.subst (fun x => v2) e1 
      Step F rf (LExpr.app m1 (LExpr.abs m2 ty e1) v2) eres

Call-by-value semantics: beta reduction.

reduce_2 {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  {m m' : Tbase.mono.base.Metadata}
  (v1 e2 e2' : LExpr Tbase.mono) :
  LExpr.isCanonicalValue F v1 = true 
    Step F rf e2 e2' 
      Step F rf (LExpr.app m v1 e2) (LExpr.app m' v1 e2')

Call-by-value semantics: argument evaluation.

reduce_1 {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  {m m' : Tbase.mono.base.Metadata}
  (e1 e1' e2 : LExpr Tbase.mono) :
  Step F rf e1 e1' 
    Step F rf (LExpr.app m e1 e2) (LExpr.app m' e1' e2)

Call-by-value semantics: function evaluation.

ite_reduce_then {Tbase : LExprParams}
  [DecidableEq Tbase.IDMeta] {F : Factory Tbase}
  {rf : Env Tbase} {m mc : Tbase.mono.base.Metadata}
  (ethen eelse : LExpr Tbase.mono) :
  Step F rf
    (LExpr.ite m (LExpr.const mc (LConst.boolConst true))
      ethen eelse)
    ethen

Lazy evaluation of ite: condition is true. To evaluate ite x e1 e2, do not first evaluate e1 and e2. In other words, ite x e1 e2 is interpreted as ite x (λ.e1) (λ.e2).

ite_reduce_else {Tbase : LExprParams}
  [DecidableEq Tbase.IDMeta] {F : Factory Tbase}
  {rf : Env Tbase} {m mc : Tbase.mono.base.Metadata}
  (ethen eelse : LExpr Tbase.mono) :
  Step F rf
    (LExpr.ite m (LExpr.const mc (LConst.boolConst false))
      ethen eelse)
    eelse

Lazy evaluation of ite: condition is false. To evaluate ite x e1 e2, do not first evaluate e1 and e2. In other words, ite x e1 e2 is interpreted as ite x (λ.e1) (λ.e2).

ite_reduce_cond {Tbase : LExprParams}
  [DecidableEq Tbase.IDMeta] {F : Factory Tbase}
  {rf : Env Tbase} {m m' : Tbase.mono.base.Metadata}
  (econd econd' ethen eelse : LExpr Tbase.mono) :
  Step F rf econd econd' 
    Step F rf (LExpr.ite m econd ethen eelse)
      (LExpr.ite m' econd' ethen eelse)

Evaluation of ite condition.

eq_reduce {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  {mc m : Tbase.mono.base.Metadata}
  (e1 e2 eres : LExpr Tbase.mono)
  (H1 : LExpr.isCanonicalValue F e1 = true)
  (H2 : LExpr.isCanonicalValue F e2 = true) :
  eres =
      LExpr.const mc
        (LConst.boolConst (LExpr.eql F e1 e2 H1 H2)) 
    Step F rf (LExpr.eq m e1 e2) eres

Evaluation of equality. Reduce after both operands evaluate to values.

eq_reduce_lhs {Tbase : LExprParams}
  [DecidableEq Tbase.IDMeta] {F : Factory Tbase}
  {rf : Env Tbase} {m m' : Tbase.mono.base.Metadata}
  (e1 e1' e2 : LExpr Tbase.mono) :
  Step F rf e1 e1' 
    Step F rf (LExpr.eq m e1 e2) (LExpr.eq m' e1' e2)

Evaluation of the left-hand side of an equality.

eq_reduce_rhs {Tbase : LExprParams}
  [DecidableEq Tbase.IDMeta] {F : Factory Tbase}
  {rf : Env Tbase} {m m' : Tbase.mono.base.Metadata}
  (v1 e2 e2' : LExpr Tbase.mono) :
  LExpr.isCanonicalValue F v1 = true 
    Step F rf e2 e2' 
      Step F rf (LExpr.eq m v1 e2) (LExpr.eq m' v1 e2')

Evaluation of the right-hand side of an equality.

expand_fn {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase}
  (e callee fnbody new_body : LExpr Tbase.mono)
  (args :
    List (LExpr { base := Tbase, TypeType := LMonoTy }))
  (fn : LFunc Tbase) :
  F.callOfLFunc e = some (callee, args, fn) 
    fn.body = some fnbody 
      new_body =
          fnbody.substFvars (fn.inputs.keys.zip args) 
        Step F rf e new_body

Evaluate a built-in function when a body expression is available in the Factory argument F. This is consistent with what LExpr.eval does (modulo the inline flag). Note that it might also be possible to evaluate with eval_fn. A key correctness property is that doing so will yield the same result. Note that this rule does not enforce an evaluation order.

eval_fn {Tbase : LExprParams} [DecidableEq Tbase.IDMeta]
  {F : Factory Tbase} {rf : Env Tbase} {m : Tbase.Metadata}
  (e callee e' : LExpr Tbase.mono)
  (args :
    List (LExpr { base := Tbase, TypeType := LMonoTy }))
  (fn : LFunc Tbase)
  (denotefn :
    Tbase.Metadata 
      List (LExpr Tbase.mono)  Option (LExpr Tbase.mono)) :
  F.callOfLFunc e = some (callee, args, fn) 
    fn.concreteEval = some denotefn 
      some e' = denotefn m args  Step F rf e e'

Evaluate a built-in function when a concrete evaluation function is available in the Factory argument F. Note that it might also be possible to evaluate with expand_fn. A key correctness property is that doing so will yield the same result. Note that this rule does not enforce an evaluation order.

Typically we will want to talk about arbitrarily long sequences of steps, such as from an initial expression to a value. The Lambda.StepStar relation describes the reflexive, transitive closure of the Lambda.Step relation.

🔗def
Lambda.StepStar {Tbase : LExprParams} [DecidableEq Tbase.IDMeta] (F : Factory Tbase) (rf : Env Tbase) : LExpr Tbase.mono LExpr Tbase.mono Prop
Lambda.StepStar {Tbase : LExprParams} [DecidableEq Tbase.IDMeta] (F : Factory Tbase) (rf : Env Tbase) : LExpr Tbase.mono LExpr Tbase.mono Prop

Multi-step execution: reflexive transitive closure of single steps.

3. Imperative🔗

The Imperative language is a standard core imperative calculus, parameterized by a type of expressions and divided into two pieces: commands and statements. Commands represent atomic operations that do not induce control flow (except possibly in the form of procedure calls that follow a stack discipline, though the current core set of commands does not include calls). Statements are parameterized by a command type and describe the control flow surrounding those commands. Currently, Imperative has structured, deterministic statements, each of which can be: a command, a sequence of statements in a block, a deterministic conditional, a deterministic loop with a condition, or a forward goto statement. (Note: we plan to replace goto with a block exit statement, and have a separate unstructured CFG representation.)

We plan to add non-deterministic statements, as in Kleene Algebra with Tests, and support a translation from structured deterministic statements into structured non-deterministic statements.

We also expect to add unstructured control-flow graphs where each basic block consists of a sequence of commands followed by a terminator command. A terminator command can be: a conditional jump to one of two blocks, termination of execution, or a non-deterministic jump to any one of an arbitrary number of successor blocks.

3.1. Command Syntax🔗

The core built-in set of commands includes variable initializations, deterministic assignments, non-deterministic assignments ("havoc"), assertions, and assumptions.

🔗inductive type
Imperative.Cmd (P : PureExpr) : Type
Imperative.Cmd (P : PureExpr) : Type

A an atomic command in the Imperative dialect.

Commands don't create local control flow, and are typically used as a parameter to Imperative.Stmt or other similar types.

Constructors

init {P : PureExpr} (name : P.Ident) (ty : P.Ty)
  (e : P.Expr) (md : MetaData P := MetaData.empty) : Cmd P

Define a variable called name with type ty and initial value e. Note: we may make the initial value optional.

set {P : PureExpr} (name : P.Ident) (e : P.Expr)
  (md : MetaData P := MetaData.empty) : Cmd P

Assign e to a pre-existing variable name.

havoc {P : PureExpr} (name : P.Ident)
  (md : MetaData P := MetaData.empty) : Cmd P

Assigns an arbitrary value to an existing variable name.

assert {P : PureExpr} (label : String) (b : P.Expr)
  (md : MetaData P := MetaData.empty) : Cmd P

Checks if condition b is true on all paths on which this command is encountered. Reports an error if b does not hold on any of these paths.

assume {P : PureExpr} (label : String) (b : P.Expr)
  (md : MetaData P := MetaData.empty) : Cmd P

Ignore any execution state in which b is not true.

cover {P : PureExpr} (label : String) (b : P.Expr)
  (md : MetaData P := MetaData.empty) : Cmd P

Checks if there exists a path that reaches this command and condition b is true. Reports an error otherwise. This is the dual of assert, and can be used for coverage analysis.

3.2. Command Operational Semantics🔗

The semantics of commands are specified in terms of how they interact with a program state, written σ. A state can be applied to a variable to obtain its current value. And an expression e can be evaluated using the evaluation function in a given state: δ σ e gives the result of evaluating e in state σ. This generic description allows the details of the program state representation to vary, as long as it supports these operations.

Given a state σ, the InitState relation describes how a variable obtains its initial value.

🔗inductive predicate
Imperative.InitState (P : PureExpr) : SemanticStore P P.Ident P.Expr SemanticStore P Prop
Imperative.InitState (P : PureExpr) : SemanticStore P P.Ident P.Expr SemanticStore P Prop

Abtract variable initialization.

This does not specify how σ is represented, only what it maps each variable to.

Constructors

init {P : PureExpr} {σ σ' : SemanticStore P} {x : P.Ident}
  {v : P.Expr} :
  σ x = none 
    σ' x = some v 
      (∀ (y : P.Ident), x  y  σ' y = σ y) 
        InitState P σ x v σ'

The state σ' is be equivalent to σ except at x, where it maps to v. Requires that x mapped to nothing beforehand.

The UpdateState relation then describes how a variable's value can change.

🔗inductive predicate
Imperative.UpdateState (P : PureExpr) : SemanticStore P P.Ident P.Expr SemanticStore P Prop
Imperative.UpdateState (P : PureExpr) : SemanticStore P P.Ident P.Expr SemanticStore P Prop

Abstract variable update.

This does not specify how σ is represented, only what it maps each variable to.

Constructors

update {P : PureExpr} {σ σ' : SemanticStore P} {x : P.Ident}
  {v v' : P.Expr} :
  σ x = some v' 
    σ' x = some v 
      (∀ (y : P.Ident), x  y  σ' y = σ y) 
        UpdateState P σ x v σ'

The state σ' is be equivalent to σ except at x, where it maps to v. Requires that x mapped to something beforehand.

Given these two state relations, the semantics of each command is specified in a standard way.

🔗inductive predicate
Imperative.EvalCmd (P : PureExpr) [HasFvar P] [HasBool P] [HasNot P] : SemanticEval P SemanticStore P Cmd P SemanticStore P Prop
Imperative.EvalCmd (P : PureExpr) [HasFvar P] [HasBool P] [HasNot P] : SemanticEval P SemanticStore P Cmd P SemanticStore P Prop

An inductively-defined operational semantics for Cmd that depends on variable lookup (σ) and expression evaluation (δ) functions. Commands do not modify the evaluator - only funcDecl statements do.

Constructors

eval_init {P : PureExpr} [HasFvar P] [HasBool P] [HasNot P]
  {x✝ : P.Ty} {x✝¹ : MetaData P} {δ : SemanticEval P}
  {σ : SemanticStore P} {x : P.Ident} {v : P.Expr}
  {σ' : SemanticStore P} {e : P.Expr} :
  δ σ e = some v 
    InitState P σ x v σ' 
      WellFormedSemanticEvalVar δ 
        EvalCmd P δ σ (Cmd.init x x✝ e x✝¹) σ'

If e evaluates to a value v, initialize x according to InitState.

eval_set {P : PureExpr} [HasFvar P] [HasBool P] [HasNot P]
  {x✝ : MetaData P} {δ : SemanticEval P}
  {σ : SemanticStore P} {x : P.Ident} {v : P.Expr}
  {σ' : SemanticStore P} {e : P.Expr} :
  δ σ e = some v 
    UpdateState P σ x v σ' 
      WellFormedSemanticEvalVar δ 
        EvalCmd P δ σ (Cmd.set x e x✝) σ'

If e evaluates to a value v, assign x according to UpdateState.

eval_havoc {P : PureExpr} [HasFvar P] [HasBool P] [HasNot P]
  {x✝ : MetaData P} {σ : SemanticStore P} {x : P.Ident}
  {v : P.Expr} {σ' : SemanticStore P} {δ : SemanticEval P} :
  UpdateState P σ x v σ' 
    WellFormedSemanticEvalVar δ 
      EvalCmd P δ σ (Cmd.havoc x x✝) σ'

Assign x an arbitrary value v according to UpdateState.

eval_assert {P : PureExpr} [HasFvar P] [HasBool P]
  [HasNot P] {x✝ : String} {x✝¹ : MetaData P}
  {δ : SemanticEval P} {σ : SemanticStore P} {e : P.Expr} :
  δ σ e = some HasBool.tt 
    WellFormedSemanticEvalBool δ 
      EvalCmd P δ σ (Cmd.assert x✝ e x✝¹) σ

If e evaluates to true in σ, evaluate to the same σ. This semantics does not have a concept of an erroneous execution.

eval_assume {P : PureExpr} [HasFvar P] [HasBool P]
  [HasNot P] {x✝ : String} {x✝¹ : MetaData P}
  {δ : SemanticEval P} {σ : SemanticStore P} {e : P.Expr} :
  δ σ e = some HasBool.tt 
    WellFormedSemanticEvalBool δ 
      EvalCmd P δ σ (Cmd.assume x✝ e x✝¹) σ

If e evaluates to true in σ, evaluate to the same σ.

eval_cover {P : PureExpr} [HasFvar P] [HasBool P] [HasNot P]
  {x✝ : String} {x✝¹ : MetaData P} {δ : SemanticEval P}
  {σ : SemanticStore P} {e : P.Expr} :
  WellFormedSemanticEvalBool δ 
    EvalCmd P δ σ (Cmd.cover x✝ e x✝¹) σ

A cover, when encountered, is essentially a skip.

3.3. Structured Deterministic Statement Syntax🔗

Statements allow commands to be organized into standard control flow arrangements, including sequencing, alternation, and iteration. Sequencing statements occurs by grouping them into blocks. Loops can be annotated with optional invariants and decreasing measures, which can be used for deductive verification.

🔗inductive type
Imperative.Stmt (P : PureExpr) (Cmd : Type) : Type
Imperative.Stmt (P : PureExpr) (Cmd : Type) : Type

Imperative statements focused on control flow.

The P parameter specifies the type of expressions that appear in conditional and loop guards. The Cmd parameter specifies the type of atomic command contained within the .cmd constructor.

Constructors

cmd {P : PureExpr} {Cmd : Type} (cmd : Cmd) : Stmt P Cmd

An atomic command.

block {P : PureExpr} {Cmd : Type} (label : String)
  (b : List (Stmt P Cmd))
  (md : MetaData P := MetaData.empty) : Stmt P Cmd

An block containing a List of Stmt.

ite {P : PureExpr} {Cmd : Type} (cond : P.Expr)
  (thenb elseb : List (Stmt P Cmd))
  (md : MetaData P := MetaData.empty) : Stmt P Cmd

A conditional execution statement.

loop {P : PureExpr} {Cmd : Type} (guard : P.Expr)
  (measure invariant : Option P.Expr)
  (body : List (Stmt P Cmd))
  (md : MetaData P := MetaData.empty) : Stmt P Cmd

An iterated execution statement. Includes an optional measure (for termination) and invariant.

goto {P : PureExpr} {Cmd : Type} (label : String)
  (md : MetaData P := MetaData.empty) : Stmt P Cmd

A semi-structured control flow statement transferring control to the given label. The control flow induced by goto must not create cycles. NOTE: This will likely be removed, in favor of an alternative view of imperative programs that is purely untructured.

funcDecl {P : PureExpr} {Cmd : Type} (decl : PureFunc P)
  (md : MetaData P := MetaData.empty) : Stmt P Cmd

A function declaration within a statement block.

🔗def
Imperative.Block (P : PureExpr) (Cmd : Type) : Type
Imperative.Block (P : PureExpr) (Cmd : Type) : Type

A block is simply an abbreviation for a list of commands.

3.4. Structured Deterministic Statement Operational Semantics🔗

The semantics of the Stmt type is defined in terms of configurations, represented by the Config type. A configuration pairs the statement(s) remaining to be executed with a state, and each step of execution goes from an initial configuration to a final configuration.

🔗inductive type
Imperative.Config (P : PureExpr) (CmdT : Type) : Type
Imperative.Config (P : PureExpr) (CmdT : Type) : Type

Configuration for small-step semantics, representing the current execution state. A configuration consists of:

  • The current statement (or list of statements) being executed

  • The current store

  • The current expression evaluator (threaded to support funcDecl)

Constructors

stmt {P : PureExpr} {CmdT : Type} :
  Stmt P CmdT 
    SemanticStore P 
      SemanticEval P  Imperative.Config P CmdT

A single statement to execute next.

stmts {P : PureExpr} {CmdT : Type} :
  List (Stmt P CmdT) 
    SemanticStore P 
      SemanticEval P  Imperative.Config P CmdT

A list of statements to execute next, in order.

terminal {P : PureExpr} {CmdT : Type} :
  SemanticStore P 
    SemanticEval P  Imperative.Config P CmdT

A terminal configuration, indicating that execution has finished.

The StepStmt type describes how each type of statement transforms configurations. Like with the other components of Strata, the rules follow standard conventions.

🔗inductive predicate
Imperative.StepStmt {CmdT : Type} (P : PureExpr) (EvalCmd : EvalCmdParam P CmdT) (extendEval : ExtendEval P) [DecidableEq P.Ident] [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : Imperative.Config P CmdT Imperative.Config P CmdT Prop
Imperative.StepStmt {CmdT : Type} (P : PureExpr) (EvalCmd : EvalCmdParam P CmdT) (extendEval : ExtendEval P) [DecidableEq P.Ident] [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : Imperative.Config P CmdT Imperative.Config P CmdT Prop

Small-step operational semantics for statements. The relation StepStmt defines a single execution step from one configuration to another. The expression evaluator is part of the configuration and can be extended by funcDecl statements.

Constructors

step_cmd {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {δ : SemanticEval P} {σ : SemanticStore P} {c : CmdT}
  {σ' : SemanticStore P} :
  EvalCmd δ σ c σ' 
    StepStmt P EvalCmd extendEval
      (Config.stmt (Stmt.cmd c) σ δ) (Config.terminal σ' δ)

A command steps to terminal configuration if it evaluates successfully. Commands preserve the evaluator (δ' = δ).

step_block {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {x✝ : String} {x✝¹ : MetaData P} {ss : List (Stmt P CmdT)}
  {σ : SemanticStore P} {δ : SemanticEval P} :
  StepStmt P EvalCmd extendEval
    (Config.stmt (Stmt.block x✝ ss x✝¹) σ δ)
    (Config.stmts ss σ δ)

A labeled block steps to its statement list.

step_ite_true {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {x✝ : MetaData P} {δ : SemanticEval P} {c : P.Expr}
  {tss ess : List (Stmt P CmdT)} {σ : SemanticStore P} :
  δ σ c = some HasBool.tt 
    WellFormedSemanticEvalBool δ 
      StepStmt P EvalCmd extendEval
        (Config.stmt (Stmt.ite c tss ess x✝) σ δ)
        (Config.stmts tss σ δ)

If the condition of an ite statement evaluates to true, step to the then branch.

step_ite_false {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {x✝ : MetaData P} {δ : SemanticEval P} {c : P.Expr}
  {tss ess : List (Stmt P CmdT)} {σ : SemanticStore P} :
  δ σ c = some HasBool.ff 
    WellFormedSemanticEvalBool δ 
      StepStmt P EvalCmd extendEval
        (Config.stmt (Stmt.ite c tss ess x✝) σ δ)
        (Config.stmts ess σ δ)

If the condition of an ite statement evaluates to false, step to the else branch.

step_loop_enter {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {δ : SemanticEval P} {g : P.Expr} {m inv : Option P.Expr}
  {body : List (Stmt P CmdT)} {md : MetaData P}
  {σ : SemanticStore P} :
  δ σ g = some HasBool.tt 
    WellFormedSemanticEvalBool δ 
      StepStmt P EvalCmd extendEval
        (Config.stmt (Stmt.loop g m inv body md) σ δ)
        (Config.stmts (body ++ [Stmt.loop g m inv body md])
          σ δ)

If a loop guard is true, execute the body and then loop again.

step_loop_exit {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {x✝ : MetaData P} {δ : SemanticEval P} {g : P.Expr}
  {m inv : Option P.Expr} {body : List (Stmt P CmdT)}
  {σ : SemanticStore P} :
  δ σ g = some HasBool.ff 
    WellFormedSemanticEvalBool δ 
      StepStmt P EvalCmd extendEval
        (Config.stmt (Stmt.loop g m inv body x✝) σ δ)
        (Config.terminal σ δ)

If a loop guard is false, terminate the loop.

step_funcDecl {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {decl : PureFunc P} {md : MetaData P}
  {σ : SemanticStore P} {δ : SemanticEval P}
  [HasSubstFvar P] [HasVarsPure P P.Expr] :
  StepStmt P EvalCmd extendEval
    (Config.stmt (Stmt.funcDecl decl md) σ δ)
    (Config.terminal σ (extendEval δ σ decl))

A function declaration extends the evaluator with the new function.

step_stmts_nil {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {σ : SemanticStore P} {δ : SemanticEval P} :
  StepStmt P EvalCmd extendEval (Config.stmts [] σ δ)
    (Config.terminal σ δ)

An empty list of statements steps to .terminal with no state changes.

step_stmt_cons {CmdT : Type} {P : PureExpr}
  {EvalCmd : EvalCmdParam P CmdT}
  {extendEval : ExtendEval P} [DecidableEq P.Ident]
  [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT]
  [HasFvar P] [HasVal P] [HasBool P] [HasNot P]
  {s : Stmt P CmdT} {σ : SemanticStore P}
  {δ : SemanticEval P} {σ' : SemanticStore P}
  {δ' : SemanticEval P} {ss : List (Stmt P CmdT)} :
  StepStmt P EvalCmd extendEval (Config.stmt s σ δ)
      (Config.terminal σ' δ') 
    StepStmt P EvalCmd extendEval
      (Config.stmts (s :: ss) σ δ) (Config.stmts ss σ' δ')

To evaluate a sequence of statements, evaluate the first statement and then evaluate the remaining statements in the resulting state.

Like with Lambda, we typically want to talk about arbitrarily long sequences of steps. The Imperative.StepStmtStar relation describes the reflexive, transitive closure of the Imperative.StepStmt relation.

🔗def
Imperative.StepStmtStar {CmdT : Type} (P : PureExpr) (EvalCmd : EvalCmdParam P CmdT) (extendEval : ExtendEval P) [DecidableEq P.Ident] [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : Imperative.Config P CmdT Imperative.Config P CmdT Prop
Imperative.StepStmtStar {CmdT : Type} (P : PureExpr) (EvalCmd : EvalCmdParam P CmdT) (extendEval : ExtendEval P) [DecidableEq P.Ident] [HasVarsImp P (List (Stmt P CmdT))] [HasVarsImp P CmdT] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : Imperative.Config P CmdT Imperative.Config P CmdT Prop

Multi-step execution: reflexive transitive closure of single steps.

4. Metadata🔗

Metadata allows additional information to be attached to nodes in the Strata AST. This may include information such as the provenance of specific AST nodes (e.g., the locations in source code that gave rise to them), facts inferred by specific analyses, or indications of the goal of a specific analysis, among many other possibilities.

Each metadata element maps a field to a value. A field can be named with a variable or an arbitrary string.

🔗inductive type
Imperative.MetaDataElem.Field (P : PureExpr) : Type
Imperative.MetaDataElem.Field (P : PureExpr) : Type

A metadata field, which can be either a variable or an arbitrary string label.

For now, we only track the variables modified by a construct, but we will expand this in the future.

Constructors

var {P : PureExpr} (v : P.Ident) : MetaDataElem.Field P

Metadata indexed by a Strata variable.

label {P : PureExpr} (l : String) : MetaDataElem.Field P

Metadata indexed by an arbitrary label.

A value can take the form of an expression or an arbitrary string.

🔗inductive type
Imperative.MetaDataElem.Value (P : PureExpr) : Type
Imperative.MetaDataElem.Value (P : PureExpr) : Type

A metadata value, which can be either an expression, a message, or a fileRange

Constructors

expr {P : PureExpr} (e : P.Expr) : MetaDataElem.Value P

Metadata value in the form of a structured expression.

msg {P : PureExpr} (s : String) : MetaDataElem.Value P

Metadata value in the form of an arbitrary string.

fileRange {P : PureExpr} (r : Strata.FileRange) :
  MetaDataElem.Value P

Metadata value in the form of a fileRange.

A metadata element pairs a field with a value.

🔗structure
Imperative.MetaDataElem (P : PureExpr) : Type
Imperative.MetaDataElem (P : PureExpr) : Type

A metadata element

Constructor

Imperative.MetaDataElem.mk

Fields

fld : MetaDataElem.Field P

The field or key used to identify the metadata.

value : MetaDataElem.Value P

The value of the metadata.

And, finally, the metadata attached to an AST node consists of an array of metadata elements.

🔗def
Imperative.MetaData (P : PureExpr) : Type
Imperative.MetaData (P : PureExpr) : Type

Metadata is an array of tagged elements.