Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
613fec6
feat(DDM): Java code generator for dialects
fabiomadge Dec 23, 2025
e18f450
Merge remote-tracking branch 'origin/main' into feat/java-codegen
fabiomadge Dec 23, 2025
f99ea87
docs: update module docstring to mention builders
fabiomadge Dec 23, 2025
780c78e
test: verify deserialized AST structure
fabiomadge Dec 23, 2025
e0b2bb5
chore: remove redundant inline comments
fabiomadge Dec 23, 2025
da67291
refactor: use #load_dialect instead of inline dialect definition
fabiomadge Dec 23, 2025
67c170d
fix: correct test 2 title
fabiomadge Dec 23, 2025
8731075
test: use Simple dialect for compile test, include builders
fabiomadge Dec 23, 2025
98c3a4a
fix: disambiguate builders filename to avoid collisions
fabiomadge Dec 23, 2025
b36929a
refactor: use get! and alter for cleaner lookups
fabiomadge Dec 23, 2025
1404ab0
fix: escape builder method names for Java reserved words
fabiomadge Dec 23, 2025
4f9e815
refactor: address PR #292 review comments
fabiomadge Dec 24, 2025
be0acbf
Merge remote-tracking branch 'origin/main' into feat/java-codegen
fabiomadge Dec 24, 2025
775600c
fix: support qualified names with dotted identifiers
fabiomadge Dec 24, 2025
22d07ed
Add LaurelGrammar.st file
keyboardDrummer Jan 2, 2026
cfc4a3a
Add laurelVerify command
keyboardDrummer Jan 5, 2026
705cfb4
Fixes
keyboardDrummer Jan 5, 2026
28c581c
Fix namespace
keyboardDrummer Jan 5, 2026
b738175
Fix concrete tree converter
keyboardDrummer Jan 5, 2026
9e5a509
Add missing files to run regenerate-testdata.sh, and enable including…
keyboardDrummer Jan 6, 2026
52e1f3f
Added filepaths as well
keyboardDrummer Jan 6, 2026
e2f5f47
Consume list of StrataFiles when consuming Laurel over ion
keyboardDrummer Jan 7, 2026
ac9400b
Remove lineOffsets from StrataFile
keyboardDrummer Jan 7, 2026
8d10e11
Store source code byte offsets in the metadata instead of 2d positions
keyboardDrummer Jan 7, 2026
e2715f1
Fix printed filepath
keyboardDrummer Jan 7, 2026
9e7994d
Fixes
keyboardDrummer Jan 7, 2026
c3aec77
fix: address PR review comments
fabiomadge Jan 7, 2026
a3e9ec9
Merge branch 'main' into feat/java-codegen
fabiomadge Jan 7, 2026
46e0e58
Refactoring
keyboardDrummer Jan 8, 2026
44155c0
Refactoring
keyboardDrummer Jan 8, 2026
7400e34
Cleanup
keyboardDrummer Jan 8, 2026
f0068d6
Merge remote-tracking branch 'fabiomadge/feat/java-codegen' into forJ…
keyboardDrummer Jan 8, 2026
9c06af7
Cleanup
keyboardDrummer Jan 8, 2026
3948293
Cleanup
keyboardDrummer Jan 8, 2026
fbe3a2c
Fixes
keyboardDrummer Jan 8, 2026
122f63d
Improve error reporting
keyboardDrummer Jan 9, 2026
8ed03a4
Better error handling around solver process
keyboardDrummer Jan 9, 2026
7abbc3e
Attempt at getting better debug output
keyboardDrummer Jan 9, 2026
6197e3c
Turns things around
keyboardDrummer Jan 9, 2026
6a865f0
Fix
keyboardDrummer Jan 9, 2026
98ca32b
Update docs Lean version to 4.26.0
keyboardDrummer Jan 12, 2026
14957b2
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 12, 2026
a4d6089
Revert toolchain update
keyboardDrummer Jan 12, 2026
2104a31
Fixes
keyboardDrummer Jan 12, 2026
d19710f
Fixes
keyboardDrummer Jan 12, 2026
285ffc8
Bump documentation to 4.26.0
joehendrix Jan 12, 2026
212226d
Merge branch 'jhx/v4.26.0_docs' into forJVerify
keyboardDrummer Jan 12, 2026
5da0ff4
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 13, 2026
e332bad
Fix merge mistakes
keyboardDrummer Jan 13, 2026
5d30ca5
Fixes
keyboardDrummer Jan 13, 2026
d15ab9a
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 14, 2026
9d40949
Refactoring
keyboardDrummer Jan 14, 2026
6d7fc13
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 14, 2026
a223c5d
Fix merge error
keyboardDrummer Jan 14, 2026
84d86e7
Fix errors
keyboardDrummer Jan 14, 2026
8da23f6
Cleanup
keyboardDrummer Jan 15, 2026
2797281
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 15, 2026
49bac76
Fix failing proof by adding ': h'
keyboardDrummer Jan 15, 2026
4b86292
Fix merge mistakes
keyboardDrummer Jan 15, 2026
bafb7d2
Remove repr usage
keyboardDrummer Jan 16, 2026
79ee155
Merge remote-tracking branch 'origin/main' into forJVerify
keyboardDrummer Jan 16, 2026
8e20c05
Fix build error
keyboardDrummer Jan 16, 2026
a5b1e4b
Additional fix
keyboardDrummer Jan 16, 2026
39d7d71
Fix
keyboardDrummer Jan 16, 2026
da8413c
Merge branch 'main' into forJVerify
keyboardDrummer Jan 16, 2026
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
34 changes: 33 additions & 1 deletion Strata/DDM/AST.lean
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module
public import Std.Data.HashMap.Basic
public import Strata.DDM.Util.ByteArray
public import Strata.DDM.Util.Decimal
public import Lean.Data.Position
public import Strata.DDM.AST.Datatype

import Std.Data.HashMap
Expand All @@ -18,6 +19,7 @@ set_option autoImplicit false

public section
namespace Strata
open Std (ToFormat Format format)

abbrev DialectName := String

Expand Down Expand Up @@ -271,16 +273,46 @@ structure SourceRange where
start : String.Pos.Raw
/-- One past the end of the range. -/
stop : String.Pos.Raw
deriving BEq, Inhabited, Repr
deriving DecidableEq, Inhabited, Repr

namespace SourceRange

def none : SourceRange := { start := 0, stop := 0 }

def isNone (loc : SourceRange) : Bool := loc.start = 0 ∧ loc.stop = 0

instance : ToFormat SourceRange where
Copy link
Contributor

Choose a reason for hiding this comment

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

As a style preference, I put instances inside the namespace so this has the name SourceRange.instToFormat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay. How would I refer to the instance : ToFormat SourceRange where definition in the location where it's currently at? Also, what are situations where you want to reference an instance definition?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not quite sure of the naming, but probably something like instToFormatSourceRange. This is a pretty minor style comment honestly.

Instance names mostly show up when trying to debug why a rewrite or simp rule isn't going through, and turning on something like pp.all. Sometimes there is a mismatch on instances in a simp theorem and the current goal (usually with polymorphic instances and coercisions).

This instance is so simple and unlikely to show up in proofs, so it doesn't matter. You could leave it as is.

format fr := f!"{fr.start}-{fr.stop}"

end SourceRange

inductive Uri where
| file (path: String)
deriving DecidableEq, Repr, Inhabited

instance : ToFormat Uri where
format fr := match fr with | .file path => path

structure FileRange where
file: Uri
range: Strata.SourceRange
deriving DecidableEq, Repr, Inhabited

instance : ToFormat FileRange where
format fr := f!"{fr.file}:{fr.range}"

structure File2dRange where
file: Uri
start: Lean.Position
ending: Lean.Position
deriving DecidableEq, Repr

instance : ToFormat File2dRange where
format fr :=
let baseName := match fr.file with
| .file path => (path.splitToList (· == '/')).getLast!
f!"{baseName}({fr.start.line}, {fr.start.column})-({fr.ending.line}, {fr.ending.column})"

abbrev Arg := ArgF SourceRange
abbrev Expr := ExprF SourceRange
abbrev Operation := OperationF SourceRange
Expand Down
66 changes: 63 additions & 3 deletions Strata/DDM/Ion.lean
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private def deserializeValue {α} (bs : ByteArray) (act : Ion SymbolId → FromI
throw s!"Error reading Ion: {msg} (offset = {off})"
| .ok a => pure a
let .isTrue p := inferInstanceAs (Decidable (a.size = 1))
| throw s!"Expected single Ion value."
| throw s!"Expected single Ion value, but got {a.size} values."
let entries := a[0]
let .isTrue p := inferInstanceAs (Decidable (entries.size = 2))
| throw s!"Expected symbol table and value in dialect."
Expand Down Expand Up @@ -1411,6 +1411,11 @@ private instance : FromIon Dialect where

end Dialect

structure StrataFile where
filePath : String
program : Program
deriving Inhabited

namespace Program

private instance : CachedToIon Program where
Expand All @@ -1436,7 +1441,7 @@ def fromIonFragment (f : Ion.Fragment)
commands := ← fromIonFragmentCommands f
}

def fromIon (dialects : DialectMap) (dialect : DialectName) (bytes : ByteArray) : Except String Strata.Program := do
def fileFromIon (dialects : DialectMap) (dialect : DialectName) (bytes : ByteArray) : Except String Strata.Program := do
let (hdr, frag) ←
match Strata.Ion.Header.parse bytes with
| .error msg =>
Expand All @@ -1451,5 +1456,60 @@ def fromIon (dialects : DialectMap) (dialect : DialectName) (bytes : ByteArray)
throw s!"{name} program found when {dialect} expected."
fromIonFragment frag dialects dialect

def filesFromIon (dialects : DialectMap) (bytes : ByteArray) : Except String (List StrataFile) := do
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like it shouldn't live in the Program namespace. Maybe move up one level and rename fileFromIon back?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this could be StrataFile.filesFromIon and appear below Program things.

Copy link
Contributor Author

@keyboardDrummer keyboardDrummer Jan 16, 2026

Choose a reason for hiding this comment

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

I notice I have 0 intuition for what should be in what namespace. filesFromIon parses Ion into programs (nested in StrataFile), so why shouldn't it live in the Program namespace?

Maybe this could be StrataFile.filesFromIon

When do you decide to put things in a namespace? Can you give me any guidelines for how to decide to put things in a particular file or in a particular namespace?

Without understanding the context very well, my inclination would be to have a single namespace per file, so you wouldn't be able to have multiple definitions with the same name in a single file, but that seems fine to me. Why not put everything in this file in a Strata.DDM.Ion namespace?

In any case, right now I'm happy to name/move things in whatever way you prefer, but it wasn't entirely clear to me what you meant with "appear below Program things". If you're willing to move/name the things yourself, that could help speed-up the PR, since then I can approve your commit and @aqjune-aws can be the second approver and code-owner.

Copy link
Contributor

Choose a reason for hiding this comment

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

For me, I mostly keep things in the same top-level namespace except for three reasons:

  1. I can use dot notation to access it (e.g., I'd put an operation like count (p : Program) : Nat in Program so I can write p.count.
  2. It is closely coupled to a particular type and has a generic name (e.g., Program.fromIon). Instances follow this pattern even if their names are typically auto-generated.
  3. If it is closely coupled to a method following pattern (1) and (2), and not intended for much use. e.g., an auxillary function needed to implementation some other methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm kind of slow at task switching and want to prioritize my regular work commitments.

I think I am fine with leaving this as is also; it doesn't matter that much if I don't see it as idiomatic.

let ctx ←
match Ion.deserialize bytes with
| .error (off, msg) => throw s!"Error reading Ion: {msg} (offset = {off})"
| .ok a =>
if h : a.size = 1 then
pure a[0]
else
throw s!"Expected single Ion value"

let .isTrue p := inferInstanceAs (Decidable (ctx.size = 2))
| throw "Expected symbol table and value"

let symbols ←
match SymbolTable.ofLocalSymbolTable ctx[0] with
| .error (p, msg) => throw s!"Error at {p}: {msg}"
| .ok symbols => pure symbols

let ionCtx : FromIonContext := ⟨symbols⟩

let ⟨filesList, _⟩ ← FromIonM.asList ctx[1]! ionCtx

let tbl := symbols
let filePathId := tbl.symbolId! "filePath"
let programId := tbl.symbolId! "program"

filesList.toList.mapM fun fileEntry => do
let fields ← FromIonM.asStruct0 fileEntry ionCtx

let some (_, filePathData) := fields.find? (·.fst == filePathId)
| throw "Could not find 'filePath' field"

let some (_, programData) := fields.find? (·.fst == programId)
| throw "Could not find 'program' field"

let filePath ← FromIonM.asString "filePath" filePathData ionCtx

let ⟨programValues, _⟩ ← FromIonM.asList programData ionCtx
let .isTrue ne := inferInstanceAs (Decidable (programValues.size ≥ 1))
| throw "Expected program header"

let hdr ← Ion.Header.fromIon programValues[0] ionCtx
let dialect ← match hdr with
| .program name => pure name
| .dialect _ => throw "Expected program, not dialect"

let frag : Ion.Fragment := {
symbols := symbols,
values := programValues,
offset := 1
}

let program ← fromIonFragment frag dialects dialect

pure { filePath := filePath, program := program }

end Strata.Program
end
28 changes: 9 additions & 19 deletions Strata/DL/Imperative/MetaData.lean
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import Strata.DL.Imperative.PureExpr
import Strata.DL.Util.DecidableEq
import Strata.DDM.AST
import Lean.Data.Position

namespace Imperative
Expand Down Expand Up @@ -65,36 +66,23 @@ instance [Repr P.Ident] : Repr (MetaDataElem.Field P) where
| .label s => f!"MetaDataElem.Field.label {s}"
Repr.addAppParen res prec

inductive Uri where
| file (path: String)
deriving DecidableEq, Repr, Inhabited

instance : ToFormat Uri where
format fr := match fr with | .file path => path

structure FileRange where
file: Uri
start: Lean.Position
ending: Lean.Position
deriving DecidableEq, Repr, Inhabited

instance : ToFormat FileRange where
format fr := f!"{fr.file}:{fr.start}-{fr.ending}"

/-- A metadata value, which can be either an expression, a message, or a fileRange -/
inductive MetaDataElem.Value (P : PureExpr) where
/-- Metadata value in the form of a structured expression. -/
| expr (e : P.Expr)
/-- Metadata value in the form of an arbitrary string. -/
| msg (s : String)
/-- Metadata value in the form of a fileRange. -/
| fileRange (r: FileRange)
| fileRange (r: Strata.FileRange)
/-- Metadata value in the form of a fileRange. -/
| file2dRange (r: Strata.File2dRange)

instance [ToFormat P.Expr] : ToFormat (MetaDataElem.Value P) where
format f := match f with
| .expr e => f!"{e}"
| .msg s => f!"{s}"
| .fileRange r => f!"{r}"
| .file2dRange r => f!"{r}"

instance [Repr P.Expr] : Repr (MetaDataElem.Value P) where
reprPrec v prec :=
Expand All @@ -103,13 +91,15 @@ instance [Repr P.Expr] : Repr (MetaDataElem.Value P) where
| .expr e => f!".expr {reprPrec e prec}"
| .msg s => f!".msg {s}"
| .fileRange fr => f!".fileRange {fr}"
| .file2dRange fr => f!".file2dRange {fr}"
Repr.addAppParen res prec

def MetaDataElem.Value.beq [BEq P.Expr] (v1 v2 : MetaDataElem.Value P) :=
match v1, v2 with
| .expr e1, .expr e2 => e1 == e2
| .msg m1, .msg m2 => m1 == m2
| .fileRange r1, .fileRange r2 => r1 == r2
| .file2dRange r1, .file2dRange r2 => r1 == r2
| _, _ => false

instance [BEq P.Expr] : BEq (MetaDataElem.Value P) where
Expand Down Expand Up @@ -185,7 +175,7 @@ instance [Repr P.Expr] [Repr P.Ident] : Repr (MetaDataElem P) where

def MetaData.fileRange : MetaDataElem.Field P := .label "fileRange"

def getFileRange {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Option Imperative.FileRange := do
def getFileRange {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Option Strata.FileRange := do
let fileRangeElement <- md.findElem Imperative.MetaData.fileRange
match fileRangeElement.value with
| .fileRange fileRange =>
Expand All @@ -196,7 +186,7 @@ def MetaData.formatFileRange? {P} [BEq P.Ident] (md : MetaData P) (includeEnd? :
Option Std.Format := do
let fileRangeElem ← md.findElem MetaData.fileRange
match fileRangeElem.value with
| .fileRange m =>
| .file2dRange m =>
let baseName := match m.file with
| .file path => (path.splitToList (· == '/')).getLast!
if includeEnd? then
Expand Down
19 changes: 10 additions & 9 deletions Strata/DL/Imperative/SMTUtils.lean
Original file line number Diff line number Diff line change
Expand Up @@ -115,31 +115,32 @@ def processModel {P : PureExpr} [ToFormat P.Ident]
| none => .error f!"Cannot find model for id: {id}"
| some p => .ok p.value

def runSolver (solver : String) (args : Array String) : IO String := do
def runSolver (solver : String) (args : Array String) : IO IO.Process.Output := do
let output ← IO.Process.output {
cmd := solver
args := args
}
-- dbg_trace f!"runSolver: exitcode: {repr output.exitCode}\n\
-- stderr: {repr output.stderr}\n\
-- stdout: {repr output.stdout}"
return output.stdout
return output

def solverResult {P : PureExpr} [ToFormat P.Ident]
(typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType))
(vars : List P.TypedIdent) (ans : String)
(vars : List P.TypedIdent) (output : IO.Process.Output)
(E : Strata.SMT.EncoderState) : Except Format (Result P.TypedIdent) := do
let pos := (ans.find (fun c => c == '\n' || c == '\r')).byteIdx
let verdict := ans.take pos
let rest := ans.drop pos
let stdout := output.stdout
let pos := (stdout.find (fun c => c == '\n' || c == '\r')).byteIdx
let verdict := stdout.take pos
let rest := stdout.drop pos
match verdict with
| "sat" =>
let rawModel ← getModel rest
let model ← processModel typedVarToSMTFn vars rawModel E
.ok (.sat model)
| "unsat" => .ok .unsat
| "unknown" => .ok .unknown
| other => .error other
| _ => .error s!"stderr:{output.stderr}\nsolver stdout: {output.stdout}\n"

def VC_folder_name: String := "vcs"

Expand Down Expand Up @@ -169,8 +170,8 @@ def dischargeObligation {P : PureExpr} [ToFormat P.Ident]
.ok "--produce-models"
else
return .error f!"Unsupported SMT solver: {smtsolver}"
let solver_out ← runSolver smtsolver #[filename, produce_models]
match solverResult typedVarToSMTFn vars solver_out estate with
let solver_output ← runSolver smtsolver #[filename, produce_models]
match solverResult typedVarToSMTFn vars solver_output estate with
| .error e => return .error e
| .ok result => return .ok (result, estate)

Expand Down
2 changes: 1 addition & 1 deletion Strata/Languages/Boogie/DDMTransform/Translate.lean
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def SourceRange.toMetaData (ictx : InputContext) (sr : SourceRange) : Imperative
let startPos := ictx.fileMap.toPosition sr.start
let endPos := ictx.fileMap.toPosition sr.stop
let uri: Uri := .file file
let fileRangeElt := ⟨ MetaData.fileRange, .fileRange ⟨ uri, startPos, endPos ⟩ ⟩
let fileRangeElt := ⟨ MetaData.fileRange, .file2dRange ⟨ uri, startPos, endPos ⟩ ⟩
#[fileRangeElt]

def getOpMetaData (op : Operation) : TransM (Imperative.MetaData Boogie.Expression) :=
Expand Down
Loading
Loading