Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions Strata/DDM/AST.lean
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,21 @@ structure DatatypeBindingSpec (argDecls : ArgDecls) where
functionTemplates : Array FunctionTemplate := #[]
deriving Repr

/--
Specification for single-constructor record type declarations.
Like `DatatypeBindingSpec` but the fields are given directly as `CommaSepBy Binding`
rather than through a `ConstructorList`; a single constructor named `T_mk` is
synthesized automatically.
-/
structure RecordBindingSpec (argDecls : ArgDecls) where
/-- deBrujin index of the record type name -/
nameIndex : DebruijnIndex argDecls.size
/-- deBrujin index of the field list (`CommaSepBy Binding`) -/
fieldsIndex : DebruijnIndex argDecls.size
/-- Optional list of function templates (selectors, etc.) to expand -/
functionTemplates : Array FunctionTemplate := #[]
deriving Repr

/--
Specification for declaring a single type variable.
Creates a .tvar binding in the result context.
Expand All @@ -954,6 +969,7 @@ inductive BindingSpec (argDecls : ArgDecls) where
| type (_ : TypeBindingSpec argDecls)
| scopedType (_ : TypeBindingSpec argDecls) -- Type added to global context
| datatype (_ : DatatypeBindingSpec argDecls)
| record (_ : RecordBindingSpec argDecls)
| tvar (_ : TvarBindingSpec argDecls)
deriving Repr

Expand All @@ -964,6 +980,7 @@ def nameIndex {argDecls} : BindingSpec argDecls → DebruijnIndex argDecls.size
| .type v => v.nameIndex
| .scopedType v => v.nameIndex
| .datatype v => v.nameIndex
| .record v => v.nameIndex
| .tvar v => v.nameIndex

end BindingSpec
Expand Down Expand Up @@ -1127,6 +1144,25 @@ def parseNewBindings (md : Metadata) (argDecls : ArgDecls) : Array (BindingSpec
constructorsIndex := ⟨constructorsIndex, constructorsP⟩,
functionTemplates
}
| { dialect := _, name := "declareRecord" } => do
let args := attr.args
if args.size < 2 then
newBindingErr "declareRecord expects at least 2 arguments (name, fields)."
return none
let .catbvar nameIndex := args[0]!
| newBindingErr "declareRecord: invalid name index"; return none
let .catbvar fieldsIndex := args[1]!
| newBindingErr "declareRecord: invalid fields index"; return none
let .isTrue nameP := decideProp (nameIndex < argDecls.size)
| return panic! "Invalid name index"
let .isTrue fieldsP := decideProp (fieldsIndex < argDecls.size)
| return panic! "Invalid fields index"
let functionTemplates ← parseFunctionTemplates (args.extract 2 args.size)
some <$> .record <$> pure {
nameIndex := ⟨nameIndex, nameP⟩,
fieldsIndex := ⟨fieldsIndex, fieldsP⟩,
functionTemplates
Comment on lines +1147 to +1164
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI blocker: Run lint checks (stable) fails on the three new panic! uses here and at line 2194. The Strata lint only flags newly added panics without -- nopanic:ok annotations (pre-existing ones in the same file, e.g. addDatatypeBindings at 1134–1138, are grandfathered), so this is strictly a new-code policy issue.

Two ways to fix:

  • Quick fix: append -- nopanic:ok after each panic! line, matching the grandfathered sibling sites. Minimal change; keeps behaviour identical. Suggested if you want to keep the parallel structure with the existing datatype code.
  • Better fix: replace panic! with error returns through the Except String channel parseNewBindings already uses. Line 1148–1155 already handles similar malformed-metadata cases via newBindingErr "..."; return none; the decideProp … | return panic! branches at lines 1156 and 1158 could do the same. addDatatypeBindings's existing panics would still be there but are out of scope.

Same choice applies to line 2194:

| a => panic! s!"Expected ident for record name {repr a}"

which could become an Except String error like the rest of addRecordBindings, or gain a -- nopanic:ok. Note that the symmetric site in addDatatypeBindings (line 2134 area for the datatype name) also panics without annotation; if you take the quick-fix route here, consider tagging those too as a small consistency pass.

Either way, CI stays red until this is resolved.

}
| q`StrataDDL.declareTVar => do
let #[.catbvar nameIndex] := attr.args
| newBindingErr "declareTVar expects 1 argument."; return none
Expand Down Expand Up @@ -1758,6 +1794,9 @@ partial def resolveBindingIndices { argDecls : ArgDecls } (m : DialectMap) (src
| a => panic! s!"Expected ident for type param {repr a}"
foldOverArgAtLevel m addBinding #[] argDecls args b.typeParamsIndex.toLevel
some <| .type params.toList none
| .record _ =>
-- Records have no type params; constructor/selectors are handled in addRecordBindings.
some <| .type [] none
| .tvar _ =>
-- tvar bindings are local only, not added to GlobalContext
none
Expand Down Expand Up @@ -1805,7 +1844,7 @@ private def getConstructorListPushAnnotation (opDecl : OpDecl) : Option (Nat ×
The accumulator is `Except String ...` because `foldOverArgBindingSpecs` fixes the
fold's accumulator type; wrapping in `Except` lets us propagate errors through
the fold without changing its generic signature. -/
private def extractFieldsFromBindings (dialects : DialectMap) (arg : Arg)
def extractFieldsFromBindings (dialects : DialectMap) (arg : Arg)
: Except String (Array (String × TypeExpr)) :=
-- We thread `Except` through the accumulator rather than changing
-- `foldOverArgBindingSpecs`, which is used broadly with plain accumulators.
Expand Down Expand Up @@ -2139,6 +2178,35 @@ FreeVarIndex values are consistent with this order.
this adds entries for: `Option` (type), `None` (constructor), `Some` (constructor),
`Option..isNone` (tester), `Option..isSome` (tester).
-/
private def addRecordBindings
(dialects : DialectMap)
(gctx : GlobalContext)
(src : SourceRange)
(dialectName : DialectName)
(preRegistered : Bool)
{argDecls : ArgDecls}
(b : RecordBindingSpec argDecls)
(args : Vector Arg argDecls.size)
: Except String GlobalContext := do
let recordName :=
match args[b.nameIndex.toLevel] with
| .ident _ e => e
| a => panic! s!"Expected ident for record name {repr a}"
-- Step 1: Register the record type (no type parameters).
let k := GlobalKind.type [] none
let gctx ← gctx.defineChecked recordName k preRegistered
let recordIndex := gctx.findIndex? recordName |>.getD (gctx.vars.size - 1)
let recordType := mkDatatypeTypeRef src recordIndex #[]
-- Step 2: Extract fields from the CommaSepBy Binding argument and
-- synthesize a single constructor named `recordName ++ "_mk"`.
let fieldsArg := args[b.fieldsIndex.toLevel]
let fields ← extractFieldsFromBindings dialects fieldsArg
let ctorInfo : Array ConstructorInfo := #[{ name := recordName ++ "_mk", fields }]
-- Step 3: Expand function templates (field selectors etc.).
let (gctx, _) := expandFunctionTemplates dialectName src
recordName recordType ctorInfo b.functionTemplates gctx
return gctx

private def addDatatypeBindings
(dialects : DialectMap)
(gctx : GlobalContext)
Expand Down Expand Up @@ -2190,7 +2258,7 @@ private def preRegisterType (dialects : DialectMap) (acc : Except String GlobalC
{argDecls} (b : BindingSpec argDecls) (args : Vector Arg argDecls.size) : Except String GlobalContext := do
let gctx ← acc
match b with
| .datatype _ | .type _ =>
| .datatype _ | .record _ | .type _ =>
let name :=
match args[b.nameIndex.toLevel] with
| .ident _ e => e
Expand All @@ -2212,6 +2280,8 @@ private def addBinding (dialects : DialectMap) (dialectName : DialectName) (preR
match b with
| .datatype datatypeSpec =>
addDatatypeBindings dialects gctx l dialectName preRegistered datatypeSpec args
| .record recordSpec =>
addRecordBindings dialects gctx l dialectName preRegistered recordSpec args
| _ =>
let name : Var :=
match args[b.nameIndex.toLevel] with
Expand Down
51 changes: 50 additions & 1 deletion Strata/DDM/Elab/Core.lean
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,33 @@ def evalBindingSpec
| .error e =>
logError loc e
pure <| .empty gctx
| .record b =>
let nameInfo := args[b.nameIndex.toLevel].info
let (_, ident) ←
match nameInfo with
| .ofIdentInfo i =>
pure (i.loc, i.val)
| _ =>
logInternalError nameInfo.loc s!"Expected ident"
return tctx
assert! tctx.bindings.size = 0
let gctx := tctx.globalContext
let gctx := gctx.ensureDefined ident (.type [] none)
let dialects := (← read).dialects
let fieldsArg := args[b.fieldsIndex.toLevel]
match extractFieldsFromBindings dialects fieldsArg.arg with
| .ok fields =>
let recordIndex := gctx.findIndex? ident |>.getD (gctx.vars.size - 1)
let recordType := mkDatatypeTypeRef loc recordIndex #[]
let ctorInfo : Array ConstructorInfo := #[{ name := ident ++ "_mk", fields }]
let (gctx, errors) := gctx.expandFunctionTemplates
dialectName loc ident recordType ctorInfo
b.functionTemplates
errors.forM (logError loc)
pure <| .empty gctx
| .error e =>
logError loc e
pure <| .empty gctx
| .tvar b =>
let ident := evalBindingNameIndex args b.nameIndex
pure <| tctx.push { ident, kind := .tvar loc ident }
Expand Down Expand Up @@ -1666,7 +1693,29 @@ partial def elabExpr (tctx : TypingContext) (stx : Syntax) : ElabM Tree := do
let einfo : ElabInfo := { loc := loc, inputCtx := tctx }
let name := elabIdent stx[0]
let some binding := tctx.lookupVar name
| logError loc s!"Unknown expr identifier {name}"
| -- Desugar dot notation: "base.field" → T..field(base)
-- A single dot (not "..") separates the variable from the field name.
let dotParts := name.splitOn ".."
let parts := name.splitOn "."
if parts.length == 2 && dotParts.length == 1 then
let baseName := parts[0]!
let fieldName := parts[1]!
if let some baseBinding := tctx.lookupVar baseName then
let typeExprOpt : Option TypeExpr := match baseBinding with
| .bvar _ (.expr tp) => some tp
| .fvar _ (.expr tp) => some tp
| _ => none
if let some (.fvar _ typeIdx _) := typeExprOpt then
if let some typeName := tctx.globalContext.nameOf? typeIdx then
let selectorName := typeName ++ ".." ++ fieldName
if let some selectorIdx := tctx.globalContext.findIndex? selectorName then
let baseExpr : Expr := match baseBinding with
| .bvar idx _ => .bvar loc idx
| .fvar idx _ => .fvar loc idx
let appExpr : Expr := .app loc (.fvar loc selectorIdx) (.expr baseExpr)
Comment on lines +1696 to +1715
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dot-notation desugaring here is the most delicate part of the PR. Three observations:

(a) Chained access is silently unsupported. "a.b.c".splitOn "." returns three parts, so the parts.length == 2 check fails, and a.b.c falls through to logError loc s!"Unknown expr identifier {name}". That's a correctness non-issue (the identifier genuinely wasn't found, and we emit a sane error) but the user doesn't learn why — they see "Unknown expr identifier a.b.c" rather than "chained field access not yet supported". Either:

  • Recursively desugar: a.b.cT..c(T..b(a)). A few extra lines, much nicer UX for struct-of-struct code.
  • Explicit check-and-report: if parts.length > 2 and none of the multi-dot tokens form a valid identifier, emit a targeted "chained field access not yet supported" diagnostic.

(b) String-split identifier parsing is fragile. Today it relies on Boole's grammar parsing val.field as a single atom with an embedded ., and on the convention that multi-dot tokens like T..field don't match the parts.length == 2 && dotParts.length == 1 guard. If the DDM elaborator ever starts producing val.field as two tokens, or the grammar adds another identifier category that uses . for a different purpose, this path silently stops applying. A grammar-level fieldAccess production (the way Core does it) would remove the string-splitting entirely. Follow-up-able, not blocking.

(c) No direct unit tests. The only coverage is struct_field_access.lean end-to-end. Four targeted #guard_msgs cases would lock the behaviour down:

  • Valid single-level val.field on a var-local record, on a parameter, and on a nested var: currently all rely on the benchmark.
  • a.b.c: pins the limitation (expected either to resolve via nested desugar, or to produce a specific error message — pick one and test it).
  • val.field where val is not a record: must keep the pre-existing "Unknown expr identifier" diagnostic intact.
  • T..field typed literally in source: must not trigger the rewrite (already handled by the dotParts.length == 1 guard; a test pins this down so a future simplification doesn't regress).

See also the main review body's suggested proof: a theorem (or informal comment) that elabExpr (val.field) produces the same tree as elabExpr (T..field(val)) would state the desugaring's invariant explicitly.

let info : ExprInfo := { toElabInfo := einfo, expr := appExpr }
return .node (.ofExprInfo info) #[]
logError loc s!"Unknown expr identifier {name}"
return default
match binding with
| .bvar idx k => do
Expand Down
12 changes: 12 additions & 0 deletions Strata/DDM/Integration/Lean/ToExpr.lean
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,17 @@ protected def toExpr {argDecls} (b : DatatypeBindingSpec argDecls) (argDeclsExpr

end DatatypeBindingSpec

namespace RecordBindingSpec

protected def toExpr {argDecls} (b : RecordBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr :=
astExpr! mk
argDeclsExpr
(toExpr b.nameIndex)
(toExpr b.fieldsIndex)
(toExpr b.functionTemplates)

end RecordBindingSpec

namespace TvarBindingSpec

protected def toExpr {argDecls} (b : TvarBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr :=
Expand All @@ -423,6 +434,7 @@ private def toExpr {argDecls} (bi : BindingSpec argDecls) (argDeclsExpr : Lean.E
| .type b => astExpr! type argDeclsExpr (b.toExpr argDeclsExpr)
| .scopedType b => astExpr! scopedType argDeclsExpr (b.toExpr argDeclsExpr)
| .datatype b => astExpr! datatype argDeclsExpr (b.toExpr argDeclsExpr)
| .record b => astExpr! record argDeclsExpr (b.toExpr argDeclsExpr)
| .tvar b => astExpr! tvar argDeclsExpr (b.toExpr argDeclsExpr)

end BindingSpec
Expand Down
9 changes: 9 additions & 0 deletions Strata/Languages/Boole/Grammar.lean
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ op for_down_to_by_statement (v : MonoBind, init : Expr, limit : Expr,
@[scope(v)] body : Block) : Statement =>
"for " v " := " init " downto " limit step invs body;

// Record/struct type declaration: `type T := { f1: A, f2: B };`
// Desugars to a single-constructor datatype `T_mk(f1: A, f2: B)` with
// field selectors `T..f1(val)`, `T..f2(val)` etc.
@[declareRecord(name, fields,
perField([.datatype, .literal "..", .field], [.datatype], .fieldType),
perField([.datatype, .literal "..", .field, .literal "!"], [.datatype], .fieldType))]
op struct_decl (name : Ident, fields : CommaSepBy Binding) : Command =>
"type " name " := " "{" fields "}" ";\n";

category Program;
op prog (commands : SpacePrefixSepBy Command) : Program =>
commands;
Expand Down
55 changes: 42 additions & 13 deletions Strata/Languages/Boole/Verify.lean
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,6 @@ private def toCoreMonoBind (b : BooleDDM.MonoBind SourceRange) : TranslateM (Cor
match b with
| .mono_bind_mk _ ⟨_, n⟩ ty => return (mkIdent n, ← toCoreMonoType ty)

def toCoreTypedUn (m : SourceRange) (ty : Boole.Type) (op : String) (a : Core.Expression.Expr) : TranslateM Core.Expression.Expr := do
let .int _ := ty
| throwAt m s!"Unsupported typed operator type: {repr ty}"
let iop : Core.Expression.Expr := .op () ⟨s!"Int.{op}", ()⟩ none
return .app () iop a

def toCoreTypedBin (m : SourceRange) (ty : Boole.Type) (op : String) (a b : Core.Expression.Expr) : TranslateM Core.Expression.Expr := do
let .int _ := ty
| throwAt m s!"Unsupported typed operator type: {repr ty}"
let iop : Core.Expression.Expr := .op () ⟨s!"Int.{op}", ()⟩ none
return mkCoreApp iop [a, b]

private def bvWidth (m : SourceRange) (ty : Boole.Type) : TranslateM Nat :=
match ty with
| .bv1 _ => return 1
Expand All @@ -262,6 +250,20 @@ private def toCoreBvBin (m : SourceRange) (ty : Boole.Type) (op : String) (a b :
let n ← bvWidth m ty
return mkCoreApp (.op () ⟨s!"Bv{n}.{op}", ()⟩ none) [a, b]

def toCoreTypedUn (m : SourceRange) (ty : Boole.Type) (op : String) (a : Core.Expression.Expr) : TranslateM Core.Expression.Expr := do
match ty with
| .int _ =>
let iop : Core.Expression.Expr := .op () ⟨s!"Int.{op}", ()⟩ none
return .app () iop a
| _ => toCoreBvUn m ty op a

def toCoreTypedBin (m : SourceRange) (ty : Boole.Type) (op : String) (a b : Core.Expression.Expr) : TranslateM Core.Expression.Expr := do
match ty with
| .int _ =>
let iop : Core.Expression.Expr := .op () ⟨s!"Int.{op}", ()⟩ none
return mkCoreApp iop [a, b]
| _ => toCoreBvBin m ty op a b

private def toCoreExtensionalEq
(m : SourceRange)
(ty : Boole.Type)
Expand Down Expand Up @@ -373,9 +375,21 @@ def toCoreExpr (e : Boole.Expr) : TranslateM Core.Expression.Expr := do
| .bvand m ty a b => toCoreBvBin m ty "And" (← toCoreExpr a) (← toCoreExpr b)
| .bvor m ty a b => toCoreBvBin m ty "Or" (← toCoreExpr a) (← toCoreExpr b)
| .bvxor m ty a b => toCoreBvBin m ty "Xor" (← toCoreExpr a) (← toCoreExpr b)
| .bvshl m ty a b => toCoreBvBin m ty "Shl" (← toCoreExpr a) (← toCoreExpr b)
| .bvshl m ty a b =>
match ty with
| .int _ => do
-- Integer << n encoded as multiplication by 2^n; n must be a literal.
let n ← match b with
| .natToInt _ ⟨_, n⟩ => pure n
| _ => throwAt m "Integer << requires a constant (literal) shift amount"
toCoreTypedBin m ty "Mul" (← toCoreExpr a) (.intConst () (Int.ofNat (1 <<< n)))
| _ => toCoreBvBin m ty "Shl" (← toCoreExpr a) (← toCoreExpr b)
Comment on lines +378 to +386
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small things on the integer-left-shift encoding:

(a) Error message could be more specific. "Integer << requires a constant (literal) shift amount" is accurate but a user who writes x << (5 + 0) may reasonably expect the evaluator to fold the RHS — the error doesn't communicate that only a numeric literal is accepted, not a constant-folded expression. Consider "Integer << requires a numeric literal as the shift amount; arbitrary expressions (even constant ones) are not yet supported."

(b) Unbounded shift amount. Int.ofNat (1 <<< n) where n is an arbitrary Nat from source produces arbitrary-precision Int literals. SMT solvers handle arbitrary-precision integers fine, but a pathological input like x << 10000 would generate a ~3000-digit literal in the SMT file. Cheap guard: reject n > SHIFT_MAX (e.g. 256, the widest BV we speak) with a targeted message.

(c) Test coverage is benchmark-only. intShlSeed in struct_field_access.lean covers one valid use (x < (1 << 51)) but nothing exercises the error paths. Two #guard_msgs tests on the "Integer << requires…" branch would pin the error message and catch future regressions silently flipping the fallback path.

| .bvushr m ty a b => toCoreBvBin m ty "UShr" (← toCoreExpr a) (← toCoreExpr b)
| .bvsshr m ty a b => toCoreBvBin m ty "SShr" (← toCoreExpr a) (← toCoreExpr b)
| .bvult m ty a b => toCoreBvBin m ty "ULt" (← toCoreExpr a) (← toCoreExpr b)
| .bvule m ty a b => toCoreBvBin m ty "ULe" (← toCoreExpr a) (← toCoreExpr b)
| .bvugt m ty a b => toCoreBvBin m ty "UGt" (← toCoreExpr a) (← toCoreExpr b)
| .bvuge m ty a b => toCoreBvBin m ty "UGe" (← toCoreExpr a) (← toCoreExpr b)
| .old _ _ a =>
return oldifyExpr (← get).currentInoutNames (← toCoreExpr a)
| _ => throw (.fromMessage s!"Unsupported expression: {repr e}")
Expand Down Expand Up @@ -747,6 +761,7 @@ private def registerCommandSymbols (cmd : BooleDDM.Command SourceRange) : List B
-- Procedure names are referenced by call statements directly and are not Expr.fvar symbols.
| .boole_procedure _ _ _ _ _ _ _ | .command_procedure _ _ _ _ _ _ => []
| .command_datatypes _ ⟨_, decls⟩ => decls.toList.map (fun _ => false)
| .struct_decl _ _ _ => [false]
| .command_block _ _ => []
| .command_axiom _ _ _ => []
| .command_distinct _ _ _ => []
Expand Down Expand Up @@ -886,6 +901,20 @@ def toCoreDecls (cmd : BooleDDM.Command SourceRange) : TranslateM (List Core.Dec
} .empty]
| .command_datatypes _ ⟨_, decls⟩ =>
return [.type (.data (← decls.toList.mapM toCoreDatatypeDecl)) .empty]
| .struct_decl m ⟨_, tname⟩ ⟨_, fields⟩ =>
-- Desugar `type T := { f1: A, f2: B }` to a single-constructor datatype.
-- DDM registration (type + selectors) is handled by @[declareRecord].
let args ← fields.toList.mapM toCoreBinding
let ctorName := tname ++ "_mk"
let constr : Lambda.LConstr Unit := {
name := mkIdent ctorName
args := args
testerName := s!"{tname}..is{ctorName}"
}
return [.type (.data [{ name := tname
typeArgs := []
constrs := [constr]
constrs_ne := by simp }]) .empty]

/-- Render a `Boole.Program` to a format object using the provided `GlobalContext` and
`DialectMap`. These should come from the originating `Strata.Program` (i.e. `env.globalContext`
Expand Down
10 changes: 10 additions & 0 deletions Strata/Languages/Core/DDMTransform/Grammar.lean
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ constructors : Ident, testerTemplate : FunctionTemplate,
accessorTemplate : FunctionTemplate,
unsafeAccessorTemplate : FunctionTemplate);

// Declare metadata for single-constructor record types (field list variant).
// Unlike declareDatatype, the constructor is synthesized as `name ++ "_mk"`.
metadata declareRecord (name : Ident, fields : Ident,
accessorTemplate : FunctionTemplate,
unsafeAccessorTemplate : FunctionTemplate);

type bool;
type int;
type string;
Expand Down Expand Up @@ -187,6 +193,10 @@ fn bvslt (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " <s " b
fn bvsle (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " <=s " b;
fn bvsgt (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " >s " b;
fn bvsge (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " >=s " b;
fn bvult (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " <u " b;
fn bvule (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " <=u " b;
fn bvugt (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " >u " b;
fn bvuge (tp : Type, a : tp, b : tp) : bool => @[prec(20), leftassoc] a " >=u " b;

fn bvconcat8 (a : bv8, b : bv8) : bv16 => "bvconcat{8}{8}" "(" a ", " b ")";
fn bvconcat16 (a : bv16, b : bv16) : bv32 => "bvconcat{16}{16}" "(" a ", " b ")";
Expand Down
Loading
Loading