diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f638679e1..87a2d815c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,13 +17,13 @@ /StrataTest/DDM/ @joehendrix @shigoel @aqjune-aws # Dialects -/Strata/DL/Lambda/ @shigoel @atomb @aqjune-aws -/StrataTest/DL/Lambda/ @shigoel @atomb @aqjune-aws +/Strata/DL/Lambda/ @shigoel @atomb @aqjune-aws @joscoh +/StrataTest/DL/Lambda/ @shigoel @atomb @aqjune-aws @joscoh -/Strata/DL/Imperative/ @atomb @shigoel @aqjune-aws -/StrataTest/DL/Imperative/ @atomb @shigoel @aqjune-aws +/Strata/DL/Imperative/ @atomb @shigoel @aqjune-aws @joscoh +/StrataTest/DL/Imperative/ @atomb @shigoel @aqjune-aws @joscoh -/Strata/DL/SMT/ @andrewmwells-amazon @atomb +/Strata/DL/SMT/ @andrewmwells-amazon @atomb @aqjune-aws /Strata/DL/Utils/ @shigoel @atomb @aqjune-aws @@ -33,6 +33,8 @@ /Strata/Languages/C_Simp/ @andrewmwells-amazon @shigoel +/Strata/Languages/Python/ @andrewmwells-amazon @joehendrix @shigoel + /Strata/Transform/ @atomb @andrewmwells-amazon # Documentation diff --git a/.github/scripts/check_lean_consistency.sh b/.github/scripts/check_lean_consistency.sh new file mode 100755 index 000000000..60a31a08f --- /dev/null +++ b/.github/scripts/check_lean_consistency.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# This checks that the version of Lean in the main package +# matches the version in the Verso documentation package. +set -e + +if cmp --silent "lean-toolchain" "docs/verso/lean-toolchain"; then + exit 0 +else + echo "Strata and StrataDoc lean versions do not match." + exit 1 +fi \ No newline at end of file diff --git a/.github/workflows/cbmc.yml b/.github/workflows/cbmc.yml new file mode 100644 index 000000000..a7fd2900d --- /dev/null +++ b/.github/workflows/cbmc.yml @@ -0,0 +1,64 @@ +name: CBMC + +on: + workflow_call: + +jobs: + cbmc_test: + name: Run CBMC tests + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install cvc5 + shell: bash + run: | + ARCH=$(uname -m) + if [ "$ARCH" = "x86_64" ]; then + ARCH_NAME="x86_64" + elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + ARCH_NAME="arm64" + else + echo "Unsupported architecture: $ARCH" + exit 1 + fi + wget https://github.com/cvc5/cvc5/releases/download/cvc5-1.2.1/cvc5-Linux-${ARCH_NAME}-static.zip + unzip cvc5-Linux-${ARCH_NAME}-static.zip + chmod +x cvc5-Linux-${ARCH_NAME}-static/bin/cvc5 + echo "$GITHUB_WORKSPACE/cvc5-Linux-${ARCH_NAME}-static/bin/" >> $GITHUB_PATH + - name: Install z3 + shell: bash + run: | + ARCH=$(uname -m) + if [ "$ARCH" = "x86_64" ]; then + ARCH_NAME="x86_64" + wget https://github.com/Z3Prover/z3/releases/download/z3-4.15.2/z3-4.15.2-x64-glibc-2.39.zip + ARCHIVE_NAME="z3-4.15.2-x64-glibc-2.39" + elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + ARCH_NAME="arm64" + wget https://github.com/Z3Prover/z3/releases/download/z3-4.15.2/z3-4.15.2-arm64-glibc-2.34.zip + ARCHIVE_NAME="z3-4.15.2-arm64-win" + else + echo "Unsupported architecture: $ARCH" + exit 1 + fi + unzip "${ARCHIVE_NAME}.zip" + chmod +x "${ARCHIVE_NAME}/bin/z3" + echo "$GITHUB_WORKSPACE/${ARCHIVE_NAME}/bin/" >> $GITHUB_PATH + - name: Install CBMC + shell: bash + run: | + wget https://github.com/diffblue/cbmc/releases/download/cbmc-6.4.1/ubuntu-22.04-cbmc-6.4.1-Linux.deb + sudo dpkg -i ubuntu-22.04-cbmc-6.4.1-Linux.deb + - name: Build Strata + uses: leanprover/lean-action@v1 + - uses: actions/setup-python@v5 + with: + python-version: '3.14' + - name: Run CBMC tests + shell: bash + run: | + export CBMC_DIR="/usr/bin/" + ./Strata/Backends/CBMC/run_strata_cbmc.sh Strata/Backends/CBMC/tests/simpleTest.csimp.st diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a72dc08f..613848e7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,12 +78,20 @@ jobs: run: dotnet test ${SOLUTION} - name: Test Strata Command line run: .github/scripts/testStrataCommand.sh - - name: Verify examples + - name: Validate examples against expected output + working-directory: Examples shell: bash - run: | - find "Examples" -maxdepth 1 -type f -name "*.st" | while IFS= read -r file; do - source ~/.profile && lake exe StrataVerify "$file" - done + run: ./run_examples.sh + - uses: actions/setup-python@v5 + with: + python-version: '3.14' + - name: Build using pip + run: pip install . + working-directory: Tools/Python + - name: Run pyAnalyze tests + working-directory: StrataTest/Languages/Python + shell: bash + run: ./run_py_analyze.sh lint_checks: name: Run lint checks @@ -104,25 +112,27 @@ jobs: run: .github/scripts/lintWhitespace.sh - name: Check for import Lean run: .github/scripts/checkLeanImport.sh + - name: Check Lean version consistncy + run: .github/scripts/check_lean_consistency.sh build_doc: - name: Build Documentation + name: Build documentation runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v4 - - name: Build documetation package + - name: Build documentation package uses: leanprover/lean-action@v1 with: build-args: '--wfail' - lake-package-directory: 'docs/ddm' - - name: Build Documentation - run: lake exe docs - working-directory: docs/ddm + lake-package-directory: 'docs/verso' + - name: Build documentation + run: ./generate.sh + working-directory: docs/verso build_python: - name: Build and test Python package + name: Build and test Python runs-on: ubuntu-latest permissions: contents: read @@ -130,7 +140,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.14' - name: Build using pip run: pip install . working-directory: Tools/Python @@ -143,3 +153,9 @@ jobs: - name: Run test script run: ./scripts/run_cpython_tests.sh working-directory: Tools/Python + + cbmc: + needs: build_and_test_lean + permissions: + contents: read + uses: ./.github/workflows/cbmc.yml diff --git a/.gitignore b/.gitignore index 49a43fd4a..38c4713de 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ vcs/*.smt2 Strata.code-workspace + +StrataTest/Internal \ No newline at end of file diff --git a/.kiro/settings/mcp.json b/.kiro/settings/mcp.json new file mode 100644 index 000000000..96d5ca525 --- /dev/null +++ b/.kiro/settings/mcp.json @@ -0,0 +1,29 @@ +{ + "mcpServers": { + "lean-lsp": { + "command": "uvx", + "args": [ + "lean-lsp-mcp" + ], + "disabled": false, + "autoApprove": [ + "lean_build", + "lean_file_contents", + "lean_diagnostic_messages", + "lean_goal", + "lean_term_goal", + "lean_hover_info", + "lean_completions", + "lean_declaration_file", + "lean_multi_attempt", + "lean_run_code", + "loan_local_search", + "lean_leansearch", + "lean_loogle", + "lean_leanfinder", + "lean_state_search", + "lean_hammer_premise" + ] + } + } +} \ No newline at end of file diff --git a/.kiro/steering/plausible-instructions.md b/.kiro/steering/plausible-instructions.md new file mode 100644 index 000000000..0e31357a8 --- /dev/null +++ b/.kiro/steering/plausible-instructions.md @@ -0,0 +1,109 @@ +--- +inclusion: fileMatch +fileMatchPattern: ['**/StrataTest/**/*.lean', '**/Examples/**/*.lean'] +--- + +# Plausible Property-Based Testing + +Use Plausible for property-based testing in Strata test files. It finds counter-examples to propositions by generating random test cases. + +## When to Use + +- Testing transformation correctness properties (e.g., `DetToNondetCorrect.lean`, `CallElimCorrect.lean`) +- Validating semantic equivalences between program representations +- Testing expression evaluation properties (Lambda, Imperative dialects) +- Verifying type system properties +- Quick sanity checks before formal proofs + +## Required Type Class Instances + +For custom types to work with Plausible, implement these three instances: + +1. **`Repr α`** - String representation (use `deriving Repr` when possible) +2. **`Shrinkable α`** - Reduces counter-examples to minimal cases +3. **`Arbitrary α`** - Random value generator + +## Implementation Patterns + +### Simple Algebraic Types +Use automatic derivation: +```lean +inductive MyType where + | case1 : Nat → MyType + | case2 : Bool → MyType + deriving Repr, Arbitrary +``` + +### Dependent Types with Invariants +Manually implement instances to maintain invariants: +```lean +structure BoundedNat where + val : Nat + h : val < 100 + deriving Repr + +instance : Shrinkable BoundedNat where + shrink := fun ⟨n, _⟩ => + (Shrinkable.shrink n).filterMap fun n' => + if h : n' < 100 then some ⟨n', h⟩ else none + +instance : Arbitrary BoundedNat := + ⟨do + let n ← SampleableExt.interpSample (Fin 100) + return ⟨n.val, n.isLt⟩⟩ +``` + +### Product Types +Shrink components independently: +```lean +instance : Shrinkable (α × β) where + shrink := fun (a, b) => + (Shrinkable.shrink a).map (·, b) ++ + (Shrinkable.shrink b).map (a, ·) +``` + +### Strata-Specific Types +For Lambda expressions, Imperative statements, or Boogie constructs, ensure generators produce well-typed, valid AST nodes. + +## Usage Modes + +### Tactic Mode (Preferred) +```lean +example (xs ys : Array Nat) : xs.size = ys.size → xs = ys := by + plausible -- Finds: xs := #[0], ys := #[1] +``` + +### Programmatic Mode +```lean +#eval Testable.check <| ∀ (x y : Nat), x + y = y + x -- Success +``` + +### Configuration +```lean +example (a b : Bool) : a = b := by + plausible (config := {quiet := true, numInst := 1000}) +``` + +## Testing Workflow + +1. **Write property** as a Lean proposition +2. **Add Plausible instances** for custom types (if needed) +3. **Run test** with `plausible` tactic or `#eval Testable.check` +4. **Interpret results**: + - Counter-example found → property is false + - Success → property likely holds (not a proof) + - Use counter-examples to refine properties or fix bugs + +## Common Pitfalls + +- **Missing instances**: Ensure `Repr`, `Shrinkable`, and `Arbitrary` are all implemented +- **Invalid generators**: Generators must respect type invariants (use guards or filtered generation) +- **Non-decidable properties**: Plausible requires decidable propositions +- **Over-constrained shrinking**: Shrinking should preserve the counter-example property + +## Integration with Strata + +- Place property tests in `StrataTest/` mirroring the `Strata/` structure +- Test transformation correctness before attempting formal proofs +- Use Plausible to validate semantic preservation properties +- Generate test cases for edge cases in dialect implementations diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md new file mode 100644 index 000000000..9fc46dc8a --- /dev/null +++ b/.kiro/steering/structure.md @@ -0,0 +1,236 @@ +--- +inclusion: always +--- + +# Strata Repository Structure + +## Overview + +Strata is a Lean4 verification framework using **dialects** as composable language building blocks. The primary target is the **Boogie dialect** for deductive program verification. + +## Repository Structure + +### Directory Structure + +- `Strata/` - Core implementation (DDM, dialects, languages, transforms, backends) +- `StrataTest/` - Unit tests (mirrors Strata/ structure) +- `Examples/` - Sample programs (`.st` files, naming: `..st`) +- `Tools/` - External tools (BoogieToStrata, Python utilities) +- `vcs/` - Generated SMT2 verification conditions + +### Core Components + +**`Strata/DDM/`** - Dialect Definition Mechanism +- Parser, elaborator, AST for defining dialects +- Lean integration and Ion format support + +**`Strata/DL/`** - Dialect Library +- `Lambda/` - Pure functional expressions (base layer) +- `Imperative/` - Statements with control flow (builds on Lambda) +- `SMT/` - SMT-LIB encoding and solver interface +- `Util/` - Shared utilities (maps, lists, string generation) + +**`Strata/Languages/`** - Concrete Language Implementations +- `Boogie/` - Primary verification language (procedures, contracts, VCG, SMT encoding) +- `C_Simp/` - Simplified C-like language +- `Dyn/` - Dynamic language example + +**`Strata/Transform/`** - Program Transformations +- Each transformation has implementation + optional correctness proof (`*Correct.lean`) +- `DetToNondet` - Deterministic to non-deterministic control flow +- `CallElim` - Procedure call elimination via inlining +- `LoopElim` - Loop elimination using invariants + +**`Strata/Backends/`** - Verification Backends +- `CBMC/` - C Bounded Model Checker integration + +## Core Concepts + +### Dialect Composition + +Dialects are composable language layers, each defining syntax, types, and semantics: +- **Lambda** - Base expression layer (functional) +- **Imperative** - Adds statements and control flow (uses Lambda expressions) +- **Boogie** - Adds procedures, contracts, and verification (uses Imperative statements) + +### Lambda Dialect (Expressions) + +**Location:** `Strata/DL/Lambda/` + +Base expression language with: +- Constants, operations, variables, abstractions, quantifiers (with triggers), application, conditionals +- Locally nameless representation (de Bruijn indices for bound variables) +- First-order and higher-order support + +**Key files:** +- `LExpr.lean` - AST definition +- `LExprEval.lean` - Evaluator +- `LExprType*.lean` - Type checking +- `LTy.lean` - Type system + +### Imperative Dialect (Statements) + +**Location:** `Strata/DL/Imperative/` + +Statement-level constructs parameterized by expression and command types: +- `cmd` - Atomic command execution +- `block` - Labeled statement sequences +- `ite` - Conditional branching +- `loop` - While loops with optional invariants and measures +- `goto` - Unconditional jumps + +**Key files:** +- `Stmt.lean` - AST definition +- `StmtSemantics.lean` - Operational semantics +- `Cmd.lean` - Command interface + +## Boogie Dialect + +**Location:** `Strata/Languages/Boogie/` + +Intermediate Verification Language for deductive program verification, mirroring the [Boogie verifier](https://github.com/boogie-org/boogie). + +### Types (`Factory.lean`) +- Primitives: `bool`, `int`, `real`, `bv`, `string` +- Constructors: `Map a b`, function types, polymorphic types +- User-defined: abstract types, type synonyms + +### Expressions (`Expressions.lean`, `Factory.lean`) +Lambda expressions with Boogie operators: +- Boolean: `And`, `Or`, `Not`, `Implies` +- Arithmetic: `Add`, `Sub`, `Mul`, `Div`, `Mod` +- Comparison: `Lt`, `Le`, `Gt`, `Ge` +- Bitvector: `BV.Add`, `BV.Shl`, `BV.ULt`, etc. +- Map: `Select`, `Store` + +### Commands (`Statement.lean`) +Atomic operations: +- `init` - Declare/initialize variable +- `set` - Assignment +- `havoc` - Non-deterministic assignment +- `assert` - Proof obligation (generates VC) +- `assume` - State restriction +- `call` - Procedure invocation + +### Procedures (`Procedure.lean`) +Main verification unit with: +- Parameters (input/output) +- Contracts: `requires` (preconditions), `ensures` (postconditions), `modifies` (frame) +- Optional body (implementation) +- `old(expr)` in postconditions refers to pre-state + +### Programs (`Program.lean`) +Top-level structure: +- Type declarations, constants, globals +- Pure functions (with optional definitions) +- Axioms (assumed facts) +- Procedures (specifications + bodies) + +## Other Languages + +**C_Simp** (`Strata/Languages/C_Simp/`) - Simplified C-like language +- Verification via transformation to Boogie +- Pipeline: Parse → Transform loops → Translate to Boogie → VCG → SMT + +**Dyn** (`Strata/Languages/Dyn/`) - Dynamic language example demonstrating dialect extensibility + +## Transformations (`Strata/Transform/`) + +Program-to-program translations for simplification and verification. Each has optional correctness proof (`*Correct.lean`). + +**DetToNondet** - Deterministic to non-deterministic control flow +- Replaces `if-then-else` with `choice` + `assume` + +**CallElim** - Procedure call elimination via contract inlining +- `call y := f(x)` → `assert requires; havoc y, globals; assume ensures` +- Enables modular verification + +**LoopElim** - Loop elimination using invariants +- `while guard invariant I { body }` → entry check + arbitrary iteration + exit assumption +- Produces loop-free programs for symbolic execution + +## Key Files Quick Reference + +| Component | File | +|-----------|------| +| Expression AST | `Strata/DL/Lambda/LExpr.lean` | +| Expression eval | `Strata/DL/Lambda/LExprEval.lean` | +| Expression types | `Strata/DL/Lambda/LTy.lean` | +| Statement AST | `Strata/DL/Imperative/Stmt.lean` | +| Statement semantics | `Strata/DL/Imperative/StmtSemantics.lean` | +| Boogie expressions | `Strata/Languages/Boogie/Expressions.lean` | +| Boogie commands | `Strata/Languages/Boogie/Statement.lean` | +| Boogie procedures | `Strata/Languages/Boogie/Procedure.lean` | +| Boogie programs | `Strata/Languages/Boogie/Program.lean` | +| Boogie operators | `Strata/Languages/Boogie/Factory.lean` | +| Boogie VCG | `Strata/Languages/Boogie/Verifier.lean` | +| SMT encoding | `Strata/Languages/Boogie/SMTEncoder.lean` | +| SMT solver | `Strata/DL/SMT/Solver.lean` | +| Transformations | `Strata/Transform/*.lean` | + +## Build System + +**Configuration:** `lakefile.toml`, `lean-toolchain` +**Main module:** `Strata.lean` + +**Executables:** +- `StrataVerify` - Main verifier +- `BoogieToGoto` - Boogie to GOTO translation +- `StrataToCBMC` - CBMC backend + +**Commands:** +```bash +lake build # Build all +lake test # Run tests +lake exe StrataVerify Examples/SimpleProc.boogie.st # Verify program +``` + +## Verification Workflow + +1. Write program (`.boogie.st` file) +2. Parse (DDM parser) +3. Type check (Boogie type system) +4. Transform (optional: eliminate calls/loops) +5. Generate VCs (symbolic execution) +6. Encode to SMT (SMT-LIB format) +7. Solve (cvc5 or Z3) +8. Report results (verified/counterexample/unknown) + +Generated VCs saved in `vcs/*.smt2` + +## Implementation Workflow + +**CRITICAL: Always read relevant files before implementing** + +Before starting any implementation task: + +1. **Identify the layer** you're working on (Lambda, Imperative, Boogie, Transform) +2. **Read the core files** for that layer from the Key Files Quick Reference table +3. **Read related files** in the same directory to understand patterns and conventions +4. **Check for similar implementations** in other dialects or transformations +5. **Review tests** in the corresponding `StrataTest/` directory for usage examples + +**Example workflows:** + +- **Adding a Boogie feature:** Read `Expressions.lean`, `Statement.lean`, `Factory.lean`, then check `StrataTest/Languages/Boogie/` for test patterns +- **Creating a transformation:** Read existing transforms (`DetToNondet.lean`, `CallElim.lean`), their correctness proofs, and tests in `StrataTest/Transform/` +- **Modifying expressions:** Read `Strata/DL/Lambda/LExpr.lean`, `LExprEval.lean`, `LTy.lean` to understand the AST, evaluation, and type system +- **Working with statements:** Read `Strata/DL/Imperative/Stmt.lean` and `StmtSemantics.lean` before making changes + +**Never assume structure or conventions - always verify by reading the actual implementation files first.** + +## Coding Conventions + +- **File organization:** Mirror test structure in `StrataTest/` to match `Strata/` +- **Naming:** Use descriptive names; transformations end with `Correct.lean` for proofs +- **Example files:** Use pattern `..st` (e.g., `SimpleProc.boogie.st`) +- **Proofs:** Transformation correctness proofs are optional but encouraged +- **Documentation:** Reference `docs/Architecture.md` for design philosophy, `docs/GettingStarted.md` for tutorials + +## Working with Dialects + +- **Expressions:** Start with Lambda dialect (`Strata/DL/Lambda/`) +- **Statements:** Build on Imperative dialect (`Strata/DL/Imperative/`) +- **New languages:** Extend existing dialects, follow Boogie as reference +- **Transformations:** Implement in `Strata/Transform/`, add tests in `StrataTest/Transform/` +- **Testing:** Add examples in `Examples/`, unit tests and property-based tests in `StrataTest/` diff --git a/.kiro/steering/testing-transformations.md b/.kiro/steering/testing-transformations.md new file mode 100644 index 000000000..90e906a4c --- /dev/null +++ b/.kiro/steering/testing-transformations.md @@ -0,0 +1,97 @@ +--- +inclusion: fileMatch +fileMatchPattern: ['StrataTest/**/*.lean', 'Strata/Transform/**/*.lean'] +--- + +# Property-Based Testing for Strata Transformations + +Use Plausible for property-based testing of transformations. Three-step process: build generators, write measurement functions, define properties. + +## Generator Design + +**Control depth to prevent infinite recursion:** +- Depth 0: Generate leaves (constants, variables) +- Depth > 0: Generate compound structures with depth-1 subexpressions +- Keep depth 2-4 for reasonable test cases + +**Generate diverse cases:** +- Expressions: constants, variables, operations (`Int.Add`, `Bool.And`), conditionals +- Statements: commands (assign, assert, assume, havoc), control flow (if, loop, block) +- Bias towards operations over constants for interesting tests + +**Key types from `Strata/Languages/Boogie/`:** +- `Expression.Expr` (Expressions.lean) +- `Statement` (Statement.lean) +- `Command` (atomic operations) +- `BoogieIdent` (identifiers) + +## Measurement Functions + +Write **total** (not `partial`) Lean functions to measure program properties. + +**Essential measurements:** +- Structural counts: calls, loops, if-statements, assertions, assumptions +- Size: AST node count for expressions/statements +- Variables: free variables, modified variables + +**Design patterns:** +- Make compositional: `countX_stmt` for single, `countX_stmts` for lists (use fold) +- Test with `#guard` before using in properties +- Total functions are easier to reason about and use in proofs + +## Property Categories + +**Structural (universal):** +- Idempotence: `transform(transform(x)) = transform(x)` +- Size monotonicity: `size(transform(x)) ≤ size(x)` +- Variable preservation: `vars(transform(x)) ⊆ vars(x)` + +**Transformation-specific:** +- CallElim: call count decreases or stays same +- LoopElim: loop count becomes zero +- DetToNondet: deterministic constructs eliminated + +**Semantic (when feasible):** +- Evaluation equivalence: `eval(transform(x)) = eval(x)` +- Type preservation: `typeOf(transform(x)) = typeOf(x)` + +**Algebraic laws (for optimizations):** +- Identity: `x * 1 = x`, `x + 0 = x`, `x && true = x` +- Annihilation: `x * 0 = 0`, `x || false = x` +- Conditionals: `if true then t else e = t` + +**Property testing pattern:** +1. Generate random input +2. Measure property in original +3. Apply transformation +4. Measure property in result +5. Assert relationship holds + +## Testing Workflow + +**Run tests:** Use Plausible interface, configure test count, examine counterexamples +**Debug failures:** Plausible shrinks to minimal case → test manually → verify helpers → simplify further + +## Key Files + +| Component | Location | +|-----------|----------| +| Expression AST | `Strata/DL/Lambda/LExpr.lean` | +| Statement AST | `Strata/DL/Imperative/Stmt.lean` | +| Boogie expressions | `Strata/Languages/Boogie/Expressions.lean` | +| Boogie statements | `Strata/Languages/Boogie/Statement.lean` | +| Transformations | `Strata/Transform/*.lean` | +| Transform tests | `StrataTest/Transform/*.lean` | + +## Implementation Checklist + +1. Add Plausible dependency to lakefile +2. Create generators with depth control (2-4 levels) +3. Write total measurement functions, test with `#guard` +4. Define structural properties (idempotence, size, variables) +5. Add transformation-specific properties +6. Test algebraic laws for optimizations +7. Run tests, debug counterexamples, iterate + +**Critical:** Always make generators depth-bounded and measurement functions total. + diff --git a/.kiro/steering/writing-proofs.md b/.kiro/steering/writing-proofs.md new file mode 100644 index 000000000..977ff15ed --- /dev/null +++ b/.kiro/steering/writing-proofs.md @@ -0,0 +1,492 @@ +--- +inclusion: always +--- + +# Writing Proofs for Strata + +## Process Overview + +**Basic three-step process:** +1. **Write informal hierarchical proof** (as Lean comments) +2. **Create Lean template with sorry's** for key intermediate results +3. **Fill in the sorry's** to complete the formal proof + +**Extended four-step process** (when intermediate results need induction): +1. **Write informal hierarchical proof** - identify which steps need induction +2. **Create initial template** - mark where separate lemmas are needed +3. **Add separate lemmas** - create lemmas for facts requiring induction +4. **Fill in all sorry's** - complete both helper lemmas and main theorem + +## Informal Proof Style + +Use hierarchical structure with Lean-inspired keywords: + +### Keywords + +- `suffices assume ... show ...` - reduce goal +- `obtain ... with ...` - introduce witness +- `case` - proof by cases +- `done` - final step proving the goal +- `by` - justification + +### Structure + +```lean +/- +Theorem: Statement of theorem + +Proof: + 1. First major step + by justification + + 2. Second major step + 2.1. Substep + by justification + 2.2. Another substep + by justification + 2.3. done + by 2.1 and 2.2 + + 3. done + by 1 and 2 +-/ +theorem name : statement := by + -- Formal Lean proof here +``` + +## Complete Example: List Length and Append + +### Step 1: Informal Hierarchical Proof + +```lean +/- +Theorem: For all lists xs and ys, length(append(xs, ys)) = length(xs) + length(ys) + +Proof: By induction on xs. + + Base case (xs = nil): + 1. append(nil, ys) = ys + by definition of append + + 2. length(append(nil, ys)) = length(ys) + by 1 + + 3. length(nil) + length(ys) = 0 + length(ys) = length(ys) + by definition of length + + 4. done + by 2 and 3 + + Inductive case (xs = cons x xs'): + Assume: length(append(xs', ys)) = length(xs') + length(ys) [IH] + + 1. append(cons x xs', ys) = cons x (append(xs', ys)) + by definition of append + + 2. length(append(cons x xs', ys)) = length(cons x (append(xs', ys))) + by 1 + + 3. length(cons x (append(xs', ys))) = 1 + length(append(xs', ys)) + by definition of length + + 4. 1 + length(append(xs', ys)) = 1 + (length(xs') + length(ys)) + by IH + + 5. 1 + (length(xs') + length(ys)) = (1 + length(xs')) + length(ys) + by arithmetic + + 6. 1 + length(xs') = length(cons x xs') + by definition of length + + 7. done + by 2, 3, 4, 5, 6 +-/ +``` + +### Step 2: Lean Template with sorry's + +The Lean template should mirror the structured natural language proof. Each major step in the informal proof becomes a `have` statement or tactic application, with `sorry` for non-trivial intermediate results. + +```lean +-- Simple list type for demonstration +inductive MyList (α : Type) where + | nil : MyList α + | cons : α → MyList α → MyList α + +def length {α : Type} : MyList α → Nat + | .nil => 0 + | .cons _ tail => 1 + length tail + +def append {α : Type} : MyList α → MyList α → MyList α + | .nil, ys => ys + | .cons x xs, ys => .cons x (append xs ys) + +theorem length_append {α : Type} (xs ys : MyList α) : + length (append xs ys) = length xs + length ys := by + -- Proof by induction on xs + induction xs with + | nil => + -- Base case: xs = nil + -- Step 1: append(nil, ys) = ys by definition + have h1 : append .nil ys = ys := sorry + -- Step 2-4: Conclude the base case + sorry + | cons x xs' ih => + -- Inductive case: xs = cons x xs' + -- IH: length(append(xs', ys)) = length(xs') + length(ys) + + -- Step 1: append(cons x xs', ys) = cons x (append(xs', ys)) + have h1 : append (.cons x xs') ys = .cons x (append xs' ys) := sorry + + -- Step 2-3: length of the result + have h2 : length (append (.cons x xs') ys) = 1 + length (append xs' ys) := sorry + + -- Step 4: Apply IH + have h3 : 1 + length (append xs' ys) = 1 + (length xs' + length ys) := sorry + + -- Step 5-7: Arithmetic and conclude + sorry +``` + +### Step 3: Fill in the sorry's + +Now fill in each `sorry` with the actual proof. The structure remains the same, but we replace placeholders with real tactics. + +```lean +-- Simple list type for demonstration +inductive MyList (α : Type) where + | nil : MyList α + | cons : α → MyList α → MyList α + +def length {α : Type} : MyList α → Nat + | .nil => 0 + | .cons _ tail => 1 + length tail + +def append {α : Type} : MyList α → MyList α → MyList α + | .nil, ys => ys + | .cons x xs, ys => .cons x (append xs ys) + +theorem length_append {α : Type} (xs ys : MyList α) : + length (append xs ys) = length xs + length ys := by + -- Proof by induction on xs + induction xs with + | nil => + -- Base case: xs = nil + -- Steps 1-4: Simplify using definitions + simp [append, length] + | cons x xs' ih => + -- Inductive case: xs = cons x xs' + -- IH: length(append(xs', ys)) = length(xs') + length(ys) + + -- Steps 1-3: Simplify using definitions + simp [append, length] + -- Step 4: Apply IH + rw [ih] + -- Steps 5-7: Arithmetic + omega +``` + +## Example with Separate Lemma: When Intermediate Result Needs Induction + +This example shows a **four-step process** when you discover an intermediate result needs induction. + +### Step 1: Informal Proof + +```lean +/- +Theorem: For all lists xs, reverse(reverse(xs)) = xs + +Proof: By induction on xs. + + Base case (xs = nil): + 1. reverse(nil) = nil + by definition + 2. reverse(reverse(nil)) = reverse(nil) = nil + by 1 + 3. done + + Inductive case (xs = cons x xs'): + Assume: reverse(reverse(xs')) = xs' [IH] + + 1. reverse(cons x xs') = append(reverse(xs'), cons x nil) + by definition of reverse + + 2. reverse(reverse(cons x xs')) = reverse(append(reverse(xs'), cons x nil)) + by 1 + + 3. reverse(append(ys, zs)) = append(reverse(zs), reverse(ys)) [LEMMA - needs induction!] + by separate lemma (requires induction on ys) + + 4. reverse(append(reverse(xs'), cons x nil)) = + append(reverse(cons x nil), reverse(reverse(xs'))) + by 3 + + 5. reverse(cons x nil) = cons x nil and reverse(reverse(xs')) = xs' + by definition and IH + + 6. append(cons x nil, xs') = cons x xs' + by definition of append + + 7. done + by 2, 4, 5, 6 +-/ +``` + +### Step 2: Initial Template (without helper lemma) + +Start with the template for the main theorem. Notice we need a fact but don't have it yet: + +```lean +theorem reverse_reverse {α : Type} (xs : MyList α) : + reverse (reverse xs) = xs := by + induction xs with + | nil => + -- Base case + sorry + | cons x xs' ih => + -- Inductive case + -- IH: reverse(reverse(xs')) = xs' + + -- Step 1-2: Unfold definitions + have h1 : reverse (.cons x xs') = append (reverse xs') (.cons x .nil) := sorry + have h2 : reverse (reverse (.cons x xs')) = + reverse (append (reverse xs') (.cons x .nil)) := sorry + + -- Step 3: We need reverse(append(ys, zs)) = append(reverse(zs), reverse(ys)) + -- The informal proof says this requires induction - we need a separate lemma! + have h3 : reverse (append (reverse xs') (.cons x .nil)) = + append (reverse (.cons x .nil)) (reverse (reverse xs')) := + sorry -- TODO: This needs a separate lemma with induction + + -- Steps 4-7: Simplify and conclude + sorry +``` + +### Step 3: Add the Separate Lemma + +Recognize that step 3 needs induction, so create a separate lemma: + +```lean +-- Helper lemma that requires induction - discovered we need this! +theorem reverse_append {α : Type} (xs ys : MyList α) : + reverse (append xs ys) = append (reverse ys) (reverse xs) := by + sorry -- This needs its own induction proof + +theorem reverse_reverse {α : Type} (xs : MyList α) : + reverse (reverse xs) = xs := by + induction xs with + | nil => + -- Base case + sorry + | cons x xs' ih => + -- Inductive case + -- IH: reverse(reverse(xs')) = xs' + + -- Step 1-2: Unfold definitions + have h1 : reverse (.cons x xs') = append (reverse xs') (.cons x .nil) := sorry + have h2 : reverse (reverse (.cons x xs')) = + reverse (append (reverse xs') (.cons x .nil)) := sorry + + -- Step 3: Apply the separate lemma (which requires induction) + have h3 : reverse (append (reverse xs') (.cons x .nil)) = + append (reverse (.cons x .nil)) (reverse (reverse xs')) := + reverse_append (reverse xs') (.cons x .nil) + + -- Steps 4-7: Simplify and conclude + sorry +``` + +### Step 4: Fill in All sorry's + +Now complete both the helper lemma and the main theorem: + +```lean +-- Helper lemma - proved separately with its own induction +theorem reverse_append {α : Type} (xs ys : MyList α) : + reverse (append xs ys) = append (reverse ys) (reverse xs) := by + induction xs with + | nil => + simp [append, reverse] + -- Need to show: reverse ys = append (reverse ys) nil + sorry -- Would need append_nil lemma + | cons x xs' ih => + simp [append, reverse] + rw [ih] + -- Need associativity of append + sorry -- Would need append_assoc lemma + +theorem reverse_reverse {α : Type} (xs : MyList α) : + reverse (reverse xs) = xs := by + induction xs with + | nil => + simp [reverse] + | cons x xs' ih => + simp [reverse] + rw [reverse_append] -- Use the separate lemma + simp [reverse, ih] +``` + +## Another Example: Case Splitting + +### Step 1: Informal Proof + +```lean +/- +Theorem: For all natural numbers n and m, max(n, m) = max(m, n) + +Proof: By cases on whether n ≤ m. + + Case 1 (n ≤ m): + 1. max(n, m) = m + by definition of max when n ≤ m + + 2. m ≥ n, so max(m, n) = m + by definition of max when m ≥ n + + 3. done + by 1 and 2 + + Case 2 (n > m): + 1. max(n, m) = n + by definition of max when n > m + + 2. n > m means m < n, so max(m, n) = n + by definition of max when m < n + + 3. done + by 1 and 2 +-/ +``` + +### Step 2: Template with sorry's + +```lean +theorem max_comm (n m : Nat) : max n m = max m n := by + -- Proof by cases on n ≤ m + cases Nat.le_total n m with + | inl h_le => + -- Case 1: n ≤ m + -- Step 1: max(n, m) = m + have h1 : max n m = m := sorry + -- Step 2: max(m, n) = m + have h2 : max m n = m := sorry + -- Step 3: Conclude + sorry + | inr h_ge => + -- Case 2: m ≤ n (equivalently, n ≥ m) + -- Step 1: max(n, m) = n + have h1 : max n m = n := sorry + -- Step 2: max(m, n) = n + have h2 : max m n = n := sorry + -- Step 3: Conclude + sorry +``` + +### Step 3: Fill in sorry's + +```lean +theorem max_comm (n m : Nat) : max n m = max m n := by + -- Proof by cases on n ≤ m + cases Nat.le_total n m with + | inl h_le => + -- Case 1: n ≤ m + simp [max, h_le] + have h_ge : m ≥ n := h_le + simp [max, h_ge] + | inr h_ge => + -- Case 2: m ≤ n + have h_not_le : ¬(n ≤ m) := Nat.not_le.mpr (Nat.lt_of_le_of_ne h_ge (Ne.symm ·)) + simp [max, h_not_le, h_ge] +``` + +## Guidelines + +**Use hierarchical structure when helpful:** +- Simple proofs: Just write the intuition +- Complex proofs: Number steps (1, 2, 3, ...) +- Very complex: Add substeps (2.1, 2.2, ...) + +**Justifications:** +- Explain why each step follows +- Reference previous steps when useful +- Cite theorems/definitions used + +**Template with sorry's:** +- Each major step in the informal proof should appear in the template +- Use `have` statements for simple intermediate results +- Create separate lemmas for complex intermediate results, especially those requiring induction +- **Rule of thumb**: If the informal proof says "by induction", make it a separate lemma +- The template structure should mirror the informal proof +- This makes it clear what needs to be proved and helps catch gaps + +**Using `plausible` instead of `sorry`:** +- Where possible, use the `plausible` tactic instead of `sorry` in templates +- `plausible` attempts to find counterexamples to false statements +- This helps catch logical errors early - if `plausible` finds a counterexample, your assertion is wrong! +- If `plausible` succeeds (no counterexample found), it doesn't prove the statement but gives confidence +- Use `sorry` only when `plausible` is not applicable (e.g., for propositions without decidable instances) + +**Choosing between `have` and separate lemmas:** +- Use `have` for: definitional equalities, simple rewrites, direct applications of existing lemmas +- Use separate lemmas for: results requiring induction, complex case analysis, reusable facts +- When in doubt, prefer separate lemmas for better modularity and reusability + +**Be flexible:** +- Don't force structure on trivial proofs +- Add detail where reasoning is subtle +- Use keywords (`suffices`, `obtain`, `case`) when they clarify + +**When to add detail:** +- When a step isn't obvious +- When there might be a gap +- When the reasoning is the interesting part + +## Workflow + +1. **Write informal proof first** - Think through the logic, identify cases and induction structure +2. **Create Lean template** - Translate structure to Lean with `plausible` or `sorry` for non-trivial steps +3. **Check template compiles** - Verify the structure is correct before filling in proofs + - If `plausible` finds a counterexample, your assertion is wrong - revisit the informal proof! +4. **Fill in sorry's/plausibles** - Replace each placeholder with actual tactics +5. **Iterate** - If a step fails, revisit the informal proof + +## Benefits + +- Catches logical gaps before writing Lean +- Makes proof structure clear +- Template with sorry's ensures the proof architecture is sound +- Easier to debug when Lean proof fails +- Documents proof strategy for readers +- Allows incremental development (template first, then fill in) + +## Key Points + +- **Write informal proof first** - Think through the logic before coding +- **Create template that mirrors informal proof** - Each major step should be visible +- **Use `plausible` or `sorry` strategically** - Mark non-trivial intermediate results + - Prefer `plausible` to catch false assertions early via counterexamples + - Fall back to `sorry` when `plausible` is not applicable +- **Choose `have` vs separate lemmas wisely**: + - `have` for simple facts (definitional equalities, direct lemma applications) + - Separate lemmas for facts requiring induction or complex proofs + - If informal proof says "by induction", make it a separate lemma +- **Add structure when it helps** - Use numbering for complex proofs +- **Justify non-obvious steps** - Explain the reasoning +- **Be flexible** - Don't force structure on simple proofs + +## Quick Reference: `have` vs Separate Lemma + +| Use `have` when... | Use separate lemma when... | +|-------------------|---------------------------| +| Follows by definition | Requires induction | +| Simple rewrite | Complex case analysis | +| Direct application of existing lemma | Reusable across multiple proofs | +| One-line proof | Multi-step proof | +| Only used once in this proof | General fact worth naming | + +**Example decision process:** +- "We need: `append xs nil = xs`" → Check informal proof +- Informal says "by induction on xs" → **Separate lemma** +- Informal says "by definition of append" → **`have` statement** + +The goal: Make it harder to prove things that aren't true by thinking through the logic explicitly, and make the proof development process incremental and manageable. diff --git a/Examples/TypeError.boogie.st b/Examples/TypeError.boogie.st new file mode 100644 index 000000000..386db3dd4 --- /dev/null +++ b/Examples/TypeError.boogie.st @@ -0,0 +1,26 @@ +program Boogie; +// +// Copyright Strata Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +var g : int; + +procedure foo() returns (y : Map int int) {}; + +procedure TestLambdaTypeCheck() returns (y : Map int (Map bool bool)) +{ + var x : Map int (Map int bool); + call y := foo(); +}; \ No newline at end of file diff --git a/Examples/dialects/Arith.dialect.st b/Examples/dialects/Arith.dialect.st index ba068c22c..c29842799 100644 --- a/Examples/dialects/Arith.dialect.st +++ b/Examples/dialects/Arith.dialect.st @@ -8,7 +8,7 @@ fn sub_expr (a : Int, b : Int) : Int => @[prec(25), leftassoc] a " - " b; fn mul_expr (a : Int, b : Int) : Int => @[prec(30), leftassoc] a " * " b; fn exp_expr (a : Int, b : Int) : Int => @[prec(32), rightassoc] a " ^ " b; -fn le (a : Int, b : Int) : Bool => @[prec(15)] a " <= " b; -fn lt (a : Int, b : Int) : Bool => @[prec(15)] a " < " b; -fn ge (a : Int, b : Int) : Bool => @[prec(15)] a " >= " b; -fn gt (a : Int, b : Int) : Bool => @[prec(15)] a " > " b; +fn le (a : Int, b : Int) : BoolType => @[prec(15)] a " <= " b; +fn lt (a : Int, b : Int) : BoolType => @[prec(15)] a " < " b; +fn ge (a : Int, b : Int) : BoolType => @[prec(15)] a " >= " b; +fn gt (a : Int, b : Int) : BoolType => @[prec(15)] a " > " b; diff --git a/Examples/dialects/Bool.dialect.st b/Examples/dialects/Bool.dialect.st index 6185e8aca..05842c319 100644 --- a/Examples/dialects/Bool.dialect.st +++ b/Examples/dialects/Bool.dialect.st @@ -1,18 +1,18 @@ dialect Bool; -// Introduce Boolean type -type Bool; -// Introduce literals as constants. -fn true_lit : Bool => "true"; -fn false_lit : Bool => "false"; +// BoolType for use in function signatures +type BoolType; + +// Function to lift Init.Bool literals to expressions +fn boolLit (b : Bool) : BoolType => b; // Introduce basic Boolean operations. fn not_expr (tp : Type, a : tp) : tp => "-" a; -fn and (a : Bool, b : Bool) : Bool => @[prec(10), leftassoc] a " && " b; -fn or (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " || " b; -fn imp (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " ==> " b; +fn and (a : BoolType, b : BoolType) : BoolType => @[prec(10), leftassoc] a " && " b; +fn or (a : BoolType, b : BoolType) : BoolType => @[prec(8), leftassoc] a " || " b; +fn imp (a : BoolType, b : BoolType) : BoolType => @[prec(8), leftassoc] a " ==> " b; // Introduce equality operations that work for arbitrary types. // The type is inferred. -fn equal (tp : Type, a : tp, b : tp) : Bool => @[prec(15)] a " == " b; -fn not_equal (tp : Type, a : tp, b : tp) : Bool => @[prec(15)] a " != " b; +fn equal (tp : Type, a : tp, b : tp) : BoolType => @[prec(15)] a " == " b; +fn not_equal (tp : Type, a : tp, b : tp) : BoolType => @[prec(15)] a " != " b; diff --git a/Examples/expected/HeapReasoning.boogie.expected b/Examples/expected/HeapReasoning.boogie.expected new file mode 100644 index 000000000..164382a9a --- /dev/null +++ b/Examples/expected/HeapReasoning.boogie.expected @@ -0,0 +1,55 @@ +Successfully parsed. +HeapReasoning.boogie.st(99, 2) [modifiesFrameRef1]: verified +HeapReasoning.boogie.st(104, 2) [modifiesFrameRef1]: verified +HeapReasoning.boogie.st(109, 2) [modifiesFrameRef1]: verified + [Container_ctor_ensures_4]: verified +HeapReasoning.boogie.st(87, 2) [Container_ctor_ensures_7]: verified +HeapReasoning.boogie.st(88, 2) [Container_ctor_ensures_8]: verified +HeapReasoning.boogie.st(89, 2) [Container_ctor_ensures_9]: verified +HeapReasoning.boogie.st(91, 2) [Container_ctor_ensures_10]: verified +HeapReasoning.boogie.st(165, 2) [modifiesFrameRef1]: verified +HeapReasoning.boogie.st(170, 2) [modifiesFrameRef2]: verified +HeapReasoning.boogie.st(173, 2) [modifiesFrameRef1Next]: verified +HeapReasoning.boogie.st(178, 2) [modifiesFrameRef2Next]: verified +HeapReasoning.boogie.st(133, 2) [UpdateContainers_ensures_5]: verified + [UpdateContainers_ensures_6]: verified +HeapReasoning.boogie.st(151, 2) [UpdateContainers_ensures_14]: verified +HeapReasoning.boogie.st(152, 2) [UpdateContainers_ensures_15]: verified +HeapReasoning.boogie.st(153, 2) [UpdateContainers_ensures_16]: verified +HeapReasoning.boogie.st(154, 2) [UpdateContainers_ensures_17]: verified +HeapReasoning.boogie.st(156, 2) [UpdateContainers_ensures_18]: verified +HeapReasoning.boogie.st(157, 2) [UpdateContainers_ensures_19]: verified +HeapReasoning.boogie.st(75, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_1]: verified +HeapReasoning.boogie.st(76, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_2]: verified +HeapReasoning.boogie.st(77, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_3]: verified +HeapReasoning.boogie.st(85, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_5]: verified +HeapReasoning.boogie.st(86, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_6]: verified +HeapReasoning.boogie.st(216, 2) [c1Lychees0]: verified +HeapReasoning.boogie.st(217, 2) [c1Pineapple1]: verified +HeapReasoning.boogie.st(75, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_1]: verified +HeapReasoning.boogie.st(76, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_2]: verified +HeapReasoning.boogie.st(77, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_3]: verified +HeapReasoning.boogie.st(85, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_5]: verified +HeapReasoning.boogie.st(86, 2) [(Origin_Container_ctor_Requires)Container_ctor_requires_6]: verified +HeapReasoning.boogie.st(227, 2) [assert_9]: verified +HeapReasoning.boogie.st(232, 2) [assert_10]: verified +HeapReasoning.boogie.st(127, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_1]: verified +HeapReasoning.boogie.st(128, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_2]: verified +HeapReasoning.boogie.st(130, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_3]: verified +HeapReasoning.boogie.st(131, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_4]: verified +HeapReasoning.boogie.st(141, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_7]: verified +HeapReasoning.boogie.st(143, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_8]: verified +HeapReasoning.boogie.st(144, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_9]: verified +HeapReasoning.boogie.st(146, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_10]: verified +HeapReasoning.boogie.st(147, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_11]: verified +HeapReasoning.boogie.st(148, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_12]: verified +HeapReasoning.boogie.st(149, 2) [(Origin_UpdateContainers_Requires)UpdateContainers_requires_13]: verified +HeapReasoning.boogie.st(238, 2) [c1Lychees1]: verified +HeapReasoning.boogie.st(239, 2) [c1Pineapple2]: verified +HeapReasoning.boogie.st(240, 2) [c2Lychees0]: verified +HeapReasoning.boogie.st(241, 2) [c2Pineapple0]: verified +HeapReasoning.boogie.st(243, 2) [c1NextEqC2]: verified +HeapReasoning.boogie.st(244, 2) [c2NextEqC1]: verified +HeapReasoning.boogie.st(198, 2) [Main_ensures_2]: verified + [Main_ensures_3]: verified +Proved all 53 goals. diff --git a/Examples/expected/LoopSimple.boogie.expected b/Examples/expected/LoopSimple.boogie.expected new file mode 100644 index 000000000..78126f608 --- /dev/null +++ b/Examples/expected/LoopSimple.boogie.expected @@ -0,0 +1,6 @@ +Successfully parsed. +LoopSimple.boogie.st(13, 2) [entry_invariant_0]: verified +LoopSimple.boogie.st(13, 2) [arbitrary_iter_maintain_invariant_0]: verified +LoopSimple.boogie.st(19, 2) [sum_assert]: verified +LoopSimple.boogie.st(20, 2) [neg_cond]: verified +Proved all 4 goals. diff --git a/Examples/expected/LoopSimple.csimp.expected b/Examples/expected/LoopSimple.csimp.expected new file mode 100644 index 000000000..78b8dff92 --- /dev/null +++ b/Examples/expected/LoopSimple.csimp.expected @@ -0,0 +1,10 @@ +Successfully parsed. + [entry_invariant]: verified + [assert_measure_pos]: verified + [measure_decreases]: verified + [measure_imp_not_guard]: verified + [arbitrary_iter_maintain_invariant]: verified + [sum_assert]: verified + [neg_cond]: verified + [post]: verified +Proved all 8 goals. diff --git a/Examples/expected/SimpleProc.boogie.expected b/Examples/expected/SimpleProc.boogie.expected new file mode 100644 index 000000000..92e1fbd84 --- /dev/null +++ b/Examples/expected/SimpleProc.boogie.expected @@ -0,0 +1,5 @@ +Successfully parsed. +SimpleProc.boogie.st(21, 2) [Test_ensures_0]: verified +SimpleProc.boogie.st(22, 2) [Test_ensures_1]: verified +SimpleProc.boogie.st(23, 2) [Test_ensures_2]: verified +Proved all 3 goals. diff --git a/Examples/expected/TypeError.boogie.expected b/Examples/expected/TypeError.boogie.expected new file mode 100644 index 000000000..be29401d5 --- /dev/null +++ b/Examples/expected/TypeError.boogie.expected @@ -0,0 +1,4 @@ +Successfully parsed. +[Strata.Boogie] Type checking error. +TypeError.boogie.st(25, 2) Impossible to unify (Map int (Map bool bool)) with (Map int int). +First mismatch: (Map bool bool) with int. diff --git a/Examples/run_examples.sh b/Examples/run_examples.sh new file mode 100755 index 000000000..e0784f1f2 --- /dev/null +++ b/Examples/run_examples.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +failed=0 + +for test_file in *.st; do + if [ -f "$test_file" ]; then + base_name=$(basename "$test_file" ".st") + expected_file="expected/${base_name}.expected" + if [ -f "$expected_file" ]; then + + output=$(cd .. && lake exe StrataVerify "Examples/${test_file}") + + if ! echo "$output" | diff -q "$expected_file" - > /dev/null; then + echo "ERROR: Analysis output for $base_name does not match expected result" + echo "$output" | diff "$expected_file" - + failed=1 + fi + fi + fi +done + +exit $failed diff --git a/Strata.lean b/Strata.lean index 326c80a6d..5c5225eef 100644 --- a/Strata.lean +++ b/Strata.lean @@ -16,18 +16,9 @@ import Strata.DL.Lambda.Lambda import Strata.DL.Imperative.Imperative /- Boogie -/ -import Strata.Languages.Boogie.Examples.Examples import Strata.Languages.Boogie.StatementSemantics -/- CSimp -/ -import Strata.Languages.C_Simp.Examples.Examples - -/- Dyn -/ -import Strata.Languages.Dyn.Examples.Examples - - /- Code Transforms -/ -import Strata.Transform.Examples import Strata.Transform.CallElimCorrect import Strata.Transform.DetToNondetCorrect diff --git a/Strata/Backends/CBMC/BoogieToCBMC.lean b/Strata/Backends/CBMC/BoogieToCBMC.lean index 8b0aea2b8..3668881f4 100644 --- a/Strata/Backends/CBMC/BoogieToCBMC.lean +++ b/Strata/Backends/CBMC/BoogieToCBMC.lean @@ -35,7 +35,7 @@ spec { #end open Boogie in -def SimpleTestEnvAST := Strata.TransM.run (Strata.translateProgram (SimpleTestEnv)) +def SimpleTestEnvAST := Strata.TransM.run Inhabited.default (Strata.translateProgram (SimpleTestEnv)) def myProc : Boogie.Procedure := match SimpleTestEnvAST.fst.decls.head!.getProc? with | .some p => p @@ -51,31 +51,31 @@ instance : IdentToStr BoogieIdent where instance : IdentToStr String where toStr := id -class HasLExpr (P : Imperative.PureExpr) (I : Type) where - expr_eq : P.Expr = Lambda.LExpr Lambda.LMonoTy I +class HasLExpr (P : Imperative.PureExpr) (I : Lambda.LExprParams) where + expr_eq : P.Expr = Lambda.LExpr I.mono -instance : HasLExpr Boogie.Expression Visibility where +instance : HasLExpr Boogie.Expression BoogieLParams where expr_eq := rfl -def exprToJson (I : Type) [IdentToStr (Lambda.Identifier I)] (e : Lambda.LExpr Lambda.LMonoTy I) (loc: SourceLoc) : Json := +def exprToJson (I : Lambda.LExprParams) [IdentToStr (Lambda.Identifier I.IDMeta)] (e : Lambda.LExpr I.mono) (loc: SourceLoc) : Json := match e with - | .app (.app (.op op _) left) right => + | .app _ (.app _ (.op _ op _) left) right => let leftJson := match left with - | .fvar varName _ => + | .fvar _ varName _ => if IdentToStr.toStr varName == "z" then mkLvalueSymbol s!"{loc.functionName}::1::z" loc.lineNum loc.functionName else mkLvalueSymbol s!"{loc.functionName}::{IdentToStr.toStr varName}" loc.lineNum loc.functionName | _ => exprToJson (I:=I) left loc let rightJson := match right with - | .fvar varName _ => mkLvalueSymbol s!"{loc.functionName}::{IdentToStr.toStr varName}" loc.lineNum loc.functionName - | .const value _ => mkConstant value "10" (mkSourceLocation "ex_prog.c" loc.functionName loc.lineNum) + | .fvar _ varName _ => mkLvalueSymbol s!"{loc.functionName}::{IdentToStr.toStr varName}" loc.lineNum loc.functionName + | .intConst _ value => mkConstant (toString value) "10" (mkSourceLocation "ex_prog.c" loc.functionName loc.lineNum) | _ => exprToJson (I:=I) right loc mkBinaryOp (opToStr (IdentToStr.toStr op)) loc.lineNum loc.functionName leftJson rightJson - | .const "true" _ => mkConstantTrue (mkSourceLocation "ex_prog.c" loc.functionName "3") - | .const n _ => - mkConstant n "10" (mkSourceLocation "ex_prog.c" loc.functionName "14") - | .fvar name _ => + | .true _ => mkConstantTrue (mkSourceLocation "ex_prog.c" loc.functionName "3") + | .intConst _ n => + mkConstant (toString n) "10" (mkSourceLocation "ex_prog.c" loc.functionName "14") + | .fvar _ name _ => mkLvalueSymbol s!"{loc.functionName}::{IdentToStr.toStr name}" loc.lineNum loc.functionName | _ => panic! "Unimplemented" @@ -102,7 +102,7 @@ def cmdToJson (e : Boogie.Command) (loc: SourceLoc) : Json := mkCodeBlock "expression" "6" loc.functionName #[ mkSideEffect "assign" "6" loc.functionName mkIntType #[ mkLvalueSymbol s!"{loc.functionName}::1::{name.toPretty}" "6" loc.functionName, - exprToJson (I:=Visibility) expr exprLoc + exprToJson (I:=BoogieLParams) expr exprLoc ] ] | .assert label expr _ => @@ -127,7 +127,7 @@ def cmdToJson (e : Boogie.Command) (loc: SourceLoc) : Json := Json.mkObj [ ("id", "arguments"), ("sub", Json.arr #[ - exprToJson (I:=Visibility) expr exprLoc, + exprToJson (I:=BoogieLParams) expr exprLoc, mkStringConstant label "7" loc.functionName ]) ] @@ -155,7 +155,7 @@ def cmdToJson (e : Boogie.Command) (loc: SourceLoc) : Json := Json.mkObj [ ("id", "arguments"), ("sub", Json.arr #[ - exprToJson (I:=Visibility) expr exprLoc + exprToJson (I:=BoogieLParams) expr exprLoc ]) ] ] @@ -163,7 +163,7 @@ def cmdToJson (e : Boogie.Command) (loc: SourceLoc) : Json := | .havoc _ _ => panic! "Unimplemented" mutual -partial def blockToJson {P : Imperative.PureExpr} (I : Type) [IdentToStr (Lambda.Identifier I)] [HasLExpr P I] +partial def blockToJson {P : Imperative.PureExpr} (I : Lambda.LExprParams) [IdentToStr (Lambda.Identifier I.IDMeta)] [HasLExpr P I] (b: Imperative.Block P Command) (loc: SourceLoc) : Json := Json.mkObj [ ("id", "code"), @@ -173,15 +173,15 @@ partial def blockToJson {P : Imperative.PureExpr} (I : Type) [IdentToStr (Lambda ("statement", Json.mkObj [("id", "block")]), ("type", emptyType) ]), - ("sub", Json.arr (b.ss.map (stmtToJson (I:=I) · loc)).toArray) + ("sub", Json.arr (b.map (stmtToJson (I:=I) · loc)).toArray) ] -partial def stmtToJson {P : Imperative.PureExpr} (I : Type) [IdentToStr (Lambda.Identifier I)] [HasLExpr P I] +partial def stmtToJson {P : Imperative.PureExpr} (I : Lambda.LExprParams) [IdentToStr (Lambda.Identifier I.IDMeta)] [HasLExpr P I] (e : Imperative.Stmt P Command) (loc: SourceLoc) : Json := match e with | .cmd cmd => cmdToJson cmd loc | .ite cond thenb elseb _ => - let converted_cond : Lambda.LExpr Lambda.LMonoTy I := @HasLExpr.expr_eq P (I:=I) _ ▸ cond + let converted_cond : Lambda.LExpr I.mono := @HasLExpr.expr_eq P (I:=I) _ ▸ cond Json.mkObj [ ("id", "code"), ("namedSub", Json.mkObj [ @@ -200,7 +200,7 @@ end def listToExpr (l: ListMap BoogieLabel Boogie.Procedure.Check) : Boogie.Expression.Expr := match l with - | _ => .const "true" none + | _ => .true () def createContractSymbolFromAST (func : Boogie.Procedure) : CBMCSymbol := let location : Location := { @@ -245,7 +245,7 @@ def createContractSymbolFromAST (func : Boogie.Procedure) : CBMCSymbol := ]), ("sub", Json.arr #[ parameterTuple, - exprToJson (I:=Visibility) (listToExpr func.spec.preconditions) {functionName := func.header.name.toPretty, lineNum := "2"} + exprToJson (I:=BoogieLParams) (listToExpr func.spec.preconditions) {functionName := func.header.name.toPretty, lineNum := "2"} ]) ] @@ -257,7 +257,7 @@ def createContractSymbolFromAST (func : Boogie.Procedure) : CBMCSymbol := ]), ("sub", Json.arr #[ parameterTuple, - exprToJson (I:=Visibility) (listToExpr func.spec.postconditions) {functionName := func.header.name.toPretty, lineNum := "2"} + exprToJson (I:=BoogieLParams) (listToExpr func.spec.postconditions) {functionName := func.header.name.toPretty, lineNum := "2"} ]) ] @@ -331,7 +331,7 @@ def createImplementationSymbolFromAST (func : Boogie.Procedure) : CBMCSymbol := -- For now, keep the hardcoded implementation but use function name from AST let loc : SourceLoc := { functionName := (func.header.name.toPretty), lineNum := "1" } - let stmtJsons := (func.body.map (stmtToJson (I:=Visibility) · loc)) + let stmtJsons := (func.body.map (stmtToJson (I:=BoogieLParams) · loc)) let implValue := Json.mkObj [ ("id", "code"), diff --git a/Strata/Backends/CBMC/StrataToCBMC.lean b/Strata/Backends/CBMC/StrataToCBMC.lean index c6a32bf26..025c73cc2 100644 --- a/Strata/Backends/CBMC/StrataToCBMC.lean +++ b/Strata/Backends/CBMC/StrataToCBMC.lean @@ -50,7 +50,7 @@ def myFunc : Strata.C_Simp.Function := SimpleTestEnvAST.fst.funcs.head! def lexprToCBMC (expr : Strata.C_Simp.Expression.Expr) (functionName : String) : Json := let cfg := CBMCConfig.empty match expr with - | .app (.app (.op op _) (.fvar varName _)) (.const value _) => + | .app () (.app () (.op () op _) (.fvar () varName _)) (.const () value) => mkBinaryOp (opToStr op.name) "2" functionName (config := cfg) (Json.mkObj [ ("id", "symbol"), @@ -63,8 +63,8 @@ def lexprToCBMC (expr : Strata.C_Simp.Expression.Expr) (functionName : String) : ("type", mkIntType cfg) ]) ]) - (mkConstant value "10" (mkSourceLocation "from_andrew.c" functionName "2" cfg) cfg) - | .const "true" _ => + (mkConstant (toString value) "10" (mkSourceLocation "from_andrew.c" functionName "2" cfg) cfg) + | .true _ => Json.mkObj [ ("id", "notequal"), ("namedSub", Json.mkObj [ @@ -194,18 +194,18 @@ def getParamJson(func: Strata.C_Simp.Function) : Json := def exprToJson (e : Strata.C_Simp.Expression.Expr) (loc: SourceLoc) : Json := let cfg := CBMCConfig.empty match e with - | .app (.app (.op op _) left) right => + | .app _ (.app _ (.op _ op _) left) right => let leftJson := match left with - | .fvar "z" _ => mkLvalueSymbol s!"{loc.functionName}::1::z" loc.lineNum loc.functionName cfg - | .fvar varName _ => mkLvalueSymbol s!"{loc.functionName}::{varName}" loc.lineNum loc.functionName cfg + | .fvar _ "z" _ => mkLvalueSymbol s!"{loc.functionName}::1::z" loc.lineNum loc.functionName cfg + | .fvar _ varName _ => mkLvalueSymbol s!"{loc.functionName}::{varName}" loc.lineNum loc.functionName cfg | _ => exprToJson left loc let rightJson := match right with - | .fvar varName _ => mkLvalueSymbol s!"{loc.functionName}::{varName}" loc.lineNum loc.functionName cfg - | .const value _ => mkConstant value "10" (mkSourceLocation "from_andrew.c" loc.functionName loc.lineNum cfg) cfg + | .fvar _ varName _ => mkLvalueSymbol s!"{loc.functionName}::{varName}" loc.lineNum loc.functionName cfg + | .const _ value => mkConstant (toString value) "10" (mkSourceLocation "from_andrew.c" loc.functionName loc.lineNum cfg) cfg | _ => exprToJson right loc mkBinaryOp (opToStr op.name) loc.lineNum loc.functionName leftJson rightJson cfg - | .const n _ => - mkConstant n "10" (mkSourceLocation "from_andrew.c" loc.functionName "14" cfg) cfg + | .intConst _ n => + mkConstant (toString n) "10" (mkSourceLocation "from_andrew.c" loc.functionName "14" cfg) cfg | _ => panic! "Unimplemented" def cmdToJson (e : Strata.C_Simp.Command) (loc: SourceLoc) : Json := @@ -299,7 +299,7 @@ partial def blockToJson (b: Imperative.Block Strata.C_Simp.Expression Strata.C_S ("statement", Json.mkObj [("id", "block")]), ("type", emptyType) ]), - ("sub", Json.arr (b.ss.map (stmtToJson · loc)).toArray) + ("sub", Json.arr (b.map (stmtToJson · loc)).toArray) ] partial def stmtToJson (e : Strata.C_Simp.Statement) (loc: SourceLoc) : Json := diff --git a/Strata/Backends/CBMC/run_strata_cbmc.sh b/Strata/Backends/CBMC/run_strata_cbmc.sh index 1036db261..b0cbef916 100755 --- a/Strata/Backends/CBMC/run_strata_cbmc.sh +++ b/Strata/Backends/CBMC/run_strata_cbmc.sh @@ -1,4 +1,4 @@ -#!/bin/zsh +#!/bin/bash # To run this script, define `CBMC_DIR`. E.g., #`export CBMC_DIR=$HOME/Development/cbmc/build/bin/` @@ -8,6 +8,14 @@ python3 Strata/Backends/CBMC/resources/process_json.py combine Strata/Backends/C $CBMC_DIR/symtab2gb full.json --out full.goto $CBMC_DIR/goto-instrument --enforce-contract simpleTest full.goto full_checking.goto -$CBMC_DIR/cbmc full_checking.goto --function simpleTest --trace +OUTPUT=$($CBMC_DIR/cbmc full_checking.goto --function simpleTest --trace) +echo "$OUTPUT" -rm foo.json full.json full.goto full_checking.goto \ No newline at end of file +if [[ "$OUTPUT" == *"VERIFICATION SUCCESSFUL" ]]; then + EXIT_CODE=0 +else + EXIT_CODE=1 +fi + +rm foo.json full.json full.goto full_checking.goto +exit $EXIT_CODE \ No newline at end of file diff --git a/Strata/DDM/AST.lean b/Strata/DDM/AST.lean index 429cc7a9a..c8c46aeb8 100644 --- a/Strata/DDM/AST.lean +++ b/Strata/DDM/AST.lean @@ -3,13 +3,18 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + +public import Std.Data.HashMap.Basic +public import Strata.DDM.Util.ByteArray +public import Strata.DDM.Util.Decimal import Std.Data.HashMap import Strata.DDM.Util.Array -import Strata.DDM.Util.Decimal set_option autoImplicit false +public section namespace Strata abbrev DialectName := String @@ -24,31 +29,30 @@ namespace QualifiedIdent def fullName (i : QualifiedIdent) : String := s!"{i.dialect}.{i.name}" instance : ToString QualifiedIdent where - toString := fullName - -syntax:max (name := quoteIdent) "q`" noWs ident : term + toString := private fullName section - open _root_.Lean +public protected def quote (i : QualifiedIdent) : Term := Syntax.mkCApp ``QualifiedIdent.mk #[quote i.dialect, quote i.name] instance : Quote QualifiedIdent where - quote i := Syntax.mkCApp ``QualifiedIdent.mk #[quote i.dialect, quote i.name] + quote := QualifiedIdent.quote -@[macro quoteIdent] def quoteIdentImpl : Macro +syntax:max (name := quoteIdent) "q`" noWs ident : term + +@[macro quoteIdent] meta def quoteIdentImpl : Macro | `(q`$l:ident) => if let .str (.str .anonymous d) suf := l.getId then - pure (quote (QualifiedIdent.mk d suf) : Term) + pure <| Syntax.mkCApp ``QualifiedIdent.mk #[quote d, quote suf] else throw (.error l.raw "Quoted identifiers must contain two components") | _ => Macro.throwUnsupported - end -end QualifiedIdent - #guard q`A.C = { dialect := "A", name := "C" } +end QualifiedIdent + /-- Denotes a fully specified syntax category in the Strata dialect. -/ @@ -134,23 +138,6 @@ protected def instTypeM {m α} [Monad m] (d : TypeExprF α) (bindings : α → N | .arrow n a b => .arrow n <$> a.instTypeM bindings <*> b.instTypeM bindings termination_by d -def flattenArrow {α} : Array (TypeExprF α) → TypeExprF α → Array (TypeExprF α) -| a, .arrow _ l r => flattenArrow (a.push l) r -| a, r => a.push r - -theorem flattenArrow_size {α} (args : Array (TypeExprF α)) (r : TypeExprF α) : - sizeOf (flattenArrow args r) ≤ 1 + sizeOf args + sizeOf r := by - unfold flattenArrow - split - case h_1 => - rename_i l r - have h := flattenArrow_size (args.push l) r - simp at * - omega - case h_2 => - decreasing_tactic - termination_by r - end TypeExprF mutual @@ -174,9 +161,10 @@ inductive ArgF (α : Type) : Type where | expr (e : ExprF α) | type (e : TypeExprF α) | ident (ann : α) (i : String) -| num (ann : α)(v : Nat) +| num (ann : α) (v : Nat) | decimal (ann : α) (v : Decimal) | strlit (ann : α) (i : String) +| bytes (ann : α) (a : ByteArray) | option (ann : α) (l : Option (ArgF α)) | seq (ann : α) (l : Array (ArgF α)) | commaSepList (ann : α) (l : Array (ArgF α)) @@ -184,87 +172,52 @@ deriving Inhabited, Repr end +mutual + +def ExprF.ann {α : Type} : ExprF α → α +| .bvar ann _ => ann +| .fvar ann _ => ann +| .fn ann _ => ann +| .app ann _ _ => ann + +def ArgF.ann {α : Type} : ArgF α → α +| .op o => o.ann +| .cat c => c.ann +| .expr e => e.ann +| .type t => t.ann +| .ident ann _ => ann +| .num ann _ => ann +| .decimal ann _ => ann +| .bytes ann _ => ann +| .strlit ann _ => ann +| .option ann _ => ann +| .seq ann _ => ann +| .commaSepList ann _ => ann + +end + namespace OperationF -def sizeOf_spec {α} [SizeOf α] (op : OperationF α) : sizeOf op = 1 + sizeOf op.ann + sizeOf op.name + sizeOf op.args := +theorem sizeOf_spec {α} [SizeOf α] (op : OperationF α) : sizeOf op = 1 + sizeOf op.ann + sizeOf op.name + sizeOf op.args := match op with | { ann, name, args } => by simp -theorem sizeOf_lt_of_op_arg {α} {e : ArgF α} {op : OperationF α} (p : e ∈ op.args) : sizeOf e < sizeOf op := by - cases op with - | mk ann name args => - have q : sizeOf e < sizeOf args := by decreasing_tactic - decreasing_tactic +private theorem sizeOf_lt_of_op_arg {α} {e : ArgF α} {op : OperationF α} (p : e ∈ op.args) : sizeOf e < sizeOf op := by + let ⟨ann, name, args⟩ := op + have q : sizeOf e < sizeOf args := by decreasing_tactic + decreasing_tactic end OperationF -/-- -Array ofelements whose sizes are bounded by a value. --/ -abbrev SizeBounded (α : Type _) [SizeOf α] {β} [SizeOf β] (e : β) (c : Int) := { a : α // sizeOf a ≤ sizeOf e + c } - namespace ExprF -/-- -Head-normal form for an expression consists of an operation --/ -structure HNF {α} (e : ExprF α) where - fn : ExprF α - args : SizeBounded (Array (ArgF α)) e 1 - -protected def hnf {α} (e0 : ExprF α) : HNF e0 := - let rec aux (e : ExprF α) (args : Array (ArgF α) := #[]) - (szP : sizeOf e + sizeOf args ≤ sizeOf e0 + 2): HNF e0 := - match e with - | .bvar .. | .fvar .. | .fn .. => - { fn := e, args := ⟨args.reverse, by simp at szP; simp; omega⟩ } - | .app _ f a => - aux f (args.push a) (by simp at *; omega) - aux e0 #[] (by simp) - -partial def flatten {α} (e : ExprF α) (prev : List (ArgF α) := []) : ExprF α × List (ArgF α) := +public def flatten {α} (e : ExprF α) (prev : List (ArgF α) := []) : ExprF α × List (ArgF α) := match e with | .app _ f e => f.flatten (e :: prev) | _ => (e, prev) end ExprF -namespace ArgF - -def asOp! {α} [Inhabited α] [Repr α] : ArgF α → OperationF α -| .op a => a -| a => panic! s!"{repr a} is not an operation." - -def asCat! {α} [Inhabited α] [Repr α] : ArgF α → SyntaxCatF α -| .cat a => a -| a => panic! s!"{repr a} is not a syntax category." - -def asExpr! {α} [Inhabited α] [Repr α] : ArgF α → ExprF α -| .expr a => a -| a => panic! s!"{repr a} is not an expression." - -def asType! {α} [Inhabited α] [Repr α] : ArgF α → TypeExprF α -| .type a => a -| a => panic! s!"{repr a} is not a type." - -def asIdent! {α} [Repr α] : ArgF α → String -| .ident _ a => a -| a => panic! s!"{repr a} is not an identifier." - -def asOption! {α} [Repr α] : ArgF α → Option (ArgF α) -| .option _ a => a -| a => panic! s!"{repr a} is not an option." - -def asSeq! {α} [Repr α] : ArgF α → Array (ArgF α) -| .seq _ a => a -| a => panic! s!"{repr a} is not an sequence." - -def asCommaSepList {α} [Repr α] : ArgF α → Array (ArgF α) -| .commaSepList _ a => a -| a => panic! s!"{repr a} is not an comma separated list." - -end ArgF - /-- Source location information in the DDM is defined by a range of bytes in a UTF-8 string with the input @@ -277,9 +230,9 @@ As an example, in the string `"123abc\ndef"`, the string -/ structure SourceRange where /-- The starting offset of the source range. -/ - start : String.Pos + start : String.Pos.Raw /-- One past the end of the range. -/ - stop : String.Pos + stop : String.Pos.Raw deriving BEq, Inhabited, Repr namespace SourceRange @@ -302,7 +255,7 @@ Decidable equality definitions of Expr, Operation and Arg. They cannot be naturally derived from 'deriving DecidableEq'. It seems the fact that their constructors use Array of themselves makes this hard. -/ -def ExprF.beq {α} [BEq α] (e1 e2 : ExprF α) : Bool := +private def ExprF.beq {α} [BEq α] (e1 e2 : ExprF α) : Bool := match e1, e2 with | .bvar a1 i1, .bvar a2 i2 | .fvar a1 i1, .fvar a2 i2 @@ -311,7 +264,7 @@ def ExprF.beq {α} [BEq α] (e1 e2 : ExprF α) : Bool := | _, _ => false termination_by sizeOf e1 -def OperationF.beq {α} [BEq α] (o1 o2 : OperationF α) : Bool := +private def OperationF.beq {α} [BEq α] (o1 o2 : OperationF α) : Bool := o1.ann == o2.ann && o1.name = o2.name && ArgF.array_beq o1.args o2.args @@ -320,7 +273,7 @@ decreasing_by simp [OperationF.sizeOf_spec] omega -def ArgF.beq {α} [BEq α] (a1 a2 : ArgF α) : Bool := +private def ArgF.beq {α} [BEq α] (a1 a2 : ArgF α) : Bool := match a1, a2 with | .op o1, .op o2 => OperationF.beq o1 o2 | .cat c1, .cat c2 => c1 == c2 @@ -342,7 +295,7 @@ def ArgF.beq {α} [BEq α] (a1 a2 : ArgF α) : Bool := | _, _ => false termination_by sizeOf a1 -def ArgF.array_beq {α} [BEq α] (a1 a2 : Array (ArgF α)) : Bool := +private def ArgF.array_beq {α} [BEq α] (a1 a2 : Array (ArgF α)) : Bool := if size_eq : a1.size = a2.size then a1.size.all fun i p => ArgF.beq a1[i] a2[i] else @@ -351,10 +304,10 @@ termination_by sizeOf a1 end --- TODO: extend these to LawfulBEq! -instance {α} [BEq α] : BEq (ExprF α) where beq := ExprF.beq -instance {α} [BEq α] : BEq (OperationF α) where beq := OperationF.beq -instance {α} [BEq α] : BEq (ArgF α) where beq := ArgF.beq +-- TODO: extend these to DecidableEq +instance {α} [BEq α] : BEq (ExprF α) where beq := private ExprF.beq +instance {α} [BEq α] : BEq (OperationF α) where beq := private OperationF.beq +instance {α} [BEq α] : BEq (ArgF α) where beq := private ArgF.beq inductive MetadataArg where | bool (e : Bool) @@ -365,7 +318,8 @@ deriving BEq, Inhabited, Repr namespace MetadataArg -protected def decEq (x y : MetadataArg) : Decidable (x = y) := +@[instance] +protected def instDecidableEq (x y : MetadataArg) : Decidable (x = y) := match x with | .bool x => match y with @@ -397,14 +351,12 @@ protected def decEq (x y : MetadataArg) : Decidable (x = y) := match x, y with | none, none => .isTrue (by grind) | some x, some y => - match MetadataArg.decEq x y with + match MetadataArg.instDecidableEq x y with | .isTrue p => .isTrue (by grind) | .isFalse p => .isFalse (by grind) | none, some _ | some _, none => .isFalse (by grind) | .bool _ | .catbvar _ | .num _ => .isFalse (by grind) -instance : DecidableEq MetadataArg := MetadataArg.decEq - end MetadataArg structure MetadataAttr where @@ -414,18 +366,12 @@ deriving DecidableEq, Inhabited, Repr namespace MetadataAttr -def scopeName := q`StrataDDL.scope +private def scopeName := q`StrataDDL.scope /-- Create scope using deBrujin index of environment. -/ def scope (idx : Nat) : MetadataAttr := { ident := scopeName, args := #[.catbvar idx ] } -def declare (varIndex typeIndex : Nat) : MetadataAttr := - { ident := q`StrataDDL.declare, args := #[.catbvar varIndex, .catbvar typeIndex]} - -def declareFn (varIndex bindingsIndex typeIndex : Nat) : MetadataAttr := - { ident := q`StrataDDL.declareFn, args := #[.catbvar varIndex, .catbvar bindingsIndex, .catbvar typeIndex]} - end MetadataAttr structure Metadata where @@ -439,40 +385,44 @@ protected def emptyWithCapacity (c : Nat) : Metadata := { toArray := .emptyWithC protected def empty : Metadata := .emptyWithCapacity 0 +instance : EmptyCollection Metadata where + emptyCollection := .empty + protected def push (md : Metadata) (attr : MetadataAttr) : Metadata := .ofArray <| md.toArray.push attr instance : Inhabited Metadata where - default := .empty + default := {} def isEmpty (md : Metadata) := md.toArray.isEmpty def toList (m : Metadata) : List MetadataAttr := m.toArray.toList instance : Membership QualifiedIdent Metadata where - mem md x := md.toArray.any fun a => a.ident = x + mem md x := private md.toArray.any fun a => a.ident = x -instance (x : QualifiedIdent) (md : Metadata) : Decidable (x ∈ md) := by +@[instance] +def instDecidableMem (x : QualifiedIdent) (md : Metadata) : Decidable (x ∈ md) := by apply instDecidableEqBool instance : GetElem? Metadata QualifiedIdent (Array MetadataArg) (fun md i => i ∈ md) where - getElem md i _p := + getElem md i _p := private match md.toArray.find? (·.ident = i) with | none => default | some a => a.args - getElem? md i := + getElem? md i := private match md.toArray.find? (·.ident = i) with | none => none | some a => a.args -def scopeIndex (metadata : Metadata) : Option Nat := +private def scopeIndex (metadata : Metadata) : Option Nat := match metadata[MetadataAttr.scopeName]? with | none => none | some #[.catbvar idx] => some idx | some _ => panic! s!"Unexpected argument count to {MetadataAttr.scopeName.fullName}" /-- Returns the index of the value in the binding for the given variable of the scope to use. -/ -def resultIndex (metadata : Metadata) : Option Nat := +private def resultIndex (metadata : Metadata) : Option Nat := match metadata[MetadataAttr.scopeName]? with | none => none | some #[.catbvar idx] => @@ -561,7 +511,8 @@ inductive SyntaxDefAtom -- Surround with parenthesis if the precedence of the argument is less than `prec`. -- Note. If `prec` is zero, then parenthesis will never be added (even with pp.parens is true). -- This is to avoid parens in categories that do not support them. -| ident (level : Nat) (prec : Nat) +-- The unwrap parameter specifies if the value should be unwrapped to a raw type. +| ident (level : Nat) (prec : Nat) (unwrap : Bool := false) | str (lit : String) | indent (n : Nat) (args : Array SyntaxDefAtom) deriving BEq, Inhabited, Repr @@ -592,22 +543,18 @@ def mkFunApp (name : String) (n : Nat) : SyntaxDef := prec := appPrec } -def ofList (atoms : List SyntaxDefAtom) (prec : Nat := maxPrec): SyntaxDef where +def ofList (atoms : List SyntaxDefAtom) (prec : Nat := maxPrec) : SyntaxDef where atoms := atoms.toArray prec := prec end SyntaxDef -/-- Structure that defines a binding introduced by an operation or function. -/ -inductive SyntaxElabType -| type : PreType → SyntaxElabType -deriving Repr - structure DebruijnIndex (n : Nat) where val : Nat isLt : val < n deriving Repr + namespace DebruijnIndex def toLevel {n} : DebruijnIndex n → Fin n @@ -647,7 +594,7 @@ An argument declaration in an operator or function. structure ArgDecl where ident : Var kind : ArgDeclKind - metadata : Metadata := .empty + metadata : Metadata := {} deriving BEq, Inhabited, Repr structure ArgDecls where @@ -659,12 +606,15 @@ namespace ArgDecls protected def empty : ArgDecls := { toArray := #[] } -protected def size (a : ArgDecls) : Nat := a.toArray.size +instance : EmptyCollection ArgDecls where + emptyCollection := .empty + +@[expose] protected def size (a : ArgDecls) : Nat := a.toArray.size protected def isEmpty (a : ArgDecls) : Bool := a.toArray.isEmpty instance : GetElem ArgDecls Nat ArgDecl fun a i => i < a.size where - getElem a i p := a.toArray[i] + getElem a i p := private a.toArray[i] protected def foldl {α} (a : ArgDecls) (f : α → ArgDecl → α) (init : α): α := a.toArray.foldl f init @@ -705,7 +655,7 @@ structure TypeBindingSpec (argDecls : ArgDecls) where defIndex : Option (DebruijnIndex argDecls.size) deriving Repr -/- +/-- A spec for introducing a new binding into a type context. -/ inductive BindingSpec (argDecls : ArgDecls) where @@ -721,7 +671,7 @@ def nameIndex {argDecls} : BindingSpec argDecls → DebruijnIndex argDecls.size end BindingSpec -abbrev NewBindingM := StateM (Array String) +private abbrev NewBindingM := StateM (Array String) private def newBindingErr (msg : String) : NewBindingM Unit := modify (·.push msg) @@ -863,7 +813,7 @@ structure OpDecl where /-- Schema for operator -/ syntaxDef : SyntaxDef /-- Metadata for operator. -/ - metadata : Metadata := .empty + metadata : Metadata := {} /-- New bindings -/ newBindings : Array (BindingSpec argDecls) := parseNewBindings! metadata argDecls deriving Inhabited, Repr @@ -871,21 +821,13 @@ deriving Inhabited, Repr namespace OpDecl instance : BEq OpDecl where - beq x y := + beq x y := private x.name = y.name && x.argDecls == y.argDecls && x.category = y.category && x.syntaxDef == y.syntaxDef && x.metadata == y.metadata -def mk1 - (name : String) - (argDecls : ArgDecls) - (category : QualifiedIdent) - (syntaxDef : SyntaxDef) - (metadata : Metadata) : OpDecl := - { name, argDecls, category, syntaxDef, metadata } - end OpDecl abbrev FnName := String @@ -895,7 +837,7 @@ structure FunctionDecl where argDecls : ArgDecls result : PreType syntaxDef : SyntaxDef - metadata : Metadata := .empty + metadata : Metadata := {} deriving BEq, Inhabited, Repr inductive MetadataArgType @@ -951,7 +893,7 @@ structure Collection (α : Type) where namespace Collection instance {m α} : ForIn m (Collection α) α where - forIn c i f := do + forIn c i f := private do let step d _h r := match c.proj d with | .some v => f v r @@ -967,22 +909,22 @@ protected def fold {α β} (f : β → α → β) (init : β) (c : Collection α c.declarations.foldl step init instance {α} : ToString (Collection α) where - toString c := + toString c := private let step i a := let r := if i.fst then i.snd else i.snd ++ ", " (false, r ++ c.pretty a) (c.fold step (true, "{") |>.snd) ++ "}" -inductive Mem {α} (c : Collection α) (nm : String) : Prop +private inductive Mem {α} (c : Collection α) (nm : String) : Prop | intro : (h : nm ∈ c.cache) → (r : α) → c.proj (c.cache[nm]) = some r → Mem c nm -def Mem.inCache {α} {c : Collection α} {nm} : Mem c nm → nm ∈ c.cache +private def Mem.inCache {α} {c : Collection α} {nm} : Mem c nm → nm ∈ c.cache | .intro h _ _ => h instance {α} : Membership String (Collection α) where - mem := Mem + mem := private Mem -instance {α} (nm : String) (c : Collection α) : Decidable (nm ∈ c) := +def decideMap {α} (nm : String) (c : Collection α) : Decidable (nm ∈ c) := match p : c.cache[nm]? with | none => isFalse fun (.intro inCache _ _) => by simp [getElem?_def] at p @@ -1006,10 +948,33 @@ instance {α} (nm : String) (c : Collection α) : Decidable (nm ∈ c) := | Exists.intro h eq => simp only [eq, q] isTrue (Mem.intro inCache z val_eq) -end Collection +@[instance] +def instDecidableMem {α} (nm : String) (c : Collection α) : Decidable (nm ∈ c) := + match p : c.cache[nm]? with + | none => isFalse fun (.intro inCache _ _) => by + simp [getElem?_def] at p + contradiction + | some d => + match q: c.proj d with + | none => isFalse fun (.intro inCache z z_eq) => by + simp [getElem?_def] at p + match p with + | .intro _ eq => + simp only [eq, q] at z_eq + contradiction + | some z => + have inCache : nm ∈ c.cache := by + simp [getElem?_def] at p + match p with + | Exists.intro i _ => exact i + have val_eq : c.proj c.cache[nm] = some z := by + simp [getElem?_def] at p + match p with + | Exists.intro h eq => simp only [eq, q] + isTrue (Mem.intro inCache z val_eq) instance {α} : GetElem? (Collection α) String α (fun c nm => nm ∈ c) where - getElem c nm p := + getElem c nm p := private have inCache : nm ∈ c.cache := p.inCache match q : c.cache[nm] with | d => @@ -1021,11 +986,13 @@ instance {α} : GetElem? (Collection α) String α (fun c nm => nm ∈ c) where | .intro inCache z h => simp only [q, r] at h contradiction - getElem? c nm := + getElem? c nm := private match c.cache[nm]? with | none => none | some d => c.proj d +end Collection + /-- A dialect definition. -/ @@ -1036,10 +1003,15 @@ structure Dialect where declarations : Array Decl := #[] cache : Std.HashMap String Decl := declarations.foldl (init := {}) fun m d => m.insert d.name d -deriving Inhabited namespace Dialect +instance : Inhabited Dialect where + default := { + name := default + imports := #[] + } + instance : BEq Dialect where beq x y := x.name = y.name && x.imports = y.imports @@ -1105,25 +1077,17 @@ def addDecl (d : Dialect) (decl : Decl) : Dialect := cache := d.cache.insert name decl } -def declareSyntaxCat (d : Dialect) (decl : SynCatDecl) := - d.addDecl (.syncat decl) - -def declareType (d : Dialect) (name : String) (argNames : Array (Ann String SourceRange)) := - d.addDecl (.type { name, argNames }) - -def declareMetadata (d : Dialect) (decl : MetadataDecl) : Dialect := - d.addDecl (.metadata decl) - instance : Membership String Dialect where - mem d nm := nm ∈ d.cache + mem d nm := private nm ∈ d.cache -instance (nm : String) (d : Dialect) : Decidable (nm ∈ d) := +@[instance] +def instDecidableMem (nm : String) (d : Dialect) : Decidable (nm ∈ d) := inferInstanceAs (Decidable (nm ∈ d.cache)) end Dialect /-- BEq between two Std HashMap; checked by doing inclusion test twice -/ -instance {α β} [BEq α] [Hashable α] [BEq β]: BEq (Std.HashMap α β) where +private instance {α β} [BEq α] [Hashable α] [BEq β]: BEq (Std.HashMap α β) where beq x y := Id.run do if x.size ≠ y.size then return false @@ -1132,25 +1096,69 @@ instance {α β} [BEq α] [Hashable α] [BEq β]: BEq (Std.HashMap α β) where return false return true +def DialectMap.Closed (map : Std.HashMap DialectName Dialect) := + ∀(d : DialectName) (p: d ∈ map), map[d].imports.all (· ∈ map) + structure DialectMap where - map : Std.HashMap DialectName Dialect -deriving BEq, Inhabited + private map : Std.HashMap DialectName Dialect + private closed : DialectMap.Closed map namespace DialectMap +private instance : BEq DialectMap where + beq x y := x.map == y.map + +protected def empty : DialectMap := { map := {}, closed := fun _ p => by simp at p } + instance : EmptyCollection DialectMap where - emptyCollection := .mk {} + emptyCollection := .empty + +instance : Inhabited DialectMap where + default := private .empty instance : Membership DialectName DialectMap where - mem m d := d ∈ m.map + mem m d := private d ∈ m.map -instance (d : DialectName) (m : DialectMap) : Decidable (d ∈ m) := +@[instance] +def instDecidableMem (d : DialectName) (m : DialectMap) : Decidable (d ∈ m) := inferInstanceAs (Decidable (d ∈ m.map)) instance : GetElem? DialectMap DialectName Dialect (fun m d => d ∈ m) where - getElem m d p := m.map[d] - getElem? m d := m.map[d]? - getElem! m d := m.map[d]! + getElem m d p := private m.map[d] + getElem? m d := private m.map[d]? + getElem! m d := private m.map[d]! + +private theorem insert_preserves_closed + (m : Std.HashMap DialectName Dialect) + (m_closed : DialectMap.Closed m) + (d : Dialect) + (d_imports_ok : d.imports.all (· ∈ m)) + (name : DialectName) + (mem : name ∈ m.insert d.name d) : + ((m.insert d.name d)[name].imports.all fun x => decide (x ∈ m.insert d.name d)) = true := by + if eq : d.name = name then + simp at d_imports_ok + simp [eq] + intro i lt + exact Or.inr (d_imports_ok i lt) + else + simp only [Std.HashMap.mem_insert, eq, beq_iff_eq, false_or] at mem + have cl := m_closed name mem + simp at cl + simp [Std.HashMap.getElem_insert, eq] + intro i lt + exact Or.inr (cl i lt) + +/-- +This inserts a new dialect into the dialect map. + +This requires propositions to ensure we do not change the semantics +of dialects and imports are already in dialect. +-/ +def insert (m : DialectMap) (d : Dialect) (_d_new : d.name ∉ m) (d_imports_ok : d.imports.all (· ∈ m)) : DialectMap := + { map := m.map.insert d.name d + closed := insert_preserves_closed m.map m.closed d d_imports_ok + } /-- This inserts a dialect in to the dialect map. @@ -1159,18 +1167,34 @@ It panics if a dialect with the same name is already in the map or if the dialect imports a dialect not already in the map. -/ def insert! (m : DialectMap) (d : Dialect) : DialectMap := - assert! d.name ∉ m - assert! d.imports.all (· ∈ m) - { map := m.map.insert d.name d } + if d_new : d.name ∈ m.map then + panic! s!"{d.name} already in map." + else + if d_imports_ok : d.imports.all (· ∈ m.map) then + m.insert d d_new d_imports_ok + else + panic! s!"Missing import." def ofList! (l : List Dialect) : DialectMap := - let m := l.foldl (init := {}) fun m d => - assert! d.name ∉ m; - m.insert d.name d - assert! l.all fun d => d.imports.all (· ∈ m) - { map := m } + let map : Std.HashMap DialectName Dialect := + l.foldl (init := .emptyWithCapacity l.length) fun m d => + m.insert d.name d + let check := map.toArray.all fun (_, d) => d.imports.all (· ∈ map) + if p : check then + { map := map, + closed := by + intro name name_mem + simp only [check, Array.all_eq_true_iff_forall_mem (xs := map.toArray)] at p + have mem : (name, map[name]) ∈ map.toArray := by + simp [Std.HashMap.mem_toArray_iff_getElem?_eq_some] + exact p (name, map[name]) mem + } + else + panic! "Invalid list" -def toList (m : DialectMap) : List Dialect := m.map.values +private def toListAux (m : DialectMap) : List Dialect := m.map.values + +protected def toList (m : DialectMap) : List Dialect := toListAux m def decl! (dm : DialectMap) (ident : QualifiedIdent) : Decl := match dm.map[ident.dialect]? with @@ -1180,34 +1204,73 @@ def decl! (dm : DialectMap) (ident : QualifiedIdent) : Decl := | some decl => decl | none => panic! s!"Unknown declaration {ident.fullName}" -def opDecl! (dm : DialectMap) (ident : QualifiedIdent) : OpDecl := - match dm.decl! ident with - | .op decl => decl - | _ => panic! s!"Unknown operation {ident.fullName}" +/-- +Return set of all dialects that are imported by `dialect`. + +This includes transitive imports. +-/ +private partial def importedDialectsAux (dmm : Std.HashMap DialectName Dialect) (dmm_closed : DialectMap.Closed dmm) (dialect : DialectName) (p : dialect ∈ dmm) : DialectMap := + aux {} #[dialect] (by simp; exact p) (by simp) + where aux (map : Std.HashMap DialectName Dialect) + (next : Array DialectName) + (nextp : ∀name, name ∈ next → name ∈ dmm) + (inv : ∀name (mem : name ∈ map), map[name].imports.all (fun i => i ∈ map ∨ i ∈ next)) + : DialectMap := + if emptyP : next.isEmpty then + { map := map, + closed := by intro d mem; grind + } + else + have next_size_pos : next.size > 0 := by + simp only [Array.isEmpty_iff] at emptyP + grind + let name := next.back (h := next_size_pos) + if name_mem : name ∈ map then + aux map next.pop + (by + intro d p + exact nextp _ (Array.of_mem_pop p)) + (by + simp only [Array.all_eq_true'] + intro d d_mem e e_mem + simp only [Array.all_eq_true'] at inv + have inv2 := inv d d_mem e e_mem + simp only [Array.mem_iff_back_or_pop e next_size_pos] at inv2 + grind) + else + have name_in_dm : name ∈ dmm := nextp name (by grind) + let d := dmm[name] + aux (map.insert name d) (next.pop ++ d.imports) + (by + intro nm nm_mem + simp at nm_mem + match nm_mem with + | .inl nm_mem => + exact nextp _ (Array.of_mem_pop nm_mem) + | .inr nm_mem => + have inv := dmm_closed name name_in_dm + simp only [Array.all_eq_true'] at inv + have inv2 := inv nm nm_mem + simp at inv2 + exact inv2) + (by + intro n n_mem + if n_eq : name = n then + simp [n_eq] + else + simp [n_eq] at n_mem + simp [n_eq, Std.HashMap.getElem_insert] + intro i lt + have mem := Array.mem_iff_back_or_pop (map[n].imports[i]) next_size_pos + grind) /-- Return set of all dialects that are imported by `dialect`. This includes transitive imports. -/ -partial def importedDialects! (map : DialectMap) (dialect : DialectName) : DialectMap := aux (.ofList [(d.name, d)]) [d] - where d := - match map[dialect]? with - | none => panic! s!"Unknown dialect {dialect}" - | some d => d - aux (all : Std.HashMap DialectName Dialect) (next : List Dialect) : DialectMap := - match next with - | d :: next => - let (all, next) := d.imports.foldl (init := (all, next)) fun (all, next) i => - if i ∈ all then - (all, next) - else - let d := match map[i]? with - | none => panic! s!"Unknown dialect {i}" - | some d => d - (all.insert i d, d :: next) - aux all next - | [] => DialectMap.mk all +partial def importedDialects (dm : DialectMap) (dialect : DialectName) (p : dialect ∈ dm) : DialectMap := + importedDialectsAux dm.map dm.closed dialect p end DialectMap @@ -1224,7 +1287,7 @@ partial def foldOverArgBindingSpecs {α β} : β := match a with | .op op => op.foldBindingSpecs m f init - | .expr _ | .type _ | .cat _ | .ident .. | .num .. | .decimal .. | .strlit .. => init + | .expr _ | .type _ | .cat _ | .ident .. | .num .. | .decimal .. | .bytes .. | .strlit .. => init | .option _ none => init | .option _ (some a) => foldOverArgBindingSpecs m f init a | .seq _ a => a.attach.foldl (init := init) fun init ⟨a, _⟩ => foldOverArgBindingSpecs m f init a @@ -1233,30 +1296,33 @@ partial def foldOverArgBindingSpecs {α β} /-- Invoke a function `f` over each of the declaration specifications for an operator. -/ -partial def OperationF.foldBindingSpecs {α β} +private partial def OperationF.foldBindingSpecs {α β} (m : DialectMap) (f : β → α → ∀{argDecls : ArgDecls}, BindingSpec argDecls → Vector (ArgF α) argDecls.size → β) (init : β) (op : OperationF α) : β := - let decl := m.opDecl! op.name - let argDecls := decl.argDecls - let args := op.args - if h : args.size = argDecls.size then - let argsV : Vector (ArgF α) argDecls.size := ⟨args, h⟩ - let init := - match decl.metadata.resultLevel argDecls.size with - | none => init - | some lvl => foldOverArgAtLevel m f init argDecls argsV lvl - decl.newBindings.foldl (init := init) fun a b => f a op.ann b argsV - else - @panic _ ⟨init⟩ "Expected arguments to match bindings" + + match m.decl! op.name with + | .op decl => + let argDecls := decl.argDecls + let args := op.args + if h : args.size = argDecls.size then + let argsV : Vector (ArgF α) argDecls.size := ⟨args, h⟩ + let init := + match decl.metadata.resultLevel argDecls.size with + | none => init + | some lvl => foldOverArgAtLevel m f init argDecls argsV lvl + decl.newBindings.foldl (init := init) fun a b => f a op.ann b argsV + else + @panic _ ⟨init⟩ "Expected arguments to match bindings" + | _ => @panic _ ⟨init⟩ s!"Unknown operation {op.name}" /-- Invoke a function `f` over a given argument for a function or operation so that the result context for that argument can be constructed. -/ -partial def foldOverArgAtLevel {α β} +private partial def foldOverArgAtLevel {α β} (m : DialectMap) (f : β → α → ∀{argDecls : ArgDecls}, BindingSpec argDecls → Vector (ArgF α) argDecls.size → β) (init : β) @@ -1327,24 +1393,28 @@ partial def resolveBindingIndices { argDecls : ArgDecls } (m : DialectMap) (src Typing environment created from declarations in an environment. -/ structure GlobalContext where - nameMap : Std.HashMap Var FreeVarIndex := {} - vars : Array (Var × GlobalKind) := #[] -deriving BEq, Repr + nameMap : Std.HashMap Var FreeVarIndex + vars : Array (Var × GlobalKind) +deriving Repr namespace GlobalContext +instance : EmptyCollection GlobalContext where + emptyCollection := private { nameMap := {}, vars := {}} + +--deriving instance BEq for GlobalContext + instance : Inhabited GlobalContext where default := {} instance : Membership Var GlobalContext where mem ctx v := v ∈ ctx.nameMap -theorem mem_def (v : Var) (ctx : GlobalContext) : v ∈ ctx ↔ v ∈ ctx.nameMap := by trivial +@[instance] +def instDecidableMem (v : Var) (ctx : GlobalContext) : Decidable (v ∈ ctx) := + inferInstanceAs (Decidable (v ∈ ctx.nameMap)) -instance (v : Var) (ctx : GlobalContext) : Decidable (v ∈ ctx) := by - rw [mem_def]; infer_instance - -def push (ctx : GlobalContext) (v : Var) (k : GlobalKind) : GlobalContext := +private def push (ctx : GlobalContext) (v : Var) (k : GlobalKind) : GlobalContext := if v ∈ ctx then panic! s!"Var {v} already defined" else @@ -1383,12 +1453,14 @@ structure Program where /-- Final global context for program. -/ globalContext : GlobalContext := commands.foldl (init := {}) (·.addCommand dialects ·) -deriving BEq namespace Program +instance : BEq Program where + beq x y := x.dialect == y.dialect && x.commands == y.commands + instance : Inhabited Program where - default := { dialects := {}, dialect := default } + default := private { dialects := .empty, dialect := default } def addCommand (env : Program) (cmd : Operation) : Program := { env with @@ -1419,3 +1491,4 @@ macro "sizeOf_op_arg_dec" : tactic => macro_rules | `(tactic| decreasing_trivial) => `(tactic| sizeOf_op_arg_dec) end Strata +end diff --git a/Strata/DDM/BuiltinDialects.lean b/Strata/DDM/BuiltinDialects.lean new file mode 100644 index 000000000..a9d6a6798 --- /dev/null +++ b/Strata/DDM/BuiltinDialects.lean @@ -0,0 +1,18 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module +public import Strata.DDM.Elab.LoadedDialects +public import Strata.DDM.BuiltinDialects.Init +public import Strata.DDM.BuiltinDialects.StrataDDL +public import Strata.DDM.BuiltinDialects.StrataHeader + +public section +namespace Strata.Elab.LoadedDialects + +def builtin : LoadedDialects := .ofDialects! #[initDialect, headerDialect, StrataDDL] + +end Strata.Elab.LoadedDialects +end diff --git a/Strata/DDM/BuiltinDialects/BuiltinM.lean b/Strata/DDM/BuiltinDialects/BuiltinM.lean index cb586d793..aff665fa2 100644 --- a/Strata/DDM/BuiltinDialects/BuiltinM.lean +++ b/Strata/DDM/BuiltinDialects/BuiltinM.lean @@ -3,27 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Elab.DeclM +public import Strata.DDM.Elab.DeclM +public import Lean.Parser.Types +public import Strata.DDM.Elab.LoadedDialects -namespace Strata -namespace Elab - -def initTokenTable : Lean.Parser.TokenTable := - initParsers.fixedParsers.fold (init := {}) fun tt _ p => tt.addParser p - -namespace DeclState - -def ofDialects (ds : LoadedDialects) : DeclState := - let s : DeclState := { - openDialects := #[] - openDialectSet := {} - tokenTable := initTokenTable - } - ds.dialects.toList.foldl (init := s) (·.openLoadedDialect! ds ·) - -end DeclState +public section +namespace Strata.Elab abbrev BuiltinM := StateT Dialect DeclM @@ -38,7 +26,6 @@ def create! (name : DialectName) (dialects : Array Dialect) (act : BuiltinM Unit else d - def addDecl (decl : Decl) : BuiltinM Unit := do modify fun d => d.addDecl decl diff --git a/Strata/DDM/BuiltinDialects/Init.lean b/Strata/DDM/BuiltinDialects/Init.lean index 63243bab7..1bd9bbe8c 100644 --- a/Strata/DDM/BuiltinDialects/Init.lean +++ b/Strata/DDM/BuiltinDialects/Init.lean @@ -3,13 +3,17 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + +public import Strata.DDM.AST import Strata.DDM.BuiltinDialects.BuiltinM +open Strata.Elab + +public section namespace Strata -open Elab -open Parser (minPrec) def SyntaxCat.mkOpt (c:SyntaxCat) : SyntaxCat := { ann := .none, name := q`Init.Option, args := #[c] } def SyntaxCat.mkSeq (c:SyntaxCat) : SyntaxCat := { ann := .none, name := q`Init.Seq, args := #[c] } @@ -22,9 +26,24 @@ def initDialect : Dialect := BuiltinM.create! "Init" #[] do declareAtomicCat q`Init.Ident declareAtomicCat q`Init.Num + declareAtomicCat q`Init.ByteArray declareAtomicCat q`Init.Decimal declareAtomicCat q`Init.Str + declareCat q`Init.Bool + declareOp { + name := "boolTrue", + argDecls := .empty, + category := q`Init.Bool, + syntaxDef := .ofList [.str "true"], + } + declareOp { + name := "boolFalse", + argDecls := .empty, + category := q`Init.Bool, + syntaxDef := .ofList [.str "false"], + } + declareCat q`Init.Option #["a"] declareCat q`Init.Seq #["a"] diff --git a/Strata/DDM/BuiltinDialects/StrataDDL.lean b/Strata/DDM/BuiltinDialects/StrataDDL.lean index d9e80a0e3..af442a448 100644 --- a/Strata/DDM/BuiltinDialects/StrataDDL.lean +++ b/Strata/DDM/BuiltinDialects/StrataDDL.lean @@ -3,12 +3,16 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public import Strata.DDM.AST +import Strata.DDM.BuiltinDialects.BuiltinM import Strata.DDM.BuiltinDialects.Init -namespace Strata +open Strata.Elab -open Elab +public section +namespace Strata def StrataDDL : Dialect := BuiltinM.create! "StrataDDL" #[initDialect] do let Ident : ArgDeclKind := .cat <| .atom .none q`Init.Ident @@ -151,7 +155,11 @@ def StrataDDL : Dialect := BuiltinM.create! "StrataDDL" #[initDialect] do declareMetadata { name := "rightassoc", args := #[] } declareMetadata { name := "scope", args := #[.mk "scope" .ident] } + declareMetadata { name := "unwrap", args := #[] } declareMetadata { name := "declareType", args := #[.mk "name" .ident, .mk "args" (.opt .ident)] } declareMetadata { name := "aliasType", args := #[.mk "name" .ident, .mk "args" (.opt .ident), .mk "def" .ident] } declareMetadata { name := "declare", args := #[.mk "name" .ident, .mk "type" .ident] } declareMetadata { name := "declareFn", args := #[.mk "name" .ident, .mk "args" .ident, .mk "type" .ident] } + +end Strata +end diff --git a/Strata/DDM/BuiltinDialects/StrataHeader.lean b/Strata/DDM/BuiltinDialects/StrataHeader.lean index 836dd325c..ef2ed4e1c 100644 --- a/Strata/DDM/BuiltinDialects/StrataHeader.lean +++ b/Strata/DDM/BuiltinDialects/StrataHeader.lean @@ -4,13 +4,19 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + +public import Strata.DDM.AST +import Strata.DDM.BuiltinDialects.BuiltinM import Strata.DDM.BuiltinDialects.Init +open Strata.Elab + +public section namespace Strata -open Elab -def headerDialect : Dialect := BuiltinM.create! "StrataHeader" #[initDialect] do +def headerDialect : Dialect := Elab.BuiltinM.create! "StrataHeader" #[initDialect] do let Ident : ArgDeclKind := .cat <| .atom .none q`Init.Ident let Command := q`Init.Command @@ -30,3 +36,5 @@ def headerDialect : Dialect := BuiltinM.create! "StrataHeader" #[initDialect] do category := Command, syntaxDef := .ofList [.str "program", .ident 0 0, .str ";"], } +end Strata +end diff --git a/Strata/DDM/Elab.lean b/Strata/DDM/Elab.lean index bb517179b..455af5073 100644 --- a/Strata/DDM/Elab.lean +++ b/Strata/DDM/Elab.lean @@ -3,42 +3,24 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public import Strata.DDM.Elab.DeclM +public import Strata.DDM.Ion import Strata.DDM.Elab.DialectM -import Strata.DDM.BuiltinDialects.StrataDDL -import Strata.DDM.BuiltinDialects.StrataHeader -import Strata.DDM.Util.ByteArray -import Strata.DDM.Ion +import Strata.DDM.BuiltinDialects +import Strata.DDM.Util.Ion.Serialize +import Strata.Util.IO -open Lean ( - Message - Name - Syntax - SyntaxNodeKind - TSyntax - TSyntaxArray - MacroM - quote - nullKind - ) +open Lean (Message) +open Strata.Parser (InputContext) -open Strata.Parser (DeclParser InputContext ParsingContext ParserState) - -namespace Strata - -open Lean - -namespace Elab - -namespace LoadedDialects - -def builtin : LoadedDialects := .ofDialects! #[initDialect, headerDialect, StrataDDL] - -end LoadedDialects +public section +namespace Strata.Elab namespace DeclState -def initDeclState : DeclState := +private def initDeclState : DeclState := let s : DeclState := { openDialects := #[] openDialectSet := {} @@ -56,9 +38,9 @@ deriving Inhabited partial def elabHeader (leanEnv : Lean.Environment) (inputContext : InputContext) - (startPos : String.Pos := 0) - (stopPos : String.Pos := inputContext.endPos) - : Header × Array Message × String.Pos := + (startPos : String.Pos.Raw := 0) + (stopPos : String.Pos.Raw := inputContext.endPos) + : Header × Array Message × String.Pos.Raw := let s : DeclState := .initDeclState let s := s.openLoadedDialect! .builtin headerDialect let s := { s with pos := startPos } @@ -80,7 +62,7 @@ partial def elabHeader else (default, s.errors, 0) -partial def runCommand (leanEnv : Lean.Environment) (commands : Array Operation) (stopPos : String.Pos) : DeclM (Array Operation) := do +private partial def runCommand (leanEnv : Lean.Environment) (commands : Array Operation) (stopPos : String.Pos.Raw) : DeclM (Array Operation) := do let iniPos := (←get).pos if iniPos >= stopPos then return commands @@ -97,31 +79,30 @@ def elabProgramRest (loader : LoadedDialects) (leanEnv : Lean.Environment) (inputContext : InputContext) - (loc : SourceRange) (dialect : DialectName) - (startPos : String.Pos) - (stopPos : String.Pos := inputContext.endPos) + (known : dialect ∈ loader.dialects) + (startPos : String.Pos.Raw) + (stopPos : String.Pos.Raw := inputContext.endPos) : Except (Array Message) Program := do - let some d := loader.dialects[dialect]? - | .error #[Lean.mkStringMessage inputContext loc.start s!"Unknown dialect {dialect}."] + let d := loader.dialects[dialect] let s := DeclState.initDeclState let s := { s with pos := startPos } let s := s.openLoadedDialect! loader d let ctx : DeclContext := { inputContext, stopPos, loader := loader, missingImport := false } let (cmds, s) := runCommand leanEnv #[] stopPos ctx s if s.errors.isEmpty then - let openDialects := loader.dialects.importedDialects! dialect + let openDialects := loader.dialects.importedDialects dialect known .ok <| .create openDialects dialect cmds else .error s.errors /- Elaborate a Strata program -/ -partial def elabProgram +def elabProgram (loader : LoadedDialects) (leanEnv : Lean.Environment) (inputContext : InputContext) - (startPos : String.Pos := 0) - (stopPos : String.Pos := inputContext.endPos) : Except (Array Message) Program := + (startPos : String.Pos.Raw := 0) + (stopPos : String.Pos.Raw := inputContext.endPos) : Except (Array Message) Program := assert! "Init" ∈ loader.dialects let (header, errors, startPos) := elabHeader leanEnv inputContext startPos stopPos if errors.size > 0 then @@ -131,7 +112,10 @@ partial def elabProgram | .dialect loc _ => .error #[Lean.mkStringMessage inputContext loc.start "Expected program name"] | .program loc dialect => do - elabProgramRest loader leanEnv inputContext loc dialect startPos stopPos + if p : dialect ∈ loader.dialects then + elabProgramRest loader leanEnv inputContext dialect p startPos stopPos + else + .error #[Lean.mkStringMessage inputContext loc.start s!"Unknown dialect {dialect}."] private def asText{m} [Monad m] [MonadExcept String m] (path : System.FilePath) (bytes : ByteArray) : m String := match String.fromUTF8? bytes with @@ -169,7 +153,7 @@ private def checkDialectName (ld : LoadedDialects) (actual : DialectName) (expec /-- Create a Lean.Message without position information from parsing a binary. -/ -private def mkBinaryMessage (fileName : System.FilePath) (msg : MessageData) : Lean.Message := +private def mkBinaryMessage (fileName : System.FilePath) (msg : Lean.MessageData) : Lean.Message := { fileName := fileName.toString pos := { line := 0, column := 0 } @@ -258,7 +242,7 @@ partial def loadDialectFromPath pure contents readDialectTextfile fm ld stk path contents (expected := expected) -partial def loadDialectRec +private partial def loadDialectRec (fm : DialectFileMap) (ld : LoadedDialects) (stk : Array DialectName) @@ -288,16 +272,16 @@ def readDialectTextfile let leanEnv ← match ← (Lean.mkEmptyEnvironment 0) |>.toBaseIO with | .ok e => pure e - | .error _ => return (ld, .error #[mkStringMessage inputContext 0 "Internal error: Failed to create Lean environment"]) + | .error _ => return (ld, .error #[Lean.mkStringMessage inputContext 0 "Internal error: Failed to create Lean environment"]) let (header, errors, startPos) := Elab.elabHeader leanEnv inputContext if errors.size > 0 then return (ld, .error errors) match header with | .program loc _ => - return (ld, .error #[mkStringMessage inputContext loc.start s!"Expected dialect."]) + return (ld, .error #[Lean.mkStringMessage inputContext loc.start s!"Expected dialect."]) | .dialect loc dialect => if let .error msg := checkDialectName ld dialect expected then - return (ld, .error #[mkStringMessage inputContext loc.start msg]) + return (ld, .error #[Lean.mkStringMessage inputContext loc.start msg]) let stk := stk.push dialect let (ld, d, s) ← Elab.elabDialectRest fm ld stk inputContext loc dialect startPos if s.errors.size > 0 then @@ -316,17 +300,17 @@ partial def elabDialectRest (inputContext : Parser.InputContext) (loc : SourceRange) (dialect : DialectName) - (startPos : String.Pos := 0) - (stopPos : String.Pos := inputContext.endPos) + (startPos : String.Pos.Raw := 0) + (stopPos : String.Pos.Raw := inputContext.endPos) : BaseIO (LoadedDialects × Dialect × DeclState) := do let leanEnv ← - match ← mkEmptyEnvironment 0 |>.toBaseIO with + match ← Lean.mkEmptyEnvironment 0 |>.toBaseIO with | .ok env => pure env | .error _ => - let m : Message := mkStringMessage inputContext 0 "Failed to create Lean environment." + let m : Message := Lean.mkStringMessage inputContext 0 "Failed to create Lean environment." return (dialects, default, { errors := #[m] }) - assert! "StrataDDL" ∈ dialects.dialects.map + assert! "StrataDDL" ∈ dialects.dialects let rec run : DialectM Unit := do let iniPos := (←getDeclState).pos if iniPos >= stopPos then @@ -380,7 +364,6 @@ partial def loadDialect (ld : LoadedDialects) (dialect : Strata.DialectName) : BaseIO (Elab.LoadedDialects × Except String Strata.Dialect) := do - loadDialectRec fm ld #[] dialect /- Elaborate a Strata dialect definition. -/ @@ -388,11 +371,11 @@ def elabDialect (fm : DialectFileMap) (dialects : LoadedDialects) (inputContext : Parser.InputContext) - (startPos : String.Pos := 0) - (stopPos : String.Pos := inputContext.endPos) + (startPos : String.Pos.Raw := 0) + (stopPos : String.Pos.Raw := inputContext.endPos) : BaseIO (LoadedDialects × Dialect × DeclState) := do let leanEnv ← - match ← mkEmptyEnvironment 0 |>.toBaseIO with + match ← Lean.mkEmptyEnvironment 0 |>.toBaseIO with | .ok env => pure env | .error _ => let m : Message := Lean.mkStringMessage inputContext 0 "Failed to create Lean environment." @@ -407,4 +390,21 @@ def elabDialect | .dialect loc dialect => elabDialectRest fm dialects #[] inputContext loc dialect startPos stopPos +def parseStrataProgramFromDialect (dialects : LoadedDialects) (dialect : DialectName) (input : InputContext) : IO Strata.Program := do + + let leanEnv ← Lean.mkEmptyEnvironment 0 + + let isTrue mem := inferInstanceAs (Decidable (dialect ∈ dialects.dialects)) + | throw <| IO.userError "Internal {dialect} missing from loaded dialects." + + let strataProgram ← + match elabProgramRest dialects leanEnv input dialect mem 0 with + | .ok program => + pure program + | .error errors => + let errMsg ← errors.foldlM (init := "Parse errors:\n") fun msg e => + return s!"{msg} {e.pos.line}:{e.pos.column}: {← e.data.toString}\n" + throw (IO.userError errMsg) + end Strata.Elab +end diff --git a/Strata/DDM/Elab/Core.lean b/Strata/DDM/Elab/Core.lean index f26ede90c..0eace2672 100644 --- a/Strata/DDM/Elab/Core.lean +++ b/Strata/DDM/Elab/Core.lean @@ -3,8 +3,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -import Strata.DDM.Elab.DeclM -import Strata.DDM.Elab.Tree +module + +public import Strata.DDM.Elab.DeclM +public import Strata.DDM.Elab.Tree + +import Strata.DDM.Util.Array +import Strata.DDM.Util.Fin +import Strata.DDM.HNF open Lean ( Message @@ -12,8 +18,9 @@ open Lean ( Syntax nullKind ) -open Strata.Parser (DeclParser InputContext ParserState) +open Strata.Parser (DeclParser InputContext) +public section namespace Strata namespace TypeExprF @@ -23,7 +30,7 @@ This applies global context to instantiate types and variables. Free type alias variables bound to alias -/ -protected def instType (d : TypeExprF α) (bindings : Array (TypeExprF α)) : TypeExprF α := Id.run <| +protected def instType {α} (d : TypeExprF α) (bindings : Array (TypeExprF α)) : TypeExprF α := Id.run <| d.instTypeM fun n idx => if p : idx < bindings.size then pure <| bindings[bindings.size - (idx+1)] @@ -138,7 +145,7 @@ structure ElabState where -- Errors found in elaboration. errors : Array Message := #[] -@[reducible] +@[reducible, expose] def ElabM α := ReaderT ElabContext (StateM ElabState) α instance : ElabClass ElabM where @@ -239,7 +246,10 @@ def translateQualifiedIdent (t : Tree) : MaybeQualifiedIdent := | q`Init.qualifiedIdentImplicit, 1 => Id.run do let .ident _ name := args[0] | return panic! "Expected ident" - .name name + let name := name.stripPrefix "«" |>.stripSuffix "»" + match name.splitOn "." with + | [dialect, rest] => .qid { dialect, name := rest } + | _ => .name name | q`Init.qualifiedIdentExplicit, 2 => Id.run do let .ident _ dialect := args[0] | return panic! "Expected ident" @@ -769,7 +779,7 @@ def evalBindingSpec panic! s!"Cannot bind {ident} unexpected category {repr info.cat}" else if !bindings.isEmpty then panic! s!"Arguments not allowed on category." - else if let .atom loc q`Init.Type := info.cat then + else if info.cat.name == q`Init.Type then pure <| .type loc [] none else pure <| .cat info.cat @@ -881,6 +891,35 @@ partial def translateTypeTree (arg : Tree) : ElabM Tree := do | _ => panic! s!"translateTypeExpr expected operator {repr arg}" +/-- +Return the arguments to the given syntax declaration. + +This should alway succeeed, but captures an internal error if an invariant check fails. +-/ +def getSyntaxArgs (stx : Syntax) (ident : QualifiedIdent) (expected : Nat) : ElabM (Vector Syntax expected) := do + let some loc := mkSourceRange? stx + | panic! s!"elabOperation missing source location {repr stx}" + let stxArgs := stx.getArgs + let .isTrue stxArgP := inferInstanceAs (Decidable (stxArgs.size = expected)) + | logInternalError loc s!"{ident} expected {expected} arguments when {stxArgs.size} seen.\n {repr stxArgs[0]!}" + return default + return ⟨stxArgs, stxArgP⟩ + +/-- +Unwrap a tree to a raw Arg based on the unwrap specification. +-/ +def unwrapTree (tree : Tree) (unwrap : Bool) : Arg := + if !unwrap then + tree.arg + else + match tree.info with + | .ofNumInfo info => .num info.loc info.val + | .ofIdentInfo info => .ident info.loc info.val + | .ofStrlitInfo info => .strlit info.loc info.val + | .ofDecimalInfo info => .decimal info.loc info.val + | .ofBytesInfo info => .bytes info.loc info.val + | _ => tree.arg -- Fallback for non-unwrappable types + mutual partial def elabOperation (tctx : TypingContext) (stx : Syntax) : ElabM Tree := do @@ -899,12 +938,19 @@ partial def elabOperation (tctx : TypingContext) (stx : Syntax) : ElabM Tree := | return panic! s!"Unknown elaborator {i.fullName}" let initSize := tctx.bindings.size let argDecls := decl.argDecls.toArray.toVector - let ((args, newCtx), success) ← runChecked <| runSyntaxElaborator se argDecls tctx stx.getArgs + let (stxArgs, success) ← runChecked <| getSyntaxArgs stx i se.syntaxCount + if not success then + return default + let ((args, newCtx), success) ← runChecked <| runSyntaxElaborator argDecls se tctx stxArgs if !success then return default let resultCtx ← decl.newBindings.foldlM (init := newCtx) <| fun ctx spec => do ctx.push <$> evalBindingSpec loc initSize spec args - let op : Operation := { ann := loc, name := i, args := args.toArray.map (·.arg) } + -- Apply unwrapping based on unwrapSpecs + let unwrappedArgs := args.toArray.mapIdx fun idx tree => + let unwrap := se.unwrapSpecs.getD idx false + unwrapTree tree unwrap + let op : Operation := { ann := loc, name := i, args := unwrappedArgs } if loc.isNone then return panic! s!"Missing position info {repr stx}." let info : OperationInfo := { loc := loc, inputCtx := tctx, op, resultCtx } @@ -912,14 +958,12 @@ partial def elabOperation (tctx : TypingContext) (stx : Syntax) : ElabM Tree := partial def runSyntaxElaborator {argc : Nat} + (argDecls : Vector ArgDecl argc) (se : SyntaxElaborator) - (b : Vector ArgDecl argc) (tctx0 : TypingContext) - (args : Array Syntax) : ElabM (Vector Tree argc × TypingContext) := do + (args : Vector Syntax se.syntaxCount) : ElabM (Vector Tree argc × TypingContext) := do let mut trees : Vector (Option Tree) argc := .replicate argc none - for ae in se.argElaborators do - let .isTrue syntaxLevelP := inferInstanceAs (Decidable (ae.syntaxLevel < args.size)) - | return panic! "Invalid syntaxLevel" + for ⟨ae, sp⟩ in se.argElaborators do let argLevel := ae.argLevel let .isTrue argLevelP := inferInstanceAs (Decidable (argLevel < argc)) | return panic! "Invalid argLevel" @@ -931,7 +975,7 @@ partial def runSyntaxElaborator | none => continue | none => pure tctx0 let astx := args[ae.syntaxLevel] - let expectedKind := b[argLevel].kind + let expectedKind := argDecls[argLevel].kind match expectedKind with | .type expectedType => let (tree, success) ← runChecked <| elabExpr tctx astx @@ -948,7 +992,7 @@ partial def runSyntaxElaborator | .error () => panic! "Could not infer type." | .ok expectedType => do - trees ← unifyTypes b ⟨argLevel, argLevelP⟩ expectedType tctx astx inferredType trees + trees ← unifyTypes argDecls ⟨argLevel, argLevelP⟩ expectedType tctx astx inferredType trees assert! trees[argLevel].isNone trees := trees.set argLevel (some tree) | .cat c => @@ -1002,6 +1046,24 @@ partial def catElaborator (c : SyntaxCat) : TypingContext → Syntax → ElabM T pure <| .node (.ofNumInfo info) #[] | none => panic! s!"Invalid Init.Num {repr stx}" + | q`Init.ByteArray => + fun tctx stx => do + let some loc := mkSourceRange? stx + | panic! "bytes missing source location" + match stx with + | .node _ _ #[.atom _ contents] => + match ByteArray.unescapeBytes contents with + | .error (_, _, msg) => panic! msg + | .ok bytes => + let info : ConstInfo ByteArray := { + inputCtx := tctx, + loc := loc, + val := bytes + } + pure <| .node (.ofBytesInfo info) #[] + | _ => + logError loc s!"Unexpected byte syntax {repr stx}" + pure default | q`Init.Decimal => fun tctx stx => do let some loc := mkSourceRange? stx @@ -1134,11 +1196,13 @@ partial def elabExpr (tctx : TypingContext) (stx : Syntax) : ElabM Tree := do { ident := "", kind := .type (.ofType tp) } let argDecls := argTypes.map mkArgDecl let se : SyntaxElaborator := { - argElaborators := Array.ofFn fun (⟨lvl, _⟩ : Fin args.size) => - { syntaxLevel := lvl, argLevel := lvl } + syntaxCount := args.size + argElaborators := Array.ofFn fun (⟨lvl, lvlp⟩ : Fin args.size) => + let e := { syntaxLevel := lvl, argLevel := lvl } + ⟨e, lvlp⟩ resultScope := none } - let (args, _) ← runSyntaxElaborator se argDecls tctx args + let (args, _) ← runSyntaxElaborator argDecls se tctx ⟨args, Eq.refl args.size⟩ let e := args.toArray.foldl (init := fvar) fun e t => .app { start := fnLoc.start, stop := t.info.loc.stop } e t.arg let info : ExprInfo := { toElabInfo := einfo, expr := e } @@ -1156,7 +1220,10 @@ partial def elabExpr (tctx : TypingContext) (stx : Syntax) : ElabM Tree := do let some se := (←read).syntaxElabs[i]? | return panic! s!"Unknown expression elaborator {i.fullName}" let argDecls := fn.argDecls.toArray.toVector - let ((args, _), success) ← runChecked <| runSyntaxElaborator se argDecls tctx stx.getArgs + let (stxArgs, success) ← runChecked <| getSyntaxArgs stx i se.syntaxCount + if !success then + return default + let ((args, _), success) ← runChecked <| runSyntaxElaborator argDecls se tctx stxArgs if !success then return default -- N.B. Every subterm gets the function location. @@ -1215,3 +1282,4 @@ partial def elabCommand (leanEnv : Lean.Environment) : DeclM (Option Tree) := do runElab <| some <$> elabOperation (.empty glbl) stx end Strata.Elab +end diff --git a/Strata/DDM/Elab/DeclM.lean b/Strata/DDM/Elab/DeclM.lean index fbeddbab9..18c5407b7 100644 --- a/Strata/DDM/Elab/DeclM.lean +++ b/Strata/DDM/Elab/DeclM.lean @@ -3,17 +3,24 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Elab.LoadedDialects -import Strata.DDM.Parser -import Strata.DDM.Util.Lean +public import Lean.Parser.Types + +public import Strata.DDM.AST +public import Strata.DDM.Parser +public import Strata.DDM.Util.Lean +public import Strata.DDM.Elab.LoadedDialects + +import Strata.DDM.Util.PrattParsingTables set_option autoImplicit false open Lean (Syntax Message) -open Strata.Parser (DeclParser InputContext Parser ParsingContext ParserState) +open Strata.Parser (DeclParser InputContext Parser ParsingContext) +public section namespace Strata def infoSourceRange (info : Lean.SourceInfo) : Option SourceRange := @@ -23,7 +30,7 @@ def infoSourceRange (info : Lean.SourceInfo) : Option SourceRange := some { start := pos, stop := endPos } | .none => none -def sourceLocPos (stx:Syntax) : Option String.Pos := +def sourceLocPos (stx:Syntax) : Option String.Pos.Raw := match stx with | .atom info .. | .ident info .. => infoSourceRange info |>.map (·.start) @@ -38,7 +45,7 @@ def sourceLocPos (stx:Syntax) : Option String.Pos := none | .missing => none -def sourceLocEnd (stx:Syntax) : Option String.Pos := +def sourceLocEnd (stx:Syntax) : Option String.Pos.Raw := match stx with | .atom info .. | .ident info .. => infoSourceRange info |>.map (·.stop) @@ -74,14 +81,14 @@ def mkSourceRange? (stx:Syntax) : Option SourceRange := namespace PrattParsingTableMap -def addSynCat! (tables : PrattParsingTableMap) (dialect : String) (decl : SynCatDecl) : PrattParsingTableMap := +private def addSynCat! (tables : PrattParsingTableMap) (dialect : String) (decl : SynCatDecl) : PrattParsingTableMap := let cat : QualifiedIdent := { dialect, name := decl.name } if cat ∈ tables then panic! s!"{cat} already declared." else tables.insert cat {} -def addParserToCat! (tables : PrattParsingTableMap) (dp : DeclParser) : PrattParsingTableMap := +private def addParserToCat! (tables : PrattParsingTableMap) (dp : DeclParser) : PrattParsingTableMap := tables.alter dp.category fun mtables => match mtables with | none => panic s!"Category {dp.category.fullName} not declared." @@ -89,7 +96,7 @@ def addParserToCat! (tables : PrattParsingTableMap) (dp : DeclParser) : PrattPar let r := tables |>.addParser dp.isLeading dp.parser dp.outerPrec some r -def addDialect! (tables : PrattParsingTableMap) (dialect : Dialect) (parsers : Array DeclParser) : PrattParsingTableMap := +private def addDialect! (tables : PrattParsingTableMap) (dialect : Dialect) (parsers : Array DeclParser) : PrattParsingTableMap := dialect.syncats.fold (init := tables) (·.addSynCat! dialect.name ·) |> parsers.foldl PrattParsingTableMap.addParserToCat! @@ -116,7 +123,7 @@ class ElabClass (m : Type → Type) extends Monad m where export ElabClass (logErrorMessage) -/- +/-- Runs action and returns result along with Bool that is true if action ran without producing errors. -/ @@ -140,7 +147,7 @@ def logErrorMF {m} [ElabClass m] (loc : SourceRange) (msg : StrataFormat) (isSil structure DeclContext where inputContext : InputContext - stopPos : String.Pos + stopPos : String.Pos.Raw -- Map from dialect names to the dialect definition loader : LoadedDialects /-- Flag indicating imports are missing (silences some errors). -/ @@ -237,6 +244,9 @@ def get (m : TypeOrCatDeclMap) (name : String) : Array (DialectName × { d : Typ end TypeOrCatDeclMap +private def initTokenTable : Lean.Parser.TokenTable := + initParsers.fixedParsers.fold (init := {}) fun tt _ p => Parser.TokenTable.addParser tt p + structure DeclState where -- Fixed parser map fixedParsers : ParsingContext := {} @@ -255,17 +265,17 @@ structure DeclState where -- Operations at the global level globalContext : GlobalContext := {} -- String position in file. - pos : String.Pos := 0 + pos : String.Pos.Raw := 0 -- Errors found in elaboration. errors : Array Message := #[] - deriving Inhabited +deriving Inhabited namespace DeclState def addParserToCat! (s : DeclState) (dp : DeclParser) : DeclState := assert! dp.category ∈ s.parserMap { s with - tokenTable := s.tokenTable.addParser dp.parser + tokenTable := Parser.TokenTable.addParser s.tokenTable dp.parser parserMap := s.parserMap.addParserToCat! dp } @@ -282,7 +292,7 @@ def openParserDialect! (s : DeclState) (loader : LoadedDialects) (dialect : Dial { s with metadataDeclMap := s.metadataDeclMap.addDialect dialect parserMap := s.parserMap.addDialect! dialect parsers - tokenTable := parsers.foldl (init := s.tokenTable) (·.addParser ·.parser) + tokenTable := parsers.foldl (init := s.tokenTable) (Parser.TokenTable.addParser · ·.parser) } mutual @@ -321,9 +331,17 @@ partial def openLoadedDialect! (s : DeclState) (loaded : LoadedDialects) (dialec else s.addDialect! loaded dialect +def ofDialects (ds : LoadedDialects) : DeclState := + let s : DeclState := { + openDialects := #[] + openDialectSet := {} + tokenTable := initTokenTable + } + ds.dialects.toList.foldl (init := s) (·.openLoadedDialect! ds ·) + end DeclState -@[reducible] +@[reducible, expose] def DeclM := ReaderT DeclContext (StateM DeclState) namespace DeclM @@ -343,3 +361,6 @@ def addTypeOrCatDecl (dialect : DialectName) (tpcd : TypeOrCatDecl) : DeclM Unit modify fun s => { s with typeOrCatDeclMap := s.typeOrCatDeclMap.add dialect tpcd } + +end Strata.Elab +end diff --git a/Strata/DDM/Elab/DialectM.lean b/Strata/DDM/Elab/DialectM.lean index 324c36c6e..a2e9baa32 100644 --- a/Strata/DDM/Elab/DialectM.lean +++ b/Strata/DDM/Elab/DialectM.lean @@ -3,16 +3,23 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Elab.Core +public import Strata.DDM.AST +public import Strata.DDM.Elab.Core + +import Std.Data.HashMap +import Strata.DDM.Util.Array +import Strata.DDM.Util.Fin set_option autoImplicit false +public section namespace Strata namespace PreType -/- +/-- Apply a function f over all bound variables in expression. Note this does not return variables referenced by .funMacro. @@ -54,7 +61,7 @@ def isType {argc} (m : ArgDeclsMap argc) (lvl : Fin argc) := m.decls[lvl].val.ki def empty (capacity : Nat := 0) : ArgDeclsMap 0 := { argIndexMap := {}, decls := .emptyWithCapacity capacity, - argIndexMapSize := rfl + argIndexMapSize := by simp mapIndicesValid := fun v p => by simp at p } @@ -513,7 +520,7 @@ structure DialectContext where /-- Callback to load dialects dynamically upon demand. -/ loadDialect : LoadDialectCallback inputContext : Parser.InputContext - stopPos : String.Pos + stopPos : String.Pos.Raw structure DialectState where loaded : LoadedDialects @@ -597,7 +604,7 @@ def elabDialectImportCommand : DialectElab := fun tree => do let loadCallback ← (·.loadDialect) <$> read let r ← fun _ ref => do let loaded := (← ref.get).loaded - assert! "StrataDDL" ∈ loaded.dialects.map.keys + assert! "StrataDDL" ∈ loaded.dialects let (loaded, r) ← loadCallback loaded name ref.modify fun s => { s with loaded := loaded } pure r @@ -737,7 +744,7 @@ def elabFnCommand : DialectElab := fun tree => do if !stxSuccess then return - let ident := { dialect := d.name, name } + let ident : QualifiedIdent := { dialect := d.name, name } match (←getDeclState).fixedParsers.opSyntaxParser q`Init.Expr ident argDecls opStx with | .error msg => logErrorMF tree.info.loc msg @@ -808,7 +815,7 @@ def dialectElabs : Std.HashMap QualifiedIdent DialectElab := ] partial def runDialectCommand (leanEnv : Lean.Environment) : DialectM Bool := do - assert! "StrataDDL" ∈ (← get).loaded.dialects.map.keys + assert! "StrataDDL" ∈ (← get).loaded.dialects let (mtree, success) ← MonadLift.monadLift <| runChecked <| elabCommand leanEnv match mtree with | none => diff --git a/Strata/DDM/Elab/Env.lean b/Strata/DDM/Elab/Env.lean index 6fbe148fd..a824dcf6b 100644 --- a/Strata/DDM/Elab/Env.lean +++ b/Strata/DDM/Elab/Env.lean @@ -3,17 +3,19 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.AST -import Lean.Parser.Basic +public import Strata.DDM.AST +public import Lean.Parser.Basic +public import Lean.Environment namespace Strata open Lean -abbrev PrattParsingTableMap := Std.HashMap QualifiedIdent Parser.PrattParsingTables +public abbrev PrattParsingTableMap := Std.HashMap QualifiedIdent Parser.PrattParsingTables -initialize parserExt : EnvExtension PrattParsingTableMap ← +public initialize parserExt : EnvExtension PrattParsingTableMap ← registerEnvExtension (pure {}) end Strata diff --git a/Strata/DDM/Elab/LoadedDialects.lean b/Strata/DDM/Elab/LoadedDialects.lean index 7010a0561..74e2a2831 100644 --- a/Strata/DDM/Elab/LoadedDialects.lean +++ b/Strata/DDM/Elab/LoadedDialects.lean @@ -3,11 +3,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - -import Strata.DDM.Elab.SyntaxElab +module +public import Std.Data.HashMap.Basic +public import Strata.DDM.Parser +public import Strata.DDM.Elab.SyntaxElab open Strata.Parser (DeclParser ParsingContext) +public section namespace Strata.Elab /-- @@ -36,16 +39,28 @@ structure LoadedDialects where dialectParsers : DialectParsers /--/ Map for elaborating operations and functions. -/ syntaxElabMap : SyntaxElabMap - deriving Inhabited +deriving Inhabited def initParsers : Parser.ParsingContext where fixedParsers := .ofList [ (q`Init.Ident, Parser.identifier), (q`Init.Num, Parser.numLit), + (q`Init.ByteArray, Parser.byteArray), (q`Init.Decimal, Parser.decimalLit), (q`Init.Str, Parser.strLit) ] +def DialectParsers.ofDialects (ds : Array Dialect) : Except String DialectParsers := + ds.foldlM (init := {}) fun m d => + match initParsers.mkDialectParsers d with + | .error msg => + .error s!"Could not add open dialect: {eformat msg |>.pretty}" + | .ok parsers => + .ok (m.insert d.name parsers) + +def SyntaxElabMap.ofDialects (ds : Array Dialect) : SyntaxElabMap := + ds.foldl (init := {}) (·.addDialect ·) + namespace LoadedDialects def empty : LoadedDialects where @@ -66,7 +81,15 @@ def addDialect! (loader : LoadedDialects) (d : Dialect) : LoadedDialects := } def ofDialects! (ds : Array Dialect) : LoadedDialects := - ds.foldl (init := .empty) (·.addDialect! ·) + match DialectParsers.ofDialects ds with + | .error msg => + panic s!"Could not add open dialect: {eformat msg |>.pretty}" + | .ok parsers => + { + dialects := .ofList! ds.toList + dialectParsers := parsers + syntaxElabMap := SyntaxElabMap.ofDialects ds + } end LoadedDialects @@ -142,3 +165,5 @@ def findPath (m : DialectFileMap) (name : DialectName) : Option System.FilePath | some (_, _, path) => pure path end DialectFileMap +end Strata +end diff --git a/Strata/DDM/Elab/SyntaxElab.lean b/Strata/DDM/Elab/SyntaxElab.lean index 3df17f538..095be72b7 100644 --- a/Strata/DDM/Elab/SyntaxElab.lean +++ b/Strata/DDM/Elab/SyntaxElab.lean @@ -3,10 +3,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public import Strata.DDM.AST +public import Std.Data.HashMap.Basic import Strata.DDM.Parser import Strata.DDM.Util.Lean +public section namespace Strata.Elab /-- @@ -20,57 +24,125 @@ structure ArgElaborator where argLevel : Nat -- Index of argument to use for typing context (if specified, must be less than argIndex) contextLevel : Option (Fin argLevel) := .none + -- Whether to unwrap this argument + unwrap : Bool := false deriving Inhabited, Repr -def mkArgElab (argDecls : ArgDecls) (syntaxLevel : Nat) (argLevel : Fin argDecls.size) : ArgElaborator := - let contextLevel : Option (Fin argLevel) := argDecls.argScopeLevel argLevel - { argLevel := argLevel.val, syntaxLevel, contextLevel } +abbrev ArgElaboratorArray (sc : Nat) := + Array { a : ArgElaborator // a.syntaxLevel < sc } -def addElaborators (argDecls : ArgDecls) (p : Nat × Array ArgElaborator) (a : SyntaxDefAtom) : Nat × Array ArgElaborator := +/-- Information needed to elaborator arguments to operations or functions. -/ +private structure ArgElaborators where + /-- Expected number of arguments elaborator will process. -/ + syntaxCount : Nat + argElaborators : ArgElaboratorArray syntaxCount +deriving Inhabited, Repr + +namespace ArgElaborators + +private def inc (as : ArgElaborators) : ArgElaborators := + let sc := as.syntaxCount + let elabs := as.argElaborators.unattach + have ext (e : ArgElaborator) (mem : e ∈ elabs) : e.syntaxLevel < sc + 1 := by + simp [elabs] at mem + grind + let elabs' := elabs.attachWith (·.syntaxLevel < sc + 1) ext + have scp : sc < sc + 1 := by grind + { syntaxCount := sc + 1 + argElaborators := elabs' + } + +private def push (as : ArgElaborators) + (argDecls : ArgDecls) + (argLevel : Fin argDecls.size) : ArgElaborators := + let sc := as.syntaxCount + let as := as.inc + let newElab : ArgElaborator := { + syntaxLevel := sc + argLevel := argLevel.val + contextLevel := argDecls.argScopeLevel argLevel + } + have scp : sc < sc + 1 := by grind + { as with argElaborators := as.argElaborators.push ⟨newElab, scp⟩ } + +private def pushWithUnwrap + (as : ArgElaborators) + (argDecls : ArgDecls) + (argLevel : Fin argDecls.size) + (unwrap : Bool) : ArgElaborators := + let sc := as.syntaxCount + let as := as.inc + let newElab : ArgElaborator := { + syntaxLevel := sc + argLevel := argLevel.val + contextLevel := argDecls.argScopeLevel argLevel + unwrap := unwrap + } + have scp : sc < sc + 1 := by grind + { as with argElaborators := as.argElaborators.push ⟨newElab, scp⟩ } + +end ArgElaborators + +private def addElaborators (argDecls : ArgDecls) (p : ArgElaborators) (a : SyntaxDefAtom) : ArgElaborators := match a with - | .ident level _prec => - let (si, es) := p + | .ident level _prec unwrap => if h : level < argDecls.size then - let argElab := mkArgElab argDecls si ⟨level, h⟩ - (si + 1, es.push argElab) + p.pushWithUnwrap argDecls ⟨level, h⟩ unwrap else panic! "Invalid index" - | .str _ => - let (si, es) := p - (si + 1, es) + | .str s => + if s.trim.isEmpty then + p + else + p.inc | .indent _ as => as.attach.foldl (init := p) (fun p ⟨a, _⟩ => addElaborators argDecls p a) -/-- Information needed to elaborator operations or functions. -/ +/-- Information needed to elaborate operations or functions. -/ structure SyntaxElaborator where - argElaborators : Array ArgElaborator + /-- Expected number of arguments elaborator will process. -/ + syntaxCount : Nat + argElaborators : ArgElaboratorArray syntaxCount resultScope : Option Nat + /-- Unwrap specifications for each argument (indexed by argLevel) -/ + unwrapSpecs : Array Bool := #[] deriving Inhabited, Repr -def mkElaborators (argDecls : ArgDecls) (as : Array SyntaxDefAtom) : Array ArgElaborator := - let init : Array ArgElaborator := Array.mkEmpty argDecls.size - let (_, es) := as.foldl (init := (0, init)) (addElaborators argDecls) - es.qsort (fun x y => x.argLevel < y.argLevel) - -def mkSyntaxElab (argDecls : ArgDecls) (stx : SyntaxDef) (opMd : Metadata) : SyntaxElaborator := { - argElaborators := mkElaborators argDecls stx.atoms, - resultScope := opMd.resultLevel argDecls.size, +private def mkSyntaxElab (argDecls : ArgDecls) (stx : SyntaxDef) (opMd : Metadata) : SyntaxElaborator := + let init : ArgElaborators := { + syntaxCount := 0 + argElaborators := Array.mkEmpty argDecls.size + } + let as := stx.atoms.foldl (init := init) (addElaborators argDecls) + -- In the case with no syntax there is still a single expected + -- syntax argument with the empty string. + let as := if as.syntaxCount = 0 then as.inc else as + let elabs := as.argElaborators.qsort (·.val.argLevel < ·.val.argLevel) + -- Build unwrapSpecs array indexed by argLevel + let unwrapSpecs := Array.replicate argDecls.size false + let unwrapSpecs := elabs.foldl (init := unwrapSpecs) fun arr ⟨ae, _⟩ => + arr.set! ae.argLevel ae.unwrap + { + syntaxCount := as.syntaxCount + argElaborators := elabs + resultScope := opMd.resultLevel argDecls.size + unwrapSpecs := unwrapSpecs } -def opDeclElaborator (decl : OpDecl) : SyntaxElaborator := +private def opDeclElaborator (decl : OpDecl) : SyntaxElaborator := mkSyntaxElab decl.argDecls decl.syntaxDef decl.metadata -def fnDeclElaborator (decl : FunctionDecl) : SyntaxElaborator := +private def fnDeclElaborator (decl : FunctionDecl) : SyntaxElaborator := mkSyntaxElab decl.argDecls decl.syntaxDef decl.metadata abbrev SyntaxElabMap := Std.HashMap QualifiedIdent SyntaxElaborator namespace SyntaxElabMap -def add (m : SyntaxElabMap) (dialect : String) (name : String) (se : SyntaxElaborator) : SyntaxElabMap := +private def add (m : SyntaxElabMap) (dialect : String) (name : String) (se : SyntaxElaborator) : SyntaxElabMap := m.insert { dialect, name := name } se -def addDecl (m : SyntaxElabMap) (dialect : String) (d : Decl) : SyntaxElabMap := +private def addDecl (m : SyntaxElabMap) (dialect : String) (d : Decl) : SyntaxElabMap := match d with | .op d => m.add dialect d.name (opDeclElaborator d) | .function d => m.add dialect d.name (fnDeclElaborator d) @@ -79,4 +151,5 @@ def addDecl (m : SyntaxElabMap) (dialect : String) (d : Decl) : SyntaxElabMap := def addDialect (m : SyntaxElabMap) (d : Dialect) : SyntaxElabMap := d.declarations.foldl (·.addDecl d.name) m -end SyntaxElabMap +end Strata.Elab.SyntaxElabMap +end diff --git a/Strata/DDM/Elab/Tree.lean b/Strata/DDM/Elab/Tree.lean index dee0552b3..46dbf0601 100644 --- a/Strata/DDM/Elab/Tree.lean +++ b/Strata/DDM/Elab/Tree.lean @@ -3,11 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -import Strata.DDM.Format +module + +public import Strata.DDM.AST +public import Strata.DDM.Format set_option autoImplicit false open Lean (Syntax) +public section namespace Strata.Elab /-- @@ -36,10 +40,10 @@ instance : Coe TypeExpr BindingKind where coe tp := .expr tp def ofCat (c : SyntaxCat) : BindingKind := - match c with - | .atom _ q`Init.Expr => panic! "Init.Expr may not appear as a category." - | .atom loc q`Init.Type => .type loc [] .none - | c => .cat c + match c.name with + | q`Init.Expr => panic! "Init.Expr may not appear as a category." + | q`Init.Type => .type c.ann [] .none + | _ => .cat c def categoryOf : BindingKind → SyntaxCat | .expr tp => .atom tp.ann q`Init.Expr @@ -47,10 +51,11 @@ def categoryOf : BindingKind → SyntaxCat | .cat c => c instance : ToStrataFormat BindingKind where - mformat - | .expr tp => mformat tp - | .type _ params _ => mformat (params.foldr (init := f!"Type") (fun a f => f!"({a} : Type) -> {f}")) - | .cat c => mformat c + mformat bk := private + match bk with + | .expr tp => mformat tp + | .type _ params _ => mformat (params.foldr (init := f!"Type") (fun a f => f!"({a} : Type) -> {f}")) + | .cat c => mformat c end BindingKind @@ -82,7 +87,7 @@ protected def isEmpty (b:Bindings) := b.toArray.isEmpty protected def size (b:Bindings) := b.toArray.size instance : GetElem Bindings Nat Binding (fun bs i => i < bs.size) where - getElem bindings idx p := bindings.toArray[idx]'p + getElem bindings idx p := bindings.toArray[idx]'(by exact p) protected def empty : Bindings where toArray := #[] @@ -216,21 +221,17 @@ structure TypeInfo extends ElabInfo where isInferred : Bool deriving Inhabited, Repr -structure IdentInfo extends ElabInfo where - val : String +structure ConstInfo (α : Type) extends ElabInfo where + val : α deriving Inhabited, Repr -structure NumInfo extends ElabInfo where - val : Nat -deriving Inhabited, Repr +abbrev IdentInfo := ConstInfo String -structure DecimalInfo extends ElabInfo where - val : Decimal -deriving Inhabited, Repr +abbrev NumInfo := ConstInfo Nat -structure StrlitInfo extends ElabInfo where - val : String -deriving Inhabited, Repr +abbrev DecimalInfo := ConstInfo Decimal + +abbrev StrlitInfo := ConstInfo String structure OptionInfo extends ElabInfo where deriving Inhabited, Repr @@ -254,6 +255,7 @@ inductive Info | ofNumInfo (info : NumInfo) | ofDecimalInfo (info : DecimalInfo) | ofStrlitInfo (info : StrlitInfo) +| ofBytesInfo (info : ConstInfo ByteArray) | ofOptionInfo (info : OptionInfo) | ofSeqInfo (info : SeqInfo) | ofCommaSepInfo (info : CommaSepInfo) @@ -287,6 +289,7 @@ def elabInfo (info : Info) : ElabInfo := | .ofNumInfo info => info.toElabInfo | .ofDecimalInfo info => info.toElabInfo | .ofStrlitInfo info => info.toElabInfo + | .ofBytesInfo info => info.toElabInfo | .ofOptionInfo info => info.toElabInfo | .ofSeqInfo info => info.toElabInfo | .ofCommaSepInfo info => info.toElabInfo @@ -303,18 +306,18 @@ deriving Inhabited, Repr namespace Tree -def info : Tree → Info +@[expose] def info : Tree → Info | .node info _ => info -def children : Tree → Array Tree +@[expose] def children : Tree → Array Tree | .node _ c => c instance : GetElem Tree Nat Tree fun t i => i < t.children.size where getElem xs i h := xs.children[i] @[simp] -theorem node_getElem (info : Info) (c : Array Tree) (i : Nat) (p : _) : - (node info c)[i]'p = c[i]'p := rfl +theorem node_getElem (info : Info) (c : Array Tree) (i : Nat) (p : i < (node info c).children.size) : + (node info c)[i]'p = c[i]'(by apply p) := by rfl def arg : Tree → Arg | .node info children => @@ -327,6 +330,7 @@ def arg : Tree → Arg | .ofNumInfo info => .num info.loc info.val | .ofDecimalInfo info => .decimal info.loc info.val | .ofStrlitInfo info => .strlit info.loc info.val + | .ofBytesInfo info => .bytes info.loc info.val | .ofOptionInfo _ => let r := match children with @@ -349,7 +353,7 @@ def resultContext (t : Tree) : TypingContext := | .ofOperationInfo info => info.resultCtx | .ofCatInfo info => info.inputCtx | .ofExprInfo _ | .ofTypeInfo _ => t.info.inputCtx - | .ofIdentInfo _ | .ofNumInfo _ | .ofDecimalInfo _ | .ofStrlitInfo _ => t.info.inputCtx + | .ofIdentInfo _ | .ofNumInfo _ | .ofDecimalInfo _ | .ofStrlitInfo _ | .ofBytesInfo .. => t.info.inputCtx | .ofOptionInfo info => if p : t.children.size > 0 then have q : sizeOf t[0] < sizeOf t := sizeOf_children _ _ _ diff --git a/Strata/DDM/Format.lean b/Strata/DDM/Format.lean index 9f24b99f4..b44486407 100644 --- a/Strata/DDM/Format.lean +++ b/Strata/DDM/Format.lean @@ -3,17 +3,72 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + +public import Strata.DDM.AST -import Strata.DDM.AST -import Strata.DDM.Util.Fin import Strata.DDM.Util.Format import Strata.DDM.Util.Nat +import Strata.DDM.Util.String import Std.Data.HashSet +meta import Strata.DDM.AST + open Std (Format format) +public section namespace Strata +/-- +Check if a character is valid for starting a regular identifier. +Regular identifiers must start with a letter or underscore. +-/ +private def isIdBegin (c : Char) : Bool := + c.isAlpha || c == '_' + +/-- +Check if a character is valid for continuing a regular identifier. +-/ +private def isIdContinue (c : Char) : Bool := + c.isAlphanum || c == '_' || c == '\'' || c == '.' || c == '?' || c == '!' + +/-- +Check if a string needs pipe delimiters when formatted as an identifier. +Returns true if the string contains special characters, spaces, or starts with a digit. +-/ +private def needsPipeDelimiters (s : String) : Bool := + if h : s.isEmpty then + true + else + let firstChar := s.startValidPos.get (by simp_all) + !isIdBegin firstChar || s.any (fun c => !isIdContinue c) + +/-- +Escape a string for use in pipe-delimited identifiers (SMT-LIB 2.6). +Escapes \ as \\ and | as \| +-/ +private def escapePipeIdent (s : String) : String := + s.foldl (init := "") fun acc c => + if c == '\\' then acc ++ "\\\\" + else if c == '|' then acc ++ "\\|" + else acc.push c + +/-- +Format a string as an identifier, using pipe delimiters if needed. +Strips Lean's «» notation if present. +Follows SMT-LIB 2.6 specification for quoted symbols. +-/ +private def formatIdent (s : String) : Format := + -- Strip Lean's «» notation if present + let s := if s.startsWith "«" && s.endsWith "»" then + s.drop 1 |>.dropRight 1 + else + s + if needsPipeDelimiters s then + Format.text ("|" ++ escapePipeIdent s ++ "|") + else + Format.text s + structure PrecFormat where format : Format prec : Nat @@ -21,9 +76,9 @@ deriving Inhabited namespace PrecFormat -def atom (format : Format) : PrecFormat := { format, prec := maxPrec } +private def atom (format : Format) : PrecFormat := { format, prec := maxPrec } -def ofFormat [Std.ToFormat α] (x : α) (prec : Nat := maxPrec) : PrecFormat := { format := Std.format x, prec } +private def ofFormat {α} [Std.ToFormat α] (x : α) (prec : Nat := maxPrec) : PrecFormat := { format := Std.format x, prec } end PrecFormat @@ -39,21 +94,21 @@ A format context provides callbacks and information needed to properly pretty-print Strata AST types. -/ structure FormatContext where - opts : FormatOptions - getFnDecl : QualifiedIdent → Option FunctionDecl - getOpDecl : QualifiedIdent → Option OpDecl - globalContext : GlobalContext + private opts : FormatOptions + private getFnDecl : QualifiedIdent → Option FunctionDecl + private getOpDecl : QualifiedIdent → Option OpDecl + private globalContext : GlobalContext namespace FormatContext /-- A format context that uses no syntactic sugar. -/ -def explicit : FormatContext where +private def explicit : FormatContext where opts := {} getFnDecl _ := none getOpDecl _ := none globalContext := {} -def fvarName (ctx : FormatContext) (idx : FreeVarIndex) : String := +private def fvarName (ctx : FormatContext) (idx : FreeVarIndex) : String := if let some name := ctx.globalContext.nameOf? idx then name else @@ -73,7 +128,7 @@ protected def ofDialects (dialects : DialectMap) (globalContext : GlobalContext) end FormatContext -/-- Format state includes local information -/ +/-- Format state -/ structure FormatState where openDialects : Std.HashSet String bindings : Array String := #[] @@ -81,23 +136,23 @@ structure FormatState where namespace FormatState /-- A format context that uses no syntactic sugar. -/ -def empty : FormatState where +private def empty : FormatState where openDialects := {} -instance : Inhabited FormatState where +private instance : Inhabited FormatState where default := .empty def pushBinding (s : FormatState) (ident : String) : FormatState := { s with bindings := s.bindings.push ident } -def lvlVarName (s : FormatState) (lvl : Nat) : String := +private def lvlVarName (s : FormatState) (lvl : Nat) : String := let b := s.bindings if h : lvl < b.size then b[lvl] else s!"bvar!{s.bindings.size - (lvl + 1)}" -def bvarName (s : FormatState) (idx : Nat) : String := +private def bvarName (s : FormatState) (idx : Nat) : String := let b := s.bindings if h : idx < b.size then b[b.size - (idx + 1)] @@ -114,49 +169,49 @@ is used for auto-inserting parenthesis when needed. Formats should return `maxPrec` when parenthesis are not required. -/ -def StrataFormat := FormatContext → FormatState → PrecFormat +@[expose] def StrataFormat := FormatContext → FormatState → PrecFormat class ToStrataFormat (α : Type u) where mformat : α → StrataFormat export ToStrataFormat (mformat) -def cformat [ToStrataFormat α] (a : α) (c : FormatContext) (s : FormatState) : Format := mformat a c s |>.format +private def cformat [ToStrataFormat α] (a : α) (c : FormatContext) (s : FormatState) : Format := mformat a c s |>.format def eformat [ToStrataFormat α] (a : α) : Format := mformat a .explicit .empty |>.format instance : ToStrataFormat String where - mformat s _ _ := .ofFormat s + mformat s _ _ := private .ofFormat s instance : ToStrataFormat Format where - mformat s _ _ := .atom s + mformat s _ _ := private .atom s instance : ToStrataFormat StrataFormat where mformat := id instance : ToStrataFormat Nat where - mformat n _ _ := .ofFormat n + mformat n _ _ := private .ofFormat n instance : ToStrataFormat Decimal where - mformat n _ _ := .ofFormat n + mformat n _ _ := private .ofFormat n namespace StrataFormat -protected def nil : StrataFormat := fun _ _ => .atom .nil +private protected def nil : StrataFormat := fun _ _ => .atom .nil /-- Pretty print a free variable with the given index -/ -protected def fvar (fvarIdx : Nat) : StrataFormat := fun c _ => .atom (c.fvarName fvarIdx) +private protected def fvar (fvarIdx : Nat) : StrataFormat := fun c _ => .atom (c.fvarName fvarIdx) /-- Pretty print a bound variable with the given deBruijn index -/ -protected def lvlVar (lvl : Nat) : StrataFormat := fun _ s => .atom (s.lvlVarName lvl) +private protected def lvlVar (lvl : Nat) : StrataFormat := fun _ s => .atom (s.lvlVarName lvl) /-- Pretty print a bound variable with the given deBruijn index -/ -protected def bvar (idx : Nat) : StrataFormat := fun _ s => .atom (s.bvarName idx) +private protected def bvar (idx : Nat) : StrataFormat := fun _ s => .atom (s.bvarName idx) /-- Join together elements in list with no separator between adjacent elements. -/ -def join [ToStrataFormat α] (a : List α) : StrataFormat := +private def join [ToStrataFormat α] (a : List α) : StrataFormat := match a with | [] => .nil | [x] => mformat x @@ -165,7 +220,7 @@ def join [ToStrataFormat α] (a : List α) : StrataFormat := /-- Join together elements in list with a separator between adjacent elements. -/ -def sepBy [ToStrataFormat α] (a : Array α) (sep : String) : StrataFormat := +private def sepBy [ToStrataFormat α] (a : Array α) (sep : String) : StrataFormat := match p : a.size with | 0 => .nil | 1 => @@ -176,17 +231,17 @@ def sepBy [ToStrataFormat α] (a : Array α) (sep : String) : StrataFormat := .atom (a.foldl (start := 1) append (mformat a[0] c s).format) instance : Append StrataFormat where - append x y ctx s := + append x y ctx s := private let xf := x ctx s |>.format let yf := y ctx s |>.format .atom (xf ++ yf) /-- Set the precedence of the `fmt` to `prec` without changing format. -/ -def setPrec (fmt : StrataFormat) (prec : Nat) : StrataFormat := fun c s => +private def setPrec (fmt : StrataFormat) (prec : Nat) : StrataFormat := fun c s => { format := fmt c s |>.format, prec := prec } /-- Add parenthesis as needed to `fmt` to ensure precedence at least `p` -/ -def ensurePrec (fmt : StrataFormat) (prec : Nat) : StrataFormat := fun c s => +private def ensurePrec (fmt : StrataFormat) (prec : Nat) : StrataFormat := fun c s => let r := fmt c s if r.prec < prec then .atom f!"({r.format})" @@ -196,7 +251,7 @@ def ensurePrec (fmt : StrataFormat) (prec : Nat) : StrataFormat := fun c s => def withState (f : FormatState → FormatState) (fmt : StrataFormat) : StrataFormat := fun c s => fmt c (f s) -def nest (n : Nat) (fmt : StrataFormat) : StrataFormat := fun c s => +private def nest (n : Nat) (fmt : StrataFormat) : StrataFormat := fun c s => let ⟨r, p⟩ := fmt c s ⟨.nest n r, p⟩ @@ -208,15 +263,15 @@ macro_rules | `(mf! $interpStr) => do interpStr.expandInterpolatedStr (← `(StrataFormat)) (← `(mformat)) instance : ToStrataFormat QualifiedIdent where - mformat (ident : QualifiedIdent) _ s := + mformat (ident : QualifiedIdent) _ s := private if ident.dialect ∈ s.openDialects then - .ofFormat ident.name + .atom (formatIdent ident.name) else - .atom f!"{ident.dialect}.{ident.name}" + .atom f!"{ident.dialect}.{formatIdent ident.name}" namespace TypeExprF -protected def mformat : TypeExprF α → StrataFormat +private protected def mformat : TypeExprF α → StrataFormat | .ident _ tp a => a.attach.foldl (init := mformat tp) fun m ⟨e, _⟩ => mf!"{m} {e.mformat.ensurePrec (appPrec + 1)}".setPrec appPrec | .bvar _ idx => .bvar idx @@ -224,14 +279,14 @@ protected def mformat : TypeExprF α → StrataFormat mf!"{m} {e.mformat.ensurePrec (appPrec + 1)}".setPrec appPrec | .arrow _ a r => mf!"{a.mformat.ensurePrec (arrowPrec+1)} -> {r.mformat.ensurePrec arrowPrec}" -instance : ToStrataFormat (TypeExprF α) where - mformat e := e.mformat +instance {α} : ToStrataFormat (TypeExprF α) where + mformat e := private e.mformat end TypeExprF namespace PreType -protected def mformat : PreType → StrataFormat +private protected def mformat : PreType → StrataFormat | .ident _ tp a => a.attach.foldl (init := mformat tp) (fun m ⟨e, _⟩ => mf!"{m} {e.mformat}") | .bvar _ idx => .bvar idx | .fvar _ idx a => a.attach.foldl (init := .fvar idx) (fun m ⟨e, _⟩ => mf!"{m} {e.mformat}") @@ -239,13 +294,13 @@ protected def mformat : PreType → StrataFormat | .funMacro _ idx r => mf!"fnOf({StrataFormat.bvar idx}, {r.mformat})" instance : ToStrataFormat PreType where - mformat := PreType.mformat + mformat := private PreType.mformat end PreType namespace SyntaxCatF -protected def mformat {α} (cat : SyntaxCatF α) : StrataFormat := +private protected def mformat {α} (cat : SyntaxCatF α) : StrataFormat := let init := mformat cat.name cat.args.foldl (init := init) (fun f a => mf!"{f} {a.mformat.ensurePrec (appPrec+1)}") decreasing_by @@ -253,7 +308,7 @@ protected def mformat {α} (cat : SyntaxCatF α) : StrataFormat := decreasing_tactic instance {α} : ToStrataFormat (SyntaxCatF α) where - mformat := SyntaxCatF.mformat + mformat := private SyntaxCatF.mformat end SyntaxCatF @@ -262,7 +317,7 @@ This pretty prints the argument an op atom has. -/ private def SyntaxDefAtom.formatArgs (opts : FormatOptions) (args : Array PrecFormat) (stx : SyntaxDefAtom) : Format := match stx with - | .ident lvl prec => + | .ident lvl prec _ => let ⟨r, innerPrec⟩ := args[lvl]! if prec > 0 ∧ (innerPrec ≤ prec ∨ opts.alwaysParen) then f!"({r})" @@ -276,9 +331,9 @@ private def SyntaxDefAtom.formatArgs (opts : FormatOptions) (args : Array PrecFo private def ppOp (opts : FormatOptions) (stx : SyntaxDef) (args : Array PrecFormat) : PrecFormat := ⟨Format.join ((·.formatArgs opts args) <$> stx.atoms).toList, stx.prec⟩ -abbrev FormatM := ReaderT FormatContext (StateM FormatState) +private abbrev FormatM := ReaderT FormatContext (StateM FormatState) -def pformat [ToStrataFormat α] (a : α) : FormatM PrecFormat := +private def pformat {α} [ToStrataFormat α] (a : α) : FormatM PrecFormat := fun c s => (mformat a c s, s) mutual @@ -309,15 +364,16 @@ private partial def ExprF.mformatM (e : ExprF α) (rargs : Array (ArgF α) := # | none => ppArgs f.fullName | .app _ f a => f.mformatM (rargs.push a) -private partial def ArgF.mformatM : ArgF α → FormatM PrecFormat +private partial def ArgF.mformatM {α} : ArgF α → FormatM PrecFormat | .op o => o.mformatM | .expr e => e.mformatM | .type e => pformat e | .cat e => pformat e -| .ident _ x => pformat x +| .ident _ x => return .atom (formatIdent x) | .num _ x => pformat x | .decimal _ v => pformat v | .strlit _ s => return .atom (.text <| escapeStringLit s) +| .bytes _ v => return .atom <| .text <| ByteArray.escapeBytes v | .option _ ma => match ma with | none => pure (.atom .nil) @@ -389,17 +445,17 @@ private partial def OperationF.mformatM (op : OperationF α) : FormatM PrecForma end instance Expr.instToStrataFormat : ToStrataFormat Expr where - mformat e c s := e.mformatM #[] c s |>.fst + mformat e c s := private e.mformatM #[] c s |>.fst instance Arg.instToStrataFormat : ToStrataFormat Arg where - mformat a c s := a.mformatM c s |>.fst + mformat a c s := private a.mformatM c s |>.fst instance Operation.instToStrataFormat : ToStrataFormat Operation where - mformat o c s := o.mformatM c s |>.fst + mformat o c s := private o.mformatM c s |>.fst namespace MetadataArg -protected def mformat : MetadataArg → StrataFormat +private protected def mformat : MetadataArg → StrataFormat | .bool b => if b then mf!"true" else mf!"false" | .num n => mformat n | .catbvar idx => StrataFormat.bvar idx @@ -409,14 +465,14 @@ protected def mformat : MetadataArg → StrataFormat | some a => a.mformat instance : ToStrataFormat MetadataArg where - mformat := MetadataArg.mformat + mformat := private MetadataArg.mformat end MetadataArg namespace MetadataAttr instance : ToStrataFormat MetadataAttr where - mformat a := + mformat a := private if a.args.isEmpty then mformat a.ident else @@ -425,7 +481,7 @@ instance : ToStrataFormat MetadataAttr where end MetadataAttr instance Metadata.instToStrataFormat : ToStrataFormat Metadata where - mformat m := + mformat m := private if m.toArray.isEmpty then .nil else @@ -434,7 +490,7 @@ instance Metadata.instToStrataFormat : ToStrataFormat Metadata where namespace ArgDeclKind instance : ToStrataFormat ArgDeclKind where - mformat + mformat private | .cat c => mformat c | .type tp => mformat tp @@ -443,7 +499,7 @@ end ArgDeclKind namespace ArgDecl instance : ToStrataFormat ArgDecl where - mformat b := + mformat b := private let r := mf!"{b.ident} : {b.kind}" if b.metadata.isEmpty then r @@ -461,7 +517,7 @@ private def mformatAux (f : Format) (c : FormatContext) (s : FormatState) (a : A else (f ++ ")", s) -protected def mformat (c : FormatContext) (s : FormatState) (l : ArgDecls) : Format × FormatState := +private protected def mformat (c : FormatContext) (s : FormatState) (l : ArgDecls) : Format × FormatState := if h : 0 < l.size then let b := l[0] mformatAux ("(" ++ cformat b c s) c (s.pushBinding b.ident) l 1 @@ -469,18 +525,18 @@ protected def mformat (c : FormatContext) (s : FormatState) (l : ArgDecls) : For ("()", s) instance : ToStrataFormat ArgDecls where - mformat l c s := .atom (l.mformat c s |>.fst) + mformat l c s := private .atom (l.mformat c s |>.fst) /- Format `fmt` in a context with additional bindings `b`. -/ -protected def formatIn [ToStrataFormat α] (b : ArgDecls) (fmt : α) : StrataFormat := fun c s => - mformat fmt c (b.toArray.foldl (init := s) (·.pushBinding ·.ident)) +private protected def formatIn [ToStrataFormat α] (b : ArgDecls) (fmt : α) : StrataFormat := fun c s => + Strata.mformat fmt c (b.toArray.foldl (init := s) (·.pushBinding ·.ident)) end ArgDecls namespace SyntaxDefAtom -protected def mformat : SyntaxDefAtom → StrataFormat -| .ident lvl prec => mf!"{StrataFormat.lvlVar lvl}:{prec}" -- FIXME. This may be wrong. +private protected def mformat : SyntaxDefAtom → StrataFormat +| .ident lvl prec _ => mf!"{StrataFormat.lvlVar lvl}:{prec}" -- FIXME. This may be wrong. | .str lit => mformat (escapeStringLit lit) | .indent n f => let r := f.attach.map fun ⟨a, _⟩ => a.mformat @@ -488,26 +544,26 @@ protected def mformat : SyntaxDefAtom → StrataFormat mf!"indent({n}, {f})" instance : ToStrataFormat SyntaxDefAtom where - mformat := SyntaxDefAtom.mformat + mformat := private SyntaxDefAtom.mformat end SyntaxDefAtom namespace SyntaxDef instance : ToStrataFormat SyntaxDef where - mformat s := .sepBy s.atoms " " + mformat s := private .sepBy s.atoms " " end SyntaxDef instance SynCatDecl.instToStrataFormat : ToStrataFormat SynCatDecl where - mformat d := + mformat d := private let args : StrataFormat := .join <| d.argNames.map (mf!" {·}") |>.toList mf!"category {d.name}{args};\n" namespace OpDecl instance : ToStrataFormat OpDecl where - mformat d := + mformat d := private let argDecls := d.argDecls let mdf := if d.metadata.isEmpty then .nil else mf!"{argDecls.formatIn d.metadata} " let argDeclsF := if argDecls.isEmpty then mf!"" else mf!" {argDecls}" @@ -516,7 +572,7 @@ instance : ToStrataFormat OpDecl where end OpDecl instance TypeDecl.instToStrataFormat : ToStrataFormat TypeDecl where - mformat d := + mformat d := private let params := d.argNames let params := if params.isEmpty then mf!"" @@ -526,7 +582,7 @@ instance TypeDecl.instToStrataFormat : ToStrataFormat TypeDecl where mf!"type {d.name}{params};\n" instance FunctionDecl.instToStrataFormat : ToStrataFormat FunctionDecl where - mformat d := + mformat d := private let argDecls := d.argDecls let mdf := if d.metadata.isEmpty then .nil else mf!"{argDecls.formatIn d.metadata} " let argDeclsF := if argDecls.isEmpty then mf!"" else mf!" {argDecls}" @@ -535,19 +591,19 @@ instance FunctionDecl.instToStrataFormat : ToStrataFormat FunctionDecl where namespace MetadataArgType -protected def mformat : MetadataArgType → StrataFormat - | .num => mf!"Num" - | .ident => mf!"Ident" - | .bool => mf!"Bool" - | .opt tp => mf!"Option {tp.mformat |>.ensurePrec (appPrec + 1)}" |>.setPrec appPrec +private protected def mformat : MetadataArgType → StrataFormat +| .num => mf!"Num" +| .ident => mf!"Ident" +| .bool => mf!"Bool" +| .opt tp => mf!"Option {tp.mformat |>.ensurePrec (appPrec + 1)}" |>.setPrec appPrec instance : ToStrataFormat MetadataArgType where - mformat := MetadataArgType.mformat + mformat := private MetadataArgType.mformat end MetadataArgType instance MetadataDecl.instToStrataFormat : ToStrataFormat MetadataDecl where - mformat d := + mformat d := private if d.args.isEmpty then mf!"metadata {d.name};\n" else @@ -555,21 +611,24 @@ instance MetadataDecl.instToStrataFormat : ToStrataFormat MetadataDecl where mf!"metadata {d.name}({StrataFormat.sepBy (d.args |>.map ppArg) ", "});\n" instance Decl.instToStrataFormat : ToStrataFormat Decl where - mformat + mformat private | .syncat d => mformat d | .op d => mformat d | .type d => mformat d | .function d => mformat d | .metadata d => mformat d -namespace Dialect +namespace DialectMap -protected def format (dialects : DialectMap) (d : Dialect) (opts : FormatOptions := {}) : Format := - assert! d.name ∈ dialects +/-- +Pretty print the dialect with the given name in the map. +-/ +protected def format (dialects : DialectMap) (name : DialectName) (mem : name ∈ dialects) (opts : FormatOptions := {}) : Format := + let d := dialects[name] let c := FormatContext.ofDialects dialects {} opts - let imports := dialects.importedDialects! d.name - let s : FormatState := { openDialects := imports.map.fold (init := {}) fun s n _ => s.insert n } - let f := f!"dialect {d.name};\n" + let imports := dialects.importedDialects name mem + let s : FormatState := { openDialects := imports.toList.foldl (init := {}) fun s d => s.insert d.name } + let f := f!"dialect {name};\n" let f := d.imports.foldl (init := f) fun f i => if i = "Init" then f @@ -577,7 +636,7 @@ protected def format (dialects : DialectMap) (d : Dialect) (opts : FormatOptions f!"{f}import {i}\n" d.declarations.foldl (init := f) fun f d => f ++ (mformat d c s).format -end Dialect +end DialectMap namespace Program @@ -585,7 +644,7 @@ protected def formatContext (p : Program) (opts : FormatOptions) : FormatContext .ofDialects p.dialects p.globalContext opts protected def formatState (p : Program) : FormatState where - openDialects := .ofArray p.dialects.map.keysArray + openDialects := p.dialects.toList.foldl (init := {}) fun a d => a.insert d.name protected def format (p : Program) (opts : FormatOptions := {}) : Format := let c := p.formatContext opts @@ -594,13 +653,15 @@ protected def format (p : Program) (opts : FormatOptions := {}) : Format := f ++ (mformat cmd c s).format instance : ToString Program where - toString p := p.format |>.render + toString p := private p.format |>.render protected def ppDialect! (p : Program) (name : DialectName := p.dialect) (opts : FormatOptions := {}) : Format := - match p.dialects[name]? with - | some d => d.format p.dialects opts - | none => panic! s!"Unknown dialect {name}" + if mem : name ∈ p.dialects then + p.dialects.format name mem opts + else + panic! s!"Unknown dialect {name}" end Program end Strata +end diff --git a/Strata/DDM/HNF.lean b/Strata/DDM/HNF.lean new file mode 100644 index 000000000..df24c9be4 --- /dev/null +++ b/Strata/DDM/HNF.lean @@ -0,0 +1,40 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +module +public import Strata.DDM.AST +import Strata.DDM.Util.Array + +public section +namespace Strata + +/-- +Array ofelements whose sizes are bounded by a value. +-/ +public abbrev SizeBounded (α : Type _) [SizeOf α] {β} [SizeOf β] (e : β) (c : Int) := { a : α // sizeOf a ≤ sizeOf e + c } + +namespace ExprF + +/-- +Head-normal form for an expression consists of an operation +-/ +structure HNF {α} (e : ExprF α) where + fn : ExprF α + args : SizeBounded (Array (ArgF α)) e 1 + +protected def hnf {α} (e0 : ExprF α) : HNF e0 := + let rec aux (e : ExprF α) (args : Array (ArgF α) := #[]) + (szP : sizeOf e + sizeOf args ≤ sizeOf e0 + 2): HNF e0 := + match e with + | .bvar .. | .fvar .. | .fn .. => + { fn := e, args := ⟨args.reverse, by simp at szP; simp; omega⟩ } + | .app _ f a => + aux f (args.push a) (by simp at *; omega) + aux e0 #[] (by simp) + +end ExprF +end Strata +end diff --git a/Strata/DDM/Integration/Categories.lean b/Strata/DDM/Integration/Categories.lean new file mode 100644 index 000000000..208b61d06 --- /dev/null +++ b/Strata/DDM/Integration/Categories.lean @@ -0,0 +1,37 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.AST + +namespace Strata.DDM.Integration + +open Strata + +/-- Init categories that map to primitive types (no interface/inductive needed) -/ +def primitiveCategories : Std.HashSet QualifiedIdent := Std.HashSet.ofList [ + q`Init.Ident, + q`Init.Num, + q`Init.Decimal, + q`Init.Str, + q`Init.ByteArray, + q`Init.Bool +] + +/-- Init categories that are internal machinery (should error if used by dialects) -/ +def forbiddenCategories : Std.HashSet QualifiedIdent := Std.HashSet.ofList [ + q`Init.TypeExpr, + q`Init.BindingType, + q`StrataDDL.Binding +] + +/-- Init categories that are abstract extension points (dialects provide implementations) -/ +def abstractCategories : Std.HashSet QualifiedIdent := Std.HashSet.ofList [ + q`Init.Expr, + q`Init.Type, + q`Init.TypeP +] + +end Strata.DDM.Integration diff --git a/Strata/DDM/Integration/Java.lean b/Strata/DDM/Integration/Java.lean new file mode 100644 index 000000000..deb694736 --- /dev/null +++ b/Strata/DDM/Integration/Java.lean @@ -0,0 +1,7 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Java.Gen diff --git a/Strata/DDM/Integration/Java/Gen.lean b/Strata/DDM/Integration/Java/Gen.lean new file mode 100644 index 000000000..577cf00dc --- /dev/null +++ b/Strata/DDM/Integration/Java/Gen.lean @@ -0,0 +1,384 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.AST +import Strata.DDM.Integration.Categories + +namespace Strata.Java + +open Strata (Dialect OpDecl ArgDecl ArgDeclKind QualifiedIdent SyntaxCat) +open Strata.DDM.Integration (primitiveCategories forbiddenCategories abstractCategories) + +/-! # Java Code Generator for DDM Dialects + +Generates Java source files from DDM dialect definitions: +- Sealed interfaces for categories with operators +- Non-sealed stub interfaces for abstract categories (e.g., Init.Expr) +- Record classes for operators +- Static factory methods for ergonomic AST construction +- Ion serializer for Lean interop + +All names are disambiguated to avoid collisions with Java reserved words, +base classes (Node, SourceRange), and each other. +-/ + +/-! ## Name Utilities -/ + +def javaReservedWords : Std.HashSet String := Std.HashSet.ofList [ + -- Reserved keywords + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "default", "do", "double", "else", "enum", + "extends", "final", "finally", "float", "for", "goto", "if", "implements", + "import", "instanceof", "int", "interface", "long", "native", "new", + "package", "private", "protected", "public", "return", "short", "static", + "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", + "transient", "try", "void", "volatile", "while", + -- Contextual keywords (restricted in some contexts) + "exports", "module", "non-sealed", "open", "opens", "permits", "provides", + "record", "sealed", "to", "transitive", "uses", "var", "when", "with", "yield", + -- Literals (cannot be used as identifiers) + "true", "false", "null", + -- Underscore (Java 9+) + "_" +] + +def escapeJavaName (name : String) : String := + -- Remove invalid characters (like ?) + let cleaned := String.ofList (name.toList.filter (fun c => c.isAlphanum || c == '_')) + let cleaned := if cleaned.isEmpty then "field" else cleaned + -- Add suffix if reserved word + if javaReservedWords.contains cleaned then cleaned ++ "_" else cleaned + +def toPascalCase (s : String) : String := + s.splitOn "_" + |>.filter (!·.isEmpty) + |>.map (fun part => match part.toList with + | [] => "" + | c :: cs => .ofList (c.toUpper :: cs)) + |> String.intercalate "" + +/-- Generate unique name by adding suffix if collision detected -/ +partial def disambiguate (base : String) (usedNames : Std.HashSet String) : String × Std.HashSet String := + let rec findUnused (n : Nat) : String := + let suffix := if n == 0 then "" else if n == 1 then "_" else s!"_{n}" + let candidate := base ++ suffix + if usedNames.contains candidate.toLower then findUnused (n + 1) else candidate + let name := findUnused 0 + (name, usedNames.insert name.toLower) + +/-! ## Type Mapping -/ + +inductive JavaType where + | simple (name : String) (boxed : Option String := none) + | array (elem : JavaType) + | optional (elem : JavaType) + | list (elem : JavaType) + deriving Inhabited + +mutual +def JavaType.toJava : JavaType → String + | .simple name _ => name + | .array elem => elem.toJava ++ "[]" + | .optional elem => s!"java.util.Optional<{elem.toJavaBoxed}>" + | .list elem => s!"java.util.List<{elem.toJavaBoxed}>" + +def JavaType.toJavaBoxed : JavaType → String + | .simple _ (some boxed) => boxed + | t => t.toJava +end + +/-- Maps a primitive Init category to its Java type. -/ +def primitiveJavaType (qid : QualifiedIdent) : JavaType := + match qid with + | q`Init.Ident => .simple "java.lang.String" + | q`Init.Num => .simple "java.math.BigInteger" + | q`Init.Decimal => .simple "java.math.BigDecimal" + | q`Init.Str => .simple "java.lang.String" + | q`Init.ByteArray => .array (.simple "byte" (some "java.lang.Byte")) + | q`Init.Bool => .simple "boolean" (some "java.lang.Boolean") + | _ => panic! s!"Not a primitive category: {qid.dialect}.{qid.name}" + +/-- Maps an abstract Init category to its Java interface name. -/ +def abstractJavaName (qid : QualifiedIdent) : String := + match qid with + | q`Init.Expr => "Expr" + | q`Init.Type => "Type_" + | q`Init.TypeP => "TypeP" + | _ => panic! s!"Not an abstract category: {qid.dialect}.{qid.name}" + +partial def syntaxCatToJavaType (cat : SyntaxCat) : JavaType := + if forbiddenCategories.contains cat.name then + panic! s!"{cat.name.dialect}.{cat.name.name} is internal DDM machinery" + else if primitiveCategories.contains cat.name then + primitiveJavaType cat.name + else if abstractCategories.contains cat.name then + .simple (abstractJavaName cat.name) + else match cat.name with + | ⟨"Init", "Option"⟩ => + match cat.args[0]? with + | some inner => .optional (syntaxCatToJavaType inner) + | none => panic! "Init.Option requires a type argument" + | ⟨"Init", "Seq"⟩ | ⟨"Init", "CommaSepBy"⟩ => + match cat.args[0]? with + | some inner => .list (syntaxCatToJavaType inner) + | none => panic! "Init.Seq/CommaSepBy requires a type argument" + | ⟨"Init", _⟩ => panic! s!"Unknown Init category: {cat.name.name}" + | ⟨_, name⟩ => .simple (escapeJavaName (toPascalCase name)) + +def argDeclKindToJavaType : ArgDeclKind → JavaType + | .type _ => .simple "Expr" + | .cat c => syntaxCatToJavaType c + +/-- Extract the QualifiedIdent for categories that need Java interfaces, or none for primitives. -/ +partial def syntaxCatToQualifiedName (cat : SyntaxCat) : Option QualifiedIdent := + if primitiveCategories.contains cat.name then none + else if abstractCategories.contains cat.name then some cat.name + else match cat.name with + | ⟨"Init", "Option"⟩ | ⟨"Init", "Seq"⟩ | ⟨"Init", "CommaSepBy"⟩ => + cat.args[0]?.bind syntaxCatToQualifiedName + | ⟨"Init", _⟩ => none + | qid => some qid + +/-! ## Java Structures -/ + +structure JavaField where + name : String + type : JavaType + +structure JavaRecord where + name : String + operationName : QualifiedIdent + implements : String + fields : Array JavaField + +structure JavaInterface where + name : String + permits : Array String + +/-- All generated Java source files for a dialect. -/ +structure GeneratedFiles where + sourceRange : String + node : String + interfaces : Array (String × String) -- (filename, content) + records : Array (String × String) + builders : String × String -- (filename, content) + serializer : String + deriving Inhabited + +/-- Mapping from DDM names to disambiguated Java identifiers. -/ +structure NameAssignments where + categories : Std.HashMap QualifiedIdent String + operators : Std.HashMap (QualifiedIdent × String) String + stubs : Std.HashMap QualifiedIdent String + builders : String + +/-! ## Code Generation -/ + +def argDeclToJavaField (decl : ArgDecl) : JavaField := + { name := escapeJavaName decl.ident + type := argDeclKindToJavaType decl.kind } + +def JavaField.toParam (f : JavaField) : String := + s!"{f.type.toJava} {f.name}" + +def JavaRecord.toJava (package : String) (r : JavaRecord) : String := + let params := String.intercalate ", " (r.fields.toList.map JavaField.toParam) + let opName := s!"{r.operationName.dialect}.{r.operationName.name}" +s!"package {package}; + +public record {r.name}( + SourceRange sourceRange{if r.fields.isEmpty then "" else ",\n " ++ params} +) implements {r.implements} \{ + @Override + public java.lang.String operationName() \{ return \"{opName}\"; } +} +" + +def JavaInterface.toJava (package : String) (i : JavaInterface) : String := + let permits := if i.permits.isEmpty then "" + else " permits " ++ String.intercalate ", " i.permits.toList +s!"package {package}; + +public sealed interface {i.name} extends Node{permits} \{} +" + +def templatePackage := "com.strata.template" + +def sourceRangeTemplate : String := include_str "templates/SourceRange.java" +def nodeTemplate : String := include_str "templates/Node.java" +def serializerTemplate : String := include_str "templates/IonSerializer.java" + +def generateSourceRange (package : String) : String := + sourceRangeTemplate.replace templatePackage package + +def generateNodeInterface (package : String) (categories : List String) : String := + let base := nodeTemplate.replace templatePackage package + if categories.isEmpty then base + else + let permits := " permits " ++ String.intercalate ", " categories + base.replace "sealed interface Node" s!"sealed interface Node{permits}" + +/-- Generate non-sealed stub interface for a category with no operators -/ +def generateStubInterface (package : String) (name : String) : String × String := + (s!"{name}.java", s!"package {package};\n\npublic non-sealed interface {name} extends Node \{}\n") + +def generateSerializer (package : String) : String := + serializerTemplate.replace templatePackage package + +/-- Assign unique Java names to all generated types -/ +def assignAllNames (d : Dialect) : NameAssignments := + let baseNames : Std.HashSet String := Std.HashSet.ofList ["node", "sourcerange", "ionserializer"] + + -- Collect unique categories and referenced types + let init : Array QualifiedIdent × Std.HashSet QualifiedIdent := (#[], {}) + let (cats, refs) := d.declarations.foldl (init := init) fun (cats, refs) decl => + match decl with + | .op op => + let cats := if cats.contains op.category then cats else cats.push op.category + let refs := op.argDecls.toArray.foldl (init := refs) fun refs arg => + match arg.kind with + | .type _ => refs.insert ⟨"Init", "Expr"⟩ + | .cat c => match syntaxCatToQualifiedName c with + | some qid => refs.insert qid + | none => refs + (cats, refs) + | _ => (cats, refs) + + -- All QualifiedIdents that need Java names (categories + refs) + let allQids := cats ++ refs.toArray.filter (!cats.contains ·) + + -- Count name occurrences to detect collisions + let nameCounts : Std.HashMap String Nat := allQids.foldl (init := {}) fun m qid => + m.alter qid.name (fun v => some (v.getD 0 + 1)) + + -- Assign Java names, prefixing with dialect when there's a collision + let assignName (used : Std.HashSet String) (qid : QualifiedIdent) : String × Std.HashSet String := + let base := if nameCounts.getD qid.name 0 > 1 + then escapeJavaName (toPascalCase s!"{qid.dialect}_{qid.name}") + else escapeJavaName (toPascalCase qid.name) + disambiguate base used + + -- Assign category names + let catInit : Std.HashMap QualifiedIdent String × Std.HashSet String := ({}, baseNames) + let (categoryNames, used) := cats.foldl (init := catInit) fun (map, used) cat => + let (name, newUsed) := assignName used cat + (map.insert cat name, newUsed) + + -- Assign operator names + let opInit : Std.HashMap (QualifiedIdent × String) String × Std.HashSet String := ({}, used) + let (operatorNames, used) := d.declarations.foldl (init := opInit) fun (map, used) decl => + match decl with + | .op op => + let base := escapeJavaName (toPascalCase op.name) + let (name, newUsed) := disambiguate base used + (map.insert (op.category, op.name) name, newUsed) + | _ => (map, used) + + -- Assign stub names (referenced types not in this dialect's categories) + let stubInit : Std.HashMap QualifiedIdent String × Std.HashSet String := ({}, used) + let (stubNames, used) := refs.toArray.foldl (init := stubInit) fun (map, used) ref => + if categoryNames.contains ref then (map, used) + else + let (name, newUsed) := assignName used ref + (map.insert ref name, newUsed) + + let (buildersName, _) := disambiguate d.name used + + { categories := categoryNames, operators := operatorNames, stubs := stubNames, builders := buildersName } + +/-- Group operators by their target category -/ +def groupOpsByCategory (d : Dialect) (names : NameAssignments) + : Std.HashMap QualifiedIdent (Array String) := + d.declarations.foldl (init := {}) fun acc decl => + match decl with + | .op op => + let javaName := names.operators[(op.category, op.name)]! + acc.alter op.category (fun ops? => some ((ops?.getD #[]).push javaName)) + | _ => acc + +def opDeclToJavaRecord (dialectName : String) (names : NameAssignments) (op : OpDecl) + : JavaRecord := + { name := names.operators[(op.category, op.name)]! + operationName := ⟨dialectName, op.name⟩ + implements := names.categories[op.category]! + fields := op.argDecls.toArray.map argDeclToJavaField } + +def generateBuilders (package : String) (dialectName : String) (d : Dialect) (names : NameAssignments) : String := + let method (op : OpDecl) := + let fields := op.argDecls.toArray.map argDeclToJavaField + let (ps, as) := fields.foldl (init := (#[], #[])) fun (ps, as) f => + match f.type with + | .simple "java.math.BigInteger" _ => (ps.push s!"long {f.name}", as.push s!"java.math.BigInteger.valueOf({f.name})") + | .simple "java.math.BigDecimal" _ => (ps.push s!"double {f.name}", as.push s!"java.math.BigDecimal.valueOf({f.name})") + | t => (ps.push s!"{t.toJava} {f.name}", as.push f.name) + let methodName := escapeJavaName op.name + s!" public static {names.categories[op.category]!} {methodName}({", ".intercalate ps.toList}) \{ return new {names.operators[(op.category, op.name)]!}(SourceRange.NONE{if as.isEmpty then "" else ", " ++ ", ".intercalate as.toList}); }" + let methods := d.declarations.filterMap fun | .op op => some (method op) | _ => none + s!"package {package};\n\npublic class {dialectName} \{\n{"\n".intercalate methods.toList}\n}\n" + +def generateDialect (d : Dialect) (package : String) : Except String GeneratedFiles := do + let names := assignAllNames d + let opsByCategory := groupOpsByCategory d names + + -- Check for unsupported declarations + for decl in d.declarations do + match decl with + | .type t => throw s!"type declaration '{t.name}' is not supported in Java generation" + | .function f => throw s!"function declaration '{f.name}' is not supported in Java generation" + | _ => pure () + + -- Categories with operators get sealed interfaces with permits clauses + let sealedInterfaces := opsByCategory.toList.map fun (cat, ops) => + let name := names.categories[cat]! + let iface : JavaInterface := { name, permits := ops } + (s!"{name}.java", iface.toJava package) + + -- Stub interfaces for referenced types without operators + let stubInterfaces := names.stubs.toList.map fun (_, name) => + generateStubInterface package name + + -- Generate records for operators + let records := d.declarations.toList.filterMap fun decl => + match decl with + | .op op => + let name := names.operators[(op.category, op.name)]! + some (s!"{name}.java", (opDeclToJavaRecord d.name names op).toJava package) + | _ => none + + -- All interface names for Node permits clause + let allInterfaceNames := (sealedInterfaces ++ stubInterfaces).map (·.1.dropRight 5) + + return { + sourceRange := generateSourceRange package + node := generateNodeInterface package allInterfaceNames + interfaces := sealedInterfaces.toArray ++ stubInterfaces.toArray + records := records.toArray + builders := (s!"{names.builders}.java", generateBuilders package names.builders d names) + serializer := generateSerializer package + } + +/-! ## File Output -/ + +def packageToPath (package : String) : System.FilePath := + let parts := package.splitOn "." + ⟨String.intercalate "/" parts⟩ + +def writeJavaFiles (baseDir : System.FilePath) (package : String) (files : GeneratedFiles) : IO Unit := do + let dir := baseDir / packageToPath package + IO.FS.createDirAll dir + + IO.FS.writeFile (dir / "SourceRange.java") files.sourceRange + IO.FS.writeFile (dir / "Node.java") files.node + IO.FS.writeFile (dir / "IonSerializer.java") files.serializer + IO.FS.writeFile (dir / files.builders.1) files.builders.2 + + for (filename, content) in files.interfaces do + IO.FS.writeFile (dir / filename) content + + for (filename, content) in files.records do + IO.FS.writeFile (dir / filename) content + +end Strata.Java diff --git a/Strata/DDM/Integration/Java/templates/IonSerializer.java b/Strata/DDM/Integration/Java/templates/IonSerializer.java new file mode 100644 index 000000000..2a0157fca --- /dev/null +++ b/Strata/DDM/Integration/Java/templates/IonSerializer.java @@ -0,0 +1,146 @@ +package com.strata.template; + +import com.amazon.ion.*; +import com.amazon.ion.system.*; + +public class IonSerializer { + private final IonSystem ion; + + public IonSerializer(IonSystem ion) { + this.ion = ion; + } + + /** Serialize a node as a top-level command (no "op" wrapper). */ + public IonValue serializeCommand(Node node) { + return serializeNode(node); + } + + /** Serialize a node as an argument (with "op" wrapper). */ + public IonValue serialize(Node node) { + return wrapOp(serializeNode(node)); + } + + private IonSexp serializeNode(Node node) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol(node.operationName())); + sexp.add(serializeSourceRange(node.sourceRange())); + + for (var component : node.getClass().getRecordComponents()) { + if (component.getName().equals("sourceRange")) continue; + try { + java.lang.Object value = component.getAccessor().invoke(node); + sexp.add(serializeArg(value, component.getType(), component.getGenericType())); + } catch (java.lang.Exception e) { + throw new java.lang.RuntimeException("Failed to serialize " + component.getName(), e); + } + } + return sexp; + } + + private IonValue wrapOp(IonValue inner) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("op")); + sexp.add(inner); + return sexp; + } + + private IonValue serializeSourceRange(SourceRange sr) { + if (sr.start() == 0 && sr.stop() == 0) { + return ion.newNull(); + } + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newInt(sr.start())); + sexp.add(ion.newInt(sr.stop())); + return sexp; + } + + private IonValue serializeArg(java.lang.Object value, java.lang.Class type, java.lang.reflect.Type genericType) { + if (value == null) { + return serializeOption(java.util.Optional.empty()); + } + if (value instanceof Node n) { + return serialize(n); + } + if (value instanceof java.lang.String s) { + return serializeIdent(s); + } + if (value instanceof java.math.BigInteger bi) { + return serializeNum(bi); + } + if (value instanceof java.math.BigDecimal bd) { + return serializeDecimal(bd); + } + if (value instanceof byte[] bytes) { + return serializeBytes(bytes); + } + if (value instanceof java.lang.Boolean b) { + return serializeBool(b); + } + if (value instanceof java.util.Optional opt) { + return serializeOption(opt); + } + if (value instanceof java.util.List list) { + return serializeSeq(list, genericType); + } + throw new java.lang.IllegalArgumentException("Unsupported type: " + type); + } + + private IonValue serializeIdent(java.lang.String s) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("ident")); + sexp.add(ion.newNull()); + sexp.add(ion.newString(s)); + return sexp; + } + + private IonValue serializeNum(java.math.BigInteger n) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("num")); + sexp.add(ion.newNull()); + sexp.add(ion.newInt(n)); + return sexp; + } + + private IonValue serializeDecimal(java.math.BigDecimal d) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("decimal")); + sexp.add(ion.newNull()); + sexp.add(ion.newDecimal(d)); + return sexp; + } + + private IonValue serializeBytes(byte[] bytes) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("bytes")); + sexp.add(ion.newNull()); + sexp.add(ion.newBlob(bytes)); + return sexp; + } + + private IonValue serializeBool(boolean b) { + IonSexp inner = ion.newEmptySexp(); + inner.add(ion.newSymbol(b ? "Init.boolTrue" : "Init.boolFalse")); + inner.add(ion.newNull()); + return wrapOp(inner); + } + + private IonValue serializeOption(java.util.Optional opt) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("option")); + sexp.add(ion.newNull()); + if (opt.isPresent()) { + sexp.add(serializeArg(opt.get(), opt.get().getClass(), opt.get().getClass())); + } + return sexp; + } + + private IonValue serializeSeq(java.util.List list, java.lang.reflect.Type genericType) { + IonSexp sexp = ion.newEmptySexp(); + sexp.add(ion.newSymbol("seq")); + sexp.add(ion.newNull()); + for (java.lang.Object item : list) { + sexp.add(serializeArg(item, item.getClass(), item.getClass())); + } + return sexp; + } +} diff --git a/Strata/DDM/Integration/Java/templates/Node.java b/Strata/DDM/Integration/Java/templates/Node.java new file mode 100644 index 000000000..e09ae3bda --- /dev/null +++ b/Strata/DDM/Integration/Java/templates/Node.java @@ -0,0 +1,6 @@ +package com.strata.template; + +public sealed interface Node { + SourceRange sourceRange(); + java.lang.String operationName(); +} diff --git a/Strata/DDM/Integration/Java/templates/SourceRange.java b/Strata/DDM/Integration/Java/templates/SourceRange.java new file mode 100644 index 000000000..e993a1ff9 --- /dev/null +++ b/Strata/DDM/Integration/Java/templates/SourceRange.java @@ -0,0 +1,5 @@ +package com.strata.template; + +public record SourceRange(long start, long stop) { + public static final SourceRange NONE = new SourceRange(0, 0); +} diff --git a/Strata/DDM/Integration/Lean.lean b/Strata/DDM/Integration/Lean.lean index 58c0a1b58..8b400b78d 100644 --- a/Strata/DDM/Integration/Lean.lean +++ b/Strata/DDM/Integration/Lean.lean @@ -5,179 +5,4 @@ -/ import Strata.DDM.Integration.Lean.Gen -import Strata.DDM.Integration.Lean.Quote -import Strata.DDM.Integration.Lean.ToExpr -import Strata.DDM.TaggedRegions - -open Lean -open Elab (throwIllFormedSyntax throwUnsupportedSyntax) -open Elab.Command (CommandElab CommandElabM elabCommand) -open Elab.Term (TermElab) -open Parser (InputContext TokenTable) -open System (FilePath) - -namespace Strata - -class HasInputContext (m : Type → Type _) [Functor m] where - getInputContext : m InputContext - getFileName : m FilePath := - (fun ctx => FilePath.mk ctx.fileName) <$> getInputContext - -export HasInputContext (getInputContext) - -instance : HasInputContext CommandElabM where - getInputContext := do - let ctx ← read - pure { - inputString := ctx.fileMap.source - fileName := ctx.fileName - fileMap := ctx.fileMap - } - getFileName := return (← read).fileName - -instance : HasInputContext CoreM where - getInputContext := do - let ctx ← read - pure { - inputString := ctx.fileMap.source - fileName := ctx.fileName - fileMap := ctx.fileMap - } - getFileName := return (← read).fileName - -declare_tagged_region command strataDialectCommand "#dialect" "#end" - -private def mkScopedName {m} [Monad m] [MonadError m] [MonadEnv m] [MonadResolveName m] (name : Name) : m (Ident × Name) := do - let scope ← getCurrNamespace - let fullName := scope ++ name - let env ← getEnv - if env.contains fullName then - throwError s!"Cannot define {name}: {fullName} already exists." - return (Lean.mkScopedIdent scope name, fullName) - -/-- -Create a new definition equal to the given term. --/ -private def elabDef (ident : Ident) (type : Term) (qdef : Term) : CommandElabM Unit := do - let cmd ← `(command| def $ident : $type := $qdef) - tryCatch (elabCommand cmd) fun e => - throwError m!"Definition of {ident} failed: {e.toMessageData}" - -private def quoteList : List Term → Term - | [] => mkCIdent ``List.nil - | (x::xs) => Syntax.mkCApp ``List.cons #[x, quoteList xs] - -/-- -Prepend the current namespace to the Lean name and convert to an identifier. --/ -private def mkAbsIdent (name : Lean.Name) : Ident := - let nameStr := toString name - .mk (.ident .none nameStr.toSubstring name [.decl name []]) - -/-- -Declare dialect and add to environment. --/ -def declareDialect (d : Dialect) : CommandElabM Unit := do - -- Identifier for dialect - let dialectName := Name.anonymous |>.str d.name - let (dialectIdent, dialectAbsName) ← mkScopedName dialectName - -- Identifier for dialect map - let (mapIdent, _) ← mkScopedName (Name.anonymous |>.str s!"{d.name}_map") - elabDef dialectIdent (mkAbsIdent ``Dialect) (quote d) - -- Add dialect to environment - modifyEnv fun env => - dialectExt.modifyState env (·.addDialect! d dialectAbsName (isNew := true)) - -- Create term to represent minimal DialectMap with dialect. - let s := (dialectExt.getState (←Lean.getEnv)) - let openDialects := s.loaded.dialects.importedDialects! d.name |>.toList - let quoteD (d : Dialect) : CommandElabM Term := do - let some name := s.nameMap[d.name]? - | throwError s!"Unknown dialect {d.name}" - return mkAbsIdent name - let ds ← openDialects.mapM quoteD - let mapTerm : Term := Syntax.mkCApp ``DialectMap.ofList! #[quoteList ds] - elabDef mapIdent (mkAbsIdent ``DialectMap) mapTerm - -@[command_elab strataDialectCommand] -def strataDialectImpl: Lean.Elab.Command.CommandElab := fun (stx : Syntax) => do - let .atom i v := stx[1] - | throwError s!"Bad {stx[1]}" - let .original _ p _ e := i - | throwError s!"Expected input context" - let inputCtx ← getInputContext - let loaded := (dialectExt.getState (←Lean.getEnv)).loaded - let (_, d, s) ← Strata.Elab.elabDialect {} loaded inputCtx p e - if !s.errors.isEmpty then - for e in s.errors do - logMessage e - return - -- Add dialect to command environment - declareDialect d - -declare_tagged_region term strataProgram "#strata" "#end" - -private def listToExpr (level : Level) (type : Lean.Expr) (es : List Lean.Expr) : Lean.Expr := - let nilFn := mkApp (mkConst ``List.nil [level]) type - let consFn := mkApp (mkConst ``List.cons [level]) type - let rec aux : List Lean.Expr → Lean.Expr - | [] => nilFn - | a::as => mkApp2 consFn a (aux as) - aux es - -@[term_elab strataProgram] -def strataProgramImpl : TermElab := fun stx tp => do - let .atom i v := stx[1] - | throwError s!"Bad {stx[1]}" - let .original _ p _ e := i - | throwError s!"Expected input context" - let inputCtx ← (getInputContext : CoreM _) - let s := (dialectExt.getState (←Lean.getEnv)) - let leanEnv ← Lean.mkEmptyEnvironment 0 - match Elab.elabProgram s.loaded leanEnv inputCtx p e with - | .ok pgm => - -- Get Lean name for dialect - let some (.str name root) := s.nameMap[pgm.dialect]? - | throwError s!"Unknown dialect {pgm.dialect}" - return astExpr! Program.create - (mkConst (name |>.str s!"{root}_map")) - (toExpr pgm.dialect) - (toExpr pgm.commands) - | .error errors => - for e in errors do - logMessage e - return mkApp2 (mkConst ``sorryAx [1]) (toTypeExpr Program) (toExpr true) - -syntax (name := loadDialectCommand) "#load_dialect" str : command - -def resolveLeanRelPath {m} [Monad m] [HasInputContext m] [MonadError m] (path : FilePath) : m FilePath := do - if path.isAbsolute then - -- TODO: Add warning about absolute paths - pure path - else - let leanPath ← HasInputContext.getFileName - let .some leanDir := leanPath.parent - | throwError "Current file {leanPath} does not have a parent." - pure <| leanDir / path - -@[command_elab loadDialectCommand] -def loadDialectImpl: CommandElab := fun (stx : Syntax) => do - match stx with - | `(command|#load_dialect $pathStx) => - let dialectPath : FilePath := pathStx.getString - let absPath ← resolveLeanRelPath dialectPath - if ! (←absPath.pathExists) then - throwError "Could not find file {dialectPath}" - let loaded := (dialectExt.getState (←Lean.getEnv)).loaded - let (_, r) ← Elab.loadDialectFromPath {} loaded #[] - (path := dialectPath) (actualPath := absPath) (expected := .none) - -- Add dialect to command environment - match r with - | .ok d => - declareDialect d - | .error errorMessages => - assert! errorMessages.size > 0 - throwError (← Elab.mkErrorReport errorMessages) - | _ => - throwUnsupportedSyntax - -end Strata +import Strata.DDM.Integration.Lean.HashCommands diff --git a/Strata/DDM/Integration/Lean/BoolConv.lean b/Strata/DDM/Integration/Lean/BoolConv.lean new file mode 100644 index 000000000..ce83f5902 --- /dev/null +++ b/Strata/DDM/Integration/Lean/BoolConv.lean @@ -0,0 +1,37 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.DDM.Integration.Lean.OfAstM + +public section +namespace Strata + +/-- Convert Init.Bool inductive to OperationF -/ +def Bool.toAst {α} [Inhabited α] (v : Ann Bool α) : OperationF α := + if v.val then + ⟨v.ann, q`Init.boolTrue, #[]⟩ + else + ⟨v.ann, q`Init.boolFalse, #[]⟩ + +/-- Convert OperationF to Init.Bool -/ +def Bool.ofAst {α} [Inhabited α] [Repr α] (op : OperationF α) : OfAstM (Ann Bool α) := + match op.name with + | q`Init.boolTrue => + if op.args.size = 0 then + pure ⟨op.ann, true⟩ + else + .error s!"boolTrue expects 0 arguments, got {op.args.size}" + | q`Init.boolFalse => + if op.args.size = 0 then + pure ⟨op.ann, false⟩ + else + .error s!"boolFalse expects 0 arguments, got {op.args.size}" + | _ => + .error s!"Unknown Bool operator: {op.name}" + +end Strata +end diff --git a/Strata/DDM/Integration/Lean/Env.lean b/Strata/DDM/Integration/Lean/Env.lean index 05ff346f0..70f7b65c4 100644 --- a/Strata/DDM/Integration/Lean/Env.lean +++ b/Strata/DDM/Integration/Lean/Env.lean @@ -3,15 +3,16 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - -import Lean.Environment -import Strata.DDM.Elab +module +public import Lean.Environment +public import Strata.DDM.Elab.LoadedDialects +import Strata.DDM.BuiltinDialects namespace Strata open Lean Parser -structure PersistentDialect where +public structure PersistentDialect where leanName : Lean.Name name : DialectName -- Names of dialects that are imported into this dialect @@ -31,19 +32,26 @@ def dialect (pd : PersistentDialect) : Dialect := end PersistentDialect -structure DialectState where - loaded : Elab.LoadedDialects := .builtin - nameMap : Std.HashMap DialectName Name := .ofList [ - (initDialect.name, ``initDialect), - (headerDialect.name, ``headerDialect), - (StrataDDL.name, ``StrataDDL), - ] - newDialects : Array (Name × Dialect) := #[] +public structure DialectState where + loaded : Elab.LoadedDialects + nameMap : Std.HashMap DialectName Name + newDialects : Array (Name × Dialect) deriving Inhabited namespace DialectState -def addDialect! (s : DialectState) (d : Dialect) (name : Name) (isNew : Bool) : DialectState where +instance : EmptyCollection DialectState where + emptyCollection := { + loaded := .builtin, + nameMap := .ofList [ + (initDialect.name, ``initDialect), + (headerDialect.name, ``headerDialect), + (StrataDDL.name, ``StrataDDL), + ], + newDialects := #[] + } + +public def addDialect! (s : DialectState) (d : Dialect) (name : Name) (isNew : Bool) : DialectState where loaded := assert! d.name ∉ s.loaded.dialects s.loaded.addDialect! d @@ -64,7 +72,7 @@ def mkImported (e : Array (Array PersistentDialect)) : ImportM DialectState := def exportEntries (s : DialectState) : Array PersistentDialect := s.newDialects.map fun (n, d) => .ofDialect n d -initialize dialectExt : PersistentEnvExtension PersistentDialect (Name × Dialect) DialectState ← +public initialize dialectExt : PersistentEnvExtension PersistentDialect (Name × Dialect) DialectState ← registerPersistentEnvExtension { mkInitial := pure {}, addImportedFn := mkImported diff --git a/Strata/DDM/Integration/Lean/Gen.lean b/Strata/DDM/Integration/Lean/Gen.lean index a19ceca23..48829ed24 100644 --- a/Strata/DDM/Integration/Lean/Gen.lean +++ b/Strata/DDM/Integration/Lean/Gen.lean @@ -3,13 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - import Lean.Elab.Command + +import Strata.DDM.BuiltinDialects.Init import Strata.DDM.BuiltinDialects.StrataDDL +import Strata.DDM.Integration.Categories +import Strata.DDM.Integration.Lean.BoolConv import Strata.DDM.Integration.Lean.Env import Strata.DDM.Integration.Lean.GenTrace import Strata.DDM.Integration.Lean.OfAstM -import Strata.DDM.Integration.Lean.Quote import Strata.DDM.Util.Graph.Tarjan open Lean (Command Name Ident Term TSyntax getEnv logError profileitM quote withTraceNode mkIdentFrom) @@ -126,14 +128,7 @@ def resolveDialects (lookup : String → Option Dialect) (dialects : Array Diale abbrev CategoryName := QualifiedIdent -/-- -Forbidden categories are categories that --/ -def forbiddenCategories : Std.HashSet CategoryName := { - q`Init.TypeExpr, - q`Init.BindingType, - q`StrataDDL.Binding -} +def forbiddenCategories : Std.HashSet CategoryName := DDM.Integration.forbiddenCategories private def forbiddenWellDefined : Bool := forbiddenCategories.all fun nm => @@ -150,11 +145,15 @@ private def forbiddenWellDefined : Bool := Special categories ignore operations introduced in Init, but are populated with operators via functions/types. -/ -def specialCategories : Std.HashSet CategoryName := { - q`Init.Expr, - q`Init.Type, - q`Init.TypeP -} +def specialCategories : Std.HashSet CategoryName := DDM.Integration.abstractCategories + +/-- +Argument declaration for code generation. +-/ +structure GenArgDecl where + name : String + cat : SyntaxCat + unwrap : Bool := false /-- A constructor in a generated datatype. @@ -173,7 +172,7 @@ structure DefaultCtor where this must be an auto generated constructor. -/ strataName : Option QualifiedIdent - argDecls : Array (String × SyntaxCat) + argDecls : Array GenArgDecl def DefaultCtor.leanName (c : DefaultCtor) : Name := .str .anonymous c.leanNameStr @@ -182,7 +181,7 @@ An operation at the category level. -/ structure CatOp where name : QualifiedIdent - argDecls : Array (String × SyntaxCat) + argDecls : Array GenArgDecl namespace CatOp @@ -192,7 +191,7 @@ partial def checkCat (op : QualifiedIdent) (c : SyntaxCat) : Except String Unit if f ∈ forbiddenCategories then throw s!"{op.fullName} refers to unsupported category {f.fullName}." -def ofArgDecl (op : QualifiedIdent) (d : ArgDecl) : Except String (String × SyntaxCat) := do +def ofArgDecl (op : QualifiedIdent) (d : ArgDecl) : Except String GenArgDecl := do let cat ← match d.kind with | .type tp => @@ -200,7 +199,9 @@ def ofArgDecl (op : QualifiedIdent) (d : ArgDecl) : Except String (String × Syn | .cat c => checkCat op c pure c - pure ⟨d.ident, cat⟩ + -- Check if unwrap metadata is present + let unwrap := q`StrataDDL.unwrap ∈ d.metadata + pure { name := d.ident, cat, unwrap } def ofOpDecl (d : DialectName) (o : OpDecl) : Except String CatOp := do let name := ⟨d, o.name⟩ @@ -209,7 +210,7 @@ def ofOpDecl (d : DialectName) (o : OpDecl) : Except String CatOp := do def ofTypeDecl (d : DialectName) (o : TypeDecl) : CatOp := { name := ⟨d, o.name⟩ - argDecls := o.argNames |>.map fun anm => ⟨anm.val, .atom .none q`Init.Type⟩ + argDecls := o.argNames |>.map fun anm => { name := anm.val, cat := .atom .none q`Init.Type } } def ofFunctionDecl (d : DialectName) (o : FunctionDecl) : Except String CatOp := do @@ -239,24 +240,26 @@ def mkRootIdent (name : Name) : Ident := let rootName := `_root_ ++ name .mk (.ident .none name.toString.toSubstring rootName [.decl name []]) -/-- -This maps category names in the Init that are already declared to their -representation. --/ +/-- Maps primitive Init categories to their Lean types. -/ def declaredCategories : Std.HashMap CategoryName Name := .ofList [ (q`Init.Ident, ``String), (q`Init.Num, ``Nat), (q`Init.Decimal, ``Decimal), (q`Init.Str, ``String), + (q`Init.ByteArray, ``ByteArray), + (q`Init.Bool, ``Bool) ] +#guard declaredCategories.keys.all (DDM.Integration.primitiveCategories.contains ·) + def ignoredCategories : Std.HashSet CategoryName := .ofList declaredCategories.keys ∪ forbiddenCategories namespace CatOpMap def addCat (m : CatOpMap) (cat : CategoryName) : CatOpMap := - if cat ∈ ignoredCategories then + -- Allow Init.Bool even though it's in ignoredCategories + if cat ∈ ignoredCategories && cat ≠ q`Init.Bool then m else m.insert cat #[] @@ -282,7 +285,9 @@ def addDecl (d : DialectName) (decl : Decl) : CatOpM Unit := | .syncat decl => addCatM ⟨d, decl.name⟩ | .op decl => do - if decl.category ∈ ignoredCategories ∨ decl.category ∈ specialCategories then + -- Allow Init.Bool operators even though Bool is in declaredCategories + let isBoolOp := decl.category == q`Init.Bool && (decl.name == "boolTrue" || decl.name == "boolFalse") + if (decl.category ∈ ignoredCategories ∨ decl.category ∈ specialCategories) && !isBoolOp then if d ≠ "Init" then .addError s!"Skipping operation {decl.name} in {d}: {decl.category.fullName} cannot be extended." else @@ -375,7 +380,7 @@ partial def mkUsedCategories.aux (m : CatOpMap) (s : WorkSet CategoryName) : Cat | _ => let ops := m.getD c #[] let addArgs {α:Type} (f : α → CategoryName → α) (a : α) (op : CatOp) := - op.argDecls.foldl (init := a) fun r (_, c) => c.foldOverAtomicCategories (init := r) f + op.argDecls.foldl (init := a) fun r arg => arg.cat.foldOverAtomicCategories (init := r) f let addName (pa : WorkSet CategoryName) (c : CategoryName) := pa.add c let s := ops.foldl (init := s) (addArgs addName) mkUsedCategories.aux m s @@ -403,11 +408,11 @@ def mkStandardCtors (exprHasEta : Bool) (cat : QualifiedIdent) : Array DefaultCt | q`Init.Expr => if exprHasEta then #[ - .mk "bvar" none #[("idx", .atom .none q`Init.Num)], + .mk "bvar" none #[{ name := "idx", cat := .atom .none q`Init.Num }], .mk "lambda" none #[ - ("var", .atom .none q`Init.Str), - ("type", .atom .none q`Init.Type), - ("fn", .atom .none cat) + { name := "var", cat := .atom .none q`Init.Str }, + { name := "type", cat := .atom .none q`Init.Type }, + { name := "fn", cat := .atom .none cat } ] ] else @@ -491,8 +496,8 @@ def orderedSyncatGroups (categories : Array (QualifiedIdent × Array DefaultCtor g.addEdge typeIdx resIdx | _ => ops.foldl (init := g) fun g op => - op.argDecls.foldl (init := g) fun g (_, c) => - addArgIndices cat op.leanNameStr c g resIdx + op.argDecls.foldl (init := g) fun g arg => + addArgIndices cat op.leanNameStr arg.cat g resIdx let indices := OutGraph.tarjan g indices.map (·.map (categories[·])) @@ -552,8 +557,8 @@ def getCategoryTerm (cat : QualifiedIdent) (annType : Ident) : GenM Term := do def getCategoryOpIdent (cat : QualifiedIdent) (name : Name) : GenM Ident := do currScopedIdent <| (← getCategoryScopedName cat) ++ name -partial def ppCat (annType : Ident) (c : SyntaxCat) : GenM Term := do - let args ← c.args.mapM (ppCat annType) +partial def ppCatWithUnwrap (annType : Ident) (c : SyntaxCat) (unwrap : Bool) : GenM Term := do + let args ← c.args.mapM (ppCatWithUnwrap annType · false) match c.name, eq : args.size with | q`Init.CommaSepBy, 1 => return mkCApp ``Ann #[mkCApp ``Array #[args[0]], annType] @@ -564,11 +569,18 @@ partial def ppCat (annType : Ident) (c : SyntaxCat) : GenM Term := do | cat, 0 => match declaredCategories[cat]? with | some nm => - pure <| mkCApp ``Ann #[mkRootIdent nm, annType] + -- Check if unwrap is specified + if unwrap && cat ∈ declaredCategories then + pure <| mkRootIdent nm -- Return unwrapped type + else + pure <| mkCApp ``Ann #[mkRootIdent nm, annType] | none => do getCategoryTerm cat annType | f, _ => throwError "Unsupported {f.fullName}" +partial def ppCat (annType : Ident) (c : SyntaxCat) : GenM Term := do + ppCatWithUnwrap annType c false + def elabCommands (commands : Array Command) : CommandElabM Unit := do let messageCount := (← get).messages.unreported.size match p : commands.size with @@ -604,8 +616,8 @@ def explicitBinder (name : String) (typeStx : Term) : CommandElabM BracketedBind def genCtor (annType : Ident) (op : DefaultCtor) : GenM (TSyntax ``ctor) := do let ctorId : Ident := localIdent op.leanNameStr - let binders ← op.argDecls.mapM fun (name, tp) => do - explicitBinder name (← ppCat annType tp) + let binders ← op.argDecls.mapM fun arg => do + explicitBinder arg.name (← ppCatWithUnwrap annType arg.cat arg.unwrap) `(ctor| | $ctorId:ident (ann : $annType) $binders:bracketedBinder* ) def mkInductive (cat : QualifiedIdent) (ctors : Array DefaultCtor) : GenM Command := do @@ -657,20 +669,50 @@ def mkAnnWithTerm (argCtor : Name) (annTerm v : Term) : Term := def annToAst (argCtor : Name) (annTerm : Term) : Term := mkCApp argCtor #[mkCApp ``Ann.ann #[annTerm], mkCApp ``Ann.val #[annTerm]] -partial def toAstApplyArg (vn : Name) (cat : SyntaxCat) : GenM Term := do +partial def toAstApplyArg (vn : Name) (cat : SyntaxCat) (unwrap : Bool := false) : GenM Term := do let v := mkIdentFrom (←read).src vn match cat.name with - | q`Init.Expr => do - let toAst ← toAstIdentM cat.name - return mkCApp ``ArgF.expr #[mkApp toAst #[v]] - | q`Init.Ident => - return annToAst ``ArgF.ident v | q`Init.Num => - return annToAst ``ArgF.num v - | q`Init.Decimal => - return annToAst ``ArgF.decimal v + if unwrap then + ``(ArgF.num default $v) + else + return annToAst ``ArgF.num v + | q`Init.Bool => do + if unwrap then + -- When unwrapped, v is a plain Bool. Create OperationF directly based on the value. + let defaultAnn ← ``(default) + let emptyArray ← ``(#[]) + let trueOp := mkCApp ``OperationF.mk #[defaultAnn, quote q`Init.boolTrue, emptyArray] + let falseOp := mkCApp ``OperationF.mk #[defaultAnn, quote q`Init.boolFalse, emptyArray] + let opExpr ← ``(if $v then $trueOp else $falseOp) + ``(ArgF.op $opExpr) + else + -- When wrapped, v is already Ann Bool α + let boolToAst := mkCApp ``Strata.Bool.toAst #[v] + return mkCApp ``ArgF.op #[boolToAst] + | q`Init.Ident => + if unwrap then + ``(ArgF.ident default $v) + else + return annToAst ``ArgF.ident v | q`Init.Str => - return annToAst ``ArgF.strlit v + if unwrap then + ``(ArgF.strlit default $v) + else + return annToAst ``ArgF.strlit v + | q`Init.Decimal => + if unwrap then + ``(ArgF.decimal default $v) + else + return annToAst ``ArgF.decimal v + | q`Init.ByteArray => + if unwrap then + ``(ArgF.bytes default $v) + else + return annToAst ``ArgF.bytes v + | cid@q`Init.Expr => do + let toAst ← toAstIdentM cid + return mkCApp ``ArgF.expr #[mkApp toAst #[v]] | q`Init.Type => do let toAst ← toAstIdentM cat.name ``(ArgF.type ($toAst $v)) @@ -747,8 +789,8 @@ def toAstMatch (cat : QualifiedIdent) (op : DefaultCtor) : GenM MatchAlt := do let argDecls := op.argDecls let (annC, annI) ← genFreshIdentPair "ann" let ctor : Ident ← getCategoryOpIdent cat op.leanName - let args : Array (Name × SyntaxCat) ← argDecls.mapM fun (nm, c) => - return (← genFreshLeanName nm, c) + let args ← argDecls.mapM fun arg => do + return (← genFreshLeanName arg.name, arg.cat, arg.unwrap) let argTerms : Array Term := args.map fun p => mkCanIdent src p.fst let pat : Term ← ``($ctor $annC $argTerms:term*) let rhs : Term ← @@ -758,14 +800,14 @@ def toAstMatch (cat : QualifiedIdent) (op : DefaultCtor) : GenM MatchAlt := do let some nm := op.strataName | return panic! s!"Unexpected builtin expression {lname}" let init := mkCApp ``ExprF.fn #[annI, quote nm] - args.foldlM (init := init) fun a (nm, tp) => do - let e ← toAstApplyArg nm tp + args.foldlM (init := init) fun a (nm, tp, unwrap) => do + let e ← toAstApplyArg nm tp unwrap return Lean.Syntax.mkCApp ``ExprF.app #[annI, a, e] | q`Init.Type => do let some nm := op.strataName | return panic! "Expected type name" let toAst ← toAstIdentM cat - let argTerms ← arrayLit <| args.map fun (v, c) => + let argTerms ← arrayLit <| args.map fun (v, c, _unwrap) => assert! c.isType Lean.Syntax.mkApp toAst #[mkIdentFrom src v] pure <| Lean.Syntax.mkCApp ``TypeExprF.ident #[annI, quote nm, argTerms] @@ -774,7 +816,7 @@ def toAstMatch (cat : QualifiedIdent) (op : DefaultCtor) : GenM MatchAlt := do match op.strataName with | some n => pure n | none => throwError s!"Internal: Operation requires strata name" - let argTerms : Array Term ← args.mapM fun (nm, tp) => toAstApplyArg nm tp + let argTerms : Array Term ← args.mapM fun (nm, tp, unwrap) => toAstApplyArg nm tp unwrap pure <| mkCApp ``OperationF.mk #[annI, quote mName, ← arrayLit argTerms] `(matchAltExpr| | $pat => $rhs) @@ -791,20 +833,62 @@ def genToAst (cat : QualifiedIdent) (ops : Array DefaultCtor) : GenM Command := `(partial def $toAst {$annType : Type} [Inhabited $annType] ($(mkCanIdent src v) : $catTerm) : $astType := match $(mkIdentFrom src v):ident with $cases:matchAlt*) +mutual + partial def getOfIdentArg (varName : String) (cat : SyntaxCat) (e : Term) : GenM Term := do + getOfIdentArgWithUnwrap varName cat false e + +partial def getOfIdentArgWithUnwrap (varName : String) (cat : SyntaxCat) (unwrap : Bool) (e : Term) : GenM Term := do match cat.name with + | q`Init.Num => + if unwrap then + ``((fun arg => match arg with + | ArgF.num _ val => pure val + | a => OfAstM.throwExpected "numeric literal" a) $e) + else + ``(OfAstM.ofNumM $e) + | q`Init.Ident => + if unwrap then + ``((fun arg => match arg with + | ArgF.ident _ val => pure val + | a => OfAstM.throwExpected "identifier" a) $e) + else + ``(OfAstM.ofIdentM $e) + | q`Init.Str => + if unwrap then + ``((fun arg => match arg with + | ArgF.strlit _ val => pure val + | a => OfAstM.throwExpected "string literal" a) $e) + else + ``(OfAstM.ofStrlitM $e) + | q`Init.Decimal => + if unwrap then + ``((fun arg => match arg with + | ArgF.decimal _ val => pure val + | a => OfAstM.throwExpected "decimal literal" a) $e) + else + ``(OfAstM.ofDecimalM $e) + | q`Init.ByteArray => + if unwrap then + ``((fun arg => match arg with + | ArgF.bytes _ val => pure val + | a => OfAstM.throwExpected "byte array" a) $e) + else + ``(OfAstM.ofBytesM $e) + | q`Init.Bool => do + if unwrap then + -- When unwrapped, extract just the Bool value from Ann Bool α + ``((fun arg => match arg with + | ArgF.op op => Functor.map Ann.val (Strata.Bool.ofAst op) + | a => OfAstM.throwExpected "boolean" a) $e) + else + let (vc, vi) ← genFreshIdentPair varName + let boolOfAst := mkCApp ``Strata.Bool.ofAst #[vi] + ``(OfAstM.ofOperationM $e fun $vc _ => $boolOfAst) | cid@q`Init.Expr => do let (vc, vi) ← genFreshIdentPair <| varName ++ "_inner" let ofAst ← ofAstIdentM cid ``(OfAstM.ofExpressionM $e fun $vc _ => $ofAst $vi) - | q`Init.Ident => do - ``(OfAstM.ofIdentM $e) - | q`Init.Num => do - ``(OfAstM.ofNumM $e) - | q`Init.Decimal => do - ``(OfAstM.ofDecimalM $e) - | q`Init.Str => do - ``(OfAstM.ofStrlitM $e) | cid@q`Init.Type => do let (vc, vi) ← genFreshIdentPair varName let ofAst ← ofAstIdentM cid @@ -833,13 +917,15 @@ partial def getOfIdentArg (varName : String) (cat : SyntaxCat) (e : Term) : GenM let ofAst ← ofAstIdentM cid ``(OfAstM.ofOperationM $e fun $vc _ => $ofAst $vi) -def ofAstArgs (argDecls : Array (String × SyntaxCat)) (argsVar : Ident) : GenM (Array Ident × Array (TSyntax ``doSeqItem)) := do +end + +def ofAstArgs (argDecls : Array GenArgDecl) (argsVar : Ident) : GenM (Array Ident × Array (TSyntax ``doSeqItem)) := do let argCount := argDecls.size let args ← Array.ofFnM (n := argCount) fun ⟨i, _isLt⟩ => do - let (vnm, c) := argDecls[i] - let (vc, vi) ← genFreshIdentPair <| vnm ++ "_bind" + let arg := argDecls[i] + let (vc, vi) ← genFreshIdentPair <| arg.name ++ "_bind" let av ← ``($argsVar[$(quote i)]) - let rhs ← getOfIdentArg vnm c av + let rhs ← getOfIdentArgWithUnwrap arg.name arg.cat arg.unwrap av let stmt ← `(doSeqItem| let $vc ← $rhs:term) return (vi, stmt) return args.unzip @@ -869,12 +955,12 @@ def ofAstExprMatch (nameIndexMap : Std.HashMap QualifiedIdent Nat) let rhs ← ofAstExprMatchRhs cat annI argsVar op ofAstMatch nameIndexMap op rhs -def ofAstTypeArgs (argDecls : Array (String × SyntaxCat)) (argsVar : Ident) : GenM (Array Ident × Array (TSyntax ``doSeqItem)) := do +def ofAstTypeArgs (argDecls : Array GenArgDecl) (argsVar : Ident) : GenM (Array Ident × Array (TSyntax ``doSeqItem)) := do let argCount := argDecls.size let ofAst ← ofAstIdentM q`Init.Type let args ← Array.ofFnM (n := argCount) fun ⟨i, _isLt⟩ => do - let (vnm, _) := argDecls[i] - let v ← genFreshLeanName vnm + let arg := argDecls[i] + let v ← genFreshLeanName arg.name let src := (←read).src let rhs ← ``($ofAst $argsVar[$(quote i)]) let stmt ← `(doSeqItem| let $(mkIdentFrom src v true) ← $rhs:term) @@ -1001,8 +1087,8 @@ def checkInhabited (cat : QualifiedIdent) (ops : Array DefaultCtor) : StateT Inh let catTerm ← getCategoryTerm cat annType for op in ops do let inhabited ← get - let isInhabited := op.argDecls.all fun (_, c) => - match c.name with + let isInhabited := op.argDecls.all fun arg => + match arg.cat.name with | q`Init.Seq => true | q`Init.CommaSepBy => true | q`Init.Option => true @@ -1105,7 +1191,7 @@ def genAstImpl : CommandElab := fun stx => let .str .anonymous dialectName := dialectStx.getId | throwErrorAt dialectStx s!"Expected dialect name" let loader := dialectExt.getState (← getEnv) |>.loaded - let depDialectNames := generateDependentDialects (loader.dialects.map[·]?) dialectName + let depDialectNames := generateDependentDialects (loader.dialects[·]?) dialectName let usedDialects ← depDialectNames.mapM fun nm => match loader.dialects[nm]? with | some d => pure d diff --git a/Strata/DDM/Integration/Lean/GenTrace.lean b/Strata/DDM/Integration/Lean/GenTrace.lean index 0819f9160..693e415b3 100644 --- a/Strata/DDM/Integration/Lean/GenTrace.lean +++ b/Strata/DDM/Integration/Lean/GenTrace.lean @@ -3,6 +3,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -- This module defines a trace class used for the generator. import Lean.Util.Trace diff --git a/Strata/DDM/Integration/Lean/HashCommands.lean b/Strata/DDM/Integration/Lean/HashCommands.lean index 1b7188593..addac2f96 100644 --- a/Strata/DDM/Integration/Lean/HashCommands.lean +++ b/Strata/DDM/Integration/Lean/HashCommands.lean @@ -3,83 +3,199 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Integration.Lean.Env -import Strata.DDM.Integration.Lean.Quote -import Strata.DDM.Integration.Lean.ToExpr +public import Lean.Parser.Types + +public import Lean.Elab.Command + +public meta import Strata.DDM.Integration.Lean.ToExpr import Strata.DDM.TaggedRegions +public meta import Strata.DDM.Integration.Lean.Env +public meta import Strata.DDM.Elab + +meta import Strata.DDM.TaggedRegions + open Lean -open Elab.Command (CommandElab CommandElabM elabCommand) -open Elab.Term (TermElab) -open Parser (InputContext) +open Lean.Elab (throwUnsupportedSyntax) +open Lean.Elab.Command (CommandElab CommandElabM liftCoreM) +open Lean.Elab.Term (TermElab) +open Lean.Parser (InputContext) +open System (FilePath) namespace Strata -class HasInputContext (m : Type → Type _) where +class HasInputContext (m : Type → Type _) [Functor m] where getInputContext : m InputContext + getFileName : m FilePath := + (fun ctx => FilePath.mk ctx.fileName) <$> getInputContext -export HasInputContext (getInputContext) +--export HasInputContext (getInputContext) -instance : HasInputContext CommandElabM where +meta instance : HasInputContext CommandElabM where getInputContext := do let ctx ← read pure { - input := ctx.fileMap.source + inputString := ctx.fileMap.source fileName := ctx.fileName - fileMap := ctx.fileMap + fileMap := ctx.fileMap } + getFileName := return (← read).fileName -instance : HasInputContext CoreM where +meta instance : HasInputContext CoreM where getInputContext := do let ctx ← read pure { - input := ctx.fileMap.source + inputString := ctx.fileMap.source fileName := ctx.fileName - fileMap := ctx.fileMap + fileMap := ctx.fileMap } + getFileName := return (← read).fileName + +private meta def mkScopedName {m} [Monad m] [MonadError m] [MonadEnv m] [MonadResolveName m] (name : Name) : m Name := do + let scope ← getCurrNamespace + let fullName := scope ++ name + let env ← getEnv + if env.contains fullName then + throwError s!"Cannot define {name}: {fullName} already exists." + return fullName + +/-- +Prepend the current namespace to the Lean name and convert to an identifier. +-/ +private def mkAbsIdent (name : Lean.Name) : Ident := + let nameStr := toString name + .mk (.ident .none nameStr.toSubstring name [.decl name []]) + +/-- +Add a definition to environment and compile it. +-/ +meta def addDefn (name : Lean.Name) + (type : Lean.Expr) + (value : Lean.Expr) + (levelParams : List Name := []) + (hints : ReducibilityHints := .abbrev) + (safety : DefinitionSafety := .safe) + (all : List Lean.Name := [name]) : CoreM Unit := do + addAndCompile <| .defnDecl { + name := name + levelParams := levelParams + type := type + value := value + hints := hints + safety := safety + all := all + } + +/-- +Declare dialect and add to environment. +-/ +public meta def declareDialect (d : Dialect) : CommandElabM Unit := do + -- Identifier for dialect + let dialectName := Name.anonymous |>.str d.name + let dialectAbsName ← mkScopedName dialectName + -- Identifier for dialect map + let mapAbsName ← mkScopedName (Name.anonymous |>.str s!"{d.name}_map") + + let dialectTypeExpr := mkConst ``Dialect + liftCoreM <| addDefn dialectAbsName dialectTypeExpr (toExpr d) + -- Add dialect to environment + modifyEnv fun env => + dialectExt.modifyState env (·.addDialect! d dialectAbsName (isNew := true)) + -- Create term to represent minimal DialectMap with dialect. + let s := (dialectExt.getState (←Lean.getEnv)) + let .isTrue mem := inferInstanceAs (Decidable (d.name ∈ s.loaded.dialects)) + | throwError "Internal error with unknown dialect" + let openDialects := s.loaded.dialects.importedDialects d.name mem |>.toList + let exprD (d : Dialect) : CommandElabM Lean.Expr := do + let some name := s.nameMap[d.name]? + | throwError s!"Unknown dialect {d.name}" + return mkConst name + let de ← openDialects.mapM exprD + let mapValue := mkApp (mkConst ``DialectMap.ofList!) + (listToExpr .zero dialectTypeExpr de) + liftCoreM <| addDefn mapAbsName (mkConst ``DialectMap) mapValue declare_tagged_region command strataDialectCommand "#dialect" "#end" @[command_elab strataDialectCommand] -def strataDialectImpl: Lean.Elab.Command.CommandElab := fun (stx : Syntax) => do +public meta def strataDialectImpl: Lean.Elab.Command.CommandElab := fun (stx : Syntax) => do let .atom i v := stx[1] | throwError s!"Bad {stx[1]}" let .original _ p _ e := i | throwError s!"Expected input context" - let emptyLeanEnv ← mkEmptyEnvironment 0 - let inputCtx ← getInputContext - let dialects := (dialectExt.getState (←Lean.getEnv)).loaded - let loadFn (dialect : String) := pure (Except.error s!"Unknown dialect {dialect}.") - let (d, s, _) ← Elab.elabDialect emptyLeanEnv loadFn dialects inputCtx p e + let inputCtx ← HasInputContext.getInputContext + let loaded := (dialectExt.getState (←Lean.getEnv)).loaded + let (_, d, s) ← Strata.Elab.elabDialect {} loaded inputCtx p e if !s.errors.isEmpty then for e in s.errors do logMessage e return - -- Add dialect to command - let cmd ← `(command| def $(Lean.mkLocalDeclId d.name) := $(quote d)) - tryCatch (elabCommand cmd) fun e => - panic! "Elab command failed: {e}" - modifyEnv fun env => - dialectExt.modifyState env (·.addDialect! d (isNew := true)) + -- Add dialect to command environment + declareDialect d declare_tagged_region term strataProgram "#strata" "#end" - @[term_elab strataProgram] -def strataProgramImpl : TermElab := fun stx tp => do +@[term_elab strataProgram] +public meta def strataProgramImpl : TermElab := fun stx tp => do let .atom i v := stx[1] | throwError s!"Bad {stx[1]}" let .original _ p _ e := i | throwError s!"Expected input context" - let inputCtx ← (getInputContext : CoreM _) - let loader := (dialectExt.getState (←Lean.getEnv)).loaded + let inputCtx ← (HasInputContext.getInputContext : CoreM _) + let s := (dialectExt.getState (←Lean.getEnv)) let leanEnv ← Lean.mkEmptyEnvironment 0 - match Elab.elabProgram loader leanEnv inputCtx p e with + match Elab.elabProgram s.loaded leanEnv inputCtx p e with | .ok pgm => - return toExpr pgm + -- Get Lean name for dialect + let some (.str name root) := s.nameMap[pgm.dialect]? + | throwError s!"Unknown dialect {pgm.dialect}" + let commandType := mkConst ``Operation + let cmdToExpr (cmd : Strata.Operation) : CoreM Lean.Expr := do + let n ← mkFreshUserName `command + addDefn n commandType (toExpr cmd) + pure <| mkConst n + let commandExprs ← monadLift <| pgm.commands.mapM cmdToExpr + return astExpr! Program.create + (mkConst (name |>.str s!"{root}_map")) + (toExpr pgm.dialect) + (arrayToExpr .zero commandType commandExprs) | .error errors => for e in errors do logMessage e return mkApp2 (mkConst ``sorryAx [1]) (toTypeExpr Program) (toExpr true) +syntax (name := loadDialectCommand) "#load_dialect" str : command + +meta def resolveLeanRelPath {m} [Monad m] [HasInputContext m] [MonadError m] (path : FilePath) : m FilePath := do + if path.isAbsolute then + pure path + else + let leanPath ← HasInputContext.getFileName + let .some leanDir := leanPath.parent + | throwError "Current file {leanPath} does not have a parent." + pure <| leanDir / path + +@[command_elab loadDialectCommand] +public meta def loadDialectImpl: CommandElab := fun (stx : Syntax) => do + match stx with + | `(command|#load_dialect $pathStx) => + let dialectPath : FilePath := pathStx.getString + let absPath ← resolveLeanRelPath dialectPath + if ! (← absPath.pathExists) then + throwErrorAt pathStx "Could not find file {dialectPath}" + let loaded := (dialectExt.getState (←Lean.getEnv)).loaded + let (_, r) ← Elab.loadDialectFromPath {} loaded #[] + (path := dialectPath) (actualPath := absPath) (expected := .none) + -- Add dialect to command environment + match r with + | .ok d => + declareDialect d + | .error errorMessages => + assert! errorMessages.size > 0 + throwError (← Elab.mkErrorReport errorMessages) + | _ => + throwUnsupportedSyntax + end Strata diff --git a/Strata/DDM/Integration/Lean/OfAstM.lean b/Strata/DDM/Integration/Lean/OfAstM.lean index d00daca83..58f85370d 100644 --- a/Strata/DDM/Integration/Lean/OfAstM.lean +++ b/Strata/DDM/Integration/Lean/OfAstM.lean @@ -3,9 +3,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public import Strata.DDM.AST +public import Strata.DDM.HNF +import Strata.DDM.Util.Array -import Strata.DDM.AST - +public section namespace Strata class HasEta (α : Type u) (β : outParam (Type v)) where @@ -20,16 +23,16 @@ def etaExpand {E T} [HasEta E T] (argTypes : Array (String × T)) (provided : Na else e -def OfAstM (α : Type _) := Except String α +@[expose] def OfAstM (α : Type _) := Except String α -instance [ToString α] : ToString (OfAstM α) where - toString e := +instance {α} [ToString α] : ToString (OfAstM α) where + toString e := private match e with | .error e => e | .ok r => toString r -instance [Repr α] : Repr (OfAstM α) where - reprPrec e prec := +instance {α} [Repr α] : Repr (OfAstM α) where + reprPrec e prec := private match e with | .error e => Repr.addAppParen ("error " ++ reprArg e) prec | .ok r => Repr.addAppParen ("ok " ++ reprArg r) prec @@ -142,11 +145,14 @@ def ofDecimalM {α} [Repr α] : ArgF α → OfAstM (Ann Decimal α) | .decimal ann val => pure { ann := ann, val := val } | a => .throwExpected "scientific literal" a -def ofStrlitM {α} [Repr α] - : ArgF α → OfAstM (Ann String α) +def ofStrlitM {α} [Repr α] : ArgF α → OfAstM (Ann String α) | .strlit ann val => pure { ann := ann, val := val } | a => .throwExpected "string literal" a +def ofBytesM {α} [Repr α] : ArgF α → OfAstM (Ann ByteArray α) +| .bytes ann val => pure { ann := ann, val := val } +| a => .throwExpected "byte array" a + def ofOptionM {α β} [Repr α] [SizeOf α] (arg : ArgF α) (act : ∀(e : ArgF α), sizeOf e < sizeOf arg → OfAstM β) @@ -222,3 +228,4 @@ def exprEtaArg{Ann α T} [Repr Ann] [HasEta α T] {e : Expr} {n : Nat} (as : Siz return HasEta.bvar i end Strata.OfAstM +end diff --git a/Strata/DDM/Integration/Lean/Quote.lean b/Strata/DDM/Integration/Lean/Quote.lean deleted file mode 100644 index b6148b159..000000000 --- a/Strata/DDM/Integration/Lean/Quote.lean +++ /dev/null @@ -1,302 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Strata.DDM.AST -import Lean.Elab.Term - -namespace Strata - -open Lean - -private def quoteOption (a : Option Term) : Term := - match a with - | none => Syntax.mkCApp ``Option.none #[] - | some a => Syntax.mkCApp ``Option.some #[a] - -private def quoteArray (a : Array Term) : Term := - if a.size <= 8 then - let terms := a - Syntax.mkCApp (Name.mkStr2 "Array" ("mkArray" ++ toString a.size)) terms - else - let e := Syntax.mkCApp ``Array.mkEmpty #[quote a.size] - a.foldl (init := e) fun t a => Syntax.mkCApp ``Array.push #[t, a] - -section -open Lean.Elab - -/-- -Lift a DDM AST constructor that takes a polymorphic annotation value to -the syntax level with the correct number of arguments. - -For example, `astQuote! ArgF.ident ann (quote e)` returns syntax for (ArgF.ident ann e). --/ -syntax:max (name := astQuoteElab) "astQuote!" ident term:max term:max* : term - -@[term_elab astQuoteElab] -def astQuoteElabImpl : Term.TermElab := fun stx _expectedType => do - let a := stx.getArgs - assert! a.size = 4 - let ident := a[1]! - assert! ident.isIdent - let ctor ← realizeGlobalConstNoOverloadWithInfo ident - let cv ← getConstVal ctor - - let ann ← Term.elabTerm a[2]! none - let annType ← Meta.inferType ann - let termExpr := toExpr `term - let annTypeInst ← Meta.synthInstance (mkApp2 (mkConst ``Quote) annType termExpr) - let .sort (.succ .zero) ← Meta.inferType annType - | throwError "Annotation must have type Type." - let annExpr := mkApp4 (mkConst ``quote) annType termExpr annTypeInst ann - - let argc := cv.type.getForallBinderNames.length - if argc < 2 then - throwErrorAt ident "Expected constructor with annotation argument." - - let termList := a[3]! - assert! termList.isOfKind nullKind - let terms := termList.getArgs - if argc - 2 ≠ terms.size then - throwErrorAt ident "Expected {argc - 2} arguments; found {terms.size} arguments." - let eltType := mkApp (mkConst ``TSyntax) (toExpr [`term]) - let a ← terms.mapM_off (init := #[annExpr]) fun ts => Term.elabTerm ts (some eltType) - return mkApp2 (mkConst ``Lean.Syntax.mkCApp) (toExpr ctor) (arrayToExpr eltType a) - -end - -namespace SyntaxCatF - -protected def quote {α} [Quote α] (cat : SyntaxCatF α) : Term := - let r := quoteArray <| cat.args.map fun x => x.quote - astQuote! SyntaxCatF.mk cat.ann (quote cat.name) r -termination_by sizeOf cat -decreasing_by - simp [sizeOf_spec cat] - decreasing_tactic - -instance {α} [Quote α] : Quote (SyntaxCatF α) where - quote := SyntaxCatF.quote - -end SyntaxCatF - -namespace TypeExprF - -protected def quote {α} [Quote α] : TypeExprF α → Term -| .ident ann nm a => - astQuote! ident ann (quote nm) (quoteArray (a.map (·.quote))) -| .bvar ann idx => - astQuote! bvar ann (quote idx) -| .fvar ann idx a => - astQuote! fvar ann (quote idx) (quoteArray (a.map (·.quote))) -| .arrow ann a r => - astQuote! arrow ann a.quote r.quote -termination_by e => e - -instance {α} [Quote α] : Quote (TypeExprF α) where - quote := TypeExprF.quote - -end TypeExprF - -mutual - -protected def ArgF.quote {α} [Quote α] : ArgF α → Term -| .op o => Syntax.mkCApp ``ArgF.op #[o.quote] -| .expr e => Syntax.mkCApp ``ArgF.expr #[e.quote] -| .type e => Syntax.mkCApp ``ArgF.type #[quote e] -| .cat e => Syntax.mkCApp ``ArgF.cat #[quote e] -| .ident ann e => astQuote! ArgF.ident ann (quote e) -| .num ann e => astQuote! ArgF.num ann (quote e) -| .decimal ann e => astQuote! ArgF.decimal ann (quote e) -| .strlit ann e => astQuote! ArgF.strlit ann (quote e) -| .option ann a => astQuote! ArgF.option ann (quoteOption (a.attach.map (fun ⟨e, _⟩ => e.quote))) -| .seq ann a => astQuote! ArgF.seq ann (quoteArray (a.map (·.quote))) -| .commaSepList ann a => astQuote! ArgF.commaSepList ann (quoteArray (a.map (·.quote))) -termination_by a => sizeOf a - -protected def ExprF.quote {α} [Quote α] : ExprF α → Term -| .bvar ann s => astQuote! ExprF.bvar ann (quote s) -| .fvar ann idx => astQuote! ExprF.fvar ann (quote idx) -| .fn ann ident => astQuote! ExprF.fn ann (quote ident) -| .app ann f a => astQuote! ExprF.app ann f.quote a.quote -termination_by e => sizeOf e - -def OperationF.quote {α} [Quote α] (op : OperationF α) : Term := - let r := quoteArray <| op.args.map fun x => x.quote - astQuote! OperationF.mk op.ann (quote op.name) r -termination_by sizeOf op -decreasing_by - simp [OperationF.sizeOf_spec] - decreasing_tactic - -end - -instance {α} [Quote α] : Quote (ArgF α) where - quote := ArgF.quote - -instance {α} [Quote α] : Quote (ExprF α) where - quote := ExprF.quote - -instance {α} [Quote α] : Quote (OperationF α) where - quote := OperationF.quote - -instance : Quote String.Pos where - quote e := Syntax.mkCApp ``String.Pos.mk #[quote e.byteIdx] - -namespace SourceRange - -instance : Quote SourceRange where - quote x := Syntax.mkCApp ``mk #[quote x.start, quote x.stop] - -end SourceRange - -namespace PreType - -protected def quote : PreType → Term -| .ident ann nm a => - Syntax.mkCApp ``ident #[quote ann, quote nm, quoteArray (a.map (·.quote))] -| .bvar ann idx => Syntax.mkCApp ``bvar #[quote ann, quote idx] -| .fvar ann idx a => - Syntax.mkCApp ``fvar #[quote ann, quote idx, quoteArray (a.map (·.quote))] -| .arrow ann a r => Syntax.mkCApp ``arrow #[quote ann, a.quote, r.quote] -| .funMacro ann i r => - Syntax.mkCApp ``funMacro #[quote ann, quote i, r.quote] - -instance : Quote PreType where - quote := PreType.quote - -end PreType - -namespace MetadataArg - -protected def quote : MetadataArg → Term - | .bool b => Syntax.mkCApp ``bool #[quote b] - | .num n => Syntax.mkCApp ``num #[quote n] - | .catbvar n => Syntax.mkCApp ``catbvar #[quote n] - | .option ma => Syntax.mkCApp ``option #[quoteOption (ma.attach.map fun ⟨a, _⟩ => a.quote)] - -instance : Quote MetadataArg where - quote := MetadataArg.quote - -end MetadataArg - -instance : Quote MetadataAttr where - quote a := Syntax.mkCApp ``MetadataAttr.mk #[quote a.ident, quote a.args] - -instance : Quote Metadata where - quote m := Syntax.mkCApp ``Metadata.ofArray #[quote m.toArray] - -namespace ArgDeclKind - -instance : Quote ArgDeclKind where - quote - | .type tp => Syntax.mkCApp ``type #[quote tp] - | .cat c => Syntax.mkCApp ``cat #[quote c] - -end ArgDeclKind - -instance ArgDecl.instQuote : Quote ArgDecl where - quote b := Syntax.mkCApp ``mk #[quote b.ident, quote b.kind, quote b.metadata] - -namespace SyntaxDefAtom - -protected def quote : SyntaxDefAtom → Term -| .ident v p => Syntax.mkCApp ``ident #[quote v, quote p] -| .str l => Syntax.mkCApp ``str #[quote l] -| .indent n a => Syntax.mkCApp ``indent #[quote n, quoteArray (a.map (·.quote))] - -instance : Quote SyntaxDefAtom where - quote := SyntaxDefAtom.quote - -end SyntaxDefAtom - -namespace SyntaxDef - -instance : Quote SyntaxDef where - quote s := Syntax.mkCApp ``SyntaxDef.mk #[quote s.atoms, quote s.prec] - -end SyntaxDef - -instance : Quote ArgDecls where - quote a := Syntax.mkCApp ``ArgDecls.ofArray #[quote a.toArray] - -instance : Quote SynCatDecl where - quote d := Syntax.mkCApp ``SynCatDecl.mk #[quote d.name, quote d.argNames] - -instance : Quote OpDecl where - quote d := Syntax.mkCApp ``OpDecl.mk1 #[ - quote d.name, - quote d.argDecls, - quote d.category, - quote d.syntaxDef, - quote d.metadata - ] - -instance {α β} [Quote α] [Quote β] : Quote (Ann α β) where - quote p := Syntax.mkCApp ``Ann.mk #[quote p.ann, quote p.val] - -instance : Quote TypeDecl where - quote d := Syntax.mkCApp ``TypeDecl.mk #[quote d.name, quote d.argNames] - -instance : Quote FunctionDecl where - quote d := Syntax.mkCApp ``FunctionDecl.mk #[ - quote d.name, - quote d.argDecls, - quote d.result, - quote d.syntaxDef, - quote d.metadata - ] - -namespace MetadataArgType - -protected def quote : MetadataArgType → Term -| .bool => mkCIdent ``bool -| .num => mkCIdent ``num -| .ident => mkCIdent ``ident -| .opt tp => Syntax.mkCApp ``opt #[tp.quote] - -instance : Quote MetadataArgType where - quote := MetadataArgType.quote - -end MetadataArgType - -instance : Quote MetadataArgDecl where - quote d := Syntax.mkCApp ``MetadataArgDecl.mk #[quote d.ident, quote d.type] - -instance : Quote MetadataDecl where - quote d := Syntax.mkCApp ``MetadataDecl.mk #[quote d.name, quote d.args] - -instance : Quote Decl where - quote - | .syncat d => Syntax.mkCApp ``Decl.syncat #[quote d] - | .op d => Syntax.mkCApp ``Decl.op #[quote d] - | .type d => Syntax.mkCApp ``Decl.type #[quote d] - | .function d => Syntax.mkCApp ``Decl.function #[quote d] - | .metadata d => Syntax.mkCApp ``Decl.metadata #[quote d] - -instance : Quote Dialect where - quote d : Term := - Syntax.mkCApp ``Dialect.ofArray #[ - quote d.name, - quote d.imports, - quote d.declarations - ] - -namespace DialectMap - -instance : Quote DialectMap where - quote d := Syntax.mkCApp ``DialectMap.ofList! #[quote d.toList] - -end DialectMap - -instance : Quote Program where - quote p : Term := - Syntax.mkCApp ``Program.create #[ - quote p.dialects, - quote p.dialect, - quote p.commands - ] - -end Strata diff --git a/Strata/DDM/Integration/Lean/ToExpr.lean b/Strata/DDM/Integration/Lean/ToExpr.lean index 591bd5c5e..9a1d96f4e 100644 --- a/Strata/DDM/Integration/Lean/ToExpr.lean +++ b/Strata/DDM/Integration/Lean/ToExpr.lean @@ -3,10 +3,18 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Lean.Elab.Term -import Strata.DDM.AST +public import Strata.DDM.AST +public import Lean.Elab.Term +import Strata.DDM.Util.ByteArray +import Strata.DDM.Util.Decimal +import Strata.DDM.Util.Lean + +meta import Lean.Elab.Term.TermElabM + +public section namespace Strata open Lean @@ -14,8 +22,8 @@ open Lean namespace QualifiedIdent instance : ToExpr QualifiedIdent where - toTypeExpr := mkConst ``QualifiedIdent - toExpr i := mkApp2 (mkConst ``QualifiedIdent.mk) (toExpr i.dialect) (toExpr i.name) + toTypeExpr := private mkConst ``QualifiedIdent + toExpr i := private mkApp2 (mkConst ``QualifiedIdent.mk) (toExpr i.dialect) (toExpr i.name) end QualifiedIdent @@ -26,7 +34,7 @@ open Lean.Elab private def rootIdent (name : Name) : Ident := .mk (.ident .none name.toString.toSubstring name [.decl name []]) -private def emptyLevel : Lean.Expr := mkApp (mkConst ``List.nil [.zero]) (mkConst ``Level) +private meta def emptyLevel : Lean.Expr := mkApp (mkConst ``List.nil [.zero]) (mkConst ``Level) /-- Lift a DDM AST constructor that takes a polymorphic annotation value to @@ -38,7 +46,7 @@ Lean expression and returns another. syntax:max (name := astExprElab) "astExpr!" ident : term @[term_elab astExprElab] -def astExprElabImpl : Term.TermElab := fun stx _expectedType => do +public meta def astExprElabImpl : Term.TermElab := fun stx _expectedType => do match stx with | `(astExpr! $ident) => do let ctor ← realizeGlobalConstNoOverloadWithInfo ident @@ -61,7 +69,7 @@ def astExprElabImpl : Term.TermElab := fun stx _expectedType => do syntax:max (name := astAnnExprElab) "astAnnExpr!" ident term:max : term @[term_elab astAnnExprElab] -def astAnnExprElabImpl : Term.TermElab := fun stx _expectedType => do +public meta def astAnnExprElabImpl : Term.TermElab := fun stx _expectedType => do match stx with | `(astAnnExpr! $ident $ann) => do let ctor ← realizeGlobalConstNoOverloadWithInfo ident @@ -85,60 +93,60 @@ end namespace SyntaxCatF -protected def typeExpr (α : Type) [ToExpr α] := mkApp (mkConst ``SyntaxCatF) (toTypeExpr α) +private protected def typeExpr (α : Type) [ToExpr α] := mkApp (mkConst ``SyntaxCatF) (toTypeExpr α) -protected def toExpr {α} [ToExpr α] (cat : SyntaxCatF α) : Lean.Expr := - let args := arrayToExpr (SyntaxCatF.typeExpr α) (cat.args.map fun e => e.toExpr) +private protected def toExpr {α} [ToExpr α] (cat : SyntaxCatF α) : Lean.Expr := + let args := arrayToExpr levelZero (SyntaxCatF.typeExpr α) (cat.args.map fun e => e.toExpr) astAnnExpr! SyntaxCatF.mk cat.ann (toExpr cat.name) args decreasing_by simp [SyntaxCatF.sizeOf_spec cat] decreasing_tactic instance {α} [ToExpr α] : ToExpr (SyntaxCatF α) where - toTypeExpr := SyntaxCatF.typeExpr α - toExpr := SyntaxCatF.toExpr + toTypeExpr := private SyntaxCatF.typeExpr α + toExpr := private SyntaxCatF.toExpr end SyntaxCatF namespace TypeExprF -protected def typeExpr (ann : Lean.Expr) : Lean.Expr := +private protected def typeExpr (ann : Lean.Expr) : Lean.Expr := mkApp (mkConst ``TypeExprF) ann -protected def toExpr {α} [ToExpr α] : TypeExprF α → Lean.Expr +private protected def toExpr {α} [ToExpr α] : TypeExprF α → Lean.Expr | .ident ann nm a => - let ae := arrayToExpr (TypeExprF.typeExpr (toTypeExpr α)) (a.map (·.toExpr)) + let ae := arrayToExpr levelZero (TypeExprF.typeExpr (toTypeExpr α)) (a.map (·.toExpr)) astAnnExpr! ident ann (toExpr nm) ae | .bvar ann idx => astAnnExpr! bvar ann (toExpr idx) | .fvar ann idx a => - let ae := arrayToExpr (TypeExprF.typeExpr (toTypeExpr α)) (a.map (·.toExpr)) + let ae := arrayToExpr levelZero (TypeExprF.typeExpr (toTypeExpr α)) (a.map (·.toExpr)) astAnnExpr! fvar ann (toExpr idx) ae | .arrow ann a r => astAnnExpr! arrow ann a.toExpr r.toExpr instance {α} [ToExpr α] : ToExpr (TypeExprF α) where - toTypeExpr := TypeExprF.typeExpr (toTypeExpr α) - toExpr := TypeExprF.toExpr + toTypeExpr := private TypeExprF.typeExpr (toTypeExpr α) + toExpr := private TypeExprF.toExpr end TypeExprF -protected def ExprF.typeExpr := mkApp (mkConst ``ExprF) +private protected def ExprF.typeExpr := mkApp (mkConst ``ExprF) -protected def ArgF.typeExpr (α : Type) [ToExpr α] := mkApp (mkConst ``ArgF) (toTypeExpr α) +private protected def ArgF.typeExpr (α : Type) [ToExpr α] := mkApp (mkConst ``ArgF) (toTypeExpr α) -protected def OperationF.typeExpr := mkApp (mkConst ``OperationF) +private protected def OperationF.typeExpr := mkApp (mkConst ``OperationF) mutual -protected def ExprF.toExpr {α} [ToExpr α] : ExprF α → Lean.Expr +private protected def ExprF.toExpr {α} [ToExpr α] : ExprF α → Lean.Expr | .bvar ann i => astAnnExpr! ExprF.bvar ann (toExpr i) | .fvar ann idx => astAnnExpr! ExprF.fvar ann (toExpr idx) | .fn ann ident => astAnnExpr! ExprF.fn ann (toExpr ident) | .app ann f a => astAnnExpr! ExprF.app ann f.toExpr a.toExpr termination_by e => sizeOf e -def ArgF.toExpr {α} [ToExpr α] : ArgF α → Lean.Expr +private def ArgF.toExpr {α} [ToExpr α] : ArgF α → Lean.Expr | .op o => mkApp2 (mkConst ``ArgF.op) (toTypeExpr α) o.toExpr | .expr e => mkApp2 (mkConst ``ArgF.expr) (toTypeExpr α) (e.toExpr) | .type e => mkApp2 (mkConst ``ArgF.type) (toTypeExpr α) (toExpr e) @@ -147,19 +155,20 @@ def ArgF.toExpr {α} [ToExpr α] : ArgF α → Lean.Expr | .num ann e => astAnnExpr! ArgF.num ann (toExpr e) | .decimal ann e => astAnnExpr! ArgF.decimal ann (toExpr e) | .strlit ann e => astAnnExpr! ArgF.strlit ann (toExpr e) +| .bytes ann a => astAnnExpr! ArgF.bytes ann (toExpr a) | .option ann a => let tpe := ArgF.typeExpr α astAnnExpr! ArgF.option ann (optionToExpr tpe <| a.attach.map fun ⟨e, _⟩ => e.toExpr) | .seq ann a => let tpe := ArgF.typeExpr α - astAnnExpr! ArgF.seq ann <| arrayToExpr tpe <| a.map (·.toExpr) + astAnnExpr! ArgF.seq ann <| arrayToExpr .zero tpe <| a.map (·.toExpr) | .commaSepList ann a => let tpe := ArgF.typeExpr α - astAnnExpr! ArgF.commaSepList ann <| arrayToExpr tpe <| a.map (·.toExpr) + astAnnExpr! ArgF.commaSepList ann <| arrayToExpr .zero tpe <| a.map (·.toExpr) termination_by a => sizeOf a -protected def OperationF.toExpr {α} [ToExpr α] (op : OperationF α) : Lean.Expr := - let args := arrayToExpr (ArgF.typeExpr α) (op.args.map (·.toExpr)) +private protected def OperationF.toExpr {α} [ToExpr α] (op : OperationF α) : Lean.Expr := + let args := arrayToExpr .zero (ArgF.typeExpr α) (op.args.map (·.toExpr)) astAnnExpr! OperationF.mk op.ann (toExpr op.name) args termination_by sizeOf op decreasing_by @@ -169,45 +178,44 @@ decreasing_by end instance ExprF.instToExpr {α} [ToExpr α] : ToExpr (ExprF α) where - toTypeExpr := ExprF.typeExpr (toTypeExpr α) - toExpr := (·.toExpr) + toTypeExpr := private ExprF.typeExpr (toTypeExpr α) + toExpr := private (·.toExpr) instance ArgF.instToExpr {α} [ToExpr α] : ToExpr (ArgF α) where - toTypeExpr := ArgF.typeExpr α - toExpr := (·.toExpr) + toTypeExpr := private ArgF.typeExpr α + toExpr := private (·.toExpr) instance OperationF.instToExpr {α} [ToExpr α] : ToExpr (OperationF α) where - toTypeExpr := OperationF.typeExpr (toTypeExpr α) - toExpr := OperationF.toExpr + toTypeExpr := private OperationF.typeExpr (toTypeExpr α) + toExpr := private OperationF.toExpr -instance : ToExpr String.Pos where - toTypeExpr := mkConst ``String.Pos - toExpr e := mkApp (mkConst ``String.Pos.mk) (toExpr e.byteIdx) +private instance : ToExpr String.Pos.Raw where + toTypeExpr := mkConst ``String.Pos.Raw + toExpr e := mkApp (mkConst ``String.Pos.Raw.mk) (toExpr e.byteIdx) instance SourceRange.instToExpr : ToExpr SourceRange where - toTypeExpr := mkConst ``SourceRange - toExpr e := mkApp2 (mkConst ``SourceRange.mk) (toExpr e.start) (toExpr e.stop) + toTypeExpr := private mkConst ``SourceRange + toExpr e := private mkApp2 (mkConst ``SourceRange.mk) (toExpr e.start) (toExpr e.stop) namespace Ann instance {Base α} [ToExpr Base] [ToExpr α] : ToExpr (Ann Base α) where - toTypeExpr := mkApp2 (mkConst ``Ann) (toTypeExpr Base) (toTypeExpr α) - toExpr a := mkApp4 (mkConst ``Ann.mk) (toTypeExpr Base) (toTypeExpr α) (toExpr a.ann) (toExpr a.val) + toTypeExpr := private mkApp2 (mkConst ``Ann) (toTypeExpr Base) (toTypeExpr α) + toExpr a := private mkApp4 (mkConst ``Ann.mk) (toTypeExpr Base) (toTypeExpr α) (toExpr a.ann) (toExpr a.val) end Ann - namespace PreType -protected def typeExpr : Lean.Expr := mkConst ``PreType +private protected def typeExpr : Lean.Expr := mkConst ``PreType -protected def toExpr : PreType → Lean.Expr +private protected def toExpr : PreType → Lean.Expr | .ident loc nm a => - let args := arrayToExpr PreType.typeExpr (a.map (·.toExpr)) + let args := arrayToExpr .zero PreType.typeExpr (a.map (·.toExpr)) astExpr! ident (toExpr loc) (toExpr nm) args | .bvar loc idx => astExpr! bvar (toExpr loc) (toExpr idx) | .fvar loc idx a => - let args := arrayToExpr PreType.typeExpr (a.map (·.toExpr)) + let args := arrayToExpr .zero PreType.typeExpr (a.map (·.toExpr)) astExpr! fvar (toExpr loc) (toExpr idx) args | .arrow loc a r => astExpr! arrow (toExpr loc) a.toExpr r.toExpr @@ -215,16 +223,16 @@ protected def toExpr : PreType → Lean.Expr astExpr! funMacro (toExpr loc) (toExpr i) r.toExpr instance : ToExpr PreType where - toTypeExpr := mkConst ``PreType - toExpr := PreType.toExpr + toTypeExpr := private mkConst ``PreType + toExpr := private PreType.toExpr end PreType namespace MetadataArg -protected def typeExpr := mkConst ``MetadataArg +private protected def typeExpr := mkConst ``MetadataArg -protected def toExpr : MetadataArg → Lean.Expr +private protected def toExpr : MetadataArg → Lean.Expr | .bool b => astExpr! bool (toExpr b) | .num n => astExpr! num (toExpr n) | .catbvar n => astExpr! catbvar (toExpr n) @@ -233,18 +241,18 @@ protected def toExpr : MetadataArg → Lean.Expr astExpr! option maExpr instance : ToExpr MetadataArg where - toTypeExpr := MetadataArg.typeExpr - toExpr := MetadataArg.toExpr + toTypeExpr := private MetadataArg.typeExpr + toExpr := private MetadataArg.toExpr end MetadataArg instance MetadataAttr.instToExpr : ToExpr MetadataAttr where - toTypeExpr := astExpr! MetadataAttr - toExpr a := astExpr! MetadataAttr.mk (toExpr a.ident) (toExpr a.args) + toTypeExpr := private astExpr! MetadataAttr + toExpr a := private astExpr! MetadataAttr.mk (toExpr a.ident) (toExpr a.args) instance Metadata.instToExpr : ToExpr Metadata where - toTypeExpr := astExpr! Metadata - toExpr m := + toTypeExpr := private astExpr! Metadata + toExpr m := private let init := astExpr! Metadata.empty let push := astExpr! Metadata.push m.toArray.foldl (init := init) fun m a => push m (toExpr a) @@ -252,8 +260,8 @@ instance Metadata.instToExpr : ToExpr Metadata where namespace ArgDeclKind instance : ToExpr ArgDeclKind where - toTypeExpr := mkConst ``ArgDeclKind - toExpr + toTypeExpr := private mkConst ``ArgDeclKind + toExpr private | .cat c => astExpr! cat (toExpr c) | .type tp => astExpr! type (toExpr tp) @@ -262,48 +270,48 @@ end ArgDeclKind namespace ArgDecl instance : ToExpr ArgDecl where - toTypeExpr := mkConst ``ArgDecl - toExpr b := astExpr! mk (toExpr b.ident) (toExpr b.kind) (toExpr b.metadata) + toTypeExpr := private mkConst ``ArgDecl + toExpr b := private astExpr! mk (toExpr b.ident) (toExpr b.kind) (toExpr b.metadata) end ArgDecl namespace SyntaxDefAtom -protected def typeExpr : Lean.Expr := mkConst ``SyntaxDefAtom +private protected def typeExpr : Lean.Expr := mkConst ``SyntaxDefAtom -protected def toExpr : SyntaxDefAtom → Lean.Expr -| .ident v p => astExpr! ident (toExpr v) (toExpr p) +private protected def toExpr : SyntaxDefAtom → Lean.Expr +| .ident v p unwrap => astExpr! ident (toExpr v) (toExpr p) (toExpr unwrap) | .str l => astExpr! str (toExpr l) | .indent n a => - let args := arrayToExpr SyntaxDefAtom.typeExpr (a.map (·.toExpr)) + let args := arrayToExpr .zero SyntaxDefAtom.typeExpr (a.map (·.toExpr)) astExpr! indent (toExpr n) args instance : ToExpr SyntaxDefAtom where - toTypeExpr := SyntaxDefAtom.typeExpr - toExpr := SyntaxDefAtom.toExpr + toTypeExpr := private SyntaxDefAtom.typeExpr + toExpr := private SyntaxDefAtom.toExpr end SyntaxDefAtom namespace SyntaxDef instance : ToExpr SyntaxDef where - toTypeExpr := mkConst ``SyntaxDef - toExpr s := astExpr! mk (toExpr s.atoms) (toExpr s.prec) + toTypeExpr := private mkConst ``SyntaxDef + toExpr s := private astExpr! mk (toExpr s.atoms) (toExpr s.prec) end SyntaxDef instance SynCatDecl.instToExpr : ToExpr SynCatDecl where - toTypeExpr := mkConst ``SynCatDecl - toExpr d := astExpr! mk (toExpr d.name) (toExpr d.argNames) + toTypeExpr := private mkConst ``SynCatDecl + toExpr d := private astExpr! mk (toExpr d.name) (toExpr d.argNames) namespace DebruijnIndex -protected def ofNat {n : Nat} [NeZero n] (a : Nat) : DebruijnIndex n := +private protected def ofNat {n : Nat} [NeZero n] (a : Nat) : DebruijnIndex n := ⟨a % n, Nat.mod_lt _ (Nat.pos_of_neZero n)⟩ instance {n} : ToExpr (DebruijnIndex n) where - toTypeExpr := .app (mkConst ``DebruijnIndex) (toExpr n) - toExpr a := + toTypeExpr := private .app (mkConst ``DebruijnIndex) (toExpr n) + toExpr a := private astExpr! DebruijnIndex.ofNat (toExpr n) (.app (.const ``Nat.instNeZeroSucc []) (mkNatLit (n-1))) @@ -313,7 +321,7 @@ end DebruijnIndex namespace ValueBindingSpec -protected def toExpr {argDecls} (b : ValueBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := +private protected def toExpr {argDecls} (b : ValueBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := astExpr! mk argDeclsExpr (toExpr b.nameIndex) @@ -325,7 +333,7 @@ end ValueBindingSpec namespace TypeBindingSpec -protected def toExpr {argDecls} (b : TypeBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := +private protected def toExpr {argDecls} (b : TypeBindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := astExpr! mk argDeclsExpr (toExpr b.nameIndex) @@ -336,14 +344,14 @@ end TypeBindingSpec namespace BindingSpec -def typeExpr (argDeclsExpr : Lean.Expr) : Lean.Expr := mkApp (mkConst ``BindingSpec) argDeclsExpr +private def typeExpr (argDeclsExpr : Lean.Expr) : Lean.Expr := mkApp (mkConst ``BindingSpec) argDeclsExpr /-- Converts a bindings specification to a Lean expression. To avoid recomputations, this takes the argDecls and its representation as an expression. -/ -def toExpr {argDecls} (bi : BindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := +private def toExpr {argDecls} (bi : BindingSpec argDecls) (argDeclsExpr : Lean.Expr) : Lean.Expr := match bi with | .value b => astExpr! value argDeclsExpr (b.toExpr argDeclsExpr) | .type b => astExpr! type argDeclsExpr (b.toExpr argDeclsExpr) @@ -351,16 +359,16 @@ def toExpr {argDecls} (bi : BindingSpec argDecls) (argDeclsExpr : Lean.Expr) : L end BindingSpec instance ArgDecls.instToExpr : ToExpr ArgDecls where - toTypeExpr := astExpr! ArgDecls - toExpr a := astExpr! ArgDecls.ofArray (toExpr a.toArray) + toTypeExpr := private astExpr! ArgDecls + toExpr a := private astExpr! ArgDecls.ofArray (toExpr a.toArray) namespace OpDecl instance : ToExpr OpDecl where - toTypeExpr := mkConst ``OpDecl - toExpr d := + toTypeExpr := private mkConst ``OpDecl + toExpr d := private let be := toExpr d.argDecls - let bindings := arrayToExpr (BindingSpec.typeExpr be) (d.newBindings.map (·.toExpr be)) + let bindings := arrayToExpr .zero (BindingSpec.typeExpr be) (d.newBindings.map (·.toExpr be)) astExpr! mk (toExpr d.name) be @@ -374,16 +382,16 @@ end OpDecl namespace TypeDecl instance : ToExpr TypeDecl where - toTypeExpr := mkConst ``TypeDecl - toExpr d := astExpr! mk (toExpr d.name) (toExpr d.argNames) + toTypeExpr := private mkConst ``TypeDecl + toExpr d := private astExpr! mk (toExpr d.name) (toExpr d.argNames) end TypeDecl namespace FunctionDecl instance : ToExpr FunctionDecl where - toTypeExpr := mkConst ``FunctionDecl - toExpr d := + toTypeExpr := private mkConst ``FunctionDecl + toExpr d := private astExpr! mk (toExpr d.name) (toExpr d.argDecls) @@ -395,31 +403,31 @@ end FunctionDecl namespace MetadataArgType -protected def toExpr : MetadataArgType → Lean.Expr +private protected def toExpr : MetadataArgType → Lean.Expr | .bool => astExpr! bool | .num => astExpr! num | .ident => astExpr! ident | .opt tp => astExpr! opt tp.toExpr instance : ToExpr MetadataArgType where - toTypeExpr := mkConst ``MetadataArgType - toExpr := MetadataArgType.toExpr + toTypeExpr := private mkConst ``MetadataArgType + toExpr := private MetadataArgType.toExpr end MetadataArgType instance MetadataArgDecl.instToExpr : ToExpr MetadataArgDecl where - toTypeExpr := mkConst ``MetadataArgDecl - toExpr d := astExpr! MetadataArgDecl.mk (toExpr d.ident) (toExpr d.type) + toTypeExpr := private mkConst ``MetadataArgDecl + toExpr d := private astExpr! MetadataArgDecl.mk (toExpr d.ident) (toExpr d.type) instance MetadataDecl.instToExpr : ToExpr MetadataDecl where - toTypeExpr := mkConst ``MetadataDecl - toExpr d := astExpr! MetadataDecl.mk (toExpr d.name) (toExpr d.args) + toTypeExpr := private mkConst ``MetadataDecl + toExpr d := private astExpr! MetadataDecl.mk (toExpr d.name) (toExpr d.args) namespace Decl instance Decl.instToExpr : ToExpr Decl where - toTypeExpr := mkConst ``Decl - toExpr + toTypeExpr := private mkConst ``Decl + toExpr private | .syncat d => astExpr! syncat (toExpr d) | .op d => astExpr! op (toExpr d) | .type d => astExpr! type (toExpr d) @@ -429,24 +437,25 @@ instance Decl.instToExpr : ToExpr Decl where end Decl instance Dialect.instToExpr : ToExpr Dialect where - toTypeExpr := mkConst ``Dialect - toExpr d := + toTypeExpr := private mkConst ``Dialect + toExpr d := private astExpr! Dialect.ofArray (toExpr d.name) (toExpr d.imports) (toExpr d.declarations) namespace DialectMap instance : ToExpr DialectMap where - toTypeExpr := mkConst ``DialectMap - toExpr d := astExpr! ofList! (toExpr d.toList) + toTypeExpr := private mkConst ``DialectMap + toExpr d := private astExpr! ofList! (toExpr d.toList) end DialectMap instance Program.instToExpr : ToExpr Program where - toTypeExpr := mkConst ``Program - toExpr ms := + toTypeExpr := private mkConst ``Program + toExpr ms := private astExpr! Program.create (toExpr ms.dialects) (toExpr ms.dialect) (toExpr ms.commands) end Strata +end diff --git a/Strata/DDM/Ion.lean b/Strata/DDM/Ion.lean index 408cb4ae7..b691d2e29 100644 --- a/Strata/DDM/Ion.lean +++ b/Strata/DDM/Ion.lean @@ -3,10 +3,21 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.AST +public import Strata.DDM.AST +public import Strata.DDM.Util.Ion + +import Strata.DDM.Util.Array import Strata.DDM.Util.Ion.Lean +open Lean +open Lean.Elab +open Lean.Elab.Command + +open Ion + +public section namespace Ion.Ion /-- @@ -19,7 +30,7 @@ consumes available memory. -/ private def maxDecimalExponent : Nat := 1000 -protected def asNat? (v : Ion SymbolId) : Option Nat := +private protected def asNat? (v : Ion SymbolId) : Option Nat := match v.app with | .int x => if x < 0 then @@ -37,7 +48,7 @@ protected def asNat? (v : Ion SymbolId) : Option Nat := | none => none | _ => none -protected def asInt? (v : Ion SymbolId) : Option Int := +private protected def asInt? (v : Ion SymbolId) : Option Int := match v.app with | .int x => some x | .decimal d => @@ -51,7 +62,7 @@ protected def asInt? (v : Ion SymbolId) : Option Int := | none => none | _ => none -protected def asDecimal? (v : Ion SymbolId) : Option Decimal := +private protected def asDecimal? (v : Ion SymbolId) : Option Decimal := match v.app with | .int x => some (.ofInt x) | .decimal d => some d @@ -61,37 +72,32 @@ end Ion.Ion namespace Strata -open _root_.Lean -open Elab Command -open _root_.Ion - -inductive StringOrSexp (v : Ion SymbolId) where +private inductive StringOrSexp (v : Ion SymbolId) where | string (s : String) | sexp (a : Array (Ion SymbolId)) (p : a.size > 0 ∧ sizeOf a < sizeOf v) -inductive Required where +private inductive Required where | req | opt deriving DecidableEq -structure StructArgMap (size : Nat) where +private structure StructArgMap (size : Nat) where map : Std.HashMap String (Fin size) := {} required : Array (String × Fin size) deriving Inhabited namespace StructArgMap -instance : Membership String (StructArgMap size) where +private instance : Membership String (StructArgMap size) where mem m nm := nm ∈ m.map -instance : GetElem? (StructArgMap size) String (Fin size) (fun m nm => nm ∈ m) where +private instance : GetElem? (StructArgMap size) String (Fin size) (fun m nm => nm ∈ m) where getElem m nm p := m.map[nm] getElem! m nm := m.map[nm]! getElem? m nm := m.map[nm]? - -def fromList! (as : List String) : StructArgMap as.length := +private def fromList! (as : List String) : StructArgMap as.length := let size := as.length let m := { map := {}, required := #[] } as.foldl (init := m) fun m name => @@ -105,7 +111,7 @@ def fromList! (as : List String) : StructArgMap as.length := else panic! "Invalid index" -def fromOptList! (as : List (String × Required)) : StructArgMap as.length := +private def fromOptList! (as : List (String × Required)) : StructArgMap as.length := let size := as.length let m := { map := {}, required := #[] } as.foldl (init := m) fun m (name, r) => @@ -127,53 +133,59 @@ end StructArgMap structure FromIonContext where symbols : Ion.SymbolTable -def FromIonM := ReaderT FromIonContext (Except String) - deriving Monad +@[expose] def FromIonM := ReaderT FromIonContext (Except String) +deriving Monad namespace FromIonM -instance : MonadExcept String FromIonM := +private instance : MonadExcept String FromIonM := inferInstanceAs (MonadExcept _ (ReaderT _ _)) -instance : MonadReader FromIonContext FromIonM := +private instance : MonadReader FromIonContext FromIonM := inferInstanceAs (MonadReader _ (ReaderT _ _)) -def readSymbolTable : FromIonM Ion.SymbolTable := +private def readSymbolTable : FromIonM Ion.SymbolTable := return (← read).symbols -protected def lookupSymbol (sym : SymbolId) : FromIonM String := do +private protected def lookupSymbol (sym : SymbolId) : FromIonM String := do let some fullname := (←readSymbolTable)[sym]? | throw s!"Could not find symbol {sym.value}" pure fullname -protected def asString (name : String) (v : Ion SymbolId) : FromIonM String := - match v with - | .string s => return s - | _ => throw s!"{name} expected to be a string. {repr v}" - -protected def asSymbolString (name : String) (v : Ion SymbolId) : FromIonM String := - match v.app with - | .symbol sym => .lookupSymbol sym - | .string name => pure name - | _ => throw s!"{name} expected to be a symbol or string." - -protected def asNat (name : String) (v : Ion SymbolId) : FromIonM Nat := +private protected def asNat (name : String) (v : Ion SymbolId) : FromIonM Nat := match v.asNat? with | some x => pure x | none => throw s!"Expected {name} to be a nat instead of {repr v}." -protected def asInt (v : Ion SymbolId) : FromIonM Int := +private protected def asInt (v : Ion SymbolId) : FromIonM Int := match v.asInt? with | some x => pure x | none => throw s!"Expected {repr v} to be an int." -protected def asList (v : Ion SymbolId) : FromIonM { a : Array (Ion SymbolId) // sizeOf a < sizeOf v} := +private protected def asString (name : String) (v : Ion SymbolId) : FromIonM String := + match v.app with + | .string s => return s + | _ => throw s!"{name} expected to be a string. {repr v}" + +private protected def asBytes (name : String) (v : Ion SymbolId) : FromIonM ByteArray := + match v.app with + | .blob a => return a + | .list a => ByteArray.ofNatArray <$> a.mapM (.asNat "name element") + | _ => throw s!"{name} expected to be a string. {repr v}" + +private protected def asSymbolString (name : String) (v : Ion SymbolId) : FromIonM String := + match v.app with + | .symbol sym => .lookupSymbol sym + | .string name => pure name + | _ => throw s!"{name} expected to be a symbol or string." + +private protected def asList (v : Ion SymbolId) : FromIonM { a : Array (Ion SymbolId) // sizeOf a < sizeOf v} := match v with | .mk (.list args) => return .mk args (by simp; omega) | _ => throw s!"Expected list" -protected def asSexp (name : String) (v : Ion SymbolId) : FromIonM ({ a : Array (Ion SymbolId) // a.size > 0 ∧ sizeOf a < sizeOf v}) := +private protected def asSexp (name : String) (v : Ion SymbolId) : FromIonM ({ a : Array (Ion SymbolId) // a.size > 0 ∧ sizeOf a < sizeOf v}) := match v with | .mk (.sexp args) | .mk (.list args) => if p : args.size > 0 then @@ -182,10 +194,10 @@ protected def asSexp (name : String) (v : Ion SymbolId) : FromIonM ({ a : Array throw s!"{name} expected non-empty expression" | _ => throw s!"{name} expected sexpression." -protected def asSymbolOrSexp (v : Ion SymbolId) : FromIonM (StringOrSexp v) := +private protected def asSymbolOrSexp (v : Ion SymbolId) : FromIonM (StringOrSexp v) := match v with - | .symbol s => .string <$> .lookupSymbol s - | .string s => return .string s + | .mk (.symbol s) => .string <$> .lookupSymbol s + | .mk (.string s) => return .string s | .mk (.sexp args) | .mk (.list args) => if p : args.size > 0 then return .sexp args ⟨p, (by decreasing_tactic)⟩ @@ -193,13 +205,13 @@ protected def asSymbolOrSexp (v : Ion SymbolId) : FromIonM (StringOrSexp v) := throw s!"Expected non-empty expression" | _ => throw s!"Expected symbol or sexpression." -def checkArgCount (name : String) (args : Array (Ion SymbolId)) (n : Nat) : FromIonM (PLift (args.size = n)) := do +private def checkArgCount (name : String) (args : Array (Ion SymbolId)) (n : Nat) : FromIonM (PLift (args.size = n)) := do if p : args.size = n then pure ⟨p⟩ else throw s!"{name} expects {n} arguments has {repr args}" -def checkArgMin (name : String) (args : Array (Ion SymbolId)) (n : Nat) : FromIonM (PLift (args.size ≥ n)) := do +private def checkArgMin (name : String) (args : Array (Ion SymbolId)) (n : Nat) : FromIonM (PLift (args.size ≥ n)) := do if p : args.size ≥ n then pure ⟨p⟩ else @@ -208,17 +220,17 @@ def checkArgMin (name : String) (args : Array (Ion SymbolId)) (n : Nat) : FromIo /-- Interpret Ion value as an array and applies function to it. -/ -def asListOf {α} (name : String) (v : Ion SymbolId) (f : Ion SymbolId → FromIonM α) : FromIonM (Array α) := +private def asListOf {α} (name : String) (v : Ion SymbolId) (f : Ion SymbolId → FromIonM α) : FromIonM (Array α) := match v with - | .list a => a.mapM f + | .mk (.list a) => a.mapM f | _ => throw s!"{name} expects a list." -def asStruct (type : String) (v : Ion SymbolId) : FromIonM { a : Array (SymbolId × Ion SymbolId) // sizeOf a < sizeOf v } := do +private def asStruct (type : String) (v : Ion SymbolId) : FromIonM { a : Array (SymbolId × Ion SymbolId) // sizeOf a < sizeOf v } := do match v with | .mk (.struct args) => pure ⟨args, by decreasing_tactic ⟩ | v => throw s!"{type} expected a struct: {repr v}" -def asStruct0 (v : Ion SymbolId) : FromIonM (Array (SymbolId × Ion SymbolId)) := do +private def asStruct0 (v : Ion SymbolId) : FromIonM (Array (SymbolId × Ion SymbolId)) := do match v with | .mk (.struct args) => pure args | _ => throw "Expected a struct0" @@ -237,7 +249,7 @@ private def sizeOfArrayLowerBound [h : SizeOf α] (a : Array α) : sizeOf a ≥ have p := sizeOfListLowerBound l decreasing_tactic -def mapFields {size} (args : Array (SymbolId × Ion SymbolId)) (m : StructArgMap size) : +private def mapFields {size} (args : Array (SymbolId × Ion SymbolId)) (m : StructArgMap size) : FromIonM (Vector (Ion SymbolId) size) := do -- We use an assigned vector below to check if args.size > size then @@ -261,13 +273,13 @@ def mapFields {size} (args : Array (SymbolId × Ion SymbolId)) (m : StructArgMap throw s!"Missing assignment to {name}." pure a -def asFieldStruct {size} (v : Ion SymbolId) (type : String) (m : StructArgMap size) : FromIonM (Vector (Ion SymbolId) size) := do +private def asFieldStruct {size} (v : Ion SymbolId) (type : String) (m : StructArgMap size) : FromIonM (Vector (Ion SymbolId) size) := do let ⟨args, _⟩ ← asStruct type v mapFields args m -def deserializeValue {α} (bs : ByteArray) (act : Ion SymbolId → FromIonM α) : Except String α := do +private def deserializeValue {α} (bs : ByteArray) (act : Ion SymbolId → FromIonM α) : Except String α := do let a ← - match Ion.deserialize bs with + match deserialize bs with | .error (off, msg) => throw s!"Error reading Ion: {msg} (offset = {off})" | .ok a => pure a @@ -292,7 +304,7 @@ end FromIonM class FromIon (α : Type) where fromIon : Ion SymbolId → FromIonM α -export Strata.FromIon (fromIon) +export FromIon (fromIon) namespace FromIon @@ -303,46 +315,46 @@ end FromIon namespace QualifiedIdent -protected def toIon (d : QualifiedIdent) : Ion.InternM (Ion SymbolId) := do +private protected def toIon (d : QualifiedIdent) : Ion.InternM (Ion SymbolId) := do .symbol <$> internSymbol d.fullName -def fromIonStringSymbol (fullname : String) : FromIonM QualifiedIdent := do +private def fromIonStringSymbol (fullname : String) : FromIonM QualifiedIdent := do let pos := fullname.find (·='.') - if pos < fullname.endPos then - let dialect := fullname.extract 0 pos + if pos < fullname.rawEndPos then + let dialect := String.Pos.Raw.extract fullname 0 pos -- . is one byte - let name := fullname.extract (pos + '.') fullname.endPos + let name := String.Pos.Raw.extract fullname (pos + '.') fullname.rawEndPos return { dialect, name } else throw s!"Invalid symbol {fullname}" -def fromIonSymbol (sym : SymbolId) : FromIonM QualifiedIdent := do +private def fromIonSymbol (sym : SymbolId) : FromIonM QualifiedIdent := do fromIonStringSymbol (← .lookupSymbol sym) -protected def fromIon (name : String) (v : Ion SymbolId) : FromIonM QualifiedIdent := +private protected def fromIon (name : String) (v : Ion SymbolId) : FromIonM QualifiedIdent := fromIonStringSymbol =<< .asSymbolString name v end QualifiedIdent -class ToIon (α : Type) where +private class ToIon (α : Type) where toIon : α → InternM (Ion SymbolId) -open ToIon (toIon) +private abbrev toIon := @ToIon.toIon namespace SyntaxCatF -protected def toIon {α} [ToIon α] (cat : SyntaxCatF α) : Ion.InternM (Ion SymbolId) := do +private protected def toIon {α} [ToIon α] (cat : SyntaxCatF α) : Ion.InternM (Ion SymbolId) := do let args := #[ ← toIon cat.ann, ← cat.name.toIon ] - let args ← cat.args.attach.mapM_off (init := args) fun ⟨e, _⟩ => e.toIon + let args := args ++ (← cat.args.attach.mapM fun ⟨e, _⟩ => e.toIon) return .sexp args decreasing_by rw [SyntaxCatF.sizeOf_spec cat] decreasing_tactic -protected def fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (SyntaxCatF α) := do +private protected def fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (SyntaxCatF α) := do let ⟨args, _⟩ ← .asSexp "Category reference" v let ⟨p⟩ ← .checkArgMin "Category" args 2 - let ann ← fromIon args[0] + let ann ← fromIon (α := α) args[0] let name ← QualifiedIdent.fromIon "Category name" args[1] let args ← args.attach.mapM_off (start := 2) fun ⟨e, _⟩ => SyntaxCatF.fromIon e return { @@ -355,14 +367,14 @@ decreasing_by have p : sizeOf e < sizeOf args := by decreasing_tactic decreasing_tactic -instance {α} [FromIon α] : FromIon (SyntaxCatF α) where +private instance {α} [FromIon α] : FromIon (SyntaxCatF α) where fromIon := SyntaxCatF.fromIon end SyntaxCatF namespace TypeExprF -protected def toIon {α} [ToIon α] (refs : SymbolIdCache) (tpe : TypeExprF α) : InternM (Ion SymbolId) := +private protected def toIon {α} [ToIon α] (refs : SymbolIdCache) (tpe : TypeExprF α) : InternM (Ion SymbolId) := ionScope! TypeExprF refs : match tpe with | .ident ann name a => do @@ -387,33 +399,33 @@ protected def toIon {α} [ToIon α] (refs : SymbolIdCache) (tpe : TypeExprF α) ] termination_by tpe -instance {α} [ToIon α] : CachedToIon (TypeExprF α) where +private instance {α} [ToIon α] : CachedToIon (TypeExprF α) where cachedToIon refs tp := tp.toIon refs -protected def fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (TypeExprF α) := do +private protected def fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (TypeExprF α) := do let ⟨args, ap⟩ ← .asSexp "TypeExpr" v match ← .asSymbolString "TypeExpr kind" args[0] with | "arrow" => do let ⟨p⟩ ← .checkArgCount "Type expression arrow" args 4 - let ann ← fromIon args[1] + let ann ← FromIon.fromIon args[1] let l ← TypeExprF.fromIon args[2] let r ← TypeExprF.fromIon args[3] return .arrow ann l r | "bvar" => let ⟨p⟩ ← .checkArgCount "Type expression bvar" args 3 return .bvar - (← fromIon args[1]) + (← FromIon.fromIon args[1]) (← .asNat "Type expression bvar" args[2]) | "fvar" => let ⟨p⟩ ← .checkArgMin "Type expression free variable" args 3 - let ann ← fromIon args[1] + let ann ← FromIon.fromIon args[1] let idx ← .asNat "Type expression free variable index" args[2] let a ← args.attach.mapM_off (start := 3) fun ⟨e, _⟩ => TypeExprF.fromIon e pure <| .fvar ann idx a | "ident" => let ⟨p⟩ ← .checkArgMin "TypeExpr identifier" args 3 - let ann ← fromIon args[1] + let ann ← FromIon.fromIon args[1] let name ← QualifiedIdent.fromIon "Type identifier name" args[2] let args ← args.attach.mapM_off (start := 3) fun ⟨e, _⟩ => TypeExprF.fromIon e @@ -431,13 +443,13 @@ decreasing_by · have p : sizeOf e < sizeOf args := by decreasing_tactic decreasing_tactic -instance {α} [FromIon α] : FromIon (TypeExprF α) where +private instance {α} [FromIon α] : FromIon (TypeExprF α) where fromIon := TypeExprF.fromIon end TypeExprF mutual -protected def OperationF.toIon {α} [ToIon α] (refs : SymbolIdCache) (op : OperationF α) : InternM (Ion SymbolId) := +private protected def OperationF.toIon {α} [ToIon α] (refs : SymbolIdCache) (op : OperationF α) : InternM (Ion SymbolId) := ionScope! OperationF refs : do let argEntry := ionRefEntry! ``ArgF let args := #[ ← op.name.toIon, ← ToIon.toIon op.ann ] @@ -449,7 +461,7 @@ decreasing_by · simp [Strata.OperationF.sizeOf_spec] decreasing_tactic -protected def ExprF.toIon {α} [ToIon α] (refs : SymbolIdCache) (e : ExprF α) : InternM (Ion SymbolId) := +private protected def ExprF.toIon {α} [ToIon α] (refs : SymbolIdCache) (e : ExprF α) : InternM (Ion SymbolId) := ionScope! ExprF refs : match e with | .bvar ann idx => do @@ -465,7 +477,7 @@ protected def ExprF.toIon {α} [ToIon α] (refs : SymbolIdCache) (e : ExprF α) · decreasing_tactic · decreasing_tactic -protected def ArgF.toIon {α} [ToIon α] (refs : SymbolIdCache) (arg : ArgF α) : InternM (Ion SymbolId) := +private protected def ArgF.toIon {α} [ToIon α] (refs : SymbolIdCache) (arg : ArgF α) : InternM (Ion SymbolId) := ionScope! ArgF refs : match arg with | .op o => @@ -484,6 +496,8 @@ protected def ArgF.toIon {α} [ToIon α] (refs : SymbolIdCache) (arg : ArgF α) return .sexp #[ ionSymbol! "decimal", ← toIon ann, .decimal d] | .strlit ann s => return .sexp #[ ionSymbol! "strlit", ← toIon ann, .string s] + | .bytes ann a => + return .sexp #[ ionSymbol! "bytes", ← toIon ann, .blob a ] | .option ann o => do let mut args : Array (Ion _) := #[ ionSymbol! "option", ← toIon ann ] match o with @@ -511,7 +525,7 @@ end mutual -protected def OperationF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (OperationF α) := do +private protected def OperationF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (OperationF α) := do let ⟨sexp, sexpP⟩ ← .asSexp "Operation" v let ⟨m⟩ ← .checkArgMin "operation" sexp 2 let name ← QualifiedIdent.fromIon "Operation name" sexp[0] @@ -524,7 +538,7 @@ decreasing_by have _ : sizeOf a < sizeOf sexp := by decreasing_tactic decreasing_tactic -protected def ExprF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (ExprF α) := do +private protected def ExprF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (ExprF α) := do let ⟨sexp, sexpP⟩ ← .asSexp "Expr" v match ← .asSymbolString "Expr kind" sexp[0] with | "bvar" => @@ -555,7 +569,7 @@ decreasing_by · have _ : sizeOf sexp[3] < sizeOf sexp := by decreasing_tactic decreasing_tactic -protected def ArgF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (ArgF α) := do +private protected def ArgF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (ArgF α) := do let ⟨sexp, sexpP⟩ ← .asSexp "Arg" v match ← .asSymbolString "Arg kind" sexp[0] with | "op" => @@ -588,11 +602,12 @@ protected def ArgF.fromIon {α} [FromIon α] (v : Ion SymbolId) : FromIonM (ArgF pure <| .decimal ann d | "strlit" => let ⟨p⟩ ← .checkArgCount "strlit" sexp 3 - match sexp[2] with - | .string s => pure () - | _ => throw s!"strlit expected to be a string. {repr v}" .strlit <$> fromIon sexp[1] <*> .asString "String literal value" sexp[2] + | "bytes" => + let ⟨p⟩ ← .checkArgCount "bytes" sexp 3 + .bytes <$> fromIon sexp[1] + <*> .asBytes "byte literal" sexp[2] | "option" => let ⟨p⟩ ← .checkArgMin "option" sexp 2 let ann ← fromIon sexp[1] @@ -633,37 +648,47 @@ end namespace OperationF -instance {α} [ToIon α] : CachedToIon (OperationF α) where +private instance {α} [ToIon α] : CachedToIon (OperationF α) where cachedToIon := OperationF.toIon end OperationF namespace SyntaxDefAtom -protected def toIon (refs : SymbolIdCache) (a : SyntaxDefAtom) : InternM (Ion SymbolId) := +private protected def toIon (refs : SymbolIdCache) (a : SyntaxDefAtom) : InternM (Ion SymbolId) := ionScope! SyntaxDefAtom refs : match a with - | .ident idx prec => - return .sexp #[ .symbol ionSymbol! "ident", .int idx, .int prec ] + | .ident idx prec unwrap => + return .sexp #[ .symbol ionSymbol! "ident", .int idx, .int prec, .bool unwrap ] | .str v => return .string v | .indent n args => return .sexp <| #[.symbol ionSymbol! "indent", .int n] ++ (← args.attach.mapM (fun ⟨a, _⟩ => a.toIon refs)) -instance : CachedToIon SyntaxDefAtom where +private instance : CachedToIon SyntaxDefAtom where cachedToIon := SyntaxDefAtom.toIon -protected def fromIon (v : Ion SymbolId) : FromIonM SyntaxDefAtom := do +private protected def fromIon (v : Ion SymbolId) : FromIonM SyntaxDefAtom := do match ← .asSymbolOrSexp v with | .string v => return .str v | .sexp args argsp => match ← .asSymbolString "SyntaxDefAtom kind" args[0] with | "ident" => do - let ⟨p⟩ ← .checkArgCount "ident" args 3 - .ident <$> .asNat "SyntaxDef ident level" args[1] - <*> .asNat "SyntaxDef ident prec" args[2] + -- Support both formats: 3 args (without unwrap) and 4 args (with unwrap spec) + if args.size = 3 then + let level ← .asNat "SyntaxDef ident level" args[1]! + let prec ← .asNat "SyntaxDef ident prec" args[2]! + return .ident level prec false + else + let ⟨p⟩ ← .checkArgCount "ident" args 4 + let level ← .asNat "SyntaxDef ident level" args[1]! + let prec ← .asNat "SyntaxDef ident prec" args[2]! + let unwrap ← match args[3]! with + | .bool b => pure b + | _ => throw "Expected boolean for unwrap" + return .ident level prec unwrap | "indent" => do .indent <$> .asNat "SyntaxDef indent value" args[1]! <*> args.attach.mapM_off (start := 2) fun ⟨u, _⟩ => @@ -672,21 +697,21 @@ protected def fromIon (v : Ion SymbolId) : FromIonM SyntaxDefAtom := do | s => throw s!"Unexpected binding kind {s}" -instance : FromIon SyntaxDefAtom where +private instance : FromIon SyntaxDefAtom where fromIon := SyntaxDefAtom.fromIon end SyntaxDefAtom namespace SyntaxDef -instance : CachedToIon SyntaxDef where +private instance : CachedToIon SyntaxDef where cachedToIon refs d := ionScope! SyntaxDef refs : return .struct #[ (ionSymbol! "atoms", .list (←d.atoms.mapM (fun (a : SyntaxDefAtom) => ionRef! a))), (ionSymbol! "prec", .int d.prec) ] -instance : FromIon SyntaxDef where +private instance : FromIon SyntaxDef where fromIon v := do let m := .fromList! ["atoms", "prec"] let ⟨args, p⟩ ← .asFieldStruct (size := 2) v "SyntaxDef" m @@ -699,7 +724,7 @@ end SyntaxDef namespace SourceRange -instance : ToIon SourceRange where +private instance : ToIon SourceRange where toIon v := pure <| if v.start = 0 ∧ v.stop = 0 then @@ -707,7 +732,7 @@ instance : ToIon SourceRange where else .sexp #[.int v.start.byteIdx, .int v.stop.byteIdx ] -instance : FromIon SourceRange where +private instance : FromIon SourceRange where fromIon v := do match v.app with | .null _ => @@ -724,7 +749,7 @@ end SourceRange namespace MetadataArg -protected def toIon (refs : SymbolIdCache) (a : MetadataArg) : InternM (Ion SymbolId) := +private protected def toIon (refs : SymbolIdCache) (a : MetadataArg) : InternM (Ion SymbolId) := ionScope! MetadataArg refs : match a with | .bool b => @@ -738,13 +763,13 @@ protected def toIon (refs : SymbolIdCache) (a : MetadataArg) : InternM (Ion Symb | some a => return .sexp #[ionSymbol! "some", ← a.toIon refs] | none => return .null -instance : CachedToIon MetadataArg where +private instance : CachedToIon MetadataArg where cachedToIon := MetadataArg.toIon -protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArg := do - if let .null _ := v then +private protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArg := do + if let .mk (.null _) := v then return .option none - if let .bool b := v then + if let .mk (.bool b) := v then return .bool b if let some i := v.asNat? then return .num i @@ -759,21 +784,21 @@ protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArg := do (.option ∘ some) <$> MetadataArg.fromIon args[1] | s => throw s!"Unexpected arg {s}" -instance : FromIon MetadataArg where +private instance : FromIon MetadataArg where fromIon := MetadataArg.fromIon end MetadataArg namespace MetadataAttr -instance : CachedToIon MetadataAttr where +private instance : CachedToIon MetadataAttr where cachedToIon refs md := ionScope! MetadataAttr refs : do let args : Array (Ion SymbolId) := .mkEmpty (1 + md.args.size) let args := args.push (←md.ident.toIon) let args ← md.args.mapM_off (init := args) fun a => ionRef! a return .sexp args -instance : FromIon MetadataAttr where +private instance : FromIon MetadataAttr where fromIon v := do let ⟨args, argsp⟩ ← .asSexp "Metadata attribute" v return { @@ -785,17 +810,17 @@ end MetadataAttr namespace Metadata -instance : CachedToIon Metadata where +private instance : CachedToIon Metadata where cachedToIon refs md := ionScope! Metadata refs : ionRef! md.toArray -instance : FromIon Metadata where +private instance : FromIon Metadata where fromIon v := .ofArray <$> .asListOf "Metadata attributes" v fromIon end Metadata namespace PreType -protected def toIon (refs : SymbolIdCache) (tpe : PreType) : InternM (Ion SymbolId) := +private protected def toIon (refs : SymbolIdCache) (tpe : PreType) : InternM (Ion SymbolId) := ionScope! PreType refs : match tpe with | .ident loc name a => do @@ -814,10 +839,10 @@ protected def toIon (refs : SymbolIdCache) (tpe : PreType) : InternM (Ion Symbol return Ion.sexp <| #[ionSymbol! "funMacro", ← toIon loc, .int i, ← r.toIon refs] termination_by tpe -instance : CachedToIon PreType where +private instance : CachedToIon PreType where cachedToIon refs tp := tp.toIon refs -protected def fromIon (v : Ion SymbolId) : FromIonM PreType := do +private protected def fromIon (v : Ion SymbolId) : FromIonM PreType := do let ⟨args, ap⟩ ← .asSexp "PreType" v match ← .asSymbolString "PreType kind" args[0] with | "arrow" => do @@ -866,14 +891,14 @@ termination_by v · have p : sizeOf args[3] < sizeOf args := by decreasing_tactic decreasing_tactic -instance : FromIon PreType where +private instance : FromIon PreType where fromIon := PreType.fromIon end PreType namespace ArgDeclKind -instance : CachedToIon ArgDeclKind where +private instance : CachedToIon ArgDeclKind where cachedToIon refs tpc := ionScope! ArgDeclKind refs : match tpc with | .cat k => @@ -881,7 +906,7 @@ instance : CachedToIon ArgDeclKind where | .type tp => return .sexp #[ionSymbol! "type", ← ionRef! tp] -protected def fromIon (v : Ion SymbolId) : FromIonM ArgDeclKind := do +private protected def fromIon (v : Ion SymbolId) : FromIonM ArgDeclKind := do let ⟨args, argsp⟩ ← .asSexp "ArgDeclKind" v match ← .asSymbolString "ArgDeclKind kind" args[0] with | "category" => do @@ -893,14 +918,14 @@ protected def fromIon (v : Ion SymbolId) : FromIonM ArgDeclKind := do | s => throw s!"Unexpected binding kind {s}" -instance : FromIon ArgDeclKind where +private instance : FromIon ArgDeclKind where fromIon := ArgDeclKind.fromIon end ArgDeclKind namespace ArgDecl -instance : CachedToIon ArgDecl where +private instance : CachedToIon ArgDecl where cachedToIon refs b := ionScope! ArgDecl refs : do let mut flds := #[ (ionSymbol! "name", .string b.ident), @@ -910,14 +935,14 @@ instance : CachedToIon ArgDecl where flds := flds.push (ionSymbol! "metadata", ← ionRef! b.metadata) return .struct flds -instance : FromIon ArgDecl where +private instance : FromIon ArgDecl where fromIon v := do let m := .fromOptList! [("name", .req), ("type", .req), ("metadata", .opt)] let ⟨fldArgs, p⟩ ← .asFieldStruct (size := 3) v "ArgDecl" m let metadata ← match fldArgs[2] with - | .null _ => pure .empty + | .mk (.null _) => pure {} | v => fromIon v pure { ident := ← .asString "ArgDecl.ident" fldArgs[0] @@ -929,7 +954,7 @@ end ArgDecl namespace MetadataArgType -protected def toIon (refs : SymbolIdCache) (tp : MetadataArgType) : Ion SymbolId := +private protected def toIon (refs : SymbolIdCache) (tp : MetadataArgType) : Ion SymbolId := ionScope! MetadataArgType refs : match tp with | .bool => ionSymbol! "bool" @@ -937,10 +962,10 @@ protected def toIon (refs : SymbolIdCache) (tp : MetadataArgType) : Ion SymbolId | .ident => ionSymbol! "ident" | .opt tp => .sexp #[ ionSymbol! "opt", tp.toIon refs] -instance : CachedToIon MetadataArgType where +private instance : CachedToIon MetadataArgType where cachedToIon refs tp := return tp.toIon refs -protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArgType := do +private protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArgType := do match ← .asSymbolOrSexp v with | .string s => match s with @@ -958,18 +983,18 @@ protected def fromIon (v : Ion SymbolId) : FromIonM MetadataArgType := do | s => throw s!"Unknown sexp arg {s}" termination_by v -instance : FromIon MetadataArgType where +private instance : FromIon MetadataArgType where fromIon := MetadataArgType.fromIon end MetadataArgType namespace MetadataArgDecl -instance : CachedToIon MetadataArgDecl where +private instance : CachedToIon MetadataArgDecl where cachedToIon refs d := ionScope! MetadataArgDecl refs : return .sexp #[.string d.ident, ← ionRef! d.type ] -instance : FromIon MetadataArgDecl where +private instance : FromIon MetadataArgDecl where fromIon v := do let ⟨args, argsp⟩ ← .asSexp "MetadataArgDecl" v let ⟨p⟩ ← .checkArgCount "MetadataArgDecl" args 2 @@ -982,7 +1007,7 @@ end MetadataArgDecl namespace SynCatDecl -instance : CachedToIon SynCatDecl where +private instance : CachedToIon SynCatDecl where cachedToIon refs d := ionScope! SynCatDecl refs : return .struct #[ (ionSymbol! "type", ionSymbol! "syncat"), @@ -990,7 +1015,7 @@ instance : CachedToIon SynCatDecl where (ionSymbol! "arguments", .list (.string <$> d.argNames)) ] -protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM SynCatDecl := do +private protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM SynCatDecl := do let args ← .mapFields fields (.fromList! ["type", "name", "arguments"]) pure { name := ← .asString "Category name" args[1], @@ -1001,7 +1026,7 @@ end SynCatDecl namespace OpDecl -instance : CachedToIon OpDecl where +private instance : CachedToIon OpDecl where cachedToIon refs d := ionScope! OpDecl refs : do let mut flds : Array (SymbolId × Ion SymbolId) := #[ (ionSymbol! "type", ionSymbol! "op"), @@ -1016,7 +1041,7 @@ instance : CachedToIon OpDecl where flds := flds.push (ionSymbol! "metadata", ← ionRef! d.metadata) return .struct flds -protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM OpDecl := do +private protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM OpDecl := do let m := .fromOptList! [ ("type", .req), ("name", .req), @@ -1029,16 +1054,16 @@ protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM OpD let name ← .asString "Op declaration name" fldArgs[1] let argDecls ← match fldArgs[2] with - | .null _ => pure .empty + | .mk (.null _) => pure {} | v => ArgDecls.ofArray <$> .asListOf "Op declaration arguments" v fromIon let category ← QualifiedIdent.fromIon "Op declaration result" fldArgs[3] let syntaxDef ← match fldArgs[4] with - | .null _ => pure (.mkFunApp name argDecls.size) + | .mk (.null _) => pure (.mkFunApp name argDecls.size) | v => fromIon v let metadata ← match fldArgs[5] with - | .null _ => pure .empty + | .mk (.null _) => pure .empty | v => fromIon v pure { name := name @@ -1052,7 +1077,7 @@ end OpDecl namespace TypeDecl -instance : CachedToIon TypeDecl where +private instance : CachedToIon TypeDecl where cachedToIon refs d := ionScope! TypeDecl refs : do let args ← d.argNames |>.mapM fun a => return .sexp #[← toIon a.ann, .string a.val] @@ -1062,7 +1087,7 @@ instance : CachedToIon TypeDecl where (ionSymbol! "argNames", .list args) ] -protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM TypeDecl := do +private protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM TypeDecl := do let m := .fromList! ["type", "name", "argNames"] let args ← .mapFields fields m let resolveArg v := do @@ -1081,7 +1106,7 @@ end TypeDecl namespace FunctionDecl -instance : CachedToIon FunctionDecl where +private instance : CachedToIon FunctionDecl where cachedToIon refs d := ionScope! FunctionDecl refs : do let mut flds : Array (SymbolId × Ion SymbolId) := #[ (ionSymbol! "type", .symbol ionSymbol! "fn"), @@ -1095,7 +1120,7 @@ instance : CachedToIon FunctionDecl where flds := flds.push (ionSymbol! "metadata", ← ionRef! d.metadata) return .struct flds -protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM FunctionDecl := do +private protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM FunctionDecl := do let m := .fromOptList! [ ("type", .req), ("name", .req), @@ -1108,17 +1133,17 @@ protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM Fun let name ← .asString "FunctionDecl.name" fldArgs[1] let argDecls ← match fldArgs[2] with - | .null _ => pure .empty - | .list a => ArgDecls.ofArray <$> Array.mapM fromIon a + | .mk (.null _) => pure .empty + | .mk (.list a) => ArgDecls.ofArray <$> Array.mapM fromIon a | r => throw s!"OpDecl.args expected a list." let returns ← fromIon fldArgs[3] let syntaxDef ← match fldArgs[4] with - | .null _ => pure (.mkFunApp name argDecls.size) + | .mk (.null _) => pure (.mkFunApp name argDecls.size) | v => fromIon v let metadata ← match fldArgs[5] with - | .null _ => pure .empty + | .mk (.null _) => pure .empty | v => fromIon v pure { name := name @@ -1132,7 +1157,7 @@ end FunctionDecl namespace MetadataDecl -instance : CachedToIon MetadataDecl where +private instance : CachedToIon MetadataDecl where cachedToIon refs d := ionScope! MetadataDecl refs : return .struct #[ (ionSymbol! "type", ionSymbol! "metadata"), @@ -1140,7 +1165,7 @@ instance : CachedToIon MetadataDecl where (ionSymbol! "args", ← ionRef! d.args) ] -protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM MetadataDecl := do +private protected def fromIon (fields : Array (SymbolId × Ion SymbolId)) : FromIonM MetadataDecl := do let m := .fromList! ["type", "name", "args"] let args ← .mapFields fields m pure { @@ -1152,7 +1177,7 @@ end MetadataDecl namespace Decl -instance : CachedToIon Decl where +private instance : CachedToIon Decl where cachedToIon refs d := ionScope! Decl refs : match d with | .syncat d => ionRef! d @@ -1161,7 +1186,7 @@ instance : CachedToIon Decl where | .function d => ionRef! d | .metadata d => ionRef! d -def fromIonFields (typeVal : String) (fields : Array (SymbolId × Ion SymbolId)) : FromIonM Decl := do +private def fromIonFields (typeVal : String) (fields : Array (SymbolId × Ion SymbolId)) : FromIonM Decl := do match typeVal with | "syncat" => .syncat <$> SynCatDecl.fromIon fields | "op" => .op <$> OpDecl.fromIon fields @@ -1170,7 +1195,7 @@ def fromIonFields (typeVal : String) (fields : Array (SymbolId × Ion SymbolId)) | "metadata" => .metadata <$> MetadataDecl.fromIon fields | typeVal => throw s!"Unknown type {typeVal}" -def fromIon (typeId : SymbolId) (v : Ion SymbolId) : FromIonM Decl := do +private def fromIon (typeId : SymbolId) (v : Ion SymbolId) : FromIonM Decl := do let fields ← .asStruct0 v let some (_, val) := fields.find? (·.fst == typeId) | throw "Could not find type" @@ -1194,7 +1219,7 @@ inductive Header namespace Header -def fromIon (v : Ion SymbolId) : FromIonM Header := do +private def fromIon (v : Ion SymbolId) : FromIonM Header := do let ⟨hdr, _⟩ ← .asSexp "Header" v let .isTrue ne := inferInstanceAs (Decidable (hdr.size ≥ 2)) | throw s!"Expected header to have two elements." @@ -1217,7 +1242,7 @@ end Ion namespace Dialect -instance : CachedToIon Dialect where +private instance : CachedToIon Dialect where cachedToIon refs d := ionScope! Dialect refs : do let c := ionSymbol! "dialect" let hdr := .sexp #[ c, .string d.name ] @@ -1253,7 +1278,7 @@ def fromIonFragment (dialect : DialectName) (f : Ion.Fragment) : Except String D declarations := decls } -instance : FromIon Dialect where +private instance : FromIon Dialect where fromIon v := do let ⟨args, _⟩ ← .asList v let .isTrue ne := inferInstanceAs (Decidable (args.size ≥ 1)) @@ -1271,7 +1296,7 @@ end Dialect namespace Program -instance : CachedToIon Program where +private instance : CachedToIon Program where cachedToIon refs pgm := ionScope! Program refs : do let hdr := Ion.sexp #[ ionSymbol! "program", .string pgm.dialect ] @@ -1280,14 +1305,34 @@ instance : CachedToIon Program where #declareIonSymbolTable Program -def fromIonFragment (f : Ion.Fragment) (dialects : DialectMap) (dialect : DialectName) : Except String Program := do +def fromIonFragmentCommands (f : Ion.Fragment) : Except String (Array Operation) := do let ctx : FromIonContext := ⟨f.symbols⟩ - let commands ← f.values.foldlM (init := #[]) (start := f.offset) fun cmds u => do + f.values.foldlM (init := #[]) (start := f.offset) fun cmds u => do cmds.push <$> OperationF.fromIon u ctx + +def fromIonFragment (f : Ion.Fragment) + (dialects : DialectMap) + (dialect : DialectName) : Except String Program := return { - dialects := dialects.importedDialects! dialect + dialects := dialects dialect := dialect - commands := commands + commands := ← fromIonFragmentCommands f } -end Program +def fromIon (dialects : DialectMap) (dialect : DialectName) (bytes : ByteArray) : Except String Strata.Program := do + let (hdr, frag) ← + match Strata.Ion.Header.parse bytes with + | .error msg => + throw msg + | .ok p => + pure p + match hdr with + | .dialect _ => + throw "Expected a Strata program instead of a dialect." + | .program name => do + if name != dialect then + throw s!"{name} program found when {dialect} expected." + fromIonFragment frag dialects dialect + +end Strata.Program +end diff --git a/Strata/DDM/Parser.lean b/Strata/DDM/Parser.lean index 5d18b507d..7f9e81112 100644 --- a/Strata/DDM/Parser.lean +++ b/Strata/DDM/Parser.lean @@ -3,11 +3,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Std.Data.HashSet -import Strata.DDM.Format -import Strata.DDM.Elab.Env -import Strata.DDM.Util.PrattParsingTables +public import Strata.DDM.Elab.Env +public import Strata.DDM.Format +import Strata.DDM.Util.ByteArray open Lean open Parser ( @@ -48,29 +48,20 @@ open Parser ( trailingNodeFn ) +public section namespace Lean.Parser.SyntaxStack -def ofArray (a:Array Syntax) : SyntaxStack := +private def ofArray (a:Array Syntax) : SyntaxStack := a.foldl SyntaxStack.push .empty -def toArray (s : SyntaxStack) : Array Syntax := +private def toArray (s : SyntaxStack) : Array Syntax := s.toSubarray.toArray -instance : Repr SyntaxStack where +private instance : Repr SyntaxStack where reprPrec s _ := "SyntaxStack.ofArray " ++ repr s.toArray -instance : Repr SyntaxStack where - reprPrec a p := reprPrec (a.toSubarray) p - end Lean.Parser.SyntaxStack -namespace Lean.Parser.TokenTable - -def addParser (tt : TokenTable) (p : Parser) : TokenTable := - let tkns := p.info.collectTokens [] - tkns.foldl (λtt t => tt.insert t t) tt - -end Lean.Parser.TokenTable namespace Strata.Parser @@ -82,12 +73,16 @@ export Lean.Parser ( skip ) -def nodeFn (n : SyntaxNodeKind) (p : ParserFn) : ParserFn := fun c s => +def TokenTable.addParser (tt : TokenTable) (p : Parser) : TokenTable := + let tkns := p.info.collectTokens [] + tkns.foldl (λtt t => tt.insert t t) tt + +private def nodeFn (n : SyntaxNodeKind) (p : ParserFn) : ParserFn := fun c s => let iniSz := s.stackSize let s := p c s s.mkNode n iniSz -private def emptySourceInfo (c : ParserContext) (pos : String.Pos) : SourceInfo := +private def emptySourceInfo (c : ParserContext) (pos : String.Pos.Raw) : SourceInfo := let empty := c.mkEmptySubstringAt pos .original empty pos empty pos @@ -122,16 +117,22 @@ def stringInputContext (fileName : System.FilePath) (contents : String) : InputC fileName := fileName.toString fileMap := FileMap.ofString contents +private def strataIsIdFirst (c : Char) : Bool := + c.isAlpha || c == '_' + +private def strataIsIdRest (c : Char) : Bool := + c.isAlphanum || c == '_' || c == '\'' || c == '.' || c == '?' || c == '!' + private def isIdFirstOrBeginEscape (c : Char) : Bool := - isIdFirst c || isIdBeginEscape c + strataIsIdFirst c || isIdBeginEscape c -private def isToken (idStartPos idStopPos : String.Pos) (tk : Option Token) : Bool := +private def isToken (idStartPos idStopPos : String.Pos.Raw) (tk : Option Token) : Bool := match tk with | none => false | some tk => -- if a token is both a symbol and a valid identifier (i.e. a keyword), -- we want it to be recognized as a symbol - tk.endPos ≥ idStopPos - idStartPos + tk.rawEndPos.byteIdx ≥ idStopPos.byteIdx - idStartPos.byteIdx /-- Create a trailing node @@ -145,7 +146,7 @@ s.lhsPrec is used in trailing nodes to indicate the precedence of the leading no To respect the invariant, we need to check that the lhsPrec is at least the minimum first argument precedence. -/ -def trailingNode (n : SyntaxNodeKind) (prec minLhsPrec : Nat) (p : Parser) : TrailingParser := +private def trailingNode (n : SyntaxNodeKind) (prec minLhsPrec : Nat) (p : Parser) : TrailingParser := { info := nodeInfo n p.info fn := fun c s => @@ -162,7 +163,7 @@ def trailingNode (n : SyntaxNodeKind) (prec minLhsPrec : Nat) (p : Parser) : Tra } variable (pushMissingOnError : Bool) in -partial def finishCommentBlock : ParserFn := fun c s => +private partial def finishCommentBlock : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then eoi s @@ -188,7 +189,7 @@ Parses a sequence of the form `many (many '_' >> many1 digit)`, but if `needDigi Note: this does not report that it is expecting `_` if we reach EOI or an unexpected character. Rationale: this error happens if there is already a `_`, and while sequences of `_` are allowed, it's a bit perverse to suggest extending the sequence. -/ -partial def takeDigitsFn (isDigit : Char → Bool) (expecting : String) (needDigit : Bool) : ParserFn := fun c s => +private partial def takeDigitsFn (isDigit : Char → Bool) (expecting : String) (needDigit : Bool) : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then if needDigit then @@ -203,7 +204,7 @@ partial def takeDigitsFn (isDigit : Char → Bool) (expecting : String) (needDig else s /-- Consume whitespace and comments -/ -partial def whitespace : ParserFn := fun c s => +private partial def whitespace : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then s else @@ -211,7 +212,16 @@ partial def whitespace : ParserFn := fun c s => if curr == '\t' then s.mkUnexpectedError (pushMissing := false) "tabs are not allowed; please configure your editor to expand them" else if curr == '\r' then - s.mkUnexpectedError (pushMissing := false) "isolated carriage returns are not allowed" + -- Allow \r\n (Windows line endings) but reject isolated \r + let j := c.next' i h + if c.atEnd j then + s.mkUnexpectedError (pushMissing := false) "isolated carriage returns are not allowed" + else + let next := c.get j + if next == '\n' then + whitespace c (s.next c j) + else + s.mkUnexpectedError (pushMissing := false) "isolated carriage returns are not allowed" else if curr.isWhitespace then whitespace c (s.next' c i h) else if curr == '/' then let j := c.next' i h @@ -232,31 +242,7 @@ partial def whitespace : ParserFn := fun c s => s else s -def mkIdResult (startPos : String.Pos) (val : String) : ParserFn := fun c s => - let stopPos := s.pos - let rawVal := c.substring startPos stopPos - let s := whitespace c s - let trailingStopPos := s.pos - let leading := c.mkEmptySubstringAt startPos - let trailing := c.substring (startPos := stopPos) (stopPos := trailingStopPos) - let info := SourceInfo.original leading startPos trailing stopPos - let atom := mkIdent info rawVal (.str .anonymous val) - s.pushSyntax atom - -/-- Push `(Syntax.node tk )` onto syntax stack if parse was successful. -/ -def mkNodeToken (n : SyntaxNodeKind) (startPos : String.Pos) : ParserFn := fun c s => Id.run do - if s.hasError then - return s - let stopPos := s.pos - let leading := c.mkEmptySubstringAt startPos - let val := c.extract startPos stopPos - let s := whitespace c s - let wsStopPos := s.pos - let trailing := c.substring (startPos := stopPos) (stopPos := wsStopPos) - let info := SourceInfo.original leading startPos trailing stopPos - s.pushSyntax (Syntax.mkLit n val info) - -def mkTokenAndFixPos (startPos : String.Pos) (tk : Option Token) : ParserFn := fun c s => +private def mkTokenAndFixPos (startPos : String.Pos.Raw) (tk : Option Token) : ParserFn := fun c s => match tk with | none => s.mkErrorAt "token" startPos | some tk => @@ -272,7 +258,35 @@ def mkTokenAndFixPos (startPos : String.Pos) (tk : Option Token) : ParserFn := f let atom := Parser.mkAtom (SourceInfo.original leading startPos trailing stopPos) tk s.pushSyntax atom -def charLitFnAux (startPos : String.Pos) : ParserFn := fun c s => +private def mkIdResult (startPos : String.Pos.Raw) (tk : Option Token) (startPart stopPart : String.Pos.Raw) : ParserFn := fun c s => + if isToken startPos s.pos tk then + mkTokenAndFixPos startPos tk c s + else + let val := c.extract startPart stopPart + let stopPos := s.pos + let rawVal := c.substring startPos stopPos + let s := whitespace c s + let trailingStopPos := s.pos + let leading := c.mkEmptySubstringAt startPos + let trailing := c.substring (startPos := stopPos) (stopPos := trailingStopPos) + let info := SourceInfo.original leading startPos trailing stopPos + let atom := mkIdent info rawVal (.str .anonymous val) + s.pushSyntax atom + +/-- Push `(Syntax.node tk )` onto syntax stack if parse was successful. -/ +private def mkNodeToken (n : SyntaxNodeKind) (startPos : String.Pos.Raw) : ParserFn := fun c s => Id.run do + if s.hasError then + return s + let stopPos := s.pos + let leading := c.mkEmptySubstringAt startPos + let val := c.extract startPos stopPos + let s := whitespace c s + let wsStopPos := s.pos + let trailing := c.substring (startPos := stopPos) (stopPos := wsStopPos) + let info := SourceInfo.original leading startPos trailing stopPos + s.pushSyntax (Syntax.mkLit n val info) + +def charLitFnAux (startPos : String.Pos.Raw) : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then s.mkEOIError else @@ -287,13 +301,83 @@ def charLitFnAux (startPos : String.Pos) : ParserFn := fun c s => if curr == '\'' then mkNodeToken charLitKind startPos c s else s.mkUnexpectedError "missing end of character literal" -def identFnAux (startPos : String.Pos) (tk : Option Token) : ParserFn := fun c s => +/-- +Parse and unescape a pipe-delimited identifier. +Returns (closing pipe position, unescaped string). +-/ +private def parsePipeDelimitedIdent (c : ParserContext) (startPos : String.Pos.Raw) : String.Pos.Raw × String := + Id.run do + let mut pos := startPos + let mut result := "" + while !c.atEnd pos do + let ch := c.get pos + if ch == '|' then + return (pos, result) + else if ch == '\\' then + pos := c.next pos + if !c.atEnd pos then + let nextCh := c.get pos + if nextCh == '|' || nextCh == '\\' then + result := result.push nextCh -- Unescape: \| -> | or \\ -> \ + pos := c.next pos + else + result := result.push '\\' -- Invalid escape, keep backslash + else + result := result.push '\\' + else + result := result.push ch + pos := c.next pos + return (pos, result) + +/-- +Create an identifier atom from an unescaped pipe-delimited identifier string. +-/ +private def mkPipeIdentResult (startPos : String.Pos.Raw) (closingPipePos : String.Pos.Raw) (unescaped : String) (tk : Option Token) : ParserFn := fun c s => + let s := s.setPos (c.next closingPipePos) -- Skip closing | + if isToken startPos s.pos tk then + mkTokenAndFixPos startPos tk c s + else + let stopPos := s.pos + let rawVal := c.substring startPos stopPos + let s := whitespace c s + let trailingStopPos := s.pos + let leading := c.mkEmptySubstringAt startPos + let trailing := c.substring (startPos := stopPos) (stopPos := trailingStopPos) + let info := SourceInfo.original leading startPos trailing stopPos + let atom := mkIdent info rawVal (.str .anonymous unescaped) + s.pushSyntax atom + +def identFnAux (startPos : String.Pos.Raw) (tk : Option Token) : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then s.mkEOIError else let curr := c.get' i h - if isIdBeginEscape curr then + if curr == '|' then + -- Pipe-delimited identifiers (SMT-LIB 2.6): |identifier| + -- Disambiguate from | operator by checking context + let nextPos := c.next' i h + if c.atEnd nextPos then + -- Single | at EOF - treat as token if matched + match tk with + | some _ => mkTokenAndFixPos startPos tk c s + | none => s.mkError "identifier" + else + let nextChar := c.get nextPos + -- Check if this is an operator token or pipe-delimited identifier + let isOperator := match tk with + | some token => token.rawEndPos.byteIdx > 1 || nextChar == '|' || nextChar.isWhitespace + | none => false + if isOperator then + mkTokenAndFixPos startPos tk c s + else + -- Parse pipe-delimited identifier with escape sequences + let (closingPipePos, unescaped) := parsePipeDelimitedIdent c nextPos + if c.atEnd closingPipePos then + s.mkUnexpectedErrorAt "unterminated pipe-delimited identifier" nextPos + else + mkPipeIdentResult startPos closingPipePos unescaped tk c s + else if isIdBeginEscape curr then let startPart := c.next' i h let s := takeUntilFn isIdEndEscape c (s.setPos startPart) if h : c.atEnd s.pos then @@ -301,24 +385,16 @@ def identFnAux (startPos : String.Pos) (tk : Option Token) : ParserFn := fun c s else let stopPart := s.pos let s := s.next' c s.pos h - if isToken startPos s.pos tk then - mkTokenAndFixPos startPos tk c s - else - let val := c.extract startPart stopPart - mkIdResult startPos val c s - else if isIdFirst curr then + mkIdResult startPos tk startPart stopPart c s + else if strataIsIdFirst curr then let startPart := i - let s := takeWhileFn isIdRest c (s.next c i) + let s := takeWhileFn strataIsIdRest c (s.next c i) let stopPart := s.pos - if isToken startPos s.pos tk then - mkTokenAndFixPos startPos tk c s - else - let val := c.extract startPart stopPart - mkIdResult startPos val c s + mkIdResult startPos tk startPart stopPart c s else mkTokenAndFixPos startPos tk c s -def decimalNumberFn (startPos : String.Pos) (c : ParserContext) : ParserState → ParserState := fun s => +def decimalNumberFn (startPos : String.Pos.Raw) (c : ParserContext) : ParserState → ParserState := fun s => let s := takeDigitsFn (fun c => c.isDigit) "decimal number" false c s let i := s.pos if h : c.atEnd i then @@ -362,16 +438,18 @@ where else s -def binNumberFn (startPos : String.Pos) : ParserFn := fun c s => +def binNumberFn (startPos : String.Pos.Raw) : ParserFn := fun c s => let s := takeDigitsFn (fun c => c == '0' || c == '1') "binary number" true c s mkNodeToken numLitKind startPos c s -def octalNumberFn (startPos : String.Pos) : ParserFn := fun c s => +def octalNumberFn (startPos : String.Pos.Raw) : ParserFn := fun c s => let s := takeDigitsFn (fun c => '0' ≤ c && c ≤ '7') "octal number" true c s mkNodeToken numLitKind startPos c s -def hexNumberFn (startPos : String.Pos) : ParserFn := fun c s => - let s := takeDigitsFn (fun c => ('0' ≤ c && c ≤ '9') || ('a' ≤ c && c ≤ 'f') || ('A' ≤ c && c ≤ 'F')) "hexadecimal number" true c s +def isHexDigit (c : Char) := ('0' ≤ c && c ≤ '9') || ('a' ≤ c && c ≤ 'f') || ('A' ≤ c && c ≤ 'F') + +def hexNumberFn (startPos : String.Pos.Raw) : ParserFn := fun c s => + let s := takeDigitsFn isHexDigit "hexadecimal number" true c s mkNodeToken numLitKind startPos c s def numberFnAux : ParserFn := fun c s => @@ -395,7 +473,19 @@ def numberFnAux : ParserFn := fun c s => else s.mkError "numeral" -partial def strLitFnAux (startPos : String.Pos) : ParserFn := fun c s => +abbrev bytesLitKind : SyntaxNodeKind := `bytes + +partial def parseByteContent (startPos : String.Pos.Raw) : ParserFn := fun c s => + if s.hasError then + s + else + match ByteArray.unescapeBytesRawAux c.inputString s.pos .empty with + | .error (_, e, msg) => + s.setPos e |>.mkError msg + | .ok (_, e) => + mkNodeToken bytesLitKind startPos c (s.setPos e) + +partial def strLitFnAux (startPos : String.Pos.Raw) : ParserFn := fun c s => let i := s.pos if h : c.atEnd i then s.mkUnexpectedErrorAt "unterminated string literal" startPos else @@ -411,15 +501,17 @@ private def tokenFnAux : ParserFn := fun c s => let curr := c.get i if curr == '\"' then strLitFnAux i c (s.next c i) - else if curr == '\'' && getNext c.inputString i != '\'' then + else if curr == '\'' && c.getNext i != '\'' then charLitFnAux i c (s.next c i) + else if curr = 'b' ∧ c.getNext i = '\"' then + parseByteContent i c (s.setPos (c.next <| c.next i)) else if curr.isDigit then numberFnAux c s else let tk := c.tokens.matchPrefix c.inputString i identFnAux i tk c s -private def updateTokenCache (startPos : String.Pos) (s : ParserState) : ParserState := +private def updateTokenCache (startPos : String.Pos.Raw) (s : ParserState) : ParserState := -- do not cache token parsing errors, which are rare and usually fatal and thus not worth an extra field in `TokenCache` match s with | ⟨stack, lhsPrec, pos, ⟨_, catCache⟩, none, errs⟩ => @@ -487,6 +579,11 @@ def numLit : Parser := { info := mkAtomicInfo "num" } +def byteArray : Parser := { + fn := fun ctx s => expectTokenFn bytesLitKind "byte sequence" ctx s + info := mkAtomicInfo "byte array" +} + def decimalLit : Parser := { fn := expectTokenFn scientificLitKind "scientific number" info := mkAtomicInfo "scientific" @@ -545,7 +642,7 @@ def indexed {α : Type} (map : TokenMap α) (c : ParserContext) (s : ParserState def longestMatchMkResult (startSize : Nat) (s : ParserState) : ParserState := if s.stackSize > startSize + 1 then s.mkNode choiceKind startSize else s -def longestMatchFnAux (left? : Option Syntax) (startSize startLhsPrec : Nat) (startPos : String.Pos) (prevPrio : Nat) (ps : List (Parser × Nat)) : ParserFn := +def longestMatchFnAux (left? : Option Syntax) (startSize startLhsPrec : Nat) (startPos : String.Pos.Raw) (prevPrio : Nat) (ps : List (Parser × Nat)) : ParserFn := let rec parse (prevPrio : Nat) (ps : List (Parser × Nat)) := match ps with | [] => fun _ s => longestMatchMkResult startSize s @@ -666,7 +763,7 @@ def checkLeftRec (thisCatName : QualifiedIdent) (argDecls : ArgDecls) (as : List checkLeftRec thisCatName argDecls (as.toList ++ bs) | .str _ :: _ => .isLeading as - | .ident v argPrec :: rest => Id.run do + | .ident v argPrec _ :: rest => Id.run do let .isTrue lt := inferInstanceAs (Decidable (v < argDecls.size)) | return panic! "Invalid index" let cat := argDecls[v].kind.categoryOf @@ -777,7 +874,7 @@ partial def catParser (ctx : ParsingContext) (cat : SyntaxCat) : Except SyntaxCa .ok (atomCatParser ctx qid) else .error cat -/- +/-- This walks the SyntaxDefAtomParser and prepends extracted parser to state. This is essentially a right-to-left fold and is implemented so that the parser starts with @@ -785,7 +882,7 @@ the first symbol. -/ private def prependSyntaxDefAtomParser (ctx : ParsingContext) (argDecls : ArgDecls) (o : SyntaxDefAtom) (r : Parser) : Parser := match o with - | .ident v prec => Id.run do + | .ident v prec _ => Id.run do let .isTrue lt := inferInstanceAs (Decidable (v < argDecls.size)) | return panic! s!"Invalid ident index {v} in bindings {eformat argDecls}" let addParser (p : Parser) := @@ -869,7 +966,7 @@ def mkDialectParsers (ctx : ParsingContext) (d : Dialect) : Except StrataFormat end ParsingContext -structure ParserState where +private structure ParserState where -- Dynamic parser categories categoryMap : PrattParsingTableMap := {} deriving Inhabited @@ -878,7 +975,7 @@ def runCatParser (tokenTable : TokenTable) (parsingTableMap : PrattParsingTableMap) (leanEnv : Lean.Environment) (inputContext : InputContext) - (pos stopPos : String.Pos) (cat : QualifiedIdent) : Lean.Parser.ParserState := + (pos stopPos : String.Pos.Raw) (cat : QualifiedIdent) : Lean.Parser.ParserState := let leanEnv := parserExt.modifyState leanEnv (fun _ => parsingTableMap) let pmc : ParserModuleContext := { env := leanEnv, options := {} } let leanParserState : Lean.Parser.ParserState := { @@ -889,6 +986,7 @@ def runCatParser (tokenTable : TokenTable) } } let p := dynamicParser cat - p.fn.run inputContext pmc tokenTable leanParserState + let f := andthenFn whitespace p.fn + f.run inputContext pmc tokenTable leanParserState end Strata.Parser diff --git a/Strata/DDM/TaggedRegions.lean b/Strata/DDM/TaggedRegions.lean index f963fa8bb..59ac1c5a7 100644 --- a/Strata/DDM/TaggedRegions.lean +++ b/Strata/DDM/TaggedRegions.lean @@ -3,21 +3,30 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Lean.Elab.Syntax +import Lean.PrettyPrinter.Formatter +import Lean.PrettyPrinter.Parenthesizer import Strata.DDM.Util.String -open Lean Elab.Command Parser +public meta import Lean.Elab.Syntax -namespace Strata +open Lean (SourceInfo Syntax SyntaxNodeKind Name format) +open Lean.Elab.Command (CommandElab CommandElabM liftTermElabM elabCommand) +open Lean.Elab.Term (addCategoryInfo) +open Lean.Parser (Parser ParserFn isParserCategory node symbol) +open Lean.Syntax (Term) +open Lean.Syntax.MonadTraverser (getCur goLeft) +open Lean.PrettyPrinter.Formatter (pushToken withMaybeTag throwBacktrack) -namespace ExternParser +public section +namespace Strata.ExternParser -def parserFn (endToken : String) : ParserFn := fun c s => Id.run do +private def parserFn (endToken : String) : ParserFn := fun c s => Id.run do if s.hasError then return s let startPos := s.pos - let some stopPos := c.inputString.indexOf endToken s.pos + let some stopPos := c.inputString.indexOfRaw endToken s.pos | s.setError { unexpected := s!"Could not find end token {endToken}" } let s := s.setPos stopPos let leading := c.mkEmptySubstringAt startPos @@ -28,14 +37,12 @@ def parserFn (endToken : String) : ParserFn := fun c s => Id.run do def mkParser (n : SyntaxNodeKind) (startToken endToken : String) : Parser := node n (symbol startToken >> { fn := parserFn endToken } >> symbol endToken) -open Syntax Syntax.MonadTraverser -open Lean.PrettyPrinter.Formatter - -private def SourceInfo.getExprPos? : SourceInfo → Option String.Pos +private def SourceInfo.getExprPos? : SourceInfo → Option String.Pos.Raw | SourceInfo.synthetic (pos := pos) .. => pos | _ => none -@[combinator_formatter mkParser] def mkParser.formatter (sym : Name) := do +@[combinator_formatter mkParser] +def mkParser.formatter (sym : Name) := do let stx ← getCur if stx.getKind != sym then do trace[PrettyPrinter.format.backtrack] "unexpected syntax '{format stx}', expected symbol '{sym}'" @@ -46,9 +53,10 @@ private def SourceInfo.getExprPos? : SourceInfo → Option String.Pos withMaybeTag (SourceInfo.getExprPos? info) (pushToken info stx[0].getId.toString false) goLeft -@[combinator_parenthesizer mkParser] def mkParser.parenthesizer (_ : Name) := +@[combinator_parenthesizer mkParser] +def mkParser.parenthesizer (_ : Name) := -- FIXME - PrettyPrinter.Parenthesizer.symbolNoAntiquot.parenthesizer + Lean.PrettyPrinter.Parenthesizer.symbolNoAntiquot.parenthesizer /-- This declares a parse for a known category. @@ -57,14 +65,14 @@ This declares a parse for a known category. - `cat` Is the category for the node - `stxNodeKind` is the name for the node kind produced by this parser. -/ -def declareParser (stx : Syntax) (cat : Name) (stxNodeKind : Name) (val : Term) : CommandElabM Unit := do - unless (isParserCategory (← getEnv) cat) do +private meta def declareParser (stx : Syntax) (cat : Name) (stxNodeKind : Name) (val : Term) : CommandElabM Unit := do + unless (isParserCategory (← Lean.getEnv) cat) do throwErrorAt stx "unknown category '{cat}'" - liftTermElabM <| Elab.Term.addCategoryInfo stx cat - let catParserId := mkIdentFrom stx (cat.appendAfter "_parser") - let declName := mkIdentFrom stx stxNodeKind (canonical := true) + liftTermElabM <| addCategoryInfo stx cat + let catParserId := Lean.mkIdentFrom stx (cat.appendAfter "_parser") + let declName := Lean.mkIdentFrom stx stxNodeKind (canonical := true) let attrInstances : Syntax.TSepArray `Lean.Parser.Term.attrInstance "," := { elemsAndSeps := #[] } - let attrInstance ← `(Lean.Parser.Term.attrInstance| $catParserId:ident $(quote 1000):num) + let attrInstance ← `(Lean.Parser.Term.attrInstance| $catParserId:ident $(Lean.quote 1000):num) let attrInstances := attrInstances.push attrInstance let d ← `(@[$attrInstances,*] def $declName:ident : Lean.Parser.Parser := $val) elabCommand d @@ -75,17 +83,17 @@ This creates declares a parser that recognizes text within a set of tags. syntax (name := declareTaggedRegion) (docComment)? "declare_tagged_region" ident ident str str : command -- declare the syntax @[command_elab declareTaggedRegion] -def declareTaggedRegionImpl: CommandElab := fun stx => do -- declare and register the elaborator +meta def declareTaggedRegionImpl: CommandElab := fun stx => do -- declare and register the elaborator let `(declare_tagged_region $catStx $cmdStx $startToken $endToken) := stx - | Elab.throwUnsupportedSyntax + | Lean.Elab.throwUnsupportedSyntax let cat : Name := catStx.getId - let cmd : Term := quote cmdStx.getId + let cmd : Term := Lean.quote cmdStx.getId let cmd : Term := ⟨cmd.raw.setInfo cmdStx.raw.getHeadInfo⟩ let decl ← `(ExternParser.mkParser $cmd $startToken $endToken) declareParser stx cat cmdStx.getId decl initialize - registerTraceClass `Strata.DDM.syntax + Lean.registerTraceClass `Strata.DDM.syntax -end ExternParser -end Strata +end Strata.ExternParser +end diff --git a/Strata/DDM/Util/Array.lean b/Strata/DDM/Util/Array.lean index 8d20695b5..2be7c3743 100644 --- a/Strata/DDM/Util/Array.lean +++ b/Strata/DDM/Util/Array.lean @@ -3,13 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module import Strata.DDM.Util.List +public section namespace Array @[simp] -theorem Array.anyM_empty {α} [Monad m] (f : α → m Bool) (start : Nat := 0) (stop : Nat := 0) +theorem anyM_empty {α} [Monad m] (f : α → m Bool) (start : Nat := 0) (stop : Nat := 0) : Array.anyM f #[] start stop = @pure m _ _ false := by unfold Array.anyM split @@ -21,11 +23,6 @@ theorem Array.anyM_empty {α} [Monad m] (f : α → m Bool) (start : Nat := 0) ( unfold anyM.loop simp -@[simp] -theorem Array.any_empty (f : α → Bool) (start : Nat := 0) (stop : Nat := 0) - : Array.any #[] f start stop = false := by - simp [Array.any] - def map_off {α β} (as : Array α) (f : α → β) (start : Nat := 0) (stop : Nat := as.size) (init : Array β := Array.mkEmpty ((min as.size stop) - start)) : Array β := @@ -38,7 +35,7 @@ def mapM_off {α β m} [Monad m] (as : Array α) (f : α → m β) as.foldlM (init := init) (start := start) (stop := stop) fun r e => r.push <$> f e -theorem extract_loop_succ_upper {α} (as b : Array α) (i j : Nat) (h : i + j < as.size) : +private theorem extract_loop_succ_upper {α} (as b : Array α) (i j : Nat) (h : i + j < as.size) : Array.extract.loop as (i + 1) j b = (Array.extract.loop as i j b).push (as[i + j]'h) := by revert b j @@ -55,14 +52,14 @@ theorem extract_loop_succ_upper {α} (as b : Array α) (i j : Nat) (h : i + j < have p : j + (i + 1) = j + 1 + i := by omega simp [g, hyp _ _ h, p] -theorem extract_succ {α} (as : Array α) {i : Nat} (g : i ≤ j) (h : j < as.size) : as.extract i (j + 1) = (as.extract i j).push (as[j]'h) := by +private theorem extract_succ {α} (as : Array α) {i : Nat} (g : i ≤ j) (h : j < as.size) : as.extract i (j + 1) = (as.extract i j).push (as[j]'h) := by have j1_le : (j + 1) ≤ as.size := by omega have j_le : j ≤ as.size := by omega have p : j + 1 - i = j - i + 1 := by omega have q : j - i + i = j := by omega simp [Array.extract, Nat.min_eq_left, j1_le, j_le, p, Array.extract_loop_succ_upper, q, h] -theorem sizeOf_toList {α} [SizeOf α] (as : Array α) : +private theorem sizeOf_toList {α} [SizeOf α] (as : Array α) : sizeOf as = 1 + sizeOf as.toList := rfl theorem sizeOf_min [SizeOf α] (as : Array α) : sizeOf as ≥ 2 := by @@ -93,8 +90,7 @@ theorem sizeOf_swap [h : SizeOf α] (a : Array α) (i : Nat) (j : Nat) (hi : i simp [Array.getElem_set] omega -private -theorem sizeOf_reverse_loop {α} [h : SizeOf α] (as : Array α) (i : Nat) (j : Fin as.size) : sizeOf (reverse.loop as i j) = sizeOf as := by +private theorem sizeOf_reverse_loop {α} [h : SizeOf α] (as : Array α) (i : Nat) (j : Fin as.size) : sizeOf (reverse.loop as i j) = sizeOf as := by unfold reverse.loop split case isTrue p => @@ -117,4 +113,14 @@ theorem sizeOf_lt_of_mem_strict [SizeOf α] {as : Array α} (h : a ∈ as) : siz cases as with | _ as => simp +arith [List.sizeOf_lt_of_mem_strict h.val] +theorem mem_iff_back_or_pop {α} (a : α) {as : Array α} (p : as.size > 0 := by get_elem_tactic) : + a ∈ as ↔ (a = as.back ∨ a ∈ as.pop) := by + simp [Array.mem_iff_getElem] + grind + +theorem of_mem_pop {α} {a : α} {as : Array α} : a ∈ as.pop → a ∈ as := by + simp [Array.mem_iff_getElem] + grind + end Array +end diff --git a/Strata/DDM/Util/ByteArray.lean b/Strata/DDM/Util/ByteArray.lean index 27a34222e..61109a5e0 100644 --- a/Strata/DDM/Util/ByteArray.lean +++ b/Strata/DDM/Util/ByteArray.lean @@ -3,19 +3,22 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module /- Functions for ByteArray that could potentially be upstreamed to Lean. -/ -namespace ByteArray +import Std.Data.HashMap +public import Lean.ToExpr -deriving instance DecidableEq for ByteArray +public section +namespace ByteArray -def back! (a : ByteArray) : UInt8 := a.get! (a.size - 1) +private def back! (a : ByteArray) : UInt8 := a.get! (a.size - 1) -def back? (a : ByteArray) : Option UInt8 := a[a.size - 1]? +private def back? (a : ByteArray) : Option UInt8 := a[a.size - 1]? -def pop (a : ByteArray) : ByteArray := a.extract 0 (a.size - 1) +private def pop (a : ByteArray) : ByteArray := a.extract 0 (a.size - 1) @[inline] def foldr {β} (f : UInt8 → β → β) (init : β) (as : ByteArray) (start := as.size) (stop := 0) : β := @@ -27,11 +30,12 @@ def foldr {β} (f : UInt8 → β → β) (init : β) (as : ByteArray) (start := aux (i-1) (by omega) (f as[i-1] b) aux (min start as.size) (Nat.min_le_right _ _) init +private def byteToHex (b : UInt8) : String := + let cl : String := .ofList (Nat.toDigits 16 b.toNat) + if cl.length < 2 then "0" ++ cl else cl + def asHex (a : ByteArray) : String := - a.foldl (init := "") fun s b => - let cl := Nat.toDigits 16 b.toNat - let cl := if cl.length < 2 then '0' :: cl else cl - s ++ cl.asString + a.foldl (init := "") fun s b => s ++ byteToHex b def startsWith (a pre : ByteArray) := if isLt : a.size < pre.size then @@ -39,6 +43,12 @@ def startsWith (a pre : ByteArray) := else pre.size.all fun i _ => a[i] = pre[i] +private protected def reprPrec (a : ByteArray) (p : Nat) := + Repr.addAppParen ("ByteArray.mk " ++ reprArg a.data) p + +instance : Repr ByteArray where + reprPrec := private ByteArray.reprPrec + end ByteArray #guard (ByteArray.empty |>.back!) = default @@ -46,3 +56,137 @@ end ByteArray #guard (ByteArray.empty |>.pop) = .empty #guard let a := ByteArray.empty |>.push 0 |>.push 1; (a |>.push 2 |>.pop) = a + +namespace Strata.ByteArray + +def ofNatArray (a : Array Nat) : ByteArray := .mk (a.map UInt8.ofNat) + +open Lean in +instance : Lean.ToExpr ByteArray where + toTypeExpr := private mkConst ``ByteArray + toExpr a := private mkApp (mkConst ``ByteArray.ofNatArray) <| toExpr <| a.data.map (·.toNat) + +def escapedBytes : Std.HashMap UInt8 Char := Std.HashMap.ofList [ + (9, 't'), + (10, 'n'), + (13, 'r'), + (34, '"'), + (92, '\\'), +] + +def escapeBytes (b : ByteArray) : String := + (b.foldl (init := "b\"") fun s b => s ++ aux b) ++ "\"" +where aux (b : UInt8) : String := + match escapedBytes[b]? with + | some c => "\\".push c + | none => + if 32 ≤ b ∧ b < 127 then + Char.ofUInt8 b |>.toString + else + "\\x" ++ ByteArray.byteToHex b + +@[inline] +def hexDigitToUInt8 (c : Char) : Option UInt8 := + if '0' ≤ c ∧ c ≤ '9' then + .some <| c.toUInt8 - '0'.toUInt8 + else if 'A' ≤ c ∧ c ≤ 'F' then + .some <| c.toUInt8 - 'A'.toUInt8 + 10 + else if 'a' ≤ c ∧ c ≤ 'f' then + .some <| c.toUInt8 - 'a'.toUInt8 + 10 + else + none + +def escapeChars : Std.HashMap Char UInt8 := .ofList <| + ByteArray.escapedBytes.toList |>.map fun (i, c) => (c, i) + +partial def unescapeBytesRawAux (s : String) (i0 : String.Pos.Raw) (a : ByteArray) : Except (String.Pos.Raw × String.Pos.Raw × String) (ByteArray × String.Pos.Raw) := + if i0 = s.rawEndPos then + .error (i0, i0, "unexpected end of input, expected closing quote") + else + let ch := i0.get s + let i := i0.next s + if ch == '"' then + .ok (a, i) + else if ch == '\\' then + -- Escape sequence + if i = s.rawEndPos then + .error (i0, i, "unexpected end of input after backslash") + else + let escCh := i.get s + let i := i.next s + if escCh = 'x' then + -- Hex escape: \xHH + if i = s.rawEndPos then + .error (i0, i, "incomplete hex escape sequence") + else + let c1 := i.get s + let j := i.next s + if j = s.rawEndPos then + .error (i0, j, "incomplete hex escape sequence") + else + let c2 := j.get s + let k := j.next s + match hexDigitToUInt8 c1, hexDigitToUInt8 c2 with + | some b1, some b2 => + let b := b1 * 16 + b2 + unescapeBytesRawAux s k (a.push b) + | none, _ => .error (i0, k, "Invalid hex escape sequence") + | _, none => .error (i0, k, "Invalid hex escape sequence") + else + match escapeChars[escCh]? with + | some b => + unescapeBytesRawAux s i (a.push b) + | none => + .error (i0, i, "invalid escape sequence: {escCh}") + else + unescapeBytesRawAux s i (a.push ch.toUInt8) + +partial def unescapeBytesAux (s : String) (i0 : String.ValidPos s) (a : ByteArray) : Except (String.ValidPos s × String.ValidPos s × String) (ByteArray × String.ValidPos s) := + if h : i0 = s.endValidPos then + .error (i0, i0, "unexpected end of input, expected closing quote") + else + let ch := i0.get h + let i := i0.next h + if ch == '"' then + .ok (a, i) + else if ch == '\\' then + -- Escape sequence + if h : i = s.endValidPos then + .error (i0, i, "unexpected end of input after backslash") + else + let escCh := i.get h + let i := i.next h + if escCh = 'x' then + -- Hex escape: \xHH + if h : i = s.endValidPos then + .error (i0, i, "incomplete hex escape sequence") + else + let c1 := i.get h + let j := i.next h + if h : j = s.endValidPos then + .error (i0, j, "incomplete hex escape sequence") + else + let c2 := j.get h + let k := j.next h + match hexDigitToUInt8 c1, hexDigitToUInt8 c2 with + | some b1, some b2 => + let b := b1 * 16 + b2 + unescapeBytesAux s k (a.push b) + | none, _ => .error (i0, k, "Invalid hex escape sequence") + | _, none => .error (i0, k, "Invalid hex escape sequence") + else + match escapeChars[escCh]? with + | some b => + unescapeBytesAux s i (a.push b) + | none => + .error (i0, i, "invalid escape sequence: {escCh}") + else + unescapeBytesAux s i (a.push ch.toUInt8) + +def unescapeBytes (s : String) : Except (String.ValidPos s × String.ValidPos s × String) ByteArray := + let i : String.ValidPos s := s.startValidPos |>.next! |>.next! + match unescapeBytesAux s i .empty with + | .error (f, e, msg) => .error (f, e, msg) + | .ok (a, _) => .ok a + +end Strata.ByteArray diff --git a/Strata/DDM/Util/CachedValue.lean b/Strata/DDM/Util/CachedValue.lean deleted file mode 100644 index 4f5850fcd..000000000 --- a/Strata/DDM/Util/CachedValue.lean +++ /dev/null @@ -1,35 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Lean.ToExpr - -structure CachedValue {α} (v : α) where - val : α := v - isEqual : val = v := by trivial - -namespace CachedValue - -protected def default {α : Sort u} (v : α) : CachedValue v := {} - -instance {α} (v : α) : Inhabited (CachedValue v) where - default := .default v - -instance {α : Type _} (v : α) : Repr (CachedValue v) where - reprPrec _ _ := .text "{}" - -instance {α} (v : α) : CoeOut (CachedValue v) α where - coe s := s.val - -open Lean - -instance {α : Type} [h : Lean.ToExpr α] (v : α) : Lean.ToExpr (CachedValue v) where - toTypeExpr := mkApp2 (mkConst ``CachedValue [levelOne]) h.toTypeExpr (toExpr v) - toExpr _ := mkApp2 (mkConst ``CachedValue.default [levelOne]) h.toTypeExpr (toExpr v) - -instance {α : Type} [h : Quote α] (v : α) : Quote (CachedValue v) where - quote _ := Syntax.mkCApp ``CachedValue.default #[quote v] - -end CachedValue diff --git a/Strata/DDM/Util/Decimal.lean b/Strata/DDM/Util/Decimal.lean index aeb01f56e..6e1ff0f49 100644 --- a/Strata/DDM/Util/Decimal.lean +++ b/Strata/DDM/Util/Decimal.lean @@ -3,11 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + import Lean.ToExpr import Strata.DDM.Util.Lean +public import Lean.ToExpr private def String.replicate (n : Nat) (c : Char) := n.repeat (a := "") (·.push c) +public section namespace Strata structure Decimal where @@ -21,9 +25,9 @@ def zero : Decimal := { mantissa := 0, exponent := 0 } protected def ofInt (x : Int) : Decimal := { mantissa := x, exponent := 0 } -opaque maxPrettyExponent : Int := 5 +private opaque maxPrettyExponent : Int := 5 -opaque minPrettyExponent : Int := -5 +private opaque minPrettyExponent : Int := -5 def toString (d : Decimal) : String := let m := d.mantissa @@ -44,7 +48,7 @@ def toString (d : Decimal) : String := s!"{m}e{e}" instance : ToString Decimal where - toString := Decimal.toString + toString := private Decimal.toString section @@ -55,7 +59,7 @@ instance : ToExpr Decimal where toExpr d := mkApp2 (mkConst ``Decimal.mk) (toExpr d.mantissa) (toExpr d.exponent) -instance : Quote Decimal where +private instance : Quote Decimal where quote d := Syntax.mkCApp ``Decimal.mk #[quote d.mantissa, quote d.exponent] end diff --git a/Strata/DDM/Util/DecimalRat.lean b/Strata/DDM/Util/DecimalRat.lean new file mode 100644 index 000000000..0a8bc008f --- /dev/null +++ b/Strata/DDM/Util/DecimalRat.lean @@ -0,0 +1,101 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module +public import Strata.DDM.Util.Decimal +meta import Strata.DDM.Util.Decimal + +public section +namespace Strata.Decimal + +def toRat (d: Decimal) : Rat := + if d.exponent < 0 then mkRat d.mantissa (10 ^ (d.exponent).natAbs) else + Rat.ofInt (d.mantissa * 10 ^ (d.exponent).natAbs) + +/-- Check if a natural number has only factors of 2 and 5.-/ +private def isTerminatingDenominator (d : Nat) : Bool := + let rec divideOut (n : Nat) (factor : Nat) (f_pos: 1 < factor) : Nat := + if h_n: n == 0 then n else + if n % factor == 0 then + have n_pos: 0 < n := by grind + have: n / factor < n := Nat.div_lt_self n_pos f_pos + divideOut (n / factor) factor f_pos else n + termination_by n + + let afterDiv2 := divideOut d 2 (by omega) + let afterDiv5 := divideOut afterDiv2 5 (by omega) + afterDiv5 == 1 + +/-- Normalize a decimal representation by removing trailing zeros from the mantissa. -/ +private def normalize (m : Int) (e : Int) : Decimal := + let rec removeTrailingZeros (mantissa : Int) (exponent : Int) : Decimal := + if hz: mantissa == 0 then + { mantissa := 0, exponent := 0 } + else if h_mod: mantissa % 10 == 0 then + have : (mantissa / 10).natAbs < mantissa.natAbs := by grind + removeTrailingZeros (mantissa / 10) (exponent + 1) + else + { mantissa := mantissa, exponent := exponent } + termination_by mantissa.natAbs + removeTrailingZeros m e + +/-- Convert a rational number to a decimal representation. + Returns `some` if the rational can be exactly represented as a terminating decimal, `none` otherwise (denominator has factors other than 2 and 5). -/ +def fromRat (r : Rat) : Option Decimal := + if r.num == 0 then + some zero + else + let n := r.num + let d := r.den + + -- Check if denominator has only factors of 2 and 5 + if !isTerminatingDenominator d then + none + else + -- Count factors of 2 and 5 in denominator + let rec countFactor (num : Nat) (factor : Nat) (f_pos: 1 < factor) : Nat := + if h_n: num == 0 then 0 else + if num % factor == 0 then + have num_pos: 0 < num := by grind + have: num / factor < num := Nat.div_lt_self num_pos f_pos + 1 + countFactor (num / factor) factor f_pos + else 0 + termination_by num + + let count2 := countFactor d 2 (by omega) + let count5 := countFactor d 5 (by omega) + + -- We need to multiply by 10^k where k = max(count2, count5) + -- This makes the denominator divide evenly into 10^k + let k := max count2 count5 + let powerOf10 := 10 ^ k + let mantissa := (n * powerOf10) / d + let exponent := -(k : Int) + some (normalize mantissa exponent) + +#guard Decimal.fromRat (5 : Rat) = some (Decimal.mk 5 0) +#guard Decimal.fromRat (0 : Rat) = some Decimal.zero +#guard Decimal.fromRat (-3 : Rat) = some (Decimal.mk (-3) 0) +#guard Decimal.fromRat (1/2 : Rat) = some (Decimal.mk 5 (-1)) +#guard Decimal.fromRat (1/4 : Rat) = some (Decimal.mk 25 (-2)) +#guard Decimal.fromRat (7/20 : Rat) = some (Decimal.mk 35 (-2)) +#guard Decimal.fromRat (-1/2 : Rat) = some (Decimal.mk (-5) (-1)) +#guard Decimal.fromRat (-7/8 : Rat) = some (Decimal.mk (-875) (-3)) +#guard Decimal.fromRat (5/2 : Rat) = some (Decimal.mk 25 (-1)) +#guard Decimal.fromRat (15/8 : Rat) = some (Decimal.mk 1875 (-3)) +#guard Decimal.fromRat (1/3 : Rat) = none +#guard Decimal.fromRat (1/7 : Rat) = none + +#guard Decimal.fromRat (Decimal.mk 5 0).toRat = some (Decimal.mk 5 0) +#guard Decimal.fromRat (Decimal.mk 25 (-1)).toRat = some (Decimal.mk 25 (-1)) +#guard Decimal.fromRat (Decimal.mk 375 (-3)).toRat = some (Decimal.mk 375 (-3)) +#guard Decimal.fromRat (Decimal.mk (-75) (-2)).toRat = some (Decimal.mk (-75) (-2)) +#guard Decimal.fromRat (Decimal.mk 100 (-2)).toRat = some (Decimal.mk 1 0) +#guard (Decimal.fromRat (5 : Rat)).get!.toRat = (5 : Rat) +#guard (Decimal.fromRat (1/2 : Rat)).get!.toRat = (1/2 : Rat) +#guard (Decimal.fromRat (22/5 : Rat)).get!.toRat = (22/5 : Rat) + +end Strata.Decimal +end diff --git a/Strata/DDM/Util/Deser.lean b/Strata/DDM/Util/Deser.lean deleted file mode 100644 index a5e746343..000000000 --- a/Strata/DDM/Util/Deser.lean +++ /dev/null @@ -1,253 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - - -namespace Strata - -namespace Deser - -abbrev Error := String - -/-- -A `BufferM` monad has access to a byte array and returns either a value of type `α` or an error. --/ -def BufferM (α : Type) := ReaderT ByteArray (Except (Nat × Error)) α - -namespace BufferM - -instance : Inhabited (BufferM α) where - default := fun _ => .error (0, "") - -instance : Monad BufferM := by unfold BufferM; exact inferInstance - -protected def contents : BufferM ByteArray := Except.ok - -protected def fail (off : Nat) (msg : String) : BufferM α := fun _ => .error (off, msg) - -end BufferM - -/-- -This tracks the remaining number of bytes in the reader left. --/ -def Fuel := Nat - -namespace Fuel - -def toNat (f : Fuel) : Nat := f - -instance : LE Fuel := inferInstanceAs (LE Nat) - -instance (x y : Fuel) : Decidable (x ≤ y) := inferInstanceAs (Decidable (x.toNat ≤ y.toNat)) - -instance : LT Fuel := inferInstanceAs (LT Nat) - -instance (x y : Fuel) : Decidable (x < y) := inferInstanceAs (Decidable (x.toNat < y.toNat)) - -def le_refl (f : Fuel) : f ≤ f := f.toNat.le_refl - -instance : OfNat Fuel x where - ofNat := x - -instance : HSub Fuel Nat Fuel where - hSub x y := x.toNat - y - -def sub_le (f : Fuel) (n : Nat) : f - n ≤ f := Nat.sub_le f n - -instance : HSub Nat Fuel Nat where - hSub x y := x - y.toNat - -end Fuel - -/-- Flag indicating whether the Reader monad has consumed file. -/ -inductive Progress where -/-- Reader must consume at least one byte. -/ -| strict -/-- Reader may consume zero bytes -/ -| any - -namespace Progress - -/-- `Satisfies m f g` holds if the transitioning from fuel `f` to `g` satisfies the progress constraint. -/ -@[simp] -def Satisfies : Fuel × Fuel → Progress → Prop -| (n, m), .strict => m < n -| (n, m), .any => m ≤ n - -infix:45 " ⊧ " => Satisfies - -/-- Return the strongest condition of two progress values. -/ -@[simp] -def meet : Progress → Progress → Progress -| strict, _ => strict -| any, x => x - -/-- This shows that we -/ -theorem meet_trans {m n : Progress} : (a, b) ⊧ m → (b, c) ⊧ n → (a, c) ⊧ meet m n := by - cases m <;> cases n <;> (simp [Fuel] ; omega) - -end Progress - -namespace Fuel - -/-- Preimage of elements less than fuel value with respect to progress constraint -/ -def Pre (f : Fuel) (m : Progress) : Type := { x : Fuel // (f, x) ⊧ m } - -/-- Unchanged value of fuel with any constraint. -/ -def unchanged (f : Fuel) : f.Pre .any := ⟨f, f.le_refl⟩ - -end Fuel - -protected def BufferM.curOffset (fuel : Fuel) : BufferM Nat := return (←.contents).size - fuel - -/- Reader is a buffer with a fuel argument for tracking progress. -/ -def Reader (m : Progress) α := ∀(f : Fuel), BufferM (α × f.Pre m) - -/- Reader with strict progress -/ -abbrev SReader := Reader .strict - -/- Reader with any progress -/ -abbrev AReader := Reader .any - -protected def BufferM.ofReader (fuel : Fuel) (act : Reader m α) : BufferM (α × fuel.Pre m) := - act fuel - -namespace Reader - -protected def pure (a : α) : Reader .any α := fun f => pure (a, f.unchanged) - -protected def map (f : α → β) (h : Reader m α) : Reader m β := fun fuel => - (fun (a, f1) => (f a, f1)) <$> h fuel - -protected def bind (g : Reader m α) (h : α → Reader n β) : Reader (.meet m n) β := fun f => do - let (a, ⟨f1, f1p⟩) ← g f - let (b, ⟨f2, f2p⟩) ← h a f1 - pure (b, ⟨f2, Progress.meet_trans f1p f2p⟩) - -/-- -Specialized bind that reads a strict reader first so any reader may follow. - -Used to ensure progress values do not need to be inferred. --/ -protected def bindAny (g : SReader α) (h : α → AReader β) : SReader β := .bind g h - -instance : Functor (Reader m) where - map := .map - -instance : Monad AReader where - pure := .pure - bind := .bind - -instance : Bind SReader where - bind := .bind - -protected def fail (off : Nat) (msg : String) : Reader m α := fun _ => .fail off msg - -protected def ofLT (act : SReader α) : AReader α := fun f => - (fun (a, ⟨f2, p⟩) => (a, ⟨f2, Nat.le_of_lt p⟩)) <$> act f - -protected def ofM (act : ∀(fuel : Fuel), BufferM (α × fuel.Pre m)) : Reader m α := act - -@[inline] -protected def peekM (act : Nat → BufferM α) : AReader α := fun f => do - return (← act f, f.unchanged) - -@[inline] -protected def peekM' (act : BufferM α) : AReader α := fun f => do - return (← act, f.unchanged) - -instance : MonadReader ByteArray AReader where - read := .peekM' .contents - -protected def curOffset : AReader Nat := - .peekM fun f => return (← .contents).size - f - -def canRead (len : Nat) : AReader Bool := - .peekM fun f => pure (f >= len) - -protected def skip! (len : Nat) : AReader Unit := fun f => do - pure ((), ⟨f - len, Fuel.sub_le f len⟩) - -protected def skip (off : Nat) (len : Nat) : AReader Unit := fun f => do - if f ≥ len then - pure ((), ⟨f - len, Fuel.sub_le f len⟩) - else - .fail off s!"Skipped past end of file." - -def readByte : Reader m UInt8 := fun f => do - let bs ← .contents - if p : f > 0 then - assert! bs.size ≥ f - pure (bs[bs.size - f]!, .mk (f - 1) (by cases m <;> simp [Fuel] at *; omega)) - else - .fail bs.size "Read past end of file." - -def readBuffer (len : Nat) : AReader ByteArray := fun f => do - let contents ← .contents - let off := contents.size - f - if f ≥ len then - pure (contents.extract off (off + len), .mk _ (Fuel.sub_le f len)) - else - .fail off s!"Read past end of file." - -end Reader - -/- readUpTo/readSeqUpto -/ - -inductive Step (α : Type u) (β : Type v) where - | done : β → Step α β - | yield : α → Step α β - deriving Inhabited - -@[simp] theorem Fuel.sub_toNat (f : Fuel) (n : Nat) : (f - n).toNat = f.toNat - n := by rfl - -namespace BufferM - -def readUpto.aux (init : Fuel) (act : ∀(fuel : init.Pre .any), α → BufferM (α × fuel.val.Pre .strict)) (v : α) (fuel : init.Pre .any) (limit : Nat) : BufferM (α × init.Pre .any) := do - if (← .curOffset fuel.val) < limit then - let (v, ⟨fuel2, p⟩) ← act fuel v - have q : fuel2 < init := Progress.meet_trans fuel.property p - readUpto.aux init act v ⟨fuel2, Nat.le_of_lt q⟩ limit - else - pure (v, fuel) -termination_by fuel.val.toNat - -def readUpto (fuel : Fuel) (init : α) (limit : Nat) (act : ∀(fuel : fuel.Pre .any), α → BufferM (α × fuel.val.Pre .strict)) : BufferM (α × fuel.Pre .any) := do - readUpto.aux fuel act init fuel.unchanged limit - -def readSeqUpto (fuel : Fuel) (limit : Nat) (act : ∀(fuel : fuel.Pre .any), BufferM (α × fuel.val.Pre .strict)) : BufferM (Array α × fuel.Pre .any) := do - readUpto fuel #[] limit fun fuel a => (fun (v, p) => (a.push v, p)) <$> act fuel - -def readWhile (fuel : Fuel) (init : α) (act : α → UInt8 → Step α β) : BufferM (β × fuel.Pre .strict) := aux fuel.unchanged init - where aux (f : fuel.Pre .any) (v : α) : BufferM (β × fuel.Pre .strict) := do - let contents ← .contents - let o := contents.size - f.val.toNat - if p : f.val.toNat > 0 then - let b := contents[o]! - match act v b with - | .yield v => - let f2 : fuel.Pre .any := ⟨f.val - 1, Progress.meet_trans f.property (Nat.sub_le f.val.toNat 1) ⟩ - aux f2 v - | .done v => - let f2 : fuel.Pre .strict := ⟨f.val - 1, Progress.meet_trans f.property (Nat.sub_lt p Nat.zero_lt_one) ⟩ - pure (v, f2) - else - .fail o s!"Unexpected end of file" - termination_by f.val.toNat - -end BufferM - -namespace Reader - -def readUpto (init : α) (limit : Nat) (act : α → SReader α) : AReader α := - .ofM fun fuel => .readUpto fuel init limit (fun fuel a => .ofReader fuel.val (act a)) - -def readSeqUpto (limit : Nat) (act : SReader α) : AReader (Array α) := - .ofM fun fuel => .readSeqUpto fuel limit (.ofReader ·.val act) - -def readWhile (init : α) (act : α → UInt8 → Step α β) : SReader β := - .ofM fun fuel => .readWhile fuel init act - -end Reader diff --git a/Strata/DDM/Util/Fin.lean b/Strata/DDM/Util/Fin.lean index 00c8b8469..1876e7675 100644 --- a/Strata/DDM/Util/Fin.lean +++ b/Strata/DDM/Util/Fin.lean @@ -3,14 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module /- Extra declarations in Fin namespace -/ - +public section namespace Fin -instance : Min (Fin n) where +instance {n} : Min (Fin n) where min x y := ⟨min x.val y.val, by omega⟩ inductive Range (n : Nat) where @@ -18,8 +19,8 @@ inductive Range (n : Nat) where namespace Range -instance : ForIn m (Range n) (Fin n) where - forIn _ b f := loop f b 0 +instance {m n} : ForIn m (Range n) (Fin n) where + forIn _ b f := private loop f b 0 where loop {m} [Monad m] {β} (f : Fin n → β → m (ForInStep β)) (b : β) (i : Nat) : m β := if p : i < n then do match ← f ⟨i, p⟩ b with @@ -31,3 +32,6 @@ instance : ForIn m (Range n) (Fin n) where end Range def range (n : Nat) : Range n := .mk + +end Fin +end diff --git a/Strata/DDM/Util/Format.lean b/Strata/DDM/Util/Format.lean index a44d36271..1811306b8 100644 --- a/Strata/DDM/Util/Format.lean +++ b/Strata/DDM/Util/Format.lean @@ -3,6 +3,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module import Strata.DDM.Util.String @@ -75,5 +76,7 @@ def renderAux (a : Array Std.Format) : RenderM Unit := renderAux (a |>.push f) /-- Alternative render format for string -/ -def render (fmt : Std.Format) : String := +public def render (fmt : Std.Format) : String := renderAux #[fmt] 0 { soFar := "" } |>.snd |>.soFar + +end Std.Format diff --git a/Strata/DDM/Util/Graph/Tarjan.lean b/Strata/DDM/Util/Graph/Tarjan.lean index fb5e50e4b..7ce3f12b7 100644 --- a/Strata/DDM/Util/Graph/Tarjan.lean +++ b/Strata/DDM/Util/Graph/Tarjan.lean @@ -3,10 +3,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - +module import Strata.DDM.Util.Fin import Strata.DDM.Util.Vector +public section namespace Strata structure OutGraph (nodeCount : Nat) where @@ -38,7 +39,7 @@ protected def ofEdges! (n : Nat) (edges : List (Nat × Nat)) : OutGraph n := def nodesOut (g : OutGraph n) (node : Node n) : Array (Node n) := g.edges[node] -structure TarjanState (n : Nat) where +private structure TarjanState (n : Nat) where index : Fin (n+1) := 0 stk : Array (Fin n) := #[] indices : Vector (Fin (n + 1)) n := .replicate n (Fin.last n) @@ -47,10 +48,10 @@ structure TarjanState (n : Nat) where components : Array (Array (Fin n)) := #[] deriving Inhabited -def TarjanState.mergeLowlink (s : TarjanState n) (v : Fin n) (w : Fin n): TarjanState n := +private def TarjanState.mergeLowlink (s : TarjanState n) (v : Fin n) (w : Fin n): TarjanState n := { s with lowlinks := s.lowlinks.modify v (min s.lowlinks[w]) } -def popTo (v : Fin n) (s : TarjanState n) (comp : Array (Fin n)) : TarjanState n := +private def popTo (v : Fin n) (s : TarjanState n) (comp : Array (Fin n)) : TarjanState n := if p : s.stk.size > 0 then let w := s.stk[s.stk.size - 1] let s := { s with stk := s.stk.pop, onStack := s.onStack.set w false } @@ -62,7 +63,7 @@ def popTo (v : Fin n) (s : TarjanState n) (comp : Array (Fin n)) : TarjanState n else panic "Unexpected empty stack" -partial def strongconnect (g : OutGraph n) (v : Node n) (s : TarjanState n) : TarjanState n := +private partial def strongconnect (g : OutGraph n) (v : Node n) (s : TarjanState n) : TarjanState n := -- Set the depth index for v to the smallest unused index let s := { s with index := s.index + 1, @@ -100,8 +101,8 @@ def tarjan {n} (g : OutGraph n) : Array (Array (Node n)) := s s.components -/-- -info: #[#[0, 1, 2, 4], #[3]] --/ -#guard_msgs in -#eval tarjan (.ofEdges! 5 [(0, 1), (1, 2), (2, 3), (2, 0), (2, 4), (4, 3), (4, 1)]) +end Strata.OutGraph +end + +open Strata.OutGraph +#guard tarjan (.ofEdges! 5 [(0, 1), (1, 2), (2, 3), (2, 0), (2, 4), (4, 3), (4, 1)]) == #[#[0, 1, 2, 4], #[3]] diff --git a/Strata/DDM/Util/Graph/TopSort.lean b/Strata/DDM/Util/Graph/TopSort.lean deleted file mode 100644 index b97949acd..000000000 --- a/Strata/DDM/Util/Graph/TopSort.lean +++ /dev/null @@ -1,107 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - --- Topological sort implementation (not currently used) -import Strata.DDM.Util.Fin -import Strata.DDM.Util.Vector - -namespace Strata - -structure Graph where - nodeCount : Nat - edges : Vector (Array (Fin nodeCount)) nodeCount - deriving Inhabited - -namespace Graph - -protected def empty (count : Nat) : Graph where - nodeCount := count - edges := .mkVector count ∅ - -protected def addEdge (g : Graph) (f : Nat) (t : Nat) : Graph := - if p : f ≥ g.nodeCount then - @panic _ ⟨g⟩ s!"Invalid from edge {f}" - else if q : t ≥ g.nodeCount then - @panic _ ⟨g⟩ s!"Invalid to edge {t}" - else - { nodeCount := g.nodeCount, - edges := g.edges.modify ⟨t, by omega⟩ (·.push ⟨f, by omega⟩) - } - -protected def ofEdges (count : Nat) (edges : List (Nat × Nat)) : Graph := - let g : Graph := edges.foldl (fun g (f, t) => g.addEdge f t) (.empty count) - g - -def nodesInto (g : Graph) (node : Fin g.nodeCount) : Array (Fin g.nodeCount) := - g.edges[node] - -protected def addPre (p : Vector Bool n × Array (Fin n)) (pre : Fin n) := - if p.fst[pre] then - p - else - (p.fst.set pre true, p.snd.push pre) - -def topSort (g : Graph) : Array (Fin g.nodeCount) := Id.run do - -- Quick sort all edges to ensure they are sorted. - let g : Graph := { edges := g.edges.map (fun a => a.qsort) } - - let mut res : Array (Fin g.nodeCount) := #[] - let mut added : Vector Bool g.nodeCount := Vector.mkVector g.nodeCount false - -- Iterate through nodes in order - for init in Fin.range g.nodeCount do - -- Skip node already added. - if added[init] then - continue - added := added.set init true - -- Stack to store nodes to visit - let mut ctx : Array (Fin g.nodeCount) := #[init] - let mut stk : Array (Fin g.nodeCount) := #[] - while !ctx.isEmpty do - let c := ctx.getD (ctx.size - 1) init - ctx := ctx.pop - stk := stk.push c - -- Add all nodes into - let (added2, ctx2) := g.nodesInto c |>.foldl Graph.addPre (added, ctx) - added := added2 - ctx := ctx2 - res := stk.foldr (fun c r => r.push c) res - return res - -abbrev Node (g : Graph) : Type := Fin g.nodeCount - -end Graph - -/-- -info: #[3, 2, 1, 0] --/ -#guard_msgs in -#eval Graph.ofEdges 4 [(2, 1), (1, 3), (1, 0), (3, 2)] |>.topSort - -/-- -info: #[1, 2, 0, 3] --/ -#guard_msgs in -#eval Graph.ofEdges 4 [(2, 0), (1,0)] |>.topSort - -/-- -info: #[1, 2, 0, 3] --/ -#guard_msgs in -#eval Graph.ofEdges 4 [(1, 0), (2,0)] |>.topSort - -/-- -info: #[1, 0] --/ -#guard_msgs in -#eval Graph.ofEdges 2 [(1, 0), (0,1)] |>.topSort - -/-- -info: #[1, 0] --/ -#guard_msgs in -#eval Graph.ofEdges 2 [(0, 1), (1, 0)] |>.topSort - -end Strata diff --git a/Strata/DDM/Util/Ion.lean b/Strata/DDM/Util/Ion.lean index 5132e22e9..4f300962f 100644 --- a/Strata/DDM/Util/Ion.lean +++ b/Strata/DDM/Util/Ion.lean @@ -3,122 +3,35 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module + +public import Strata.DDM.Util.Ion.AST +public import Strata.DDM.Util.Ion.Deserialize +public import Strata.DDM.Util.Ion.Serialize +public import Strata.DDM.Util.Ion.SymbolTable -/- -A standalone Ion serialization file. --/ import Strata.DDM.Util.Fin import Strata.DDM.Util.Ion.Deserialize import Strata.DDM.Util.Ion.JSON -import Strata.DDM.Util.Ion.Serialize -import Strata.DDM.Util.Lean -import Lean.Elab.Command +public section namespace Ion -structure SymbolTableImport where - name : String - version : Nat - max_id : Option Nat - -structure SymbolTable where - array : Array String - map : Std.HashMap String SymbolId - locals : Array String -deriving Inhabited - -namespace SymbolTable - -def ionSharedSymbolTableEntries : Array String := #[ - "$ion", "$ion_1_0", "$ion_symbol_table", "name", "version", - "imports", "symbols", "max_id", "$ion_shared_symbol_table" -] - -/-- -Minimal system symbol table. --/ -def system : SymbolTable where - array := #[""] ++ ionSharedSymbolTableEntries - map := ionSharedSymbolTableEntries.size.fold (init := {}) fun i _ m => - m.insert ionSharedSymbolTableEntries[i] (.mk (i+1)) - locals := #[] - -instance : GetElem? SymbolTable SymbolId String (fun tbl idx => idx.value < tbl.array.size) where - getElem tbl idx p := tbl.array[idx.value] - getElem! tbl idx := assert! idx.value < tbl.array.size; tbl.array[idx.value]! - getElem? tbl idx := tbl.array[idx.value]? - -def symbolId! (sym : String) (tbl : SymbolTable) : SymbolId := - match tbl.map[sym]? with - | some i => i - | none => panic! s!"Unbound symbol {sym}" - -/-- -Intern a string into a symbol. --/ -def intern (sym : String) (tbl : SymbolTable) : SymbolId × SymbolTable := - match tbl.map[sym]? with - | some i => (i, tbl) - | none => - let i : SymbolId := .mk (tbl.array.size) - let tbl := { - array := tbl.array.push sym, - map := tbl.map.insert sym i, - locals := tbl.locals.push sym - } - (i, tbl) - -def ofLocals (locals : Array String) : SymbolTable := - locals.foldl (init := .system) (fun tbl sym => tbl.intern sym |>.snd) - -instance : Lean.Quote SymbolTable where - quote st := Lean.Syntax.mkCApp ``SymbolTable.ofLocals #[Lean.quote st.locals] - -end SymbolTable - -namespace SymbolId - -def systemSymbolId! (sym : String) : SymbolId := SymbolTable.system |>.symbolId! sym - --- Use metaprogramming to declare `{sym}SymbolId : SymbolId` for each system symbol. -section -open Lean -open Elab.Command - -syntax (name := declareSystemSymbolIds) "#declare_system_symbol_ids" : command -- declare the syntax - -@[command_elab declareSystemSymbolIds] -def declareSystemSymbolIdsImpl : CommandElab := fun _stx => do - for sym in SymbolTable.ionSharedSymbolTableEntries do - -- To simplify name, strip out non-alphanumeric characters. - let simplifiedName : String := .mk <| sym.data.filter (·.isAlphanum) - let leanName := Lean.mkLocalDeclId simplifiedName - let cmd : TSyntax `command ← `(command| - def $(leanName) : SymbolId := systemSymbolId! $(Lean.Syntax.mkStrLit sym) - ) - elabCommand cmd - -#declare_system_symbol_ids - -end - -end SymbolId - structure Position where indices : Array Nat := #[] deriving Repr namespace Position -def root : Position := {} +private def root : Position := {} -def push (p : Position) (index : Nat) : Position where +private def push (p : Position) (index : Nat) : Position where indices := p.indices.push index -def ofList (l : List Nat) : Position where +private def ofList (l : List Nat) : Position where indices := l.toArray -instance : ToString Position where +public instance : ToString Position where toString p := let l := p.indices |>.map toString |>.toList .intercalate "." ("root" :: l) @@ -138,17 +51,20 @@ def localSymbolTableValue (tbl : SymbolTable) : Ion SymbolId := (.symbols, .list <| tbl.locals |>.map .string) ] -instance : Repr SymbolTable where +private instance : Repr SymbolTable where reprPrec tbl _ := repr tbl.localSymbolTableValue def ofLocalSymbolTable (v : Ion SymbolId) : Except (Position × String) SymbolTable := do let throwAt {α : Type} p s : Except _ α := throw (p, s) - let .annotation #[a] s := v + let .annotation as s := v.app | throwAt .root "Expected annotation." + let .isTrue asz := inferInstanceAs (Decidable (as.size = 1)) + | throwAt .root "Expected single element" + let a := as[0] if a ≠ SymbolId.ionsymboltable then throwAt .root "Expected ionsymboltable annotation." let spos : Position := .root |>.push 0 - let .struct e := s + let .struct e := s.app | throwAt spos "Expected struct" let mut importsSeen : Bool := false let mut locals : Array String := #[] @@ -160,10 +76,10 @@ def ofLocalSymbolTable (v : Ion SymbolId) : Except (Position × String) SymbolTa throwAt p s!"Multiple imports" importsSeen := true else if nm = .symbols then - let .list localVals := v + let .list localVals := v.app | throwAt p s!"Expected locals" for i in Fin.range localVals.size do - let .string s := localVals[i] + let .string s := localVals[i].app | throwAt (p.push i) "Expected string" locals := locals.push s pure <| .ofLocals locals @@ -173,7 +89,7 @@ end SymbolTable /-- Monad for constructing local symbol table for values. -/ -def InternM := StateM SymbolTable +@[expose] def InternM := StateM SymbolTable deriving Monad def runIntern (act : InternM α) (symbols : SymbolTable := .system) : SymbolTable × α := @@ -187,7 +103,7 @@ namespace Ion /-- Resolve string symbols to symbol ids by constructing local symbol table. -/ -def mapSymbolM [Monad m] (f : α → m β) : Ion α → m (Ion β) +def mapSymbolM {m α β} [Monad m] (f : α → m β) : Ion α → m (Ion β) | .null tp => pure <| .null tp | .bool x => pure <| .bool x | .int x => pure <| .int x @@ -195,6 +111,7 @@ def mapSymbolM [Monad m] (f : α → m β) : Ion α → m (Ion β) | .decimal d => pure <| .decimal d | .string s => pure <| .string s | .symbol s => .symbol <$> f s +| .blob s => pure <| .blob s | .list a => .list <$> a.attach.mapM fun ⟨a, _⟩ => a.mapSymbolM f | .sexp a => .sexp <$> a.attach.mapM fun ⟨a, _⟩ => a.mapSymbolM f | .struct a => .struct <$> a.attach.mapM fun ⟨(nm, v), p⟩ => @@ -230,3 +147,6 @@ Write a list of Ion values to file. -/ def writeBinaryFile (path : System.FilePath) (values : List (Ion String)) (symbols : SymbolTable := system): IO Unit := do IO.FS.writeBinFile path (internAndSerialize values symbols) + +end Ion +end diff --git a/Strata/DDM/Util/Ion/AST.lean b/Strata/DDM/Util/Ion/AST.lean index 28b1e63c3..79275a425 100644 --- a/Strata/DDM/Util/Ion/AST.lean +++ b/Strata/DDM/Util/Ion/AST.lean @@ -3,7 +3,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -import Strata.DDM.Util.Decimal +module + +public import Strata.DDM.Util.ByteArray +public import Strata.DDM.Util.Decimal + +public section namespace Ion @@ -36,7 +41,6 @@ def codes : Array CoreType := #[ end CoreType - /-- Ion values. @@ -53,7 +57,8 @@ inductive IonF (Sym : Type) (Ind : Type) -- TODO: Add timestamp | string (s : String) | symbol (s : Sym) --- TODO: Add blob and clob +| blob (a : ByteArray) +-- TODO: Add clob | struct (a : Array (Sym × Ind)) | list (a : Array Ind) | sexp (a : Array Ind) @@ -66,27 +71,29 @@ structure Ion (α : Type) where namespace Ion -def null (tp : CoreType := .null) : Ion Sym := .mk (.null tp) +@[expose] def null {Sym} (tp : CoreType := .null) : Ion Sym := .mk (.null tp) + +@[expose] def bool {Sym} (b : Bool) : Ion Sym := .mk (.bool b) -def bool (b : Bool) : Ion Sym := .mk (.bool b) +@[expose] def int {Sym} (i : Int) : Ion Sym := .mk (.int i) -def int (i : Int) : Ion Sym := .mk (.int i) +@[expose] def float {Sym} (f : Float) : Ion Sym := .mk (.float f) -def float (f : Float) : Ion Sym := .mk (.float f) +@[expose] def decimal {Sym} (d : Decimal) : Ion Sym := .mk (.decimal d) -def decimal (d : Decimal) : Ion Sym := .mk (.decimal d) +@[expose] def string {Sym} (s : String) : Ion Sym := .mk (.string s) -def string (s : String) : Ion Sym := .mk (.string s) +@[expose] def symbol {Sym} (s : Sym) : Ion Sym := .mk (.symbol s) -def symbol (s : Sym) : Ion Sym := .mk (.symbol s) +@[expose] def blob {Sym} (s : ByteArray) : Ion Sym := .mk (.blob s) -def struct (s : Array (Sym × Ion Sym)) : Ion Sym := .mk (.struct s) +@[expose] def struct {Sym} (s : Array (Sym × Ion Sym)) : Ion Sym := .mk (.struct s) -def list (a : Array (Ion Sym)) : Ion Sym := .mk (.list a) +@[expose] def list {Sym} (a : Array (Ion Sym)) : Ion Sym := .mk (.list a) -def sexp (a : Array (Ion Sym)) : Ion Sym := .mk (.sexp a) +@[expose] def sexp {Sym} (a : Array (Ion Sym)) : Ion Sym := .mk (.sexp a) -def annotation (annot : Array Sym) (v : Ion Sym) : Ion Sym := .mk (.annotation annot v) +@[expose] def annotation {Sym} (annot : Array Sym) (v : Ion Sym) : Ion Sym := .mk (.annotation annot v) end Ion @@ -104,6 +111,7 @@ protected def zero : SymbolId := ⟨0⟩ end SymbolId instance : Coe SymbolId (Ion SymbolId) where - coe := .symbol + coe := private .symbol end Ion +end diff --git a/Strata/DDM/Util/Ion/Deserialize.lean b/Strata/DDM/Util/Ion/Deserialize.lean index 2b9f72cb3..d8d2cde05 100644 --- a/Strata/DDM/Util/Ion/Deserialize.lean +++ b/Strata/DDM/Util/Ion/Deserialize.lean @@ -3,9 +3,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Util.Deser -import Strata.DDM.Util.Ion.AST +public import Strata.DDM.Util.Ion.AST + +import Strata.DDM.Util.ByteArray namespace Ion @@ -15,38 +17,94 @@ structure TypeDesc where def TypeDesc.code (d : TypeDesc) : UInt8 := d.toByte >>> 0x4 def TypeDesc.length (d : TypeDesc) : UInt8 := d.toByte &&& 0xF -open Strata.Deser - -inductive PartialValue -| list (vals : Array (Ion SymbolId)) -| sexp (vals : Array (Ion SymbolId)) -| struct (vals : Array (SymbolId × Ion SymbolId)) -| ann (annot : Array SymbolId) -deriving Inhabited, Repr - -namespace PartialValue +inductive Step (α : Type u) (β : Type v) where + | done : β → Step α β + | yield : α → Step α β + deriving Inhabited -def append (pv : PartialValue) (s : SymbolId) (v : Ion SymbolId) : PartialValue ⊕ Ion SymbolId := - match pv with - | .list vals => .inl <| .list <| vals.push v - | .sexp vals => .inl <| .sexp <| vals.push v - | .struct vals => .inl <| .struct <| vals.push (s, v) - | .ann a => .inr <| .mk (.annotation a v) +abbrev ReadM := Except (Nat × String) -end PartialValue +abbrev SReadM (off : Nat) (α : Type) := ReadM (α × { new : Nat // new > off }) -def readTypeDesc : SReader TypeDesc := .ofByte <$> .readByte +@[inline] +def rfail {α} (off : Nat) (msg : String) : ReadM α := .error (off, msg) -def readVarUInt : SReader Nat := - .readWhile 0 fun v b => - let v := (v <<< 7) ||| (b &&& 0x7f).toNat - if b &&& 0x80 = 0x80 then - .done v +@[specialize] +def readWhileAux {α β} (bytes : @ByteArray) (base : Nat) (init : α) (off : Nat) (act : α → UInt8 → Step α β) (p : off ≥ base := by omega) : SReadM base β := + if p : off < bytes.size then + match act init bytes[off] with + | .done r => .ok (r, ⟨off+1, by omega⟩) + | .yield a => readWhileAux bytes base a (off+1) act + else + rfail off "Unexpected end of file" + +@[inline] +def readWhile {α β} (bytes : @ByteArray) (init : α) (base : Nat) (p : base < bytes.size) (act : α → UInt8 → Step α β) : SReadM base β := do + match act init bytes[base] with + | .done r => + .ok (r, ⟨base+1, by omega⟩) + | .yield a => + readWhileAux bytes base a (base+1) act + +@[specialize] +def readUpTo {α} (bytes : @ByteArray) (init : α) (off : Nat) (limit : Nat) (limitp : limit ≤ bytes.size) (f : α → UInt8 → α) : α := + if offp : off < limit then + let b := bytes[off] + let v := f init b + readUpTo bytes v (off+1) limit limitp f + else + init + +@[inline] +def readVarUInt (bytes : @ByteArray) (off : Nat) (p : off < bytes.size := by omega) : SReadM off Nat := + let b := bytes[off] + if b &&& 0x80 = 0x80 then + let v := b &&& 0x7f + (pure (v.toNat, ⟨off+1, by omega⟩) : ReadM _) + else + -- VarUInts are on critical path so we special case for 16bit version too. + let base := off + let off := off + 1 + if p : off ≥ bytes.size then + rfail off s!"Unexpected end of file" else - .yield v + let v := b + let b := bytes[off] + if b &&& 0x80 = 0x80 then + let v := (v.toNat.toUInt16 <<< 7) ||| (b &&& 0x7f).toNat.toUInt16 + .ok (v.toNat, ⟨off+1, by omega⟩) + else + let v := (v.toNat <<< 7) ||| b.toNat + readWhileAux bytes base v (off+1) fun v b => + let v := (v <<< 7) ||| (b &&& 0x7f).toNat + if b &&& 0x80 = 0x80 then + .done v + else + .yield v + +@[inline] +def readVarUInt' (bytes : @ByteArray) (off : Nat) : SReadM off Nat := do + let .isTrue p := inferInstanceAs (Decidable (off < bytes.size)) + | rfail off s!"Unexpected end of file" + readVarUInt bytes off + +def readVarInt (bytes : @ByteArray) (off : Nat) (p : off < bytes.size := by omega) : SReadM off Int := do + let b := bytes[off] + let isNeg := b &&& 0x40 = 0x40 + let v := (b &&& 0x3f).toNat + if b &&& 0x80 = 0x80 then + let v : Int := if isNeg then .negOfNat v else .ofNat v + .ok (v, ⟨off+1, by omega⟩) + else + readWhileAux bytes off v (off+1) fun (v : Nat) b => + let v := (v <<< 7) ||| (b &&& 0x7f).toNat + if b &&& 0x80 = 0x80 then + .done <| if isNeg then .negOfNat v else .ofNat v + else + .yield v -def readVarInt : SReader Int := - .readWhile 0 fun (v : Nat) b => +/- + readWhile bytes 0 off (by omega) fun (v : Nat) b => if b &&& 0x80 = 0x80 then let isNeg := b &&& 0x40 = 0x40 let v := (v <<< 7) ||| (b &&& 0x3f).toNat @@ -55,321 +113,439 @@ def readVarInt : SReader Int := else let v := (v <<< 7) ||| (b &&& 0x7f).toNat .yield v +-/ -def readUInt (limit : Nat) : AReader Nat := do - .readUpto 0 limit fun v => (v <<< 8 ||| ·.toNat) <$> .readByte +@[inline] +def readUInt (bytes : @ByteArray) (off : Nat) (limit : Nat) (limitp : limit ≤ bytes.size := by omega) : Nat := + if limit ≤ off + 8 then + let r := readUpTo (α := UInt64) bytes 0 off limit limitp fun v b => v <<< 8 ||| b.toNat.toUInt64 + r.toNat + else + readUpTo bytes 0 off limit limitp fun v b => v <<< 8 ||| b.toNat -def readInt (limit : Nat) : SReader (Bool × Nat) := do - .bindAny .readByte fun b => do +@[inline] +def readInt (bytes : @ByteArray) (off : Nat) (limit : Nat) (limitp : limit ≤ bytes.size) : Bool × Nat := + let b := bytes[off]! let isNeg := b &&& 0x80 = 0x80 - let r ← .readUpto (b &&& 0x7f).toNat limit fun v => - (v <<< 8 ||| ·.toNat) <$> .readByte - pure (isNeg, r) + let r := readUpTo bytes (b &&& 0x7f).toNat (off+1) limit limitp fun v b => v <<< 8 ||| b.toNat + (isNeg, r) @[reducible] def NatLe (n : Nat) := { x : Nat // x ≤ n } -instance : Inhabited (NatLe n) where +instance {n} : Inhabited (NatLe n) where default := ⟨0, by omega⟩ -def readLength (td : TypeDesc) (limit : Nat) : AReader (NatLe limit) := do - let len := td.length.toNat - if len < 14 then - let off ← .curOffset - if p : off + len ≤ limit then - pure <| .mk (off + len) p - else - .fail ((←.curOffset) - 1) s!"Length is too large" +@[inline] +def readString (bytes : @ByteArray) (off : Nat) (limit : Nat) : ReadM String := do + let b := bytes.extract off limit + if h : b.IsValidUTF8 then + pure <| String.fromUTF8 b h else - let len ← .ofLT readVarUInt - let off ← .curOffset - if p : off + len ≤ limit then - return .mk (off + len) p - else - .fail off s!"Length is too large" + rfail off s!"Invalid UTF8 string" -def readString (limit : Nat) : AReader String := do - let off ← .curOffset - let b ← .readBuffer (limit - off) - match String.fromUTF8? b with - | some s => pure s - | none => .fail off s!"Invalid UTF8 string" - -def readDouble : AReader Float := do - let n ← readUInt <| (←.curOffset) + 8 - pure <| .ofBits n.toUInt64 +def readDouble (bytes : @ByteArray) (off : Nat) (limitp : off + 8 ≤ bytes.size) : Float := + let n := readUInt bytes off (off+8) + .ofBits n.toUInt64 inductive Token (limit : Nat) -| null (tp : CoreType) -| bool (b : Bool) -| int (i : Int) -| float (f : Float) -| decimal (d : Decimal) --- TODO: Add timestamp -| string (s : String) -| symbol (s : SymbolId) -| bvm (major minor : UInt8) -| nop | startList (end_limit : NatLe limit) | startSExp (end_limit : NatLe limit) | startStruct (end_limit : NatLe limit) | startAnn (end_limit : NatLe limit) (annot : Array SymbolId) deriving Inhabited -def readToken (limit : Nat) : SReader (Token limit) := - .bind .curOffset fun off => - .bindAny readTypeDesc fun typeDesc => do - if typeDesc.length = 15 then - match typeDesc.code with - | 0xE => - .fail off s!"Annotation cannot have length 15" - | 0xF => - .fail off s!"Reserved type descriptor code {typeDesc.code}" - | _ => - return .null (Ion.CoreType.codes[typeDesc.code.toNat]!) - match typeDesc.code with - | 0x0 => - -- This is a no-op - let .mk limit _ ← readLength typeDesc limit - .skip off (limit - (←.curOffset)) - return .nop - | 0x1 => - match typeDesc.length with - | 0 => - return .bool false - | 1 => - return .bool true - | _ => - .fail off s!"Invalid bool type description length {typeDesc.length}" - | 0x2 | 0x3 => - let .mk limit _ ← readLength typeDesc limit - let off ← .curOffset - let mag ← readUInt limit - if typeDesc.code = 0x3 then - if mag = 0 then - .fail off s!"Negative number must have non-zero agnitude." - return .int (.negOfNat mag) - else - return .int (.ofNat mag) - | 0x4 => - let .mk limit _ ← readLength typeDesc limit - if limit ≠ (← .curOffset) + 8 then - .fail off s!"Only double float fields supported." - let v ← readDouble - return .float v - | 0x5 => -- decimal - if typeDesc.length = 0 then - return .decimal .zero - else - let .mk limit _ ← readLength typeDesc limit - let e ← .ofLT readVarInt - let (isn, c) ← .ofLT <| readInt limit - let mantissa : Int := if isn then .negOfNat c else .ofNat c - let d : Decimal := { mantissa, exponent := e } - pure (.decimal d) - | 0x6 => - .fail off "Timestamps not supported" - | 0x7 => -- symbol - let .mk limit _ ← readLength typeDesc limit - let symId ← Ion.SymbolId.mk <$> readUInt limit - return (.symbol symId) - | 0x8 => -- string - let .mk limit _ ← readLength typeDesc limit - let s ← readString limit - return .string s - | 0x9 => - .fail off "clob not supported" - | 0xA => - .fail off "blob not supported" - | 0xB => -- list - .startList <$> readLength typeDesc limit - | 0xC => -- sexp - .startSExp <$> readLength typeDesc limit - | 0xD => -- struct - .startStruct <$> readLength typeDesc limit - | 0xE => -- annotation - let len := typeDesc.length - if len = 0 then - -- binary Version marker - let off ← .curOffset - let contents ← read - if p : off+2 < contents.size then - let major := contents[off] - let minor := contents[off+1] - let marker := contents[off+2] - if (major, minor) ≠ (1, 0) then - .fail (off-1) s!"Ion {major}.{minor} not supported." - if marker ≠ 0xea then - .fail (off-1) s!"Bad terminator for binary version marker {marker}" - .skip! 3 - pure (.bvm major minor) - else - .fail off s!"End of file" - else - let limit ← readLength typeDesc limit - let annot_len ← .ofLT readVarUInt - let annot_limit := (← .curOffset) + annot_len - if annot_limit ≥ limit.val then - .fail (← .curOffset) "Annotation missing value." - let ann ← .readSeqUpto annot_limit (.mk <$> readVarUInt) - pure (.startAnn limit ann) - | _ => - .fail off s!"Reserved type descriptor code {typeDesc.code}" +@[specialize] +def readSymbolIdSeqAux (bytes : @ByteArray) (ss : Array SymbolId) (off : Nat) (limit : Nat) (limitp : limit ≤ bytes.size) : ReadM (Array SymbolId) := do + if p : off < limit then + let (symId, ⟨off, _⟩) ← readVarUInt bytes off + let ss := ss.push (.mk symId) + readSymbolIdSeqAux bytes ss off limit limitp + else + .ok ss + +@[inline] +def readSymbolIdSeq (bytes : @ByteArray) (off : Nat) (limit : Nat) (limitp : limit ≤ bytes.size) : ReadM (Array SymbolId) := do + readSymbolIdSeqAux bytes #[] off limit limitp + +structure Length (off : Nat) (limit : Nat) where + new_off : Nat + new_limit : Nat + valid : off ≤ new_off ∧ new_off ≤ new_limit ∧ new_limit ≤ limit + +@[inline] +def readLength (bytes : @ByteArray) (off : Nat)(td_length : UInt8) + : ReadM (Length off bytes.size) := do + let len := td_length.toNat + if len < 14 then + if p : off + len ≤ bytes.size then + return ⟨off, off+len, by omega⟩ + else + rfail (off - 1) s!"Length {len} at {off} is too large" + else + let (len, ⟨off, offp⟩) ← readVarUInt' bytes off + if p : off + len ≤ bytes.size then + return ⟨off, off + len, by omega⟩ + else + rfail off s!"Long length is too large" + +abbrev NatGT base := { new : Nat // new > base } + +@[inline] +def appendIfNonempty (prev : Array (Array (Ion SymbolId))) (v : Array (Ion SymbolId)) : Array (Array (Ion SymbolId)) := + if v.isEmpty then + prev + else + prev.push v + +inductive PartialTag +| list +| sexp +| struct +| ann (annot : Array SymbolId) +deriving Inhabited, Repr + +namespace PartialTag + +@[inline] +def isList : PartialTag → Bool +| .list => true +| _ => false + +@[inline] +def isStruct : PartialTag → Bool +| .struct => true +| _ => false + +end PartialTag structure DeserializeState (size : Nat) where prev : Array (Array (Ion SymbolId)) - cur : Array (Ion SymbolId) - stack : Array (SymbolId × PartialValue × NatLe size) + symbols : Array SymbolId + values : Array (Ion SymbolId) + tags : Array PartialTag + value_indices : Vector Nat tags.size + limits : Vector (NatLe size) tags.size + off : Nat deriving Inhabited, Repr namespace DeserializeState +@[inline] def empty (size : Nat) : DeserializeState size where prev := #[] - cur := #[] - stack := #[] - -def inStruct {size} (r : DeserializeState size) : Bool := - match r.stack.back? with - | some (_, .struct _, _) => true - | _ => false + symbols := #[] + values := #[] + tags := #[] + value_indices := #v[] + limits := #v[] + off := 0 def limit {size} (r : DeserializeState size) : NatLe size := - match r.stack.back? with + match r.limits.back? with + | some l => l | none => ⟨size, Nat.le_refl _⟩ - | some (_, _, l) => l - -def appendValue {size : Nat} (r : DeserializeState size) (s : SymbolId) (v : Ion SymbolId) : DeserializeState size := - if isz : r.stack.size = 0 then - { r with cur := r.cur.push v } - else - let (old_sym, pv, limit) := r.stack.back! - let stack' := r.stack.pop - match p : pv.append s v with - | .inl pv => - { r with stack := stack'.push (old_sym, pv, limit) } - | .inr v => - have _ : stack'.size < r.stack.size := by - simp [stack'] - omega - let r' := { r with stack := stack' } - r'.appendValue old_sym v -termination_by r.stack.size - -theorem appendValue_stackSize {size : Nat} (r : DeserializeState size) (s : SymbolId) (v : Ion SymbolId) : - (r.appendValue s v).stack.size ≤ r.stack.size := by - unfold appendValue - if isz : r.stack.size = 0 then - simp [isz] - else - simp [isz] - split - case h_1 eq => - simp - omega - case h_2 eq => - apply Nat.le_trans - apply appendValue_stackSize - simp -termination_by r.stack.size /-- Updates the deserialization state by finalizing finished partial values on top of stack. -/ -def popFinishedValues {size} (off : Nat) (ds : DeserializeState size) : Except String (DeserializeState size) := - if off < ds.limit.val then - pure ds - else if p : ds.stack.size = 0 then - pure ds +partial def popFinishedValues {size} {base} (ds : DeserializeState size) (off : Nat) (p : off > base := by grind) + : ReadM { s : DeserializeState size // s.off > base } := + if h : ds.tags.size = 0 then + return ⟨{ds with off := off }, p⟩ else do - let (sym, pv, _) := ds.stack.back! - let top2 : DeserializeState size := { ds with stack := ds.stack.pop } - have p : top2.stack.size < ds.stack.size := by - simp [top2] - omega - let v : Value SymbolId ← - match pv with - | .list a => pure <| .mk <| .list a - | .sexp a => pure <| .mk <| .sexp a - | .struct a => pure <| .mk <| .struct a - | .ann _ => .error s!"Annotation without value" - let top3 : DeserializeState size := top2.appendValue sym v - have q := top2.appendValue_stackSize sym v - have p : top3.stack.size < ds.stack.size := by - simp [top3] - omega - popFinishedValues off top3 -termination_by ds.stack.size - -def close {size} (r : DeserializeState size) : Array (Array (Ion SymbolId)) := - if r.cur.isEmpty then - r.prev + let { prev, symbols, values, tags, value_indices, limits, .. } := ds + have _ : NeZero tags.size := ⟨h⟩ + let ⟨limit, _⟩ := limits.back + if off < limit then + return ⟨{ds with off := off }, p⟩ + let value_index := value_indices.back + let (symbols, v) ← + match tags.back with + | .list => + let a := values.extract value_index + pure <| (symbols, Ion.mk (.list a)) + | .sexp => + let a := values.extract value_index + pure (symbols, .mk <| (.sexp a)) + | .struct => + let cnt := values.size - value_index + let .isTrue symp := inferInstanceAs (Decidable (symbols.size ≥ cnt)) + | rfail off "Bad symbols" + let base := symbols.size - cnt + let v := Array.ofFn fun (i : Fin cnt) => (symbols[base + i.val], values[value_index + i]) + pure (symbols.shrink base, .mk <| .struct v) + | .ann ann => + let a := values.extract value_index + if p : a.size = 1 then + pure (symbols, .annotation ann a[0]) + else + rfail off s!"Annotation without value" + let values := values.shrink value_index + let ds := { + prev + symbols + values := values.push v + tags := tags.pop + value_indices := value_indices.pop.cast (by grind) + limits := limits.pop.cast (by grind) + off + } + popFinishedValues ds off p + +def startNew {size} (ds : DeserializeState size) (_ : ds.tags.size = 0) : DeserializeState size := + assert! ds.symbols.size = 0 + if ds.values.isEmpty then + ds else - r.prev.push r.cur - -def startNew {size} (r : DeserializeState size) : DeserializeState size := - { prev := r.close, cur := #[], stack := #[] } - -def pushPartialValue {size} (r : DeserializeState size) (sym : SymbolId) (pv : PartialValue) (limit : NatLe size) : DeserializeState size := - { r with stack := r.stack.push (sym, pv, limit) } + { ds with prev := appendIfNonempty ds.prev ds.values, values := #[] } + +@[inline] +def pushValue {size : Nat} (ds : DeserializeState size) (v : Ion SymbolId) : DeserializeState size := + { ds with values := ds.values.push v } + +@[inline] +def appendValue {size : Nat} {base : Nat} (ds : DeserializeState size) (off : Nat) (v : Ion SymbolId) (p : base < off := by omega) + : ReadM { s : DeserializeState size // s.off > base } := do + ds |>.pushValue v |>.popFinishedValues off + +@[inline] +def pushPartialValue {size} {base} + (ds : DeserializeState size) + (off : Nat) + (tag : PartialTag) + (limit : NatLe size) + (p : off > base := by omega) + : ReadM { s : DeserializeState size // s.off > base } := do + if off < limit then + let ds := { + ds with + tags := ds.tags.push tag + value_indices := ds.value_indices.push ds.values.size |>.cast (by simp) + limits := ds.limits.push limit |>.cast (by simp) + off := off + } + pure ⟨ds, p⟩ + else + let v ← + match tag with + | .list => + pure <| Ion.mk (.list #[]) + | .sexp => + pure <| Ion.mk (.sexp #[]) + | .struct => + pure <| .mk (.struct #[]) + | .ann ann => + rfail off s!"Annotation without value" + ds.pushValue v |>.popFinishedValues off p end DeserializeState -def cleanupRecords {size} (s : DeserializeState size) : AReader (DeserializeState size) := do - let off ← .curOffset - match s.popFinishedValues off with - | .error msg => - .fail off msg - return default - | .ok top => - pure top - +def readNopToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + -- This is a no-op + let ⟨_, limit, vp⟩ ← readLength bytes (off+1) td_length + ds.popFinishedValues limit + +def readBoolToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + if td_length ≥ 2 then + rfail off s!"Invalid bool type description length {td_length}" + let b := td_length == 1 + ds.appendValue (off+1) (.bool b) + +def readPosIntToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let mag := readUInt bytes off limit + let v := .ofNat mag + ds.appendValue limit (.int v) + +def readNegIntToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let mag := readUInt bytes off limit + if mag = 0 then + rfail off s!"Negative number must have non-zero agnitude." + let v := .negOfNat mag + ds.appendValue limit (.int v) + +def readDoubleToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off2, limit, p⟩ ← readLength bytes (off+1) td_length + if h : limit ≠ off2 + 8 then + rfail off s!"Only double float fields supported." + else + let v := readDouble bytes off2 (by grind) + ds.appendValue limit (.float v) + +def readDecimalToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + if td_length = 0 then + ds.appendValue (off+1) (.decimal .zero) + else + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let .isTrue p := inferInstanceAs (Decidable (off < limit)) + | rfail off s!"Unexpected empty decimal" + let ⟨e, off⟩ ← readVarInt bytes off + let d : Decimal := + if off < limit then + let (isn, c) := readInt bytes off limit (by omega) + let mantissa : Int := if isn then .negOfNat c else .ofNat c + { mantissa, exponent := e } + else + { mantissa := 0, exponent := e } + ds.appendValue limit (.decimal d) + +def readSymbolToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let symId := Ion.SymbolId.mk <| readUInt bytes off limit + ds.appendValue limit (.symbol symId) + +def readStringToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let s ← readString bytes off limit + ds.appendValue limit (.string s) + +def readBlobToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + let a := bytes.extract off limit + ds.appendValue limit (.blob a) + +def readListToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + ds.pushPartialValue off .list ⟨limit, by omega⟩ + +def readSexpToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + ds.pushPartialValue off .sexp ⟨limit, by omega⟩ + +def readStructToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let ⟨off, limit, p⟩ ← readLength bytes (off+1) td_length + ds.pushPartialValue off .struct ⟨limit, by omega⟩ + +def readAnnotationToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) + (td_length : UInt8) (off : Nat) (_ : off < bytes.size) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let off := off+1 + if td_length = 0 then + -- binary Version marker + if p : off+2 < bytes.size then + let major := bytes[off] + let minor := bytes[off+1] + let marker := bytes[off+2] + if (major, minor) ≠ (1, 0) then + rfail (off-1) s!"Ion {major}.{minor} not supported." + if marker ≠ 0xea then + rfail (off-1) s!"Bad terminator for binary version marker {marker}" + if p : !ds.tags.isEmpty then + rfail off s!"Encountered binary version marker inside term" + else + return ⟨{ ds.startNew (by simp_all) with off := off + 3 }, by grind⟩ + else + rfail off s!"End of file" + else + let ⟨off, limit, validp⟩ ← readLength bytes off td_length + let ⟨annot_len, off⟩ ← readVarUInt' bytes off + let annot_limit := off + annot_len + if h : annot_limit ≥ limit then + rfail off "Annotation missing value." + else + let ann ← readSymbolIdSeq bytes off annot_limit (by omega) + ds.pushPartialValue annot_limit (.ann ann) ⟨limit, by omega⟩ + +@[inline] +def readNotSupportedToken {α} (name : String) (off : Nat) : ReadM α := + rfail off s!"{name} not supported" + +@[inline] +def readToken (bytes : @ByteArray) (ds : DeserializeState bytes.size) (off : Nat) (p : off < bytes.size := by omega) + : ReadM { s : DeserializeState bytes.size // s.off > off } := do + let typeDesc := TypeDesc.ofByte bytes[off] + let td_code := typeDesc.code + let td_length := typeDesc.length + if td_length = 15 then + if q : td_code.toNat < Ion.CoreType.codes.size then + let tp := Ion.CoreType.codes[td_code.toNat] + return ← ds.appendValue (off+1) (.null tp) + else + if td_code = 0xE then + rfail off s!"Annotation cannot have length 15" + else + rfail off s!"Reserved type descriptor code {td_code}" + match td_code.toNat with + | 0 => readNopToken bytes ds td_length off (by omega) + | 1 => readBoolToken bytes ds td_length off (by omega) + | 2 => readPosIntToken bytes ds td_length off (by omega) + | 3 => readNegIntToken bytes ds td_length off (by omega) + | 4 => readDoubleToken bytes ds td_length off (by omega) + | 5 => readDecimalToken bytes ds td_length off (by omega) + | 6 => readNotSupportedToken "Timestamps" off + | 7 => readSymbolToken bytes ds td_length off (by omega) + | 8 => readStringToken bytes ds td_length off (by omega) + | 9 => readNotSupportedToken "Clob" off + | 10 => readBlobToken bytes ds td_length off (by omega) + | 11 => readListToken bytes ds td_length off (by omega) + | 12 => readSexpToken bytes ds td_length off (by omega) + | 13 => readStructToken bytes ds td_length off (by omega) + | 14 => readAnnotationToken bytes ds td_length off (by omega) + | _ => rfail off s!"Reserved type descriptor code {td_code}" + +abbrev InRange base limit := { n : Nat // base ≤ n ∧ n < limit } /-- Main loop driving deserialization -/ -def deserializeAux {size} (ds : DeserializeState size) : AReader (DeserializeState size) := do - .readUpto (init := ds) size fun ds => - .bind - (if ds.inStruct then - Ion.SymbolId.mk <$> .ofLT readVarUInt +def deserializeAux (bytes : ByteArray) (ds : DeserializeState bytes.size) + : ReadM (DeserializeState bytes.size) := + if init_offp : ds.off < bytes.size then do + let inStruct := + if p : ds.tags.size = 0 then + false + else + ds.tags.back.isStruct + let off0 := ds.off + let (ds, ⟨off, sym_offp⟩) ← + if inStruct then + let (symId, ⟨off, offp⟩) ← readVarUInt bytes ds.off + let .isTrue offp := inferInstanceAs (Decidable (off < bytes.size)) + | rfail off "Unexpected end of file" + let ds := { ds with symbols := ds.symbols.push ⟨symId⟩ } + let off' : InRange off0 bytes.size := ⟨off, by omega⟩ + pure (ds, off') else - pure Ion.SymbolId.zero) fun sym => - .bindAny (readToken size) fun tkn => - match tkn with - | .null tp => - cleanupRecords <| ds.appendValue sym (.null tp) - | .bool b => - cleanupRecords <| ds.appendValue sym (.bool b) - | .int i => - cleanupRecords <| ds.appendValue sym (.int i) - | .float v => - cleanupRecords <| ds.appendValue sym (.float v) - | .decimal v => - cleanupRecords <| ds.appendValue sym (.decimal v) - | .symbol v => - cleanupRecords <| ds.appendValue sym (.symbol v) - | .string v => - cleanupRecords <| ds.appendValue sym (.string v) - | .bvm major minor => do - if !ds.stack.isEmpty then - .fail (←.curOffset) s!"Encountered binary version marker inside term" - if (major, minor) ≠ (1, 0) then - .fail (←.curOffset) s!"Unxpected Ion version {major}.{minor}" - pure <| ds.startNew - | .nop => - cleanupRecords ds - | .startList l => - cleanupRecords <| ds.pushPartialValue sym (.list #[]) l - | .startSExp l => - cleanupRecords <| ds.pushPartialValue sym (.sexp #[]) l - | .startStruct l => - cleanupRecords <| ds.pushPartialValue sym (.struct #[]) l - | .startAnn l annot => - cleanupRecords <| ds.pushPartialValue sym (.ann annot) l - -def deserialize (contents : ByteArray) : Except (Nat × String) (Array (Array (Ion.Ion SymbolId))) := + let off' : InRange off0 bytes.size := ⟨ds.off, by omega⟩ + pure (ds, off') + let ⟨ds_new, p⟩ ← readToken bytes ds off + deserializeAux bytes ds_new + else do + pure ds +termination_by bytes.size - ds.off +decreasing_by + omega + +public def deserialize (contents : ByteArray) : Except (Nat × String) (Array (Array (Ion.Ion SymbolId))) := if contents.isEmpty then return #[] else - match BufferM.ofReader contents.size (deserializeAux (.empty contents.size)) contents with + match deserializeAux contents (.empty contents.size) with | .error (pos, msg) => .error (pos, msg) - | .ok (r, _) => .ok r.close + | .ok r => .ok <| appendIfNonempty r.prev r.values + +end Ion diff --git a/Strata/DDM/Util/Ion/Env.lean b/Strata/DDM/Util/Ion/Env.lean index 6bbeaf21a..a0c64146a 100644 --- a/Strata/DDM/Util/Ion/Env.lean +++ b/Strata/DDM/Util/Ion/Env.lean @@ -3,15 +3,16 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Lean.Environment -import Lean.ToExpr +public import Lean.Environment +public import Lean.ToExpr namespace Ion open Lean -inductive SymbolTableEntry where +public inductive SymbolTableEntry where | string (s : String) | record (name : Lean.Name) deriving DecidableEq, Hashable, Repr @@ -19,17 +20,18 @@ deriving DecidableEq, Hashable, Repr instance : Coe String SymbolTableEntry where coe := .string -instance : ToExpr SymbolTableEntry where - toTypeExpr := mkConst ``SymbolTableEntry - toExpr - | .string s => mkApp (mkConst ``SymbolTableEntry.string) (toExpr s) - | .record n => mkApp (mkConst ``SymbolTableEntry.record) (toExpr n) +public instance : ToExpr SymbolTableEntry where + toTypeExpr := private mkConst ``SymbolTableEntry + toExpr := private fun e => + match e with + | .string s => mkApp (mkConst ``SymbolTableEntry.string) (toExpr s) + | .record n => mkApp (mkConst ``SymbolTableEntry.record) (toExpr n) -structure NameSymbols where +public structure NameSymbols where name : Lean.Name entries : Array SymbolTableEntry -structure SymbolTableEntries where +public structure SymbolTableEntries where array : Array SymbolTableEntry := #[] names : Std.HashMap SymbolTableEntry Nat := {} @@ -44,7 +46,7 @@ def SymbolTableEntries.ofArray (a : Array SymbolTableEntry) : SymbolTableEntries array := a names := a.size.fold (init := {}) fun i lt m => m.insert a[i] i -structure IonTypeState where +public structure IonTypeState where map₁ : Std.HashMap Name (Array SymbolTableEntry) := {} map₂ : Lean.PHashMap Name SymbolTableEntries := {} scope : Option (Name × Expr) := .none @@ -57,17 +59,17 @@ def addType (s : IonTypeState) (d : NameSymbols) : IonTypeState where map₂ := s.map₂.insert d.name (.ofArray d.entries) scope := s.scope -def getEntries? (s : IonTypeState) (name : Lean.Name) : Option (Array SymbolTableEntry) := +public def getEntries? (s : IonTypeState) (name : Lean.Name) : Option (Array SymbolTableEntry) := match s.map₂.find? name with | some e => some e.array | none => s.map₁[name]? -def getEntries (s : IonTypeState) (name : Lean.Name) : Array SymbolTableEntry := +public def getEntries (s : IonTypeState) (name : Lean.Name) : Array SymbolTableEntry := match s.map₂.find? name with | some e => e.array | none => s.map₁.getD name #[] -def getIndexOf (s : IonTypeState) (name : Lean.Name) (entry : SymbolTableEntry) : Nat := +public def getIndexOf (s : IonTypeState) (name : Lean.Name) (entry : SymbolTableEntry) : Nat := if name ∈ s.map₁ then panic! "Cannot extend imported names" else @@ -75,7 +77,7 @@ def getIndexOf (s : IonTypeState) (name : Lean.Name) (entry : SymbolTableEntry) | none => panic! s!"Cannot find {name}" | some e => e.names.getD entry e.array.size -def addEntry (s : IonTypeState) (name : Lean.Name) (entry : SymbolTableEntry) : IonTypeState := +public def addEntry (s : IonTypeState) (name : Lean.Name) (entry : SymbolTableEntry) : IonTypeState := if name ∈ s.map₁ then panic! "Cannot extend imported names" else @@ -96,7 +98,7 @@ def mkImported (e : Array (Array NameSymbols)) : ImportM IonTypeState := end IonTypeState -initialize ionDialectExt : PersistentEnvExtension NameSymbols NameSymbols IonTypeState ← +public initialize ionDialectExt : PersistentEnvExtension NameSymbols NameSymbols IonTypeState ← registerPersistentEnvExtension { mkInitial := pure {}, addImportedFn := IonTypeState.mkImported diff --git a/Strata/DDM/Util/Ion/JSON.lean b/Strata/DDM/Util/Ion/JSON.lean index 330130169..7d4543026 100644 --- a/Strata/DDM/Util/Ion/JSON.lean +++ b/Strata/DDM/Util/Ion/JSON.lean @@ -3,6 +3,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module import Lean.Data.Json.Basic @@ -46,25 +47,29 @@ Specific changes include: - Sexpressions become lists. - Annotations are discarded. -/ -def toJson : Ion String → Lean.Json -| .null _ => .null -| .bool b => .bool b -| .int i => .num <| .fromInt i -| .float f => .str (toString f) -| .decimal d => .num d.toJsonNumber -| .string s => .str s -| .symbol s => .str s -| .struct a => .obj <| a.attach.foldl (init := {}) fun m ⟨(nm, v), _⟩ => - m.insert nm v.toJson -| .sexp l | .list l => .arr <| l.map (·.toJson) -| .annotation _ v => v.toJson - termination_by t => t - decreasing_by - · rename_i p - have q := Array.sizeOf_lt_of_mem p - simp only [Prod.mk.sizeOf_spec] at q - decreasing_tactic - all_goals decreasing_tactic +def toJson : Ion String -> Lean.Json +| { app := ap } => + match ap with + | .null _ => .null + | .bool b => .bool b + | .int i => .num <| .fromInt i + | .float f => .str (toString f) + | .decimal d => .num d.toJsonNumber + | .string s => .str s + | .symbol s => .str s + | .blob v => .arr <| v.data.map fun b => .num (.fromNat b.toNat) + | .struct a => .obj <| a.attach.foldl (init := {}) fun m ⟨(nm, v), _⟩ => + m.insert nm v.toJson + | .sexp l | .list l => .arr <| l.map (·.toJson) + | .annotation _ v => v.toJson +termination_by t => t +decreasing_by + · rename_i mem + have q := Array.sizeOf_lt_of_mem mem + simp only [Prod.mk.sizeOf_spec] at q + simp + decreasing_tactic + all_goals decreasing_tactic /-- Constructs an ion value from a JSON object. -/ partial def ofJson : Lean.Json → Ion String diff --git a/Strata/DDM/Util/Ion/Lean.lean b/Strata/DDM/Util/Ion/Lean.lean index b0c934d90..9d32e3d82 100644 --- a/Strata/DDM/Util/Ion/Lean.lean +++ b/Strata/DDM/Util/Ion/Lean.lean @@ -8,18 +8,26 @@ This file provides type classes that work together with a Lean environment extension to create high performance Ion serialization and deserialization. -/ -import Strata.DDM.Util.Ion -import Strata.DDM.Util.Ion.Env -import Lean.Meta.Eval +module -namespace Ion +public import Lean.Elab.Command +public import Lean.Elab.Term.TermElabM +public import Strata.DDM.Util.Ion + +public meta import Strata.DDM.Util.Ion.Env +public meta import Strata.DDM.Util.Ion.SymbolTable +public meta import Lean.Meta.Eval -open Lean Elab +open Lean +open Lean.Elab + +public section +namespace Ion /-- Stores tables used to efficiently serialize values. -/ -structure SymbolIdCache where +public structure SymbolIdCache where /-- Global array for entries -/ globalCache : Array Nat /-- Offset into global array for this type. -/ @@ -33,7 +41,7 @@ def id! (refs : SymbolIdCache) (i : Nat) : SymbolId := else panic! s!"Invalid string id {refs.offset} + {i} (max = {refs.globalCache.size})" -/- +/-- Returns the symbol id cache for the given type and index. -/ def ref! (refs : SymbolIdCache) (tp : String) (i : Nat) : SymbolIdCache where @@ -46,7 +54,7 @@ def ref! (refs : SymbolIdCache) (tp : String) (i : Nat) : SymbolIdCache where end SymbolIdCache -structure LeanSymbolTableMap where +meta structure LeanSymbolTableMap where symtab : Ion.SymbolTable := .system nameMap : Std.HashMap Lean.Name Nat := {} entries : Array Nat := #[] @@ -54,7 +62,7 @@ deriving Inhabited namespace LeanSymbolTableMap -def addEntry : SymbolTableEntry → StateM LeanSymbolTableMap Nat +meta def addEntry : SymbolTableEntry → StateM LeanSymbolTableMap Nat | .record nm, tbl => match tbl.nameMap[nm]? with | none => panic! s!"Unknown name {nm}" @@ -65,7 +73,7 @@ def addEntry : SymbolTableEntry → StateM LeanSymbolTableMap Nat let (sym, symtab) := symtab.intern s (sym.value, { tbl with symtab := symtab }) -def addRecord (tbl : LeanSymbolTableMap) (name : Lean.Name) (entries : Array SymbolTableEntry) : Nat × LeanSymbolTableMap := +meta def addRecord (tbl : LeanSymbolTableMap) (name : Lean.Name) (entries : Array SymbolTableEntry) : Nat × LeanSymbolTableMap := let (entries, tbl) := entries.mapM addEntry tbl let thisIdx := tbl.entries.size let tbl := { tbl with @@ -74,7 +82,7 @@ def addRecord (tbl : LeanSymbolTableMap) (name : Lean.Name) (entries : Array Sym } (thisIdx, tbl) -partial def addToSymbolTable (s : IonTypeState) (name : Name) : StateT LeanSymbolTableMap (Except String) Nat := do +meta partial def addToSymbolTable (s : IonTypeState) (name : Name) : StateT LeanSymbolTableMap (Except String) Nat := do match (← get).nameMap[name]? with | some n => return n | none => pure () @@ -106,11 +114,11 @@ partial def addToSymbolTable (s : IonTypeState) (name : Name) : StateT LeanSymbo end LeanSymbolTableMap -structure FromIonCache where +private structure FromIonCache where entries : Array String cache : SymbolIdCache -class FromIon (α : Type _) where +private class FromIon (α : Type _) where fromIon : FromIonCache → Ion SymbolId → α class CachedToIon (α : Type _) where @@ -118,15 +126,15 @@ class CachedToIon (α : Type _) where namespace CachedToIon -instance [h : CachedToIon α] : CachedToIon (Array α) where - cachedToIon refs a := .list <$> a.mapM (cachedToIon refs) +instance {α} [h : CachedToIon α] : CachedToIon (Array α) where + cachedToIon refs a := private .list <$> a.mapM (cachedToIon refs) -instance [h : CachedToIon α] : CachedToIon (List α) where - cachedToIon refs a := .list <$> a.toArray.mapM (cachedToIon refs) +instance {α} [h : CachedToIon α] : CachedToIon (List α) where + cachedToIon refs a := private .list <$> a.toArray.mapM (cachedToIon refs) end CachedToIon -private def resolveGlobalDecl {m : Type → Type} [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (tp : Syntax) : m Name := do +private meta def resolveGlobalDecl {m : Type → Type} [AddMessageContext m] [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] [MonadLog m] [MonadOptions m] (tp : Syntax) : m Name := do let cs ← resolveGlobalName tp.getId match cs with | [(tpName, [])] => @@ -134,7 +142,7 @@ private def resolveGlobalDecl {m : Type → Type} [Monad m] [MonadResolveName m] | _ => throwErrorAt tp s!"Could not identify unique type for {tp}." -def resolveEntry (stx : Syntax) (entry : SymbolTableEntry) : TermElabM (Lean.Expr × Lean.Expr) := do +private meta def resolveEntry (stx : Syntax) (entry : SymbolTableEntry) : TermElabM (Lean.Expr × Lean.Expr) := do let s := Ion.ionDialectExt.getState (← getEnv) match s |>.scope with | .none => throw <| .error stx m!"Mising scope: Use ionScope!" @@ -146,7 +154,7 @@ def resolveEntry (stx : Syntax) (entry : SymbolTableEntry) : TermElabM (Lean.Exp syntax (name := declareIonScope) "ionScope!" ident term ":" term : term -- declare the syntax @[term_elab declareIonScope] -def declareIonScopeImpl : Elab.Term.TermElab := fun stx expectedType => +public meta def declareIonScopeImpl : Elab.Term.TermElab := fun stx expectedType => match stx with | `(ionScope! $tp $r : $e) => do match Ion.ionDialectExt.getState (← getEnv) |>.scope with @@ -172,7 +180,7 @@ def declareIonScopeImpl : Elab.Term.TermElab := fun stx expectedType => syntax:max (name := declareIonSymbol) "ionSymbol!" str : term -- declare the syntax @[term_elab declareIonSymbol] -def declareIonSymbolImpl : Elab.Term.TermElab := fun stx _ => +meta def declareIonSymbolImpl : Elab.Term.TermElab := fun stx _ => match stx with | `(ionSymbol! $fld) => do let (r, e) ← resolveEntry stx (.string fld.getString) @@ -180,14 +188,14 @@ def declareIonSymbolImpl : Elab.Term.TermElab := fun stx _ => | _ => throwUnsupportedSyntax -def typeOf {α : Type u} (_ : α) : Type u := α +private def typeOf {α : Type u} (_ : α) : Type u := α initialize Lean.registerTraceClass `Strata.ionTypeOf syntax (name := declareIonTypeOf) "ionTypeOf!" term : term -- declare the syntax @[term_elab declareIonTypeOf] -def declareIonTypeOfImpl : Elab.Term.TermElab := fun stx _ => +meta def declareIonTypeOfImpl : Elab.Term.TermElab := fun stx _ => match stx with | `(ionTypeOf! $fld) => do let fldName ← do @@ -204,11 +212,10 @@ def declareIonTypeOfImpl : Elab.Term.TermElab := fun stx _ => | _ => throwUnsupportedSyntax - syntax (name := declareIonRefEntry) "ionRefEntry!" term : term -- declare the syntax @[term_elab declareIonRefEntry] -unsafe def declareIonRefCacheImpl : Elab.Term.TermElab := fun stx _ => +meta unsafe def declareIonRefCacheImpl : Elab.Term.TermElab := fun stx _ => match stx with | `(ionRefEntry! $fldNameStx) => do let nameType : Expr := .const `Lean.Name [] @@ -224,7 +231,7 @@ notation "ionRef!" s => CachedToIon.cachedToIon (ionRefEntry! (ionTypeOf! s)) s syntax (name := getIonEntries) "ionEntries!" "(" ident ")" : term -- declare the syntax @[term_elab getIonEntries] -def getIonEntriesImpl : Lean.Elab.Term.TermElab := fun stx _ => +public meta def getIonEntriesImpl : Lean.Elab.Term.TermElab := fun stx _ => match stx with | `(ionEntries!($tp)) => do let name := tp.getId @@ -233,12 +240,12 @@ def getIonEntriesImpl : Lean.Elab.Term.TermElab := fun stx _ => | _ => throwUnsupportedSyntax -private def mkIdent (si : SourceInfo) (n : Name) : TSyntax `ident := ⟨.ident si n.toString.toSubstring n []⟩ +private meta def mkIdent (si : SourceInfo) (n : Name) : TSyntax `ident := ⟨.ident si n.toString.toSubstring n []⟩ syntax (name := declareIonSymbolTable) "#declareIonSymbolTable" ident : command -- declare the syntax @[command_elab declareIonSymbolTable] -def declareIonSymbolTableImpl : Command.CommandElab := fun stx => +public meta def declareIonSymbolTableImpl : Command.CommandElab := fun stx => match stx with | `(#declareIonSymbolTable $tp) => do let name ← resolveGlobalDecl tp @@ -254,16 +261,16 @@ def declareIonSymbolTableImpl : Command.CommandElab := fun stx => let toIonPair : TSyntax `ident := mkIdent si (.str name "toIonValues") let toIon : TSyntax `ident := mkIdent si (.str name "toIon") Command.elabCommand =<< `( - def $ionSymbolCache : SymbolIdCache := { globalCache := $(quote tbl.entries), offset := $(quote tblIdx) } + private def $ionSymbolCache : SymbolIdCache := { globalCache := $(quote tbl.entries), offset := $(quote tblIdx) } ) Command.elabCommand =<< `( - def $ionSymbolTable : SymbolTable := $(quote tbl.symtab) + private def $ionSymbolTable : SymbolTable := $(quote tbl.symtab) ) let toIonImplDef ← ``(fun v => runIntern (symbols := $ionSymbolTable) (CachedToIon.cachedToIon $ionSymbolCache v) ) Command.elabCommand =<< `( - def $toIonPair : $tp → SymbolTable × Ion SymbolId := $toIonImplDef + private def $toIonPair : $tp → SymbolTable × Ion SymbolId := $toIonImplDef ) Command.elabCommand =<< `( def $toIon (x : $tp) : ByteArray := @@ -274,3 +281,4 @@ def declareIonSymbolTableImpl : Command.CommandElab := fun stx => throwUnsupportedSyntax end Ion +end diff --git a/Strata/DDM/Util/Ion/Serialize.lean b/Strata/DDM/Util/Ion/Serialize.lean index 075105b04..da550d6ba 100644 --- a/Strata/DDM/Util/Ion/Serialize.lean +++ b/Strata/DDM/Util/Ion/Serialize.lean @@ -3,12 +3,83 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Strata.DDM.Util.Ion.AST +public import Strata.DDM.Util.Ion.AST import Strata.DDM.Util.ByteArray +namespace Strata.ByteArray + +theorem size_set (a : ByteArray) (i : Nat) (v : UInt8) (p : _) : (a.set i v p).size = a.size := by + simp only [ByteArray.set, ByteArray.size, Array.set] + simp + +theorem size_push (as : ByteArray) (v : UInt8) : (as.push v).size = as.size + 1 := by + simp only [ByteArray.size] + simp + +public def zeros (n : Nat) : ByteArray := + n.fold (init := .emptyWithCapacity n) fun _ _ a => a.push 0 + +@[simp] +theorem zeros_size (n : Nat) : (zeros n).size = n := by + unfold zeros + induction n with + | zero => + simp + | succ n hyp => + simp_all [ByteArray.size_push] + exact hyp + +end Strata.ByteArray + namespace Ion +abbrev ByteVector n := { a : ByteArray // a.size = n } + +namespace ByteVector + +@[inline] +def set {n} (bs : ByteVector n) (i : Nat) (b : UInt8) (p : i < n := by get_elem_tactic) : ByteVector n := + match bs with + | ⟨a, p⟩ => ⟨a.set i b, by simp [Strata.ByteArray.size_set]; exact p⟩ + +def setBytes {n} (v : ByteVector n) (off : Nat) (bs : ByteArray) + (p : off + bs.size ≤ n) : ByteVector n := + let ⟨as, ap⟩ := v + ⟨bs.copySlice 0 as off bs.size, by + let ⟨as⟩ := as + let ⟨bs⟩ := bs + simp only [ByteArray.copySlice, ByteArray.size, Array.size_append] at *; + simp + omega⟩ + +@[specialize] +def setFoldrBytes' {n} {α β} (s : Nat) (e : Nat) (ep : e ≤ n) + (f : α → UInt8 × α) + (g : α → ByteVector n → β) + (x : α) + (cur : ByteVector n) : β := + if p : s < e then + let e := e - 1 + let (b, x) := f x + setFoldrBytes' s e (by omega) f g x (cur.set e b) + else + g x cur +termination_by e + +@[inline] +def setFoldrBytes {n} {α} (s : Nat) (e : Nat) (ep : e ≤ n) (f : α → UInt8 × α) (x : α) + (cur : ByteVector n) : ByteVector n := + setFoldrBytes' s e ep f (fun _ bytes => bytes) x cur + +open Strata + +def zeros (n : Nat) : ByteVector n := + ⟨.zeros n, ByteArray.zeros_size n⟩ + +end ByteVector + namespace CoreType def code : CoreType → UInt8 @@ -28,165 +99,279 @@ def code : CoreType → UInt8 end CoreType -namespace Ion +structure SerializeState where + prev : Array (Σ (a : ByteArray), Fin a.size) + prev_size : Nat + cur : ByteArray + next : Nat + next_valid : next ≤ cur.size := by grind -abbrev SerializeM := StateM ByteArray +namespace SerializeState -abbrev Serialize := SerializeM Unit +abbrev init_cap := 1024 -def runSerialize (act : Serialize) : SerializeM ByteArray := - return act .empty |>.snd +def empty : SerializeState where + prev := #[] + prev_size := 0 + cur := .empty + next := 0 -def encodeTypeByte (tp : UInt8) (v : UInt8) : UInt8 := tp <<< 4 ||| v +instance : Inhabited SerializeState where + default := empty -def emitByte (v : UInt8) : Serialize := do - modify (·.push v) - -def emitBytes (bytes : ByteArray) : Serialize := - modify (· ++ bytes) - -def emitReversed (bytes : ByteArray) : Serialize := - modify fun s => bytes.foldr (init := s) fun b s => s.push b - -def encodeVarUIntLsb (x : Nat) : ByteArray := - let rec aux (x : Nat) (b : ByteArray) : ByteArray := - if x = 0 then - b - else - aux (x >>> 7) (b.push (x.toUInt8 &&& 0x7f)) - let init : ByteArray := .empty |>.push (0x80 ||| (x.toUInt8 &&& 0x7f)) - aux (x >>> 7) init - -def encodeVarIntLsb (i : Int) : ByteArray := - let rec aux (x : Nat) (b : ByteArray) (l : UInt8) : ByteArray × UInt8 := - if x = 0 then - (b, l) - else - aux (x >>> 7) (b.push l) (x.toUInt8 &&& 0x7f) - let n := i.natAbs - let first := 0x80 ||| (n.toUInt8 &&& 0x7f) - let (b, l) := aux (n >>> 7) .empty first - let signValue : UInt8 := if i < 0 then 0x40 else 0 - if l &&& 0x40 = 0 then - b |>.push (l ||| signValue) - else - b |>.push l |>.push signValue +def size (s : SerializeState) : Nat := s.prev_size + (s.cur.size - s.next) -def emitVarUInt (x : Nat) : Serialize := - emitReversed <| encodeVarUIntLsb x +def render (s : SerializeState) : ByteArray := + let a := ByteArray.emptyWithCapacity s.size + let a := ByteArray.copySlice s.cur s.next a 0 (s.cur.size - s.next) + let r := s.prev.foldr (init := a) fun ⟨c, o⟩ a => + c.copySlice o.val a a.size (c.size - o.val) + assert! r.size = s.size + r -def encodeUIntLsbAux (x : Nat) (b : ByteArray) : ByteArray := - if x = 0 then - b + +end SerializeState + + +def Serialize := SerializeState → SerializeState + +@[inline] +def Serialize.cat (a : Serialize) (b : Serialize) : Serialize := fun s => a (b s) + +@[inline] +def withReserve (cnt : Nat) + (act : ∀{n}, ByteVector n → ∀(i : Nat), i + cnt ≤ n → ByteVector n) + : Serialize := fun s => + let { prev, prev_size, cur, next, next_valid } := s + if p : next ≥ cnt then + let next' := next - cnt + let ⟨cur, curp⟩ := act ⟨cur, Eq.refl cur.size⟩ next' (by omega) + { prev, prev_size, cur, next := next', next_valid := by omega } else - encodeUIntLsbAux (x >>> 8) (b.push x.toUInt8) + let prev := + if p : next < cur.size then + prev.push ⟨cur, ⟨next, p⟩⟩ + else + prev + let prev_size := prev_size + (cur.size - next) + let min := SerializeState.init_cap + if p : min > cnt then + let cur := Strata.ByteArray.zeros min + let next' := min - cnt + have p : cur.size = min := by simp [cur] + let ⟨cur, curp⟩ := act ⟨cur, p⟩ next' (by omega) + { prev, prev_size, cur, next := next', next_valid := by omega } + else + let cur := Strata.ByteArray.zeros cnt + have p : cur.size = cnt := by simp [cur] + let ⟨cur, curp⟩ := act ⟨cur, p⟩ 0 (by omega) + { prev, prev_size, cur, next := 0, next_valid := by omega } -def encodeUIntLsb0 (x : Nat) : ByteArray := - encodeUIntLsbAux x .empty +def serializeArray (a : ByteArray) : Serialize := + withReserve a.size fun bytes off offp => + bytes.setBytes off a offp -def encodeUIntLsb1 (x : Nat) : ByteArray := - let init : ByteArray := .empty |>.push x.toUInt8 - encodeUIntLsbAux (x >>> 8) init +def encodeTypeByte (tp : UInt8) (v : UInt8) : UInt8 := tp <<< 4 ||| v -/- +/-- +Return the number of bytes required to encode a natural number. +-/ +@[specialize] +def bytesRequired (x : Nat) : Nat := aux 0 x + where aux c x := + if x = 0 then + c + else + aux (c+1) (x >>> 8) + termination_by x + +/-- +Return the number of bytes using the 7-bit varint encoding. +-/ +@[specialize] +def varbytesRequired (x : Nat) : Nat := aux 0 x + where aux c x := + if x = 0 then + c + else + aux (c+1) (x >>> 7) + termination_by x + +#guard varbytesRequired 0x7f = 1 +#guard varbytesRequired 0x80 = 2 + +def appendUInt {n} (x : Nat) (cnt : Nat) + (bytes : ByteVector n) (off : Nat) (offp : off + cnt ≤ n := by omega) : ByteVector n := + let f x := (x.toUInt8, x >>> 8) + bytes.setFoldrBytes off (off+cnt) offp f x + +/-- +`appendVarUInt x nt_cnt bytes off p` encodes `7*(nt_cnt + 1)` low order +bits of `x` into `bytes` starting at offset `off`. +-/ +def appendVarUInt (x : Nat) (nt_cnt : Nat) {n} (bytes : ByteVector n) (off : Nat) (offp : off + nt_cnt < n) : ByteVector n := + let f x : UInt8 × Nat := ((x.toUInt8 &&& 0x7f), x >>> 7) + let bytes := bytes.set (off+nt_cnt) (0x80 ||| (x.toUInt8 &&& 0x7f)) + let bytes := bytes.setFoldrBytes off (off+nt_cnt) (by omega) f (x >>> 7) + bytes + +def serializeVarUInt (x : Nat) : Serialize := + let cnt := varbytesRequired (x >>> 7) + withReserve (cnt+1) fun bytes off offp => + appendVarUInt x cnt bytes off offp + +/-- Emit a UInt64 with most-significant byte first. -/ -def emitUInt64_msb (u : UInt64) : Serialize := - let rec appendBytes cnt s := - match cnt with - | 0 => s - | cnt + 1 => appendBytes cnt (s.push (u >>> (8*cnt).toUInt64).toUInt8) - modify (appendBytes 8) - -def encodeIntLsb (isNeg : Bool) (x : Nat) : ByteArray := - let rec aux (x : Nat) (b : ByteArray) (l : UInt8) : ByteArray × UInt8 := - if x = 0 then - (b, l) - else - aux (x >>> 8) (b.push l) x.toUInt8 - let (b, l) := aux (x >>> 8) .empty x.toUInt8 - let signValue : UInt8 := if isNeg then 0x80 else 0 - if l &&& 0x80 = 0 then - b |>.push (l ||| signValue) +def appendUInt64 {n} (u : UInt64) + (bytes : ByteVector n) (off : Nat) (offp : off + 8 ≤ n) : ByteVector n := + let f (x : UInt64) := (x.toUInt8, x >>> 8) + bytes.setFoldrBytes off (off+8) offp f u + +/-- +Given an integer, return a pair consisting of the number of bytes +and the natural number value to encode. +-/ +def encodeInt (v : Int) : Nat × Nat := + if v = 0 then + (0, 0) + else + let isNeg := v < 0 + let x := v.natAbs + -- Compute number of bytes required excluding byte with sign. + let base_cnt := bytesRequired (x >>> 7) + let r := if isNeg then (0x80 <<< (8 * base_cnt)) ||| x else x + (base_cnt + 1, r) + +def encodeVarInt (v : Int) : Nat × Nat := + let isNeg := v < 0 + let x := v.natAbs + -- Compute number of bytes required excluding byte with sign. + let base_cnt := varbytesRequired (x >>> 6) + let r := if isNeg then (0x40 <<< (7 * base_cnt)) ||| x else x + (base_cnt, r) + +@[inline] +def serializeTypeDesc (tp : UInt8) (v : UInt8) : Serialize := + withReserve 1 fun bytes off offp => + bytes.set off (encodeTypeByte tp v) offp + +def typeDescSize (contents_size : Nat) : Nat := + if contents_size < 14 then + 1 + else if contents_size < 0x80 then + 2 else - b |>.push l |>.push signValue + 2 + varbytesRequired (contents_size >>> 7) -def emitTypeByte (tp : UInt8) (v : UInt8) : Serialize := - emitByte <| encodeTypeByte tp v +def appendTypeDesc + {n len cnt} + (tp : UInt8) + (cnt_eq : cnt = typeDescSize len) + (bytes : ByteVector n) + (off : Nat) + (offp : off + cnt ≤ n) + : ByteVector n := + if h : len < 14 then + have p : cnt > 0 := by simp [cnt_eq, typeDescSize]; grind + bytes.set off (encodeTypeByte tp len.toUInt8) + else + have cntp : cnt ≥ 2 := by simp [cnt_eq, typeDescSize, h]; grind + let bytes := bytes.set off (encodeTypeByte tp 14) + appendVarUInt len (cnt-2) bytes (off+1) (by omega) + +def serializeTypedBytes (tp : CoreType) (contents : ByteArray) : Serialize := + let cnt := typeDescSize contents.size + withReserve (cnt + contents.size) fun bytes off offp => + let bytes := appendTypeDesc tp.code (.refl cnt) bytes off (by omega) + let bytes := bytes.setBytes (off+cnt) contents (by omega) + bytes + +@[inline] +def serializeTyped (tp : UInt8) (act : Serialize) : Serialize := fun s => + let old_mark := s.size + let s := act s + let new_mark := s.size + assert! new_mark ≥ old_mark + let contents_size := new_mark - old_mark + let header_size := typeDescSize contents_size + let header_act {n} (bytes : ByteVector n) off offp := + appendTypeDesc tp (.refl header_size) bytes off offp + withReserve header_size header_act s -def emitTypeAndLen (tp : UInt8) (len : Nat) : Serialize := - if len < 14 then - emitTypeByte tp len.toUInt8 - else do - emitTypeByte tp 14 - emitVarUInt len +@[inline] +def serializeTypedUInt (tp : UInt8) (x : Nat) : Serialize := + let len := bytesRequired x + let cnt := typeDescSize len + withReserve (cnt+len) fun bytes off offp => + let bytes := appendTypeDesc tp (.refl cnt) bytes off (by omega) + appendUInt x len bytes (off + cnt) -def emitTypedBytes (tp : CoreType) (contents : ByteArray) : Serialize := do - emitTypeAndLen tp.code contents.size - emitBytes contents +@[inline] +def serializeTypedArray {α} (tp : UInt8) (as : Array α) (act : α → Serialize) : Serialize := + serializeTyped tp (fun s => as.foldr (init := s) act) -def serialize : Ion SymbolId → Serialize +namespace Ion + +partial def serialize : Ion SymbolId → Serialize | .mk app => match app with | .null tp => - emitTypeByte tp.code 0xf + serializeTypeDesc tp.code 0xf | .bool b => - emitTypeByte CoreType.bool.code (if b then 1 else 0) - | .int i => do - let b := encodeUIntLsb0 i.natAbs - emitTypeAndLen (if i ≥ 0 then 2 else 3) b.size - emitReversed b - | .float v => do - emitTypeByte CoreType.float.code 8 - emitUInt64_msb v.toBits - | .decimal v => do + serializeTypeDesc CoreType.bool.code (if b then 1 else 0) + | .int i => + serializeTypedUInt (if i ≥ 0 then 2 else 3) i.natAbs + | .float v => + withReserve 9 fun bytes off offp => + let bytes := bytes.set off (encodeTypeByte CoreType.float.code 8) + appendUInt64 v.toBits bytes (off+1) (by omega) + + | .decimal v => if v = .zero then - emitTypeByte CoreType.decimal.code 0 + serializeTypeDesc CoreType.decimal.code 0 else - let exp := encodeVarIntLsb v.exponent - let coef := encodeIntLsb (v.mantissa < 0) v.mantissa.natAbs - let len := exp.size + coef.size - emitTypeAndLen CoreType.decimal.code len - emitReversed exp - emitReversed coef - | .symbol v => do - let sym := encodeUIntLsb0 v.value - let len := sym.size - emitTypeAndLen CoreType.symbol.code len - emitReversed sym - | .string v => do - emitTypedBytes .string v.toUTF8 - | .list v => do - let s ← runSerialize (v.size.forM fun i isLt => serialize v[i]) - emitTypedBytes .list s - | .sexp v => do - let s ← runSerialize (v.size.forM fun i isLt => serialize v[i]) - emitTypedBytes .sexp s - | .struct v => do - let s ← runSerialize <| v.size.forM fun i isLt => do - let p := v[i] - emitVarUInt p.fst.value - have p1 : sizeOf v[i].snd < sizeOf v[i] := by - match v[i] with - | ⟨nm, v⟩ => decreasing_trivial - have p2 : sizeOf v[i] < sizeOf v := by - apply Array.sizeOf_getElem - serialize p.snd - emitTypedBytes .struct s - | .annotation annot v => do - let s ← runSerialize do - let s := annot.foldl (init := .empty) (fun s v => s ++ encodeVarUIntLsb v.value) - emitVarUInt s.size - emitReversed s - v.serialize - emitTypeAndLen 0xE s.size - emitBytes s + let (nt_exp_cnt, exp) := encodeVarInt v.exponent + let (mantissa_cnt, mantissa) := encodeInt v.mantissa + let contents_size := nt_exp_cnt + 1 + mantissa_cnt + let header_size := typeDescSize contents_size + withReserve (header_size + contents_size) fun bytes off offp => + let code := CoreType.decimal.code + let bytes := appendTypeDesc code (.refl header_size) bytes off (by omega) + let off := off + header_size + let bytes := appendVarUInt exp nt_exp_cnt bytes off (by omega) + let off := off + nt_exp_cnt + 1 + appendUInt mantissa mantissa_cnt bytes off + | .string v => + serializeTypedBytes .string v.toUTF8 + | .symbol v => + serializeTypedUInt CoreType.symbol.code v.value + | .blob v => + serializeTypedBytes .blob v + | .list v => + serializeTypedArray CoreType.list.code v.attach fun ⟨e, _⟩ => serialize e + | .sexp v => + serializeTypedArray CoreType.sexp.code v.attach fun ⟨e, _⟩ => serialize e + | .struct v => + serializeTypedArray CoreType.struct.code v.attach fun ⟨⟨sym, e⟩, _⟩ => + .cat (serializeVarUInt sym.value) (serialize e) + | .annotation annot v => + serializeTyped 0xE $ fun s => + let s := v.serialize s + let old_mark := s.size + let s := annot.foldr (fun v s => serializeVarUInt v.value s) s + let new_mark := s.size + assert! new_mark ≥ old_mark + serializeVarUInt (new_mark - old_mark) s end Ion /-- Create binary version marker -/ -def binaryVersionMarker (major : UInt8 := 1) (minor : UInt8 := 0) : ByteArray := +public def binaryVersionMarker (major : UInt8 := 1) (minor : UInt8 := 0) : ByteArray := .mk #[ 0xE0, major, minor, 0xEA ] -def serialize (values : Array (Ion SymbolId)) : ByteArray := - values.foldl (init := binaryVersionMarker) fun s v => v.serialize s |>.snd +public def serialize (values : Array (Ion SymbolId)) : ByteArray := + let s : Ion.SerializeState := .empty + let s := values.foldr (init := s) fun v s => v.serialize s + let s := Ion.serializeArray binaryVersionMarker s + s.render diff --git a/Strata/DDM/Util/Ion/SymbolTable.lean b/Strata/DDM/Util/Ion/SymbolTable.lean new file mode 100644 index 000000000..43a70f202 --- /dev/null +++ b/Strata/DDM/Util/Ion/SymbolTable.lean @@ -0,0 +1,94 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.DDM.Util.Ion.AST +meta import Lean.Elab.Command +meta import Strata.DDM.Util.Lean + +public section +namespace Ion + +structure SymbolTable where + array : Array String + map : Std.HashMap String SymbolId + locals : Array String +deriving Inhabited + +namespace SymbolTable + +instance : GetElem? SymbolTable SymbolId String (fun tbl idx => idx.value < tbl.array.size) where + getElem tbl idx p := tbl.array[idx.value] + getElem! tbl idx := assert! idx.value < tbl.array.size; tbl.array[idx.value]! + getElem? tbl idx := tbl.array[idx.value]? + +def symbolId! (sym : String) (tbl : SymbolTable) : SymbolId := + match tbl.map[sym]? with + | some i => i + | none => panic! s!"Unbound symbol {sym}" + +/-- +Intern a string into a symbol. +-/ +def intern (sym : String) (tbl : SymbolTable) : SymbolId × SymbolTable := + match tbl.map[sym]? with + | some i => (i, tbl) + | none => + let i : SymbolId := .mk (tbl.array.size) + let tbl := { + array := tbl.array.push sym, + map := tbl.map.insert sym i, + locals := tbl.locals.push sym + } + (i, tbl) + +def ionSharedSymbolTableEntries : Array String := #[ + "$ion", "$ion_1_0", "$ion_symbol_table", "name", "version", + "imports", "symbols", "max_id", "$ion_shared_symbol_table" +] + +/-- +Minimal system symbol table. +-/ +def system : SymbolTable where + array := #[""] ++ ionSharedSymbolTableEntries + map := ionSharedSymbolTableEntries.size.fold (init := {}) fun i _ m => + m.insert ionSharedSymbolTableEntries[i] (.mk (i+1)) + locals := #[] + +def ofLocals (locals : Array String) : SymbolTable := + locals.foldl (init := .system) (fun tbl sym => tbl.intern sym |>.snd) + +public instance : Lean.Quote SymbolTable where + quote st := Lean.Syntax.mkCApp ``SymbolTable.ofLocals #[Lean.quote st.locals] + +end SymbolTable + +namespace SymbolId + +def systemSymbolId! (sym : String) : SymbolId := SymbolTable.system |>.symbolId! sym + +-- Use metaprogramming to declare `{sym}SymbolId : SymbolId` for each system symbol. +section +open Lean +open Elab.Command + +-- Declare all system symbol ids as constants +run_cmd do + for sym in SymbolTable.ionSharedSymbolTableEntries do + -- To simplify name, strip out non-alphanumeric characters. + let simplifiedName : String := .ofList <| sym.toList.filter (·.isAlphanum) + let leanName := Lean.mkLocalDeclId simplifiedName + let cmd : TSyntax `command ← `(command| + public def $(leanName) : SymbolId := systemSymbolId! $(Lean.Syntax.mkStrLit sym) + ) + elabCommand cmd + +end + +end SymbolId + +end Ion diff --git a/Strata/DDM/Util/Lean.lean b/Strata/DDM/Util/Lean.lean index 7a74c8419..2fb0aba36 100644 --- a/Strata/DDM/Util/Lean.lean +++ b/Strata/DDM/Util/Lean.lean @@ -3,11 +3,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - -import Lean.Parser.Types +module +public import Lean.Expr +public import Lean.Message +public import Lean.Parser.Types open Lean Parser +public section namespace Lean /- Creates a local variable name from a string -/ @@ -15,7 +18,7 @@ def mkLocalDeclId (name : String) : TSyntax `Lean.Parser.Command.declId := let dName := .anonymous |>.str name .mk (.ident .none name.toSubstring dName []) -partial def mkErrorMessage (c : InputContext) (pos : String.Pos) (stk : SyntaxStack) (e : Parser.Error) (isSilent : Bool := false) : Message := Id.run do +partial def mkErrorMessage (c : InputContext) (pos : String.Pos.Raw) (stk : SyntaxStack) (e : Parser.Error) (isSilent : Bool := false) : Message := Id.run do let mut pos := pos let mut endPos? := none let mut e := e @@ -42,12 +45,12 @@ partial def mkErrorMessage (c : InputContext) (pos : String.Pos) (stk : SyntaxSt data := toString e } where -- Error recovery might lead to there being some "junk" on the stack - lastTrailing (s : SyntaxStack) : Option Substring := + lastTrailing (s : SyntaxStack) : Option Substring.Raw := s.toSubarray.findSomeRevM? (m := Id) fun stx => if let .original (trailing := trailing) .. := stx.getTailInfo then pure (some trailing) else none -partial def mkStringMessage (c : InputContext) (pos : String.Pos) (msg : String) (isSilent : Bool := false) : Message := +def mkStringMessage (c : InputContext) (pos : String.Pos.Raw) (msg : String) (isSilent : Bool := false) : Message := mkErrorMessage c pos SyntaxStack.empty { unexpected := msg } (isSilent := isSilent) instance : Quote Int where @@ -62,9 +65,15 @@ def optionToExpr (type : Lean.Expr) (a : Option Lean.Expr) : Lean.Expr := | some a => mkApp2 (mkConst ``Option.some [levelZero]) type a @[inline] -def arrayToExpr (type : Lean.Expr) (a : Array Lean.Expr) : Lean.Expr := - let init := mkApp2 (mkConst ``Array.mkEmpty [levelZero]) type (toExpr a.size) - let pushFn := mkApp (mkConst ``Array.push [levelZero]) type +def arrayToExpr (level : Level) (type : Lean.Expr) (a : Array Lean.Expr) : Lean.Expr := + let init := mkApp2 (mkConst ``Array.mkEmpty [level]) type (toExpr a.size) + let pushFn := mkApp (mkConst ``Array.push [level]) type a.foldl (init := init) (mkApp2 pushFn) +def listToExpr (level : Level) (type : Lean.Expr) (es : List Lean.Expr) : Lean.Expr := + let nilFn := mkApp (mkConst ``List.nil [level]) type + let consFn := mkApp (mkConst ``List.cons [level]) type + es.foldr (init := nilFn) (mkApp2 consFn) + end Lean +end diff --git a/Strata/DDM/Util/List.lean b/Strata/DDM/Util/List.lean index 7321755b4..fc72f1194 100644 --- a/Strata/DDM/Util/List.lean +++ b/Strata/DDM/Util/List.lean @@ -3,7 +3,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public section namespace List theorem sizeOf_pos {α} [SizeOf α] (l : List α) : sizeOf l > 0 := by @@ -21,7 +23,7 @@ theorem sizeOf_append {α} [SizeOf α] (k l : List α) : have p := sizeOf_pos l decreasing_tactic -/- +/-- This is similiar to `Array.sizeOf_lt_of_mem`, but stren -/ theorem sizeOf_lt_of_mem_strict {α} [inst : SizeOf α] {as : List α} {a} (h : a ∈ as) : sizeOf a + 2 ≤ sizeOf as := by @@ -56,3 +58,4 @@ theorem sizeOf_set [h : SizeOf α] (as : List α) (i : Nat) (v : α) : simp end List +end diff --git a/Strata/DDM/Util/Nat.lean b/Strata/DDM/Util/Nat.lean index bf4eeab67..0f9681a22 100644 --- a/Strata/DDM/Util/Nat.lean +++ b/Strata/DDM/Util/Nat.lean @@ -3,7 +3,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public section namespace Nat /-- @@ -16,3 +18,4 @@ A fold over natural numbers similar to `Nat.fold` but with an optional starting pure init end Nat +end diff --git a/Strata/DDM/Util/PrattParsingTables.lean b/Strata/DDM/Util/PrattParsingTables.lean index 13f3bd5c6..1b1d3e783 100644 --- a/Strata/DDM/Util/PrattParsingTables.lean +++ b/Strata/DDM/Util/PrattParsingTables.lean @@ -3,12 +3,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import Lean.Parser.Basic +public import Lean.Parser.Basic +public section namespace Lean.Parser.PrattParsingTables -def addLeadingParser (tables : PrattParsingTables) (p : Parser) (prio : Nat) : PrattParsingTables := +private def addLeadingParser (tables : PrattParsingTables) (p : Parser) (prio : Nat) : PrattParsingTables := let addTokens (tks : List Token) : PrattParsingTables := let tks := tks.map Name.mkSimple let tables := tks.eraseDups.foldl (init := tables) fun tables tk => @@ -35,3 +37,4 @@ def addParser (tables : PrattParsingTables) (leading : Bool) (p : Parser) (prio | false, p => addTrailingParser tables p prio end Lean.Parser.PrattParsingTables +end diff --git a/Strata/DDM/Util/String.lean b/Strata/DDM/Util/String.lean index fd99ea986..7b4257581 100644 --- a/Strata/DDM/Util/String.lean +++ b/Strata/DDM/Util/String.lean @@ -3,12 +3,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +import all Init.Data.String.Defs /- -This file contains auxillary definitions for Lean core types that could be +This file contains auxillary definitions for String that could be potentially useful to add. -/ +public section namespace Strata /-- @@ -42,10 +45,30 @@ private def escapeStringLitAux (acc : String) (c : Char) : String := def escapeStringLit (s : String) : String := s.foldl escapeStringLitAux "\"" ++ "\"" +namespace String + +@[simp] +theorem isEmpty_eq (s : _root_.String) : s.isEmpty = (s == "") := by + simp only [String.isEmpty, BEq.beq, String.utf8ByteSize_eq_zero_iff] + +end String + end Strata namespace String +/-- +Indicates s has a substring at the given index. + +Requires a bound check that shows index is in bounds. +-/ +private def hasSubstringAt (s sub : String) (i : Pos.Raw) (index_bound : i.byteIdx + sub.utf8ByteSize ≤ s.utf8ByteSize) : Bool := + sub.bytes.size.all fun j jb => + have p : i.byteIdx + j < s.bytes.size := by + change i.byteIdx + sub.bytes.size ≤ s.bytes.size at index_bound + grind + s.bytes[i.byteIdx + j]'p == sub.bytes[j] + /-- Auxiliary for `indexOf`. Preconditions: * `sub` is not empty @@ -54,34 +77,19 @@ Auxiliary for `indexOf`. Preconditions: It represents the state where the first `j` bytes of `sep` match the bytes `i-j .. i` of `s`. -/ -def indexOfAux (s sub : String) (i : Pos) (j : Pos) : Option Pos := - if s.atEnd i then - none - else - if s.get i == sub.get j then - let i := s.next i - let j := sub.next j - if sub.atEnd j then - some (i - j) - else - indexOfAux s sub i j +private def Pos.Raw.indexOfAux (s sub : String) (subp : sub.utf8ByteSize > 0) (i : Pos.Raw) : Option Pos.Raw := + if h : i.byteIdx + sub.utf8ByteSize ≤ s.utf8ByteSize then + if s.hasSubstringAt sub i h then + some i else - indexOfAux s sub (s.next (i - j)) 0 -termination_by (s.endPos.1 - (i - j).1, sub.endPos.1 - j.1) + (i.next s).indexOfAux s sub subp + else + none +termination_by s.rawEndPos.byteIdx - i.byteIdx decreasing_by - focus - rename_i i₀ j₀ _ eq h' - rw [show (s.next i₀ - sub.next j₀).1 = (i₀ - j₀).1 by - show (_ + Char.utf8Size _) - (_ + Char.utf8Size _) = _ - rw [(beq_iff_eq ..).1 eq, Nat.add_sub_add_right]; rfl] - right; exact Nat.sub_lt_sub_left - (Nat.lt_of_le_of_lt (Nat.le_add_right ..) (Nat.gt_of_not_le (mt decide_eq_true h'))) - (lt_next sub _) - focus - rename_i h _ - left; exact Nat.sub_lt_sub_left - (Nat.lt_of_le_of_lt (Nat.sub_le ..) (Nat.gt_of_not_le (mt decide_eq_true h))) - (lt_next s _) + simp only [Pos.Raw.next, Pos.Raw.add_char_eq, rawEndPos] + have p : (i.get s).utf8Size > 0 := Char.utf8Size_pos _ + grind /-- This return the first index in `s` greater than or equal to `b` that contains @@ -90,54 +98,13 @@ the bytes in `sub`. N.B. This will potentially read the same character multiple times. It could be made more efficient by using Boyer-Moore string search. -/ -def indexOf (s sub : String) (b : Pos := 0) : Option Pos := - if sub.isEmpty then - some b +def indexOfRaw (s sub : String) (b : Pos.Raw := 0) : Option Pos.Raw := + if subp : sub.utf8ByteSize > 0 then + b.indexOfAux s sub subp else - indexOfAux s sub b 0 - -theorem le_def (p q : String.Pos) : p ≤ q ↔ p.byteIdx ≤ q.byteIdx := by - trivial - -theorem Pos.le_of_lt {p q : String.Pos} (a : p < q) : p ≤ q := by - simp at a - simp [String.le_def] - omega + some b -@[simp] -theorem pos_le_refl (pos : String.Pos) : pos ≤ pos := by - unfold LE.le - simp [instLEPos] - -theorem next_mono (s : String) (p : String.Pos) : p < s.next p := by - simp [String.next, Char.utf8Size] - repeat (split; omega) - omega - -theorem findAux_mono (s : String) (pred : Char → Bool) (stop p : String.Pos) - : p ≤ s.findAux pred stop p := by - unfold String.findAux - split - case isFalse _ => - simp - case isTrue p2_le_stop => - split - case isTrue _ => - simp - case isFalse _ => - have termProof : sizeOf (stop - s.next p) < sizeOf (stop - p) := by - have g : p < (s.next p) := String.next_mono _ _ - simp at g - simp at p2_le_stop; - simp [sizeOf, String.Pos._sizeOf_1] - omega - apply String.Pos.le_trans - apply String.Pos.le_of_lt - apply String.next_mono s - apply String.findAux_mono - termination_by (stop - p) - -def splitLines (s : String) := s.split (· ∈ ['\n', '\r']) +def splitLines (s : String) := s.splitToList (· ∈ ['\n', '\r']) /-- info: [" ab", "cd", "", "de", ""] @@ -152,3 +119,4 @@ info: [""] #eval "".splitLines end String +end diff --git a/Strata/DDM/Util/Syntax.lean b/Strata/DDM/Util/Syntax.lean deleted file mode 100644 index ddbabd9f6..000000000 --- a/Strata/DDM/Util/Syntax.lean +++ /dev/null @@ -1,39 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -namespace Lean - -theorem node_getelem (info : SourceInfo) (i : Nat) : (Syntax.node info kind args)[i] = args.getD i .missing := by - simp [GetElem.getElem, Syntax.getArg] - -theorem atom_getelem (info : SourceInfo) (i : Nat) : (Syntax.atom info val)[i] = Syntax.missing := by - simp [GetElem.getElem, Syntax.getArg] - -theorem ident_getelem (info : SourceInfo) (rv : Substring) (v : Name) (pre : List Syntax.Preresolved) - (i : Nat) : (Syntax.ident info rv v pre)[i] = Syntax.missing := by - simp [GetElem.getElem, Syntax.getArg] - -def sizeOfSyntaxArgLt (stx : Syntax) (i : Nat) - (nm : stx.getKind ≠ `missing) : sizeOf stx[i] < sizeOf stx := by - cases stx with - | missing => - simp [Syntax.getKind] at nm - | node info kind args => - simp [node_getelem, Array.getD_getElem?] - if p : i < args.size then - simp [p] - decreasing_trivial - else - simp [p] - cases args; decreasing_trivial - | atom info val => - simp [atom_getelem] - cases val; decreasing_trivial - | ident info rv v pre => - simp [ident_getelem] - cases rv; decreasing_trivial - -end Lean diff --git a/Strata/DDM/Util/Trie.lean b/Strata/DDM/Util/Trie.lean deleted file mode 100644 index c6e960e22..000000000 --- a/Strata/DDM/Util/Trie.lean +++ /dev/null @@ -1,54 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Lean.Data.Trie - -open Lean.Data (Trie) - -namespace Lean.Data.Trie - -private def ppElt (s : String) (lineStarted : Bool) (indent : String) (val : String) : String := - if lineStarted then - s ++ " " ++ val - else - s ++ indent ++ val - -private def ppAux [ToString α] (t : Trie α) (lineStarted : Bool) (indent : String) (s : String) : String := - match t with - | .leaf none => - ppElt s lineStarted indent (toString ".none\n") - | .leaf (some a) => - ppElt s lineStarted indent (toString a ++ "\n") - | .node1 none v u => - let s := ppElt s lineStarted (indent ++ " ") (toString v) - ppAux u true indent s - | .node1 ma v u => - let (started, s) := - match ma with - | some a => (false, ppElt s lineStarted indent (toString a ++ "\n")) - | none => (lineStarted, s) - let s := ppElt s started (indent ++ " ") (toString v) - ppAux u true indent s - | .node ma bytes tries => - if q : bytes.size = tries.size then - let s := - match ma with - | some a => ppElt s lineStarted indent (toString a ++ "\n") - | none => ppElt s lineStarted indent (".end\n") - let indent := indent ++ " " - bytes.size.fold (init := s) fun i h s => - let b := bytes[i] - let t := tries[i] - let s := ppElt s false indent (toString b) - ppAux t true indent s - else - panic! s!"Bad sizes {bytes.size} {tries.size}" - -/-- -This renders the trie so that it is a bit easier to see the structure. --/ -private def ppStructure [ToString α] (t : Trie α) : String := - ppAux t false "" "" diff --git a/Strata/DDM/Util/Vector.lean b/Strata/DDM/Util/Vector.lean index 597ed0782..15308f871 100644 --- a/Strata/DDM/Util/Vector.lean +++ b/Strata/DDM/Util/Vector.lean @@ -3,7 +3,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module +public section namespace Vector @[inline] @@ -15,3 +17,4 @@ def modify! (v : Vector α n) (i : Nat) (f : α → α) : Vector α n where def modify (v : Vector α n) (i : Fin n) (f : α → α) : Vector α n := v.modify! i.val f end Vector +end diff --git a/Strata/DL/Imperative/Cmd.lean b/Strata/DL/Imperative/Cmd.lean index 455f043cf..b297bc723 100644 --- a/Strata/DL/Imperative/Cmd.lean +++ b/Strata/DL/Imperative/Cmd.lean @@ -28,19 +28,22 @@ variable declaration and assignment, and assertions and assumptions. -/ /-- -A command in the Imperative dialect +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. -/ inductive Cmd (P : PureExpr) : Type where - /-- `init` defines a variable called `name` with type `ty` and - initial value `e`. -/ + /-- Define a variable called `name` with type `ty` and initial value `e`. + Note: we may make the initial value optional. -/ | init (name : P.Ident) (ty : P.Ty) (e : P.Expr) (md : (MetaData P) := .empty) - /-- `set` assigns `e` to a pre-existing variable `name`. -/ + /-- Assign `e` to a pre-existing variable `name`. -/ | set (name : P.Ident) (e : P.Expr) (md : (MetaData P) := .empty) - /-- `havoc` assigns a pre-existing variable `name` a random value. -/ + /-- Assigns an arbitrary value to an existing variable `name`. -/ | havoc (name : P.Ident) (md : (MetaData P) := .empty) - /-- `assert` checks whether condition `b` is true. -/ + /-- Check whether condition `b` is true, failing if not. -/ | assert (label : String) (b : P.Expr) (md : (MetaData P) := .empty) - /-- `assume` constrains execution by adding assumption `b`. -/ + /-- Ignore any execution state in which `b` is not true. -/ | assume (label : String) (b : P.Expr) (md : (MetaData P) := .empty) abbrev Cmds (P : PureExpr) := List (Cmd P) @@ -74,12 +77,12 @@ instance (P : PureExpr) : SizeOf (Imperative.Cmd P) where --------------------------------------------------------------------- class HasPassiveCmds (P : PureExpr) (CmdT : Type) where - assume : String → P.Expr → CmdT - assert : String → P.Expr → CmdT + assume : String → P.Expr → MetaData P → CmdT + assert : String → P.Expr → MetaData P → CmdT instance : HasPassiveCmds P (Cmd P) where - assume l e := .assume l e - assert l e := .assert l e + assume l e (md := MetaData.empty):= .assume l e md + assert l e (md := MetaData.empty):= .assert l e md class HasHavoc (P : PureExpr) (CmdT : Type) where havoc : P.Ident → CmdT @@ -160,11 +163,11 @@ open Std (ToFormat Format format) def formatCmd (P : PureExpr) (c : Cmd P) [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] : Format := match c with - | .init name ty e md => f!"{md}init ({name} : {ty}) := {e}" - | .set name e md => f!"{md}{name} := {e}" - | .havoc name md => f!"{md}havoc {name}" - | .assert label b md => f!"{md}assert [{label}] {b}" - | .assume label b md => f!"{md}assume [{label}] {b}" + | .init name ty e _md => f!"init ({name} : {ty}) := {e}" + | .set name e _md => f!"{name} := {e}" + | .havoc name _md => f!"havoc {name}" + | .assert label b _md => f!"assert [{label}] {b}" + | .assume label b _md => f!"assume [{label}] {b}" instance [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] : ToFormat (Cmd P) where diff --git a/Strata/DL/Imperative/CmdSemantics.lean b/Strata/DL/Imperative/CmdSemantics.lean index 3aa6a1b77..bf39e1ecd 100644 --- a/Strata/DL/Imperative/CmdSemantics.lean +++ b/Strata/DL/Imperative/CmdSemantics.lean @@ -24,8 +24,8 @@ lookups. The evaluation functions take two states: an old state and a current state. This allows for two-state expressions and predicates. -/ abbrev SemanticStore := P.Ident → Option P.Expr -abbrev SemanticEval := SemanticStore P → SemanticStore P → P.Expr → Option P.Expr -abbrev SemanticEvalBool := SemanticStore P → SemanticStore P → P.Expr → Option Bool +abbrev SemanticEval := SemanticStore P → P.Expr → Option P.Expr +abbrev SemanticEvalBool := SemanticStore P → P.Expr → Option Bool /-- @@ -33,8 +33,7 @@ Evaluation relation of an Imperative command `Cmd`. -/ -- (FIXME) Change to a type class? abbrev EvalCmdParam (P : PureExpr) (Cmd : Type) := - SemanticEval P → SemanticStore P → SemanticStore P → Cmd → - SemanticStore P → Prop + SemanticEval P → SemanticStore P → Cmd → SemanticStore P → Prop /-- ### Well-Formedness of `SemanticStore`s -/ @@ -219,26 +218,31 @@ theorem invStoresExceptComm : -/ def WellFormedSemanticEvalBool {P : PureExpr} [HasBool P] [HasNot P] (δ : SemanticEval P) : Prop := - ∀ σ₀ σ e, - (δ σ₀ σ e = some Imperative.HasBool.tt ↔ δ σ₀ σ (Imperative.HasNot.not e) = (some HasBool.ff)) ∧ - (δ σ₀ σ e = some Imperative.HasBool.ff ↔ δ σ₀ σ (Imperative.HasNot.not e) = (some HasBool.tt)) + ∀ σ e, + (δ σ e = some Imperative.HasBool.tt ↔ δ σ (Imperative.HasNot.not e) = (some HasBool.ff)) ∧ + (δ σ e = some Imperative.HasBool.ff ↔ δ σ (Imperative.HasNot.not e) = (some HasBool.tt)) def WellFormedSemanticEvalVal {P : PureExpr} [HasVal P] (δ : SemanticEval P) : Prop := -- evaluator only evaluates to values - (∀ v v' σ₀ σ, δ σ₀ σ v = some v' → HasVal.value v') ∧ + (∀ v v' σ, δ σ v = some v' → HasVal.value v') ∧ -- evaluator is identity on values - (∀ v' σ₀ σ, HasVal.value v' → δ σ₀ σ v' = some v') + (∀ v' σ, HasVal.value v' → δ σ v' = some v') def WellFormedSemanticEvalVar {P : PureExpr} [HasFvar P] (δ : SemanticEval P) - : Prop := (∀ e v σ₀ σ, HasFvar.getFvar e = some v → δ σ₀ σ e = σ v) + : Prop := (∀ e v σ, HasFvar.getFvar e = some v → δ σ e = σ v) def WellFormedSemanticEvalExprCongr {P : PureExpr} [HasVarsPure P P.Expr] (δ : SemanticEval P) - : Prop := ∀ e σ₀ σ σ', (∀ x ∈ HasVarsPure.getVars e, σ x = σ' x) → δ σ₀ σ e = δ σ₀ σ' e + : Prop := ∀ e σ σ', (∀ x ∈ HasVarsPure.getVars e, σ x = σ' x) → δ σ e = δ σ' e + /-- -An inductive rule for state update. +Abstract variable update. + +This does not specify how `σ` is represented, only what it maps each variable to. -/ inductive UpdateState : SemanticStore P → P.Ident → P.Expr → SemanticStore P → Prop where + /-- The state `σ'` is be equivalent to `σ` except at `x`, where it maps to + `v`. Requires that `x` mapped to something beforehand. -/ | update : σ x = .some v' → σ' x = .some v → @@ -247,9 +251,13 @@ inductive UpdateState : SemanticStore P → P.Ident → P.Expr → SemanticStore UpdateState σ x v σ' /-- -An inductive rule for state init. +Abtract variable initialization. + +This does not specify how `σ` is represented, only what it maps each variable to. -/ inductive InitState : SemanticStore P → P.Ident → P.Expr → SemanticStore P → Prop where + /-- The state `σ'` is be equivalent to `σ` except at `x`, where it maps to + `v`. Requires that `x` mapped to nothing beforehand. -/ | init : σ x = none → σ' x = .some v → @@ -258,42 +266,48 @@ inductive InitState : SemanticStore P → P.Ident → P.Expr → SemanticStore P InitState σ x v σ' /-- -An inductively-defined operational semantics that depends on -environment lookup and evaluation functions for expressions. +An inductively-defined operational semantics for `Cmd` that depends on variable +lookup (`σ`) and expression evaluation (`δ`) functions. -/ inductive EvalCmd [HasFvar P] [HasBool P] [HasNot P] : - SemanticEval P → SemanticStore P → SemanticStore P → Cmd P → SemanticStore P → Prop where + SemanticEval P → SemanticStore P → Cmd P → SemanticStore P → Prop where + /-- If `e` evaluates to a value `v`, initialize `x` according to `InitState`. -/ | eval_init : - δ σ₀ σ e = .some v → + δ σ e = .some v → InitState P σ x v σ' → WellFormedSemanticEvalVar δ → --- - EvalCmd δ σ₀ σ (.init x _ e _) σ' + EvalCmd δ σ (.init x _ e _) σ' + /-- If `e` evaluates to a value `v`, assign `x` according to `UpdateState`. -/ | eval_set : - δ σ₀ σ e = .some v → + δ σ e = .some v → UpdateState P σ x v σ' → WellFormedSemanticEvalVar δ → ---- - EvalCmd δ σ₀ σ (.set x e _) σ' + EvalCmd δ σ (.set x e _) σ' + /-- Assign `x` an arbitrary value `v` according to `UpdateState`. -/ | eval_havoc : UpdateState P σ x v σ' → WellFormedSemanticEvalVar δ → ---- - EvalCmd δ σ₀ σ (.havoc x _) σ' + EvalCmd δ σ (.havoc x _) σ' + /-- If `e` evaluates to true in `σ`, evaluate to the same `σ`. This semantics + does not have a concept of an erroneous execution. -/ | eval_assert : - δ σ₀ σ e = .some HasBool.tt → + δ σ e = .some HasBool.tt → WellFormedSemanticEvalBool δ → ---- - EvalCmd δ σ₀ σ (.assert _ e _) σ + EvalCmd δ σ (.assert _ e _) σ + /-- If `e` evaluates to true in `σ`, evaluate to the same `σ`. -/ | eval_assume : - δ σ₀ σ e = .some HasBool.tt → + δ σ e = .some HasBool.tt → WellFormedSemanticEvalBool δ → ---- - EvalCmd δ σ₀ σ (.assume _ e _) σ + EvalCmd δ σ (.assume _ e _) σ end section diff --git a/Strata/DL/Imperative/CmdType.lean b/Strata/DL/Imperative/CmdType.lean index 29b421d08..434821a5b 100644 --- a/Strata/DL/Imperative/CmdType.lean +++ b/Strata/DL/Imperative/CmdType.lean @@ -16,19 +16,21 @@ open Std (ToFormat Format format) /-- Type checker for an Imperative Command. + +The `TypeError` parameter for the `TypeContext` instance `TC` is a concrete type +here. We can change this to a more generic type in the future, if needed. -/ -def Cmd.typeCheck [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] - [DecidableEq P.Ident] [TC : TypeContext P C T] +def Cmd.typeCheck {P C T} [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] + [DecidableEq P.Ident] [TC : TypeContext P C T Format] (ctx: C) (τ : T) (c : Cmd P) : Except Format (Cmd P × T) := do - match c with + try match c with | .init x xty e md => match TC.lookup τ x with | none => if x ∈ TC.freeVars e then - .error f!"Type Checking [{c}]: \ - Variable {x} cannot appear in its own initialization expression!" + .error f!"Variable {x} cannot appear in its own initialization expression!" else let (xty, τ) ← TC.preprocess ctx τ xty let (e, ety, τ) ← TC.inferType ctx τ c e @@ -38,11 +40,11 @@ def Cmd.typeCheck [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] let c := Cmd.init x xty e md .ok (c, τ) | some xty => - .error f!"Type Checking [{c}]: Variable {x} of type {xty} already in context." + .error f!"Variable {x} of type {xty} already in context." | .set x e md => match TC.lookup τ x with - | none => .error f!"Type Checking [{c}]: Cannot set undefined variable {x}." + | none => .error f!"Cannot set undeclared variable {x}." | some xty => let (e, ety, τ) ← TC.inferType ctx τ c e let τ ← TC.unifyTypes τ [(xty, ety)] @@ -52,7 +54,7 @@ def Cmd.typeCheck [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] | .havoc x _md => match TC.lookup τ x with | some _ => .ok (c, τ) - | none => .error f!"Type Checking [{c}]: Cannot havoc undefined variable {x}." + | none => .error f!"Cannot havoc undeclared variable {x}." | .assert label e md => let (e, ety, τ) ← TC.inferType ctx τ c e @@ -60,8 +62,8 @@ def Cmd.typeCheck [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] let c := Cmd.assert label e md .ok (c, τ) else - .error f!"Type Checking [assert {label}]: \ - Assertion expected to be of type boolean, but inferred type is {ety}." + .error f!"Assertion {label} expected to be of type boolean, \ + but inferred type is {ety}." | .assume label e md => let (e, ety, τ) ← TC.inferType ctx τ c e @@ -69,14 +71,21 @@ def Cmd.typeCheck [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] let c := Cmd.assume label e md .ok (c, τ) else - .error f!"Type Checking [assume {label}]: \ - Assumption expected to be of type boolean, but inferred type is {ety}." + .error f!"Assumption {label} expected to be of type boolean, \ + but inferred type is {ety}." + catch e => + -- Add source location to error messages. + let sourceLoc := MetaData.formatFileRangeD c.getMetaData + if sourceLoc.isEmpty then + .error e + else + .error f!"{sourceLoc} {e}" /-- Type checker for Imperative's Commands. -/ def Cmds.typeCheck {P C T} [ToFormat P.Ident] [ToFormat P.Ty] [ToFormat (Cmd P)] - [DecidableEq P.Ident] [TC : TypeContext P C T] + [DecidableEq P.Ident] [TC : TypeContext P C T Format] (ctx: C) (τ : T) (cs : Cmds P) : Except Format (Cmds P × T) := do match cs with | [] => .ok ([], τ) diff --git a/Strata/DL/Imperative/Imperative.lean b/Strata/DL/Imperative/Imperative.lean index e7acff171..a219be614 100644 --- a/Strata/DL/Imperative/Imperative.lean +++ b/Strata/DL/Imperative/Imperative.lean @@ -12,6 +12,7 @@ import Strata.DL.Imperative.CmdEval import Strata.DL.Imperative.CmdType import Strata.DL.Imperative.CmdSemantics import Strata.DL.Imperative.StmtSemantics +import Strata.DL.Imperative.StmtSemanticsSmallStep import Strata.DL.Imperative.NondetStmt import Strata.DL.Imperative.NondetStmtSemantics diff --git a/Strata/DL/Imperative/MetaData.lean b/Strata/DL/Imperative/MetaData.lean index 956556cc3..3364a5fa8 100644 --- a/Strata/DL/Imperative/MetaData.lean +++ b/Strata/DL/Imperative/MetaData.lean @@ -4,9 +4,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - - import Strata.DL.Imperative.PureExpr +import Strata.DL.Util.DecidableEq +import Lean.Data.Position namespace Imperative @@ -22,39 +22,117 @@ implicitly modified by a language construct). -/ open Std (ToFormat Format format) +open Lean (Position) variable {Identifier : Type} [DecidableEq Identifier] [ToFormat Identifier] [Inhabited Identifier] -/-- A metadata field. +/-- 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. -/ inductive MetaDataElem.Field (P : PureExpr) where + /-- Metadata indexed by a Strata variable. -/ | var (v : P.Ident) + /-- Metadata indexed by an arbitrary label. -/ | label (l : String) +@[grind] +def MetaDataElem.Field.beq [BEq P.Ident] (f1 f2 : MetaDataElem.Field P) := + match f1, f2 with + | .var v1, .var v2 => v1 == v2 + | .label l1, .label l2 => l1 == l2 + | _, _ => false + instance [BEq P.Ident] : BEq (MetaDataElem.Field P) where - beq f1 f2 := - match f1, f2 with - | .var v1, .var v2 => v1 == v2 - | .label l1, .label l2 => l1 == l2 - | _, _ => false + beq f1 f2 := f1.beq f2 + +theorem MetaDataElem.Field.beq_eq {P : PureExpr} [DecidableEq P.Ident] + (f1 f2 : MetaDataElem.Field P) : MetaDataElem.Field.beq f1 f2 = true ↔ f1 = f2 := by + solve_beq f1 f2 + +instance [DecidableEq P.Ident] : DecidableEq (MetaDataElem.Field P) := + beq_eq_DecidableEq MetaDataElem.Field.beq MetaDataElem.Field.beq_eq instance [ToFormat P.Ident] : ToFormat (MetaDataElem.Field P) where format f := match f with | .var v => f!"var {v}" | .label l => f!"[{l}]" -/-- A metadata value. -/ +instance [Repr P.Ident] : Repr (MetaDataElem.Field P) where + reprPrec f prec := + let res := + match f with + | .var v => f!"MetaDataElem.Field.var {repr v}" + | .label s => f!"MetaDataElem.Field.label {s}" + Repr.addAppParen res prec + +inductive Uri where + | file (path: String) + deriving DecidableEq, Repr + +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 + +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) + instance [ToFormat P.Expr] : ToFormat (MetaDataElem.Value P) where - format f := match f with | .expr e => f!"{e}" | .msg s => f!"{s}" + format f := match f with | .expr e => f!"{e}" | .msg s => f!"{s}" | .fileRange r => f!"{r}" + +instance [Repr P.Expr] : Repr (MetaDataElem.Value P) where + reprPrec v prec := + let res := + match v with + | .expr e => f!"MetaDataElem.Value.expr {reprPrec e prec}" + | .msg s => f!"MetaDataElem.Value.msg {s}" + | .fileRange fr => f!"MetaDataElem.Value.fileRange {repr 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 + | _, _ => false + +instance [BEq P.Expr] : BEq (MetaDataElem.Value P) where + beq v1 v2 := v1.beq v2 + +-- TODO: this is exactly the same proof as MetaDataElem.Field.beq_eq. Is there +-- some existing automation we could use? +theorem MetaDataElem.Value.beq_eq {P : PureExpr} [DecidableEq P.Expr] + (v1 v2 : MetaDataElem.Value P) : MetaDataElem.Value.beq v1 v2 = true ↔ v1 = v2 := by + constructor <;> intro h + case mp => + -- Soundness: beq = true → e1 = e2 + unfold beq at h; induction v1 generalizing v2 <;> (cases v2 <;> grind) + case mpr => + -- Completeness: e1 = e2 → beq = true + rw[h]; induction v2 generalizing v1 <;> simp only [MetaDataElem.Value.beq] <;> grind + +instance [DecidableEq P.Expr] : DecidableEq (MetaDataElem.Value P) := + beq_eq_DecidableEq MetaDataElem.Value.beq MetaDataElem.Value.beq_eq /-- A metadata element -/ structure MetaDataElem (P : PureExpr) where + /-- The field or key used to identify the metadata. -/ fld : MetaDataElem.Field P + /-- The value of the metadata. -/ value : MetaDataElem.Value P /-- Metadata is an array of tagged elements. -/ @@ -72,6 +150,22 @@ def MetaData.eraseElem {P : PureExpr} [BEq P.Ident] (md : MetaData P) (fld : MetaDataElem.Field P) : MetaData P := md.eraseP (fun e => e.fld == fld) +/-- Retrieve the first metadata element with tag `fld`. -/ +def MetaData.findElem {P : PureExpr} [BEq P.Ident] + (md : MetaData P) (fld : MetaDataElem.Field P) : Option (MetaDataElem P) := + md.find? (λ e => e.fld == fld) + +def MetaDataElem.beq {P : PureExpr} [DecidableEq P.Ident] [DecidableEq P.Expr] + (e1 e2 : MetaDataElem P) : Bool := e1.fld.beq e2.fld && e1.value.beq e2.value + +theorem MetaDataElem.beq_eq {P : PureExpr} [DecidableEq P.Ident] [DecidableEq P.Expr] + (e1 e2 : MetaDataElem P) : MetaDataElem.beq e1 e2 = true ↔ e1 = e2 := by + unfold MetaDataElem.beq + constructor <;> (cases e1 ; cases e2 ; grind [MetaDataElem.Field.beq_eq, MetaDataElem.Value.beq_eq]) + +instance [DecidableEq P.Ident] [DecidableEq P.Expr] : DecidableEq (MetaDataElem P) := + beq_eq_DecidableEq MetaDataElem.beq MetaDataElem.beq_eq + instance [ToFormat (MetaDataElem.Field P)] [ToFormat (MetaDataElem.Value P)] : ToFormat (MetaDataElem P) where format m := f!"<{m.fld}: {m.value}>" @@ -79,6 +173,38 @@ instance [ToFormat (MetaDataElem.Field P)] [ToFormat (MetaDataElem.Value P)] : instance [ToFormat (MetaDataElem P)] : ToFormat (MetaData P) where format md := if md.isEmpty then f!"" else f!"{md} " +instance [Repr P.Expr] [Repr P.Ident] : Repr (MetaDataElem P) where + reprPrec e prec := + Repr.addAppParen (f!"fld := {repr e.fld}, value := {repr e.value}") prec + +--------------------------------------------------------------------- + +/-! ### Common metadata fields -/ + +def MetaData.fileRange : MetaDataElem.Field P := .label "fileRange" + +def MetaData.formatFileRange? {P} [BEq P.Ident] (md : MetaData P) (includeEnd? : Bool := false) : + Option Std.Format := do + let fileRangeElem ← md.findElem MetaData.fileRange + match fileRangeElem.value with + | .fileRange m => + let baseName := match m.file with + | .file path => (path.splitToList (· == '/')).getLast! + if includeEnd? then + if m.start.line == m.ending.line then + return f!"{baseName}({m.start.line}, ({m.start.column}-{m.ending.column}))" + else + return f!"{baseName}(({m.start.line}, {m.start.column})-({m.ending.line}, {m.ending.column}))" + else -- don't include the end position. + return f!"{baseName}({m.start.line}, {m.start.column})" + | _ => none + +def MetaData.formatFileRangeD {P} [BEq P.Ident] (md : MetaData P) (includeEnd? : Bool := false) + : Std.Format := + match formatFileRange? md includeEnd? with + | .none => "" + | .some f => f + --------------------------------------------------------------------- end Imperative diff --git a/Strata/DL/Imperative/NondetStmt.lean b/Strata/DL/Imperative/NondetStmt.lean index 2eb88f87e..8b22aec85 100644 --- a/Strata/DL/Imperative/NondetStmt.lean +++ b/Strata/DL/Imperative/NondetStmt.lean @@ -22,10 +22,23 @@ Comamnds](https://en.wikipedia.org/wiki/Guarded_Command_Language), and in [Kleene Algebra with Tests](https://www.cs.cornell.edu/~kozen/Papers/kat.pdf). -/ +/-- +A non-deterministic statement, parameterized by a type of pure expressions (`P`) +and a type of commands (`Cmd`). + +This encodes the same types of control flow as `Stmt`, but using only +non-deterministic choices: arbitrarily choosing one of two sub-statements to +execute or executing a sub-statement an arbitrary number of times. Conditions +can be encoded if the command type includes assumptions. +-/ inductive NondetStmt (P : PureExpr) (Cmd : Type) : Type where + /-- An atomic command, of an arbitrary type. -/ | cmd (cmd : Cmd) + /-- Execute `s1` followed by `s2`. -/ | seq (s1 s2 : NondetStmt P Cmd) + /-- Execute either `s1` or `s2`, arbitrarily. -/ | choice (s1 s2 : NondetStmt P Cmd) + /-- Execute `s` an arbitrary number of times (possibly zero). -/ | loop (s : NondetStmt P Cmd) deriving Inhabited diff --git a/Strata/DL/Imperative/NondetStmtSemantics.lean b/Strata/DL/Imperative/NondetStmtSemantics.lean index ba01e3fe5..78a402926 100644 --- a/Strata/DL/Imperative/NondetStmtSemantics.lean +++ b/Strata/DL/Imperative/NondetStmtSemantics.lean @@ -14,36 +14,36 @@ namespace Imperative mutual /-- An inductively-defined operational semantics for non-deterministic -statements that depends on environment lookup and evaluation functions -for expressions. -/ +statements that depends on environment lookup and evaluation functions for +expressions. **NOTE:** This will probably be replaced with a small-step +semantics. -/ inductive EvalNondetStmt (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasBool P] [HasNot P] : - SemanticEval P → SemanticStore P → SemanticStore P → - NondetStmt P Cmd → SemanticStore P → Prop where + SemanticEval P → SemanticStore P → NondetStmt P Cmd → SemanticStore P → Prop where | cmd_sem : - EvalCmd δ σ₀ σ c σ' → + EvalCmd δ σ c σ' → -- We only require definedness on the statement level so that the requirement is fine-grained isDefinedOver (HasVarsImp.modifiedVars) σ c → ---- - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ (NondetStmt.cmd c) σ' + EvalNondetStmt P Cmd EvalCmd δ σ (NondetStmt.cmd c) σ' | seq_sem : - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ s1 σ' → - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ' s2 σ'' → + EvalNondetStmt P Cmd EvalCmd δ σ s1 σ' → + EvalNondetStmt P Cmd EvalCmd δ σ' s2 σ'' → ---- - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ (.seq s1 s2) σ'' + EvalNondetStmt P Cmd EvalCmd δ σ (.seq s1 s2) σ'' | choice_left_sem : WellFormedSemanticEvalBool δ → - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ s1 σ' → + EvalNondetStmt P Cmd EvalCmd δ σ s1 σ' → ---- - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ (.choice s1 s2) σ' + EvalNondetStmt P Cmd EvalCmd δ σ (.choice s1 s2) σ' | choice_right_sem : WellFormedSemanticEvalBool δ → - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ s2 σ' → + EvalNondetStmt P Cmd EvalCmd δ σ s2 σ' → ---- - EvalNondetStmt P Cmd EvalCmd δ σ₀ σ (.choice s1 s2) σ' + EvalNondetStmt P Cmd EvalCmd δ σ (.choice s1 s2) σ' /- | loop_sem : diff --git a/Strata/DL/Imperative/PureExpr.lean b/Strata/DL/Imperative/PureExpr.lean index 191a46f19..9428839c2 100644 --- a/Strata/DL/Imperative/PureExpr.lean +++ b/Strata/DL/Imperative/PureExpr.lean @@ -13,15 +13,24 @@ Expected interface for pure expressions that can be used to specialize the Imperative dialect. -/ structure PureExpr : Type 1 where + /-- Kinds of identifiers allowed in expressions. We expect identifiers to have + decidable equality; see `EqIdent`. -/ Ident : Type + EqIdent : DecidableEq Ident + /-- Expressions -/ Expr : Type + /-- Types -/ Ty : Type - /-- Typing environment -/ + /-- Typing environment, expected to contain a map of variables to their types, + type substitution, etc. + -/ TyEnv : Type + /-- Typing context, expected to contain information that does not change + during type checking/inference (e.g., known types and known functions.) + -/ TyContext : Type /-- Evaluation environment -/ EvalEnv : Type - EqIdent : DecidableEq Ident abbrev PureExpr.TypedIdent (P : PureExpr) := P.Ident × P.Ty abbrev PureExpr.TypedExpr (P : PureExpr) := P.Expr × P.Ty diff --git a/Strata/DL/Imperative/SMTUtils.lean b/Strata/DL/Imperative/SMTUtils.lean index db3f41c01..832238382 100644 --- a/Strata/DL/Imperative/SMTUtils.lean +++ b/Strata/DL/Imperative/SMTUtils.lean @@ -129,7 +129,7 @@ def solverResult {P : PureExpr} [ToFormat P.Ident] (typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType)) (vars : List P.TypedIdent) (ans : String) (E : Strata.SMT.EncoderState) : Except Format (Result P.TypedIdent) := do - let pos := (ans.find (fun c => c == '\n')).byteIdx + let pos := (ans.find (fun c => c == '\n' || c == '\r')).byteIdx let verdict := ans.take pos let rest := ans.drop pos match verdict with diff --git a/Strata/DL/Imperative/SemanticsProps.lean b/Strata/DL/Imperative/SemanticsProps.lean index 196709db4..ba753b437 100644 --- a/Strata/DL/Imperative/SemanticsProps.lean +++ b/Strata/DL/Imperative/SemanticsProps.lean @@ -11,17 +11,17 @@ namespace Imperative theorem eval_assert_store_cst [HasFvar P] [HasBool P] [HasNot P]: - EvalCmd P δ σ₀ σ (.assert l e md) σ' → σ = σ' := by + EvalCmd P δ σ (.assert l e md) σ' → σ = σ' := by intros Heval; cases Heval with | eval_assert _ => rfl theorem eval_stmt_assert_store_cst [HasVarsImp P (List (Stmt P (Cmd P)))] [HasVarsImp P (Cmd P)] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (Cmd.assert l e md)) σ' → σ = σ' := by + EvalStmt P (Cmd P) (EvalCmd P) δ σ (.cmd (Cmd.assert l e md)) σ' → σ = σ' := by intros Heval; cases Heval with | cmd_sem Hcmd => exact eval_assert_store_cst Hcmd theorem eval_stmts_assert_store_cst [HasVarsImp P (List (Stmt P (Cmd P)))] [HasVarsImp P (Cmd P)] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ [(.cmd (Cmd.assert l e md))] σ' → σ = σ' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ [(.cmd (Cmd.assert l e md))] σ' → σ = σ' := by intros Heval; cases Heval with | stmts_some_sem H1 H2 => cases H1 with @@ -32,8 +32,8 @@ theorem eval_stmts_assert_store_cst theorem eval_stmt_assert_eq_of_pure_expr_eq [HasVarsImp P (List (Stmt P (Cmd P)))] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : WellFormedSemanticEvalBool δ → - (EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (Cmd.assert l1 e md1)) σ' ↔ - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (Cmd.assert l2 e md2)) σ') := by + (EvalStmt P (Cmd P) (EvalCmd P) δ σ (.cmd (Cmd.assert l1 e md1)) σ' ↔ + EvalStmt P (Cmd P) (EvalCmd P) δ σ (.cmd (Cmd.assert l2 e md2)) σ') := by intro Hwf constructor <;> ( @@ -49,19 +49,19 @@ theorem eval_stmt_assert_eq_of_pure_expr_eq theorem eval_stmts_assert_elim [HasVarsImp P (List (Stmt P (Cmd P)))] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : WellFormedSemanticEvalBool δ → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (.assert l1 e md1) :: cmds) σ' → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ cmds σ' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ (.cmd (.assert l1 e md1) :: cmds) σ' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ cmds σ' := by intros Hwf Heval cases Heval with - | @stmts_some_sem _ _ _ _ σ1 _ _ Has1 Has2 => + | @stmts_some_sem _ _ _ σ1 _ _ Has1 Has2 => rw [← eval_stmt_assert_store_cst Has1] at Has2 assumption theorem assert_elim [HasVarsImp P (List (Stmt P (Cmd P)))] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : WellFormedSemanticEvalBool δ → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (.assert l1 e md1) :: [.cmd (.assert l2 e md2)]) σ' → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ [.cmd (.assert l3 e md3)] σ' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ (.cmd (.assert l1 e md1) :: [.cmd (.assert l2 e md2)]) σ' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ [.cmd (.assert l3 e md3)] σ' := by intro Hwf Heval have Heval := eval_stmts_assert_elim Hwf Heval rw [eval_stmts_singleton] at * @@ -124,11 +124,11 @@ theorem semantic_eval_eq_of_eval_cmd_set_unrelated_var [HasFvar P] [HasVal P] [HasBool P] [HasNot P]: WellFormedSemanticEvalExprCongr δ → ¬ v ∈ HasVarsPure.getVars e → - EvalCmd P δ σ₀ σ (Cmd.set v e') σ' → - δ σ₀ σ e = δ σ₀ σ' e := by + EvalCmd P δ σ (Cmd.set v e') σ' → + δ σ e = δ σ' e := by intro Hwf Hnin Heval unfold WellFormedSemanticEvalExprCongr at Hwf - specialize Hwf e σ₀ σ σ' + specialize Hwf e σ σ' have: ∀ (v : P.Ident), v ∈ HasVarsPure.getVars e → σ v = σ' v := by cases Heval rename_i Hu @@ -146,12 +146,12 @@ theorem eval_cmd_set_comm' [HasVarsImp P (List (Stmt P (Cmd P)))] [HasVarsImp P (Cmd P)] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] [DecidableEq P.Ident] : ¬ x1 = x2 → - δ σ₀ σ v1 = δ σ₀ σ2 v1 → - δ σ₀ σ v2 = δ σ₀ σ1 v2 → - EvalCmd P δ σ₀ σ (Cmd.set x1 v1) σ1 → - EvalCmd P δ σ₀ σ1 (Cmd.set x2 v2) σ' → - EvalCmd P δ σ₀ σ (Cmd.set x2 v2) σ2 → - EvalCmd P δ σ₀ σ2 (Cmd.set x1 v1) σ'' → + δ σ v1 = δ σ2 v1 → + δ σ v2 = δ σ1 v2 → + EvalCmd P δ σ (Cmd.set x1 v1) σ1 → + EvalCmd P δ σ1 (Cmd.set x2 v2) σ' → + EvalCmd P δ σ (Cmd.set x2 v2) σ2 → + EvalCmd P δ σ2 (Cmd.set x1 v1) σ'' → σ' = σ'' := by intro Hneq Heq1 Heq2 Hs1 Hs2 Hs3 Hs4 cases Hs1; cases Hs2; cases Hs3; cases Hs4 @@ -166,10 +166,10 @@ theorem eval_cmd_set_comm ¬ x1 = x2 → ¬ x1 ∈ HasVarsPure.getVars v2 → ¬ x2 ∈ HasVarsPure.getVars v1 → - EvalCmd P δ σ₀ σ (Cmd.set x1 v1) σ1 → - EvalCmd P δ σ₀ σ1 (Cmd.set x2 v2) σ' → - EvalCmd P δ σ₀ σ (Cmd.set x2 v2) σ2 → - EvalCmd P δ σ₀ σ2 (Cmd.set x1 v1) σ'' → + EvalCmd P δ σ (Cmd.set x1 v1) σ1 → + EvalCmd P δ σ1 (Cmd.set x2 v2) σ' → + EvalCmd P δ σ (Cmd.set x2 v2) σ2 → + EvalCmd P δ σ2 (Cmd.set x1 v1) σ'' → σ' = σ'' := by intro Hwf Hneq Hnin1 Hnin2 Hs1 Hs2 Hs3 Hs4 have Heval2:= semantic_eval_eq_of_eval_cmd_set_unrelated_var Hwf Hnin1 Hs1 @@ -183,10 +183,10 @@ theorem eval_stmt_set_comm ¬ x1 = x2 → ¬ x1 ∈ HasVarsPure.getVars v2 → ¬ x2 ∈ HasVarsPure.getVars v1 → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (Cmd.set x1 v1)) σ1 → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ1 (.cmd (Cmd.set x2 v2)) σ' → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (.cmd (Cmd.set x2 v2)) σ2 → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ2 (.cmd (Cmd.set x1 v1)) σ'' → + EvalStmt P (Cmd P) (EvalCmd P) δ σ (.cmd (Cmd.set x1 v1)) σ1 → + EvalStmt P (Cmd P) (EvalCmd P) δ σ1 (.cmd (Cmd.set x2 v2)) σ' → + EvalStmt P (Cmd P) (EvalCmd P) δ σ (.cmd (Cmd.set x2 v2)) σ2 → + EvalStmt P (Cmd P) (EvalCmd P) δ σ2 (.cmd (Cmd.set x1 v1)) σ'' → σ' = σ'' := by intro Hwf Hneq Hnin1 Hnin2 Hs1 Hs2 Hs3 Hs4 cases Hs1; cases Hs2; cases Hs3; cases Hs4 @@ -200,8 +200,8 @@ theorem eval_stmts_set_comm ¬ x1 = x2 → ¬ x1 ∈ HasVarsPure.getVars v2 → ¬ x2 ∈ HasVarsPure.getVars v1 → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ [(.cmd (Cmd.set x1 v1)), (.cmd (Cmd.set x2 v2))] σ' → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ [(.cmd (Cmd.set x2 v2)), (.cmd (Cmd.set x1 v1))] σ'' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ [(.cmd (Cmd.set x1 v1)), (.cmd (Cmd.set x2 v2))] σ' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ [(.cmd (Cmd.set x2 v2)), (.cmd (Cmd.set x1 v1))] σ'' → σ' = σ'' := by intro Hwf Hneq Hnin1 Hnin2 Hss1 Hss2 cases Hss1; cases Hss2 diff --git a/Strata/DL/Imperative/Stmt.lean b/Strata/DL/Imperative/Stmt.lean index fb9c20d79..0e1ffafdb 100644 --- a/Strata/DL/Imperative/Stmt.lean +++ b/Strata/DL/Imperative/Stmt.lean @@ -17,29 +17,73 @@ Imperative's Statements include commands and add constructs like structured and unstructured control-flow. -/ -mutual +/-- 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. +-/ inductive Stmt (P : PureExpr) (Cmd : Type) : Type where + /-- An atomic command. -/ | cmd (cmd : Cmd) - | block (label : String) (b : Block P Cmd) (md : MetaData P := .empty) - /-- `ite` (if-then-else) statement provides structured control flow. -/ - | ite (cond : P.Expr) (thenb : Block P Cmd) (elseb : Block P Cmd) (md : MetaData P := .empty) - /-- `loop` Loop statement with optional measure (for termination) and invariant. -/ - | loop (guard : P.Expr) (measure : Option P.Expr) (invariant : Option P.Expr) (body : Block P Cmd) (md : MetaData P := .empty) - /-- `goto` provides unstructured control flow. -/ + /-- An block containing a `List` of `Stmt`. -/ + | block (label : String) (b : List (Stmt P Cmd)) (md : MetaData P := .empty) + /-- A conditional execution statement. -/ + | ite (cond : P.Expr) (thenb : List (Stmt P Cmd)) (elseb : List (Stmt P Cmd)) (md : MetaData P := .empty) + /-- An iterated execution statement. Includes an optional measure (for + termination) and invariant. -/ + | loop (guard : P.Expr) (measure : Option P.Expr) (invariant : Option P.Expr) (body : List (Stmt P Cmd)) (md : MetaData P := .empty) + /-- 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. -/ | goto (label : String) (md : MetaData P := .empty) deriving Inhabited -structure Block (P : PureExpr) (Cmd : Type) where - ss : List (Stmt P Cmd) -end - -abbrev Stmts (P : PureExpr) (Cmd : Type) := List (Stmt P Cmd) +/-- A block is simply an abbreviation for a list of commands. -/ +abbrev Block (P : PureExpr) (Cmd : Type) := List (Stmt P Cmd) def Stmt.isCmd {P : PureExpr} {Cmd : Type} (s : Stmt P Cmd) : Bool := match s with | .cmd _ => true | _ => false + +/-- +Induction principle for `Stmt` +-/ +@[elab_as_elim] +def Stmt.inductionOn {P : PureExpr} {Cmd : Type} + {motive : Stmt P Cmd → Sort u} + (cmd_case : ∀ (cmd : Cmd), motive (Stmt.cmd cmd)) + (block_case : ∀ (label : String) (b : List (Stmt P Cmd)) (md : MetaData P), + (∀ s, s ∈ b → motive s) → + motive (Stmt.block label b md)) + (ite_case : ∀ (cond : P.Expr) (thenb elseb : List (Stmt P Cmd)) (md : MetaData P), + (∀ s, s ∈ thenb → motive s) → + (∀ s, s ∈ elseb → motive s) → + motive (Stmt.ite cond thenb elseb md)) + (loop_case : ∀ (guard : P.Expr) (measure invariant : Option P.Expr) + (body : List (Stmt P Cmd)) (md : MetaData P), + (∀ s, s ∈ body → motive s) → + motive (Stmt.loop guard measure invariant body md)) + (goto_case : ∀ (label : String) (md : MetaData P), + motive (Stmt.goto label md)) + (s : Stmt P Cmd) : motive s := + match s with + | Stmt.cmd c => cmd_case c + | Stmt.block label b md => + block_case label b md (fun s _ => inductionOn cmd_case block_case ite_case loop_case goto_case s) + | Stmt.ite cond thenb elseb md => + ite_case cond thenb elseb md + (fun s _ => inductionOn cmd_case block_case ite_case loop_case goto_case s) + (fun s _ => inductionOn cmd_case block_case ite_case loop_case goto_case s) + | Stmt.loop guard measure invariant body md => + loop_case guard measure invariant body md + (fun s _ => inductionOn cmd_case block_case ite_case loop_case goto_case s) + | Stmt.goto label md => goto_case label md + termination_by s + --------------------------------------------------------------------- /-! ### SizeOf -/ @@ -48,30 +92,23 @@ mutual @[simp] def Stmt.sizeOf (s : Imperative.Stmt P C) : Nat := match s with - | .cmd c => 1 + sizeOf c - | .block _ ⟨ bss ⟩ _ => 1 + Stmts.sizeOf bss - | .ite c ⟨ tss ⟩ ⟨ ess ⟩ _ => 3 + sizeOf c + Stmts.sizeOf tss + Stmts.sizeOf ess - | .loop g _ _ ⟨ bss ⟩ _ => 3 + sizeOf g + Stmts.sizeOf bss + | .cmd c => 1 + SizeOf.sizeOf c + | .block _ bss _ => 1 + Block.sizeOf bss + | .ite c tss ess _ => 3 + sizeOf c + Block.sizeOf tss + Block.sizeOf ess + | .loop g _ _ bss _ => 3 + sizeOf g + Block.sizeOf bss | .goto _ _ => 1 @[simp] -def Stmts.sizeOf (ss : Imperative.Stmts P C) : Nat := +def Block.sizeOf (ss : Imperative.Block P C) : Nat := match ss with | [] => 1 - | s :: srest => 1 + Stmt.sizeOf s + Stmts.sizeOf srest - -@[simp] -def Block.sizeOf : Imperative.Block P C → Nat - | ⟨ bss ⟩ => 1 + Stmts.sizeOf bss + | s :: srest => 1 + Stmt.sizeOf s + Block.sizeOf srest end instance (P : PureExpr) : SizeOf (Imperative.Stmt P C) where sizeOf := Stmt.sizeOf -instance (P : PureExpr) : SizeOf (Imperative.Stmts P C) where - sizeOf := Stmts.sizeOf - instance (P : PureExpr) : SizeOf (Imperative.Block P C) where sizeOf := Block.sizeOf @@ -88,19 +125,19 @@ mutual /-- Does statement `s` contain any block labeled `label`? -/ def Stmt.hasLabelInside (label : String) (s : Stmt P C) : Bool := match s with - | .block label' ⟨ bss ⟩ _ => label = label' || Stmts.hasLabelInside label bss - | .ite _ ⟨ tss ⟩ ⟨ ess ⟩ _ => Stmts.hasLabelInside label tss || Stmts.hasLabelInside label ess + | .block label' bss _ => label = label' || Block.hasLabelInside label bss + | .ite _ tss ess _ => Block.hasLabelInside label tss || Block.hasLabelInside label ess | _ => false termination_by (Stmt.sizeOf s) /-- Do statements `ss` contain any block labeled `label`? -/ -def Stmts.hasLabelInside (label : String) (ss : List (Stmt P C)) : Bool := +def Block.hasLabelInside (label : String) (ss : List (Stmt P C)) : Bool := match ss with | [] => false - | s :: ss => Stmt.hasLabelInside label s || Stmts.hasLabelInside label ss - termination_by (Stmts.sizeOf ss) + | s :: ss => Stmt.hasLabelInside label s || Block.hasLabelInside label ss + termination_by (Block.sizeOf ss) end --------------------------------------------------------------------- @@ -112,17 +149,17 @@ mutual def Stmt.getVars [HasVarsPure P P.Expr] [HasVarsPure P C] (s : Stmt P C) : List P.Ident := match s with | .cmd cmd => HasVarsPure.getVars cmd - | .block _ ⟨ bss ⟩ _ => Stmts.getVars bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => Stmts.getVars tbss ++ Stmts.getVars ebss - | .loop _ _ _ ⟨ bss ⟩ _ => Stmts.getVars bss + | .block _ bss _ => Block.getVars bss + | .ite _ tbss ebss _ => Block.getVars tbss ++ Block.getVars ebss + | .loop _ _ _ bss _ => Block.getVars bss | .goto _ _ => [] termination_by (Stmt.sizeOf s) -def Stmts.getVars [HasVarsPure P P.Expr] [HasVarsPure P C] (ss : Stmts P C) : List P.Ident := +def Block.getVars [HasVarsPure P P.Expr] [HasVarsPure P C] (ss : Block P C) : List P.Ident := match ss with | [] => [] - | s :: srest => Stmt.getVars s ++ Stmts.getVars srest - termination_by (Stmts.sizeOf ss) + | s :: srest => Stmt.getVars s ++ Block.getVars srest + termination_by (Block.sizeOf ss) end instance (P : PureExpr) [HasVarsPure P P.Expr] [HasVarsPure P C] @@ -130,24 +167,24 @@ instance (P : PureExpr) [HasVarsPure P P.Expr] [HasVarsPure P C] getVars := Stmt.getVars instance (P : PureExpr) [HasVarsPure P P.Expr] [HasVarsPure P C] - : HasVarsPure P (Stmts P C) where - getVars := Stmts.getVars + : HasVarsPure P (Block P C) where + getVars := Block.getVars mutual /-- Get all variables defined by the statement `s`. -/ def Stmt.definedVars [HasVarsImp P C] (s : Stmt P C) : List P.Ident := match s with | .cmd cmd => HasVarsImp.definedVars cmd - | .block _ ⟨ bss ⟩ _ => Stmts.definedVars bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => Stmts.definedVars tbss ++ Stmts.definedVars ebss + | .block _ bss _ => Block.definedVars bss + | .ite _ tbss ebss _ => Block.definedVars tbss ++ Block.definedVars ebss | _ => [] termination_by (Stmt.sizeOf s) -def Stmts.definedVars [HasVarsImp P C] (ss : Stmts P C) : List P.Ident := +def Block.definedVars [HasVarsImp P C] (ss : Block P C) : List P.Ident := match ss with | [] => [] - | s :: srest => Stmt.definedVars s ++ Stmts.definedVars srest - termination_by (Stmts.sizeOf ss) + | s :: srest => Stmt.definedVars s ++ Block.definedVars srest + termination_by (Block.sizeOf ss) end mutual @@ -156,16 +193,16 @@ def Stmt.modifiedVars [HasVarsImp P C] (s : Stmt P C) : List P.Ident := match s with | .cmd cmd => HasVarsImp.modifiedVars cmd | .goto _ _ => [] - | .block _ ⟨ bss ⟩ _ => Stmts.modifiedVars bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => Stmts.modifiedVars tbss ++ Stmts.modifiedVars ebss - | .loop _ _ _ ⟨ bss ⟩ _ => Stmts.modifiedVars bss + | .block _ bss _ => Block.modifiedVars bss + | .ite _ tbss ebss _ => Block.modifiedVars tbss ++ Block.modifiedVars ebss + | .loop _ _ _ bss _ => Block.modifiedVars bss termination_by (Stmt.sizeOf s) -def Stmts.modifiedVars [HasVarsImp P C] (ss : Stmts P C) : List P.Ident := +def Block.modifiedVars [HasVarsImp P C] (ss : Block P C) : List P.Ident := match ss with | [] => [] - | s :: srest => Stmt.modifiedVars s ++ Stmts.modifiedVars srest - termination_by (Stmts.sizeOf ss) + | s :: srest => Stmt.modifiedVars s ++ Block.modifiedVars srest + termination_by (Block.sizeOf ss) end mutual @@ -175,17 +212,17 @@ mutual @[simp] def Stmt.touchedVars [HasVarsImp P C] (s : Stmt P C) : List P.Ident := match s with - | .block _ ⟨ bss ⟩ _ => Stmts.touchedVars bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => Stmts.touchedVars tbss ++ Stmts.touchedVars ebss + | .block _ bss _ => Block.touchedVars bss + | .ite _ tbss ebss _ => Block.touchedVars tbss ++ Block.touchedVars ebss | _ => Stmt.definedVars s ++ Stmt.modifiedVars s termination_by (Stmt.sizeOf s) @[simp] -def Stmts.touchedVars [HasVarsImp P C] (ss : Stmts P C) : List P.Ident := +def Block.touchedVars [HasVarsImp P C] (ss : Block P C) : List P.Ident := match ss with | [] => [] - | s :: srest => Stmt.touchedVars s ++ Stmts.touchedVars srest - termination_by (Stmts.sizeOf ss) + | s :: srest => Stmt.touchedVars s ++ Block.touchedVars srest + termination_by (Block.sizeOf ss) end instance (P : PureExpr) [HasVarsImp P C] : HasVarsImp P (Stmt P C) where @@ -194,11 +231,11 @@ instance (P : PureExpr) [HasVarsImp P C] : HasVarsImp P (Stmt P C) where -- order matters for Havoc, so needs to override the default touchedVars := Stmt.touchedVars -instance (P : PureExpr) [HasVarsImp P C] : HasVarsImp P (Stmts P C) where - definedVars := Stmts.definedVars - modifiedVars := Stmts.modifiedVars +instance (P : PureExpr) [HasVarsImp P C] : HasVarsImp P (Block P C) where + definedVars := Block.definedVars + modifiedVars := Block.modifiedVars -- order matters for Havoc, so needs to override the default - touchedVars := Stmts.touchedVars + touchedVars := Block.touchedVars --------------------------------------------------------------------- @@ -211,22 +248,22 @@ partial def formatStmt (P : PureExpr) (s : Stmt P C) [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] [ToFormat C] : Format := match s with | .cmd cmd => format cmd - | .block label bl md => f!"{md}{label} : " ++ Format.bracket "{" f!"{formatStmts P bl.ss}" "}" + | .block label bl md => f!"{md}{label} : " ++ Format.bracket "{" f!"{formatBlock P bl}" "}" | .ite cond th el md => f!"{md}if {cond} then " ++ - Format.bracket "{" f!"{formatStmts P th.ss}" "}" ++ + Format.bracket "{" f!"{formatBlock P th}" "}" ++ f!"{Format.line}else" ++ - Format.bracket "{" f!"{formatStmts P el.ss}" "}" + Format.bracket "{" f!"{formatBlock P el}" "}" | .loop guard measure invariant body md => f!"{md}while ({guard}) ({measure}) ({invariant}) " ++ - Format.bracket "{" f!"{formatStmts P body.ss}" "}" + Format.bracket "{" f!"{formatBlock P body}" "}" | .goto label md => f!"{md}goto {label}" -partial def formatStmts (P : PureExpr) (ss : List (Stmt P C)) +partial def formatBlock (P : PureExpr) (ss : List (Stmt P C)) [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] [ToFormat C] : Format := match ss with | [] => f!"" | s :: rest => formatStmt P s ++ if rest.isEmpty then f!"" - else f!"\n{formatStmts P rest}" + else f!"\n{formatBlock P rest}" end instance [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] @@ -239,7 +276,7 @@ instance [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] [ToFormat C] instance [ToFormat P.Ident] [ToFormat P.Expr] [ToFormat P.Ty] [ToFormat C] : ToFormat (List (Stmt P C)) where - format ss := formatStmts P ss + format ss := formatBlock P ss --------------------------------------------------------------------- diff --git a/Strata/DL/Imperative/StmtSemantics.lean b/Strata/DL/Imperative/StmtSemantics.lean index d9c0dca3d..7264f6982 100644 --- a/Strata/DL/Imperative/StmtSemantics.lean +++ b/Strata/DL/Imperative/StmtSemantics.lean @@ -23,73 +23,63 @@ evaluation relation `EvalCmd`. -/ inductive EvalStmt (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - SemanticEval P → SemanticStore P → SemanticStore P → - Stmt P Cmd → SemanticStore P → Prop where + SemanticEval P → SemanticStore P → Stmt P Cmd → SemanticStore P → Prop where | cmd_sem : - EvalCmd δ σ₀ σ c σ' → + EvalCmd δ σ c σ' → -- We only require definedness on the statement level so that the requirement is fine-grained -- For example, if we require definedness on a block, then we won't be able to evaluate -- a block containing init x; havoc x, because it will require x to exist prior to the block isDefinedOver (HasVarsImp.modifiedVars) σ c → ---- - EvalStmt P Cmd EvalCmd δ σ₀ σ (Stmt.cmd c) σ' + EvalStmt P Cmd EvalCmd δ σ (Stmt.cmd c) σ' | block_sem : - EvalBlock P Cmd EvalCmd δ σ₀ σ b σ' → + EvalBlock P Cmd EvalCmd δ σ b σ' → ---- - EvalStmt P Cmd EvalCmd δ σ₀ σ (.block _ b) σ' + EvalStmt P Cmd EvalCmd δ σ (.block _ b) σ' | ite_true_sem : - δ σ₀ σ c = .some HasBool.tt → + δ σ c = .some HasBool.tt → WellFormedSemanticEvalBool δ → - EvalBlock P Cmd EvalCmd δ σ₀ σ t σ' → + EvalBlock P Cmd EvalCmd δ σ t σ' → ---- - EvalStmt P Cmd EvalCmd δ σ₀ σ (.ite c t e) σ' + EvalStmt P Cmd EvalCmd δ σ (.ite c t e) σ' | ite_false_sem : - δ σ₀ σ c = .some HasBool.ff → + δ σ c = .some HasBool.ff → WellFormedSemanticEvalBool δ → - EvalBlock P Cmd EvalCmd δ σ₀ σ e σ' → + EvalBlock P Cmd EvalCmd δ σ e σ' → ---- - EvalStmt P Cmd EvalCmd δ σ₀ σ (.ite c t e) σ' + EvalStmt P Cmd EvalCmd δ σ (.ite c t e) σ' -- (TODO): Define semantics of `goto`. -inductive EvalStmts (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) +inductive EvalBlock (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - SemanticEval P → SemanticStore P → SemanticStore P → - List (Stmt P Cmd) → SemanticStore P → Prop where + SemanticEval P → SemanticStore P → List (Stmt P Cmd) → SemanticStore P → Prop where | stmts_none_sem : - EvalStmts P _ _ δ σ₀ σ [] σ + EvalBlock P _ _ δ σ [] σ | stmts_some_sem : - EvalStmt P Cmd EvalCmd δ σ₀ σ s σ' → - EvalStmts P Cmd EvalCmd δ σ₀ σ' ss σ'' → - EvalStmts P Cmd EvalCmd δ σ₀ σ (s :: ss) σ'' - -inductive EvalBlock (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) - [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - SemanticEval P → SemanticStore P → SemanticStore P → - Block P Cmd → SemanticStore P → Prop where - | block_sem : - EvalStmts P Cmd EvalCmd δ σ₀ σ b.ss σ' → - EvalBlock P Cmd EvalCmd δ σ₀ σ b σ' + EvalStmt P Cmd EvalCmd δ σ s σ' → + EvalBlock P Cmd EvalCmd δ σ' ss σ'' → + EvalBlock P Cmd EvalCmd δ σ (s :: ss) σ'' end theorem eval_stmts_singleton [HasVarsImp P (List (Stmt P (Cmd P)))] [HasVarsImp P (Cmd P)] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ [cmd] σ' ↔ - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ cmd σ' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ [cmd] σ' ↔ + EvalStmt P (Cmd P) (EvalCmd P) δ σ cmd σ' := by constructor <;> intro Heval - cases Heval with | @stmts_some_sem _ _ _ _ σ1 _ _ Heval Hempty => + cases Heval with | @stmts_some_sem _ _ _ σ1 _ _ Heval Hempty => cases Hempty; assumption - apply EvalStmts.stmts_some_sem Heval (EvalStmts.stmts_none_sem) + apply EvalBlock.stmts_some_sem Heval (EvalBlock.stmts_none_sem) theorem eval_stmts_concat [HasVarsImp P (List (Stmt P (Cmd P)))] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ cmds1 σ' → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ' cmds2 σ'' → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ (cmds1 ++ cmds2) σ'' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ cmds1 σ' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ' cmds2 σ'' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ (cmds1 ++ cmds2) σ'' := by intro Heval1 Heval2 induction cmds1 generalizing cmds2 σ simp only [List.nil_append] @@ -97,12 +87,12 @@ theorem eval_stmts_concat assumption rename_i cmd cmds ind cases Heval1 - apply EvalStmts.stmts_some_sem (by assumption) + apply EvalBlock.stmts_some_sem (by assumption) apply ind (by assumption) (by assumption) theorem EvalCmdDefMonotone [HasFvar P] [HasBool P] [HasNot P] : isDefined σ v → - EvalCmd P δ σ₀ σ c σ' → + EvalCmd P δ σ c σ' → isDefined σ' v := by intros Hdef Heval cases Heval <;> try exact Hdef @@ -110,10 +100,10 @@ theorem EvalCmdDefMonotone [HasFvar P] [HasBool P] [HasNot P] : next _ _ Hup => exact UpdateStateDefMonotone Hdef Hup next _ _ Hup => exact UpdateStateDefMonotone Hdef Hup -theorem EvalStmtsEmpty {P : PureExpr} {Cmd : Type} {EvalCmd : EvalCmdParam P Cmd} - { σ σ' σ₀: SemanticStore P } { δ : SemanticEval P } +theorem EvalBlockEmpty {P : PureExpr} {Cmd : Type} {EvalCmd : EvalCmdParam P Cmd} + { σ σ': SemanticStore P } { δ : SemanticEval P } [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmts P Cmd EvalCmd δ σ₀ σ ([]: (List (Stmt P Cmd))) σ' → σ = σ' := by + EvalBlock P Cmd EvalCmd δ σ ([]: (List (Stmt P Cmd))) σ' → σ = σ' := by intros H; cases H <;> simp mutual @@ -121,46 +111,44 @@ theorem EvalStmtDefMonotone [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] : isDefined σ v → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ s σ' → + EvalStmt P (Cmd P) (EvalCmd P) δ σ s σ' → isDefined σ' v := by intros Hdef Heval match s with | .cmd c => cases Heval; next Hwf Hup => exact EvalCmdDefMonotone Hdef Hup - | .block l ⟨ bss ⟩ _ => - cases Heval; next Hwf Hup => cases Hup; next Hup => - apply EvalStmtsDefMonotone (ss:=bss) <;> try assumption - | .ite c ⟨ tss ⟩ ⟨ bss ⟩ _ => cases Heval with + | .block l bss _ => + cases Heval; next Hwf Hup => + apply EvalBlockDefMonotone <;> assumption + | .ite c tss bss _ => cases Heval with | ite_true_sem Hsome Hwf Heval => - cases Heval; next Heval => - apply EvalStmtsDefMonotone (ss:=tss) <;> try assumption + apply EvalBlockDefMonotone <;> assumption | ite_false_sem Hsome Hwf Heval => - cases Heval; next Heval => - apply EvalStmtsDefMonotone (ss:=bss) <;> try assumption + apply EvalBlockDefMonotone <;> assumption | .goto _ _ => cases Heval | .loop _ _ _ _ _ => cases Heval termination_by (Stmt.sizeOf s) decreasing_by all_goals simp [*] at * <;> omega -theorem EvalStmtsDefMonotone +theorem EvalBlockDefMonotone [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] : isDefined σ v → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ ss σ' → + EvalBlock P (Cmd P) (EvalCmd P) δ σ ss σ' → isDefined σ' v := by intros Hdef Heval cases ss with | nil => - have Heq := EvalStmtsEmpty Heval + have Heq := EvalBlockEmpty Heval simp [← Heq] assumption | cons h t => cases Heval <;> try assumption next σ1 Heval1 Heval2 => - apply EvalStmtsDefMonotone (σ:=σ1) + apply EvalBlockDefMonotone (σ:=σ1) apply EvalStmtDefMonotone <;> assumption assumption - termination_by (Stmts.sizeOf ss) + termination_by (Block.sizeOf ss) decreasing_by all_goals simp [*] at * <;> omega end diff --git a/Strata/DL/Imperative/StmtSemanticsSmallStep.lean b/Strata/DL/Imperative/StmtSemanticsSmallStep.lean new file mode 100644 index 000000000..5cb908987 --- /dev/null +++ b/Strata/DL/Imperative/StmtSemanticsSmallStep.lean @@ -0,0 +1,211 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Imperative.CmdSemantics +import Strata.DL.Util.Relations + +--------------------------------------------------------------------- + +namespace Imperative + +/-! ## Small-Step Operational Semantics for Statements + +This module defines small-step operational semantics for the Imperative +dialect's statement constructs. +-/ + +/-- +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 +-/ +inductive Config (P : PureExpr) (CmdT : Type) : Type where + /-- A single statement to execute next. -/ + | stmt : Stmt P CmdT → SemanticStore P → Config P CmdT + /-- A list of statements to execute next, in order. -/ + | stmts : List (Stmt P CmdT) → SemanticStore P → Config P CmdT + /-- A terminal configuration, indicating that execution has finished. -/ + | terminal : SemanticStore P → Config P CmdT + +/-- +Small-step operational semantics for statements. The relation `StepStmt` +defines a single execution step from one configuration to another. +-/ +inductive StepStmt + {CmdT : Type} + (P : PureExpr) + (EvalCmd : EvalCmdParam P CmdT) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] : + SemanticEval P → SemanticStore P → Config P CmdT → Config P CmdT → Prop where + + /-- A command steps to terminal configuration if it evaluates successfully -/ + | step_cmd : + EvalCmd δ σ c σ' → + ---- + StepStmt P EvalCmd δ σ + (.stmt (.cmd c) σ) + (.terminal σ') + + /-- A labeled block steps to its statement list. -/ + | step_block : + StepStmt P EvalCmd δ σ + (.stmt (.block _ ss _) σ) + (.stmts ss σ) + + /-- If the condition of an `ite` statement evaluates to true, step to the then + branch. -/ + | step_ite_true : + δ σ c = .some HasBool.tt → + WellFormedSemanticEvalBool δ → + ---- + StepStmt P EvalCmd δ σ + (.stmt (.ite c tss ess _) σ) + (.stmts tss σ) + + /-- If the condition of an `ite` statement evaluates to false, step to the else + branch. -/ + | step_ite_false : + δ σ c = .some HasBool.ff → + WellFormedSemanticEvalBool δ → + ---- + StepStmt P EvalCmd δ σ + (.stmt (.ite c tss ess _) σ) + (.stmts ess σ) + + /-- If a loop guard is true, execute the body and then loop again. -/ + | step_loop_enter : + δ σ g = .some HasBool.tt → + WellFormedSemanticEvalBool δ → + ---- + StepStmt P EvalCmd δ σ + (.stmt (.loop g m inv body md) σ) + (.stmts (body ++ [.loop g m inv body md]) σ) + + /-- If a loop guard is false, terminate the loop. -/ + | step_loop_exit : + δ σ g = .some HasBool.ff → + WellFormedSemanticEvalBool δ → + ---- + StepStmt P EvalCmd δ σ + (.stmt (.loop g m inv body _) σ) + (.terminal σ) + + /- Goto: not implemented, because we plan to remove it. -/ + + /-- An empty list of statements steps to `.terminal` with no state changes. -/ + | step_stmts_nil : + StepStmt P EvalCmd δ σ + (.stmts [] σ) + (.terminal σ) + + /-- To evaluate a sequence of statements, evaluate the first statement and + then evaluate the remaining statements in the resulting state. -/ + | step_stmt_cons : + StepStmt P EvalCmd δ σ (.stmt s σ) (.terminal σ') → + ---- + StepStmt P EvalCmd δ σ + (.stmts (s :: ss) σ) + (.stmts ss σ') + +/-- +Multi-step execution: reflexive transitive closure of single steps. +-/ +def StepStmtStar + {CmdT : Type} + (P : PureExpr) + (EvalCmd : EvalCmdParam P CmdT) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] : + SemanticEval P → SemanticStore P → Config P CmdT → Config P CmdT → Prop := fun δ σ => ReflTrans (StepStmt P EvalCmd δ σ) + +/-- A statement evaluates successfully if it can step to a terminal +configuration. +-/ +def EvalStmtSmall + {CmdT : Type} + (P : PureExpr) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] + (EvalCmd : EvalCmdParam P CmdT) + (δ : SemanticEval P) + (σ : SemanticStore P) + (s : Stmt P CmdT) + (σ' : SemanticStore P) : Prop := + StepStmtStar P EvalCmd δ σ (.stmt s σ) (.terminal σ') + +/-- A list of statements evaluates successfully if it can step to a terminal +configuration. +-/ +def EvalStmtsSmall + (P : PureExpr) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] + (EvalCmd : EvalCmdParam P CmdT) + (δ : SemanticEval P) + (σ : SemanticStore P) + (ss : List (Stmt P CmdT)) + (σ' : SemanticStore P) : Prop := + StepStmtStar P EvalCmd δ σ (.stmts ss σ) (.terminal σ') + +--------------------------------------------------------------------- + +/-! ## Basic Properties and Theorems -/ + +/-- +Empty statement list evaluation. +-/ +theorem evalStmtsSmallNil + (P : PureExpr) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] + (δ : SemanticEval P) + (σ : SemanticStore P) + (EvalCmd : EvalCmdParam P CmdT) : + EvalStmtsSmall P EvalCmd δ σ [] σ := by + unfold EvalStmtsSmall + apply ReflTrans.step + · exact StepStmt.step_stmts_nil + · apply ReflTrans.refl + +/-- +Configuration is terminal if no further steps are possible. +-/ +def IsTerminal + {CmdT : Type} + (P : PureExpr) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] + (δ : SemanticEval P) + (σ : SemanticStore P) + (EvalCmd : EvalCmdParam P CmdT) + (c : Config P CmdT) : Prop := + ∀ c', ¬ StepStmt P EvalCmd δ σ c c' + +/-- +Terminal configurations are indeed terminal. +-/ +theorem terminalIsTerminal + {CmdT : Type} + (P : PureExpr) + [HasVarsImp P (List (Stmt P CmdT))] + [HasVarsImp P CmdT] [HasFvar P] [HasVal P] + [HasBool P] [HasNot P] + (σ : SemanticStore P) + (δ : SemanticEval P) + (EvalCmd : EvalCmdParam P CmdT) : + IsTerminal P δ σ EvalCmd (.terminal σ) := by + intro c' h + cases h + +end Imperative diff --git a/Strata/DL/Imperative/TypeContext.lean b/Strata/DL/Imperative/TypeContext.lean index 84189606e..86480f7a0 100644 --- a/Strata/DL/Imperative/TypeContext.lean +++ b/Strata/DL/Imperative/TypeContext.lean @@ -13,15 +13,30 @@ open Std (ToFormat Format format) --------------------------------------------------------------------- -class TypeContext (P : PureExpr) (C: Type) (T : Type) where +/-- +Interface that must be provided to instantiate the type checker for Imperative's +commands (`Cmds.typeCheck`) for expressions specified using `PureExpr`. A +description of some parameters is as follows: + +`TypeError`: Kinds of errors that can arise during type checking Imperative's +commands. + +`Context`: contains information that does not change throughout type checking, +such as known types and known functions. + +`TypeEnv`: contains a map of variables to their types, type substitution +information, etc. +-/ +class TypeContext (P : PureExpr) (Context TypeEnv TypeError : Type) where isBoolType : P.Ty → Bool freeVars : P.Expr → List P.Ident - preprocess : C → T → P.Ty → Except Format (P.Ty × T) - postprocess : C → T → P.Ty → Except Format (P.Ty × T) - update : T → P.Ident → P.Ty → T - lookup : T → P.Ident → Option P.Ty - inferType : C → T → Cmd P → P.Expr → Except Format (P.Expr × P.Ty × T) - unifyTypes : T → List (P.Ty × P.Ty) → Except Format T + preprocess : Context → TypeEnv → P.Ty → Except TypeError (P.Ty × TypeEnv) + postprocess : Context → TypeEnv → P.Ty → Except TypeError (P.Ty × TypeEnv) + update : TypeEnv → P.Ident → P.Ty → TypeEnv + lookup : TypeEnv → P.Ident → Option P.Ty + inferType : Context → TypeEnv → Cmd P → P.Expr → Except TypeError (P.Expr × P.Ty × TypeEnv) + unifyTypes : TypeEnv → List (P.Ty × P.Ty) → Except TypeError TypeEnv + typeErrorFmt : TypeError → Std.Format --------------------------------------------------------------------- end Imperative diff --git a/Strata/DL/Lambda/Factory.lean b/Strata/DL/Lambda/Factory.lean index 42cfaba20..68e8341c8 100644 --- a/Strata/DL/Lambda/Factory.lean +++ b/Strata/DL/Lambda/Factory.lean @@ -26,10 +26,14 @@ namespace Lambda open Std (ToFormat Format format) +variable {T : LExprParams} [Inhabited T.Metadata] [Inhabited T.IDMeta] [DecidableEq T.IDMeta] [BEq T.IDMeta] [ToFormat T.IDMeta] + --------------------------------------------------------------------- open LTy.Syntax +section Factory + variable {IDMeta : Type} [DecidableEq IDMeta] [Inhabited IDMeta] /-- @@ -80,22 +84,25 @@ has the right number and type of arguments, etc.? (TODO) Use `.bvar`s in the body to correspond to the formals instead of using `.fvar`s. -/ -structure LFunc (IDMeta : Type) where - name : Identifier IDMeta +structure LFunc (T : LExprParams) where + name : T.Identifier typeArgs : List TyIdentifier := [] - inputs : @LMonoTySignature IDMeta + isConstr : Bool := false --whether function is datatype constructor + inputs : @LMonoTySignature T.IDMeta output : LMonoTy - body : Option (LExpr LMonoTy IDMeta) := .none + body : Option (LExpr T.mono) := .none -- (TODO): Add support for a fixed set of attributes (e.g., whether to inline -- a function, etc.). attr : Array String := #[] - concreteEval : Option ((LExpr LMonoTy IDMeta) → List (LExpr LMonoTy IDMeta) → (LExpr LMonoTy IDMeta)) := .none - axioms : List (LExpr LMonoTy IDMeta) := [] -- For axiomatic definitions + -- The T.Metadata argument is the metadata that will be attached to the + -- resulting expression of concreteEval if evaluation was successful. + concreteEval : Option (T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono)) := .none + axioms : List (LExpr T.mono) := [] -- For axiomatic definitions -instance : Inhabited (LFunc IDMeta) where +instance [Inhabited T.Metadata] [Inhabited T.IDMeta] : Inhabited (LFunc T) where default := { name := Inhabited.default, inputs := [], output := LMonoTy.bool } -instance : ToFormat (LFunc IDMeta) where +instance : ToFormat (LFunc T) where format f := let attr := if f.attr.isEmpty then f!"" else f!"@[{f.attr}]{Format.line}" let typeArgs := if f.typeArgs.isEmpty @@ -108,12 +115,12 @@ instance : ToFormat (LFunc IDMeta) where func {f.name} : {type}{sep}\ {body}" -def LFunc.type (f : (LFunc IDMeta)) : Except Format LTy := do - if !f.inputs.keys.Nodup then +def LFunc.type (f : (LFunc T)) : Except Format LTy := do + if !(decide f.inputs.keys.Nodup) then .error f!"[{f.name}] Duplicates found in the formals!\ {Format.line}\ {f.inputs}" - else if !f.typeArgs.Nodup then + else if !(decide f.typeArgs.Nodup) then .error f!"[{f.name}] Duplicates found in the universally \ quantified type identifiers!\ {Format.line}\ @@ -125,21 +132,21 @@ def LFunc.type (f : (LFunc IDMeta)) : Except Format LTy := do | ity :: irest => .ok (.forAll f.typeArgs (Lambda.LMonoTy.mkArrow ity (irest ++ output_tys))) -def LFunc.opExpr (f: LFunc IDMeta) : LExpr LMonoTy IDMeta := +def LFunc.opExpr [Inhabited T.Metadata] (f: LFunc T) : LExpr T.mono := let input_tys := f.inputs.values let output_tys := Lambda.LMonoTy.destructArrow f.output let ty := match input_tys with | [] => f.output | ity :: irest => Lambda.LMonoTy.mkArrow ity (irest ++ output_tys) - .op f.name ty + .op (default : T.Metadata) f.name (some ty) -def LFunc.inputPolyTypes (f : (LFunc IDMeta)) : @LTySignature IDMeta := +def LFunc.inputPolyTypes (f : (LFunc T)) : @LTySignature T.IDMeta := f.inputs.map (fun (id, mty) => (id, .forAll f.typeArgs mty)) -def LFunc.outputPolyType (f : (LFunc IDMeta)) : LTy := +def LFunc.outputPolyType (f : (LFunc T)) : LTy := .forAll f.typeArgs f.output -def LFunc.eraseTypes (f : LFunc IDMeta) : LFunc IDMeta := +def LFunc.eraseTypes (f : LFunc T) : LFunc T := { f with body := f.body.map LExpr.eraseTypes, axioms := f.axioms.map LExpr.eraseTypes @@ -154,24 +161,24 @@ IDMeta)` -- lambdas are our only tool. `Factory` gives us a way to add support for concrete/symbolic evaluation and type checking for `FunFactory` functions without actually modifying any core logic or the ASTs. -/ -def Factory := Array (LFunc IDMeta) +def Factory (T : LExprParams) := Array (LFunc T) -def Factory.default : @Factory IDMeta := #[] +def Factory.default : @Factory T := #[] -instance : Inhabited (@Factory IDMeta) where - default := @Factory.default IDMeta +instance : Inhabited (@Factory T) where + default := @Factory.default T -def Factory.getFunctionNames (F : @Factory IDMeta) : Array (Identifier IDMeta) := +def Factory.getFunctionNames (F : @Factory T) : Array T.Identifier := F.map (fun f => f.name) -def Factory.getFactoryLFunc (F : @Factory IDMeta) (name : Identifier IDMeta) : Option (LFunc IDMeta) := - F.find? (fun fn => fn.name == name) +def Factory.getFactoryLFunc (F : @Factory T) (name : String) : Option (LFunc T) := + F.find? (fun fn => fn.name.name == name) /-- Add a function `func` to the factory `F`. Redefinitions are not allowed. -/ -def Factory.addFactoryFunc (F : @Factory IDMeta) (func : (LFunc IDMeta)) : Except Format (@Factory IDMeta) := - match F.getFactoryLFunc func.name with +def Factory.addFactoryFunc (F : @Factory T) (func : LFunc T) : Except Format (@Factory T) := + match F.getFactoryLFunc func.name.name with | none => .ok (F.push func) | some func' => .error f!"A function of name {func.name} already exists! \ @@ -183,39 +190,81 @@ def Factory.addFactoryFunc (F : @Factory IDMeta) (func : (LFunc IDMeta)) : Excep Append a factory `newF` to an existing factory `F`, checking for redefinitions along the way. -/ -def Factory.addFactory (F newF : @Factory IDMeta) : Except Format (@Factory IDMeta) := +def Factory.addFactory (F newF : @Factory T) : Except Format (@Factory T) := Array.foldlM (fun factory func => factory.addFactoryFunc func) F newF -def getLFuncCall (e : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) × List (LExpr LMonoTy IDMeta) := +def getLFuncCall {GenericTy} (e : LExpr ⟨T, GenericTy⟩) : LExpr ⟨T, GenericTy⟩ × List (LExpr ⟨T, GenericTy⟩) := go e [] - where go e (acc : List (LExpr LMonoTy IDMeta)) := + where go e (acc : List (LExpr ⟨T, GenericTy⟩)) := match e with - | .app (.app e' arg1) arg2 => go e' ([arg1, arg2] ++ acc) - | .app (.op fn fnty) arg1 => ((.op fn fnty), ([arg1] ++ acc)) + | .app _ (.app _ e' arg1) arg2 => go e' ([arg1, arg2] ++ acc) + | .app _ (.op m fn fnty) arg1 => ((.op m fn fnty), ([arg1] ++ acc)) | _ => (e, acc) -def getConcreteLFuncCall (e : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) × List (LExpr LMonoTy IDMeta) := +def getConcreteLFuncCall (e : LExpr ⟨T, GenericTy⟩) : LExpr ⟨T, GenericTy⟩ × List (LExpr ⟨T, GenericTy⟩) := let (op, args) := getLFuncCall e - if args.all LExpr.isConst then (op, args) else (e, []) + if args.all (@LExpr.isConst ⟨T, GenericTy⟩) then (op, args) else (e, []) /-- If `e` is a call of a factory function, get the operator (`.op`), a list of all the actuals, and the `(LFunc IDMeta)`. -/ -def Factory.callOfLFunc (F : @Factory IDMeta) (e : (LExpr LMonoTy IDMeta)) : Option ((LExpr LMonoTy IDMeta) × List (LExpr LMonoTy IDMeta) × (LFunc IDMeta)) := +def Factory.callOfLFunc {GenericTy} (F : @Factory T) (e : LExpr ⟨T, GenericTy⟩) + (allowPartialApp := false) + : Option (LExpr ⟨T, GenericTy⟩ × List (LExpr ⟨T, GenericTy⟩) × LFunc T) := let (op, args) := getLFuncCall e match op with - | .op name _ => - let maybe_func := getFactoryLFunc F name + | .op _ name _ => + let maybe_func := getFactoryLFunc F name.name match maybe_func with | none => none | some func => -- Note that we don't do any type or well-formedness checking here; this -- is just a simple arity check. - match args.length == func.inputs.length with + let matchesArg:Bool := + if allowPartialApp then Nat.ble args.length func.inputs.length + else args.length == func.inputs.length + match matchesArg with | true => (op, args, func) | false => none | _ => none +end Factory + +theorem getLFuncCall.go_size {T: LExprParamsT} {e: LExpr T} {op args acc} : getLFuncCall.go e acc = (op, args) → +op.sizeOf + List.sum (args.map LExpr.sizeOf) <= e.sizeOf + List.sum (acc.map LExpr.sizeOf) := by + fun_induction go generalizing op args + case case1 acc e' arg1 arg2 IH => + intros Hgo; specialize (IH Hgo); simp_all; omega + case case2 acc fn fnty arg1 => + simp_all; intros op_eq args_eq; subst op args; simp; omega + case case3 op' args' _ _ => intros Hop; cases Hop; omega + +theorem LExpr.sizeOf_pos {T} (e: LExpr T): 0 < sizeOf e := by + cases e<;> simp <;> omega + +theorem List.sum_size_le (f: α → Nat) {l: List α} {x: α} (x_in: x ∈ l): f x ≤ List.sum (l.map f) := by + induction l; simp_all; grind + +theorem getLFuncCall_smaller {T} {e: LExpr T} {op args} : getLFuncCall e = (op, args) → (forall a, a ∈ args → a.sizeOf < e.sizeOf) := by + unfold getLFuncCall; intros Hgo; have Hsize := (getLFuncCall.go_size Hgo); + simp_all; have Hop:= LExpr.sizeOf_pos op; intros a a_in; + have Ha := List.sum_size_le LExpr.sizeOf a_in; omega + +theorem Factory.callOfLFunc_smaller {T} {F : @Factory T.base} {e : LExpr T} {op args F'} + {allowPartialMatch} + : Factory.callOfLFunc F e (allowPartialApp := allowPartialMatch) = some (op, args, F') → + (forall a, a ∈ args → a.sizeOf < e.sizeOf) := by + simp[Factory.callOfLFunc]; cases Hfunc: (getLFuncCall e) with | mk op args; + simp; cases op <;> simp + rename_i o ty; cases (F.getFactoryLFunc o.name) <;> simp + rename_i F' + cases allowPartialMatch + · cases (args.length == List.length F'.inputs) <;> simp; intros op_eq args_eq F_eq + subst op args F'; exact (getLFuncCall_smaller Hfunc) + · cases (Nat.ble args.length (List.length F'.inputs)) <;> simp + intros op_eq args_eq F_eq + subst op args F'; exact (getLFuncCall_smaller Hfunc) + end Lambda --------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/Identifiers.lean b/Strata/DL/Lambda/Identifiers.lean index e101eb811..82944ec1c 100644 --- a/Strata/DL/Lambda/Identifiers.lean +++ b/Strata/DL/Lambda/Identifiers.lean @@ -20,7 +20,10 @@ section Identifiers Identifiers with a name and additional metadata -/ structure Identifier (IDMeta : Type) : Type where + /-- A unique name. -/ name : String + /-- Any additional metadata that it would be useful to attach to an + identifier. -/ metadata : IDMeta deriving Repr, DecidableEq, Inhabited @@ -34,26 +37,26 @@ instance {IDMeta} [Inhabited IDMeta] : Coe String (Identifier IDMeta) where coe s := ⟨s, Inhabited.default⟩ /-- -Identifiers, optionally with their inferred monotype. +Identifiers, optionally with their inferred type. -/ -abbrev IdentT (IDMeta : Type) := (Identifier IDMeta) × Option LMonoTy -abbrev IdentTs (IDMeta : Type) := List (IdentT IDMeta) +abbrev IdentT (ITy IDMeta: Type) := (Identifier IDMeta) × Option ITy +abbrev IdentTs (ITy IDMeta: Type) := List (IdentT ITy IDMeta) -instance {IDMeta : Type} : ToFormat (IdentT IDMeta) where +instance {IDMeta ITy: Type} [ToFormat ITy]: ToFormat (IdentT ITy IDMeta) where format i := match i.snd with | none => f!"{i.fst}" | some ty => f!"({i.fst} : {ty})" -def IdentT.ident (x : (IdentT IDMeta)) : Identifier IDMeta := +def IdentT.ident (x : (IdentT ITy IDMeta)) : Identifier IDMeta := x.fst -def IdentT.monoty? (x : (IdentT IDMeta)) : Option LMonoTy := +def IdentT.ty? (x : (IdentT ITy IDMeta)) : Option ITy := x.snd -def IdentTs.idents (xs : (IdentTs IDMeta)) : List (Identifier IDMeta) := +def IdentTs.idents (xs : (IdentTs ITy IDMeta)) : List (Identifier IDMeta) := xs.map Prod.fst -def IdentTs.monotys? (xs : (IdentTs IDMeta)) : List (Option LMonoTy) := +def IdentTs.tys? (xs : (IdentTs ITy IDMeta)) : List (Option ITy) := xs.map Prod.snd abbrev Identifiers IDMeta := Std.HashMap String IDMeta @@ -79,7 +82,9 @@ def Identifiers.containsName {IDMeta} [DecidableEq IDMeta] (m: Identifiers IDMet m[n]?.isSome theorem Identifiers.addWithErrorNotin {IDMeta} [DecidableEq IDMeta] {m m': Identifiers IDMeta} {x: Identifier IDMeta}: m.addWithError x f = .ok m' → m.contains x = false := by - unfold addWithError contains; grind + unfold addWithError contains + simp + grind theorem Identifiers.addWithErrorContains {IDMeta} [DecidableEq IDMeta] {m m': Identifiers IDMeta} {x: Identifier IDMeta}: m.addWithError x f = .ok m' → ∀ y, m'.contains y ↔ x = y ∨ m.contains y := by unfold addWithError contains; diff --git a/Strata/DL/Lambda/IntBoolFactory.lean b/Strata/DL/Lambda/IntBoolFactory.lean index 0cf91fb45..82eaad674 100644 --- a/Strata/DL/Lambda/IntBoolFactory.lean +++ b/Strata/DL/Lambda/IntBoolFactory.lean @@ -19,148 +19,147 @@ open LExpr LTy section IntBoolFactory -def unaryOp[Coe String (Identifier IDMeta)] - (n : Identifier IDMeta) +variable {T : LExprParams} [Coe String T.Identifier] + +def unaryOp (n : T.Identifier) (ty : LMonoTy) - (ceval : Option (LExpr LMonoTy IDMeta → List (LExpr LMonoTy IDMeta) → LExpr LMonoTy IDMeta)) : LFunc IDMeta := + (ceval : Option (T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono))) : LFunc T := { name := n, inputs := [("x", ty)], output := ty, concreteEval := ceval } -def binaryOp [Coe String (Identifier IDMeta)] - (n : Identifier IDMeta) +def binaryOp (n : T.Identifier) (ty : LMonoTy) - (ceval : Option (LExpr LMonoTy IDMeta → List (LExpr LMonoTy IDMeta) → LExpr LMonoTy IDMeta)) : LFunc IDMeta := + (ceval : Option (T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono))) : LFunc T := { name := n, inputs := [("x", ty), ("y", ty)], output := ty, concreteEval := ceval } -def binaryPredicate [Coe String (Identifier IDMeta)] - (n : Identifier IDMeta) +def binaryPredicate (n : T.Identifier) (ty : LMonoTy) - (ceval : Option (LExpr LMonoTy IDMeta → List (LExpr LMonoTy IDMeta) → LExpr LMonoTy IDMeta)) : LFunc IDMeta := + (ceval : Option (T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono))) : LFunc T := { name := n, inputs := [("x", ty), ("y", ty)], output := .bool, concreteEval := ceval } -def unOpCeval {IDMeta : Type} (InTy OutTy : Type) [ToString OutTy] - (cevalInTy : (LExpr LMonoTy IDMeta) → Option InTy) (op : InTy → OutTy) - (ty : LMonoTy) : - (LExpr LMonoTy IDMeta) → List (LExpr LMonoTy IDMeta) → (LExpr LMonoTy IDMeta) := - (fun e args => match args with +def unOpCeval (InTy OutTy : Type) [ToString OutTy] + (mkConst : T.Metadata → OutTy → LExpr T.mono) + (cevalInTy : (LExpr T.mono) → Option InTy) (op : InTy → OutTy) : + T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono) := + (fun m args => match args with | [e1] => let e1i := cevalInTy e1 match e1i with - | some x => (LExpr.const (toString (op x)) ty) - | _ => e - | _ => e) - -def binOpCeval {IDMeta : Type} (InTy OutTy : Type) [ToString OutTy] - (cevalInTy : (LExpr LMonoTy IDMeta) → Option InTy) (op : InTy → InTy → OutTy) - (ty : LMonoTy) : - (LExpr LMonoTy IDMeta) → List (LExpr LMonoTy IDMeta) → (LExpr LMonoTy IDMeta) := - (fun e args => match args with + | some x => .some (mkConst m (op x)) + | _ => .none + | _ => .none) + +def binOpCeval (InTy OutTy : Type) [ToString OutTy] + (mkConst : T.Metadata → OutTy → LExpr T.mono) + (cevalInTy : LExpr T.mono → Option InTy) (op : InTy → InTy → OutTy) : + T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono) := + (fun m args => match args with | [e1, e2] => let e1i := cevalInTy e1 let e2i := cevalInTy e2 match e1i, e2i with - | some x, some y => (LExpr.const (toString (op x y)) ty) - | _, _ => e - | _ => e) + | some x, some y => mkConst m (op x y) + | _, _ => .none + | _ => .none) -- We hand-code a denotation for `Int.Div` to leave the expression -- unchanged if we have `0` for the denominator. -def cevalIntDiv (e : LExpr LMonoTy IDMeta) (args : List (LExpr LMonoTy IDMeta)) : LExpr LMonoTy IDMeta := +def cevalIntDiv (m:T.Metadata) (args : List (LExpr T.mono)) : Option (LExpr T.mono) := match args with | [e1, e2] => let e1i := LExpr.denoteInt e1 let e2i := LExpr.denoteInt e2 match e1i, e2i with | some x, some y => - if y == 0 then e else (.const (toString (x / y)) (.some .int)) - | _, _ => e - | _ => e + if y == 0 then .none else .some (.intConst m (x / y)) + | _, _ => .none + | _ => .none -- We hand-code a denotation for `Int.Mod` to leave the expression -- unchanged if we have `0` for the denominator. -def cevalIntMod (e : LExpr LMonoTy IDMeta) (args : List (LExpr LMonoTy IDMeta)) : LExpr LMonoTy IDMeta := +def cevalIntMod (m:T.Metadata) (args : List (LExpr T.mono)) : Option (LExpr T.mono) := match args with | [e1, e2] => let e1i := LExpr.denoteInt e1 let e2i := LExpr.denoteInt e2 match e1i, e2i with | some x, some y => - if y == 0 then e else (.const (toString (x % y)) (.some .int)) - | _, _ => e - | _ => e + if y == 0 then .none else .some (.intConst m (x % y)) + | _, _ => .none + | _ => .none /- Integer Arithmetic Operations -/ -def intAddFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intAddFunc : LFunc T := binaryOp "Int.Add" .int - (some (binOpCeval Int Int LExpr.denoteInt Int.add .int)) + (some (binOpCeval Int Int (@intConst T.mono) LExpr.denoteInt Int.add)) -def intSubFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intSubFunc : LFunc T := binaryOp "Int.Sub" .int - (some (binOpCeval Int Int LExpr.denoteInt Int.sub .int)) + (some (binOpCeval Int Int (@intConst T.mono) LExpr.denoteInt Int.sub)) -def intMulFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intMulFunc : LFunc T := binaryOp "Int.Mul" .int - (some (binOpCeval Int Int LExpr.denoteInt Int.mul .int)) + (some (binOpCeval Int Int (@intConst T.mono) LExpr.denoteInt Int.mul)) -def intDivFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intDivFunc : LFunc T := binaryOp "Int.Div" .int (some cevalIntDiv) -def intModFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intModFunc : LFunc T := binaryOp "Int.Mod" .int (some cevalIntMod) -def intNegFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intNegFunc : LFunc T := unaryOp "Int.Neg" .int - (some (unOpCeval Int Int LExpr.denoteInt Int.neg .int)) + (some (unOpCeval Int Int (@intConst T.mono) LExpr.denoteInt Int.neg)) -def intLtFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intLtFunc : LFunc T := binaryPredicate "Int.Lt" .int - (some (binOpCeval Int Bool LExpr.denoteInt (fun x y => x < y) .bool)) + (some (binOpCeval Int Bool (@boolConst T.mono) LExpr.denoteInt (fun x y => x < y))) -def intLeFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intLeFunc : LFunc T := binaryPredicate "Int.Le" .int - (some (binOpCeval Int Bool LExpr.denoteInt (fun x y => x <= y) .bool)) + (some (binOpCeval Int Bool (@boolConst T.mono) LExpr.denoteInt (fun x y => x <= y))) -def intGtFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta:= +def intGtFunc : LFunc T := binaryPredicate "Int.Gt" .int - (some (binOpCeval Int Bool LExpr.denoteInt (fun x y => x > y) .bool)) + (some (binOpCeval Int Bool (@boolConst T.mono) LExpr.denoteInt (fun x y => x > y))) -def intGeFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def intGeFunc : LFunc T := binaryPredicate "Int.Ge" .int - (some (binOpCeval Int Bool LExpr.denoteInt (fun x y => x >= y) .bool)) + (some (binOpCeval Int Bool (@boolConst T.mono) LExpr.denoteInt (fun x y => x >= y))) /- Boolean Operations -/ -def boolAndFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def boolAndFunc : LFunc T := binaryOp "Bool.And" .bool - (some (binOpCeval Bool Bool LExpr.denoteBool Bool.and .bool)) + (some (binOpCeval Bool Bool (@boolConst T.mono) LExpr.denoteBool Bool.and)) -def boolOrFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def boolOrFunc : LFunc T := binaryOp "Bool.Or" .bool - (some (binOpCeval Bool Bool LExpr.denoteBool Bool.or .bool)) + (some (binOpCeval Bool Bool (@boolConst T.mono) LExpr.denoteBool Bool.or)) -def boolImpliesFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def boolImpliesFunc : LFunc T := binaryOp "Bool.Implies" .bool - (some (binOpCeval Bool Bool LExpr.denoteBool (fun x y => ((not x) || y)) .bool)) + (some (binOpCeval Bool Bool (@boolConst T.mono) LExpr.denoteBool (fun x y => ((not x) || y)))) -def boolEquivFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def boolEquivFunc : LFunc T := binaryOp "Bool.Equiv" .bool - (some (binOpCeval Bool Bool LExpr.denoteBool (fun x y => (x == y)) .bool)) + (some (binOpCeval Bool Bool (@boolConst T.mono) LExpr.denoteBool (fun x y => (x == y)))) -def boolNotFunc [Coe String (Identifier IDMeta)] : LFunc IDMeta := +def boolNotFunc : LFunc T := unaryOp "Bool.Not" .bool - (some (unOpCeval Bool Bool LExpr.denoteBool Bool.not .bool)) + (some (unOpCeval Bool Bool (@boolConst T.mono) LExpr.denoteBool Bool.not)) -def IntBoolFactory : @Factory Unit := +def IntBoolFactory : @Factory T := open LTy.Syntax in #[ intAddFunc, intSubFunc, diff --git a/Strata/DL/Lambda/LExpr.lean b/Strata/DL/Lambda/LExpr.lean index 75f1e6b79..d32689bb2 100644 --- a/Strata/DL/Lambda/LExpr.lean +++ b/Strata/DL/Lambda/LExpr.lean @@ -7,6 +7,7 @@ import Strata.DL.Lambda.LTy import Strata.DL.Lambda.Identifiers import Strata.DL.Lambda.MetaData +import Strata.DL.Util.DecidableEq /-! ## Lambda Expressions with Quantifiers @@ -24,8 +25,84 @@ inductive QuantifierKind | exist deriving Repr, DecidableEq +/- +Traceable class for combining multiple metadata with labeled provenance. + +Takes a list of (reason, metadata) pairs and combines them into a single metadata. +Each pair describes why that metadata is being included in the combination. + +Usage: + Traceable.combine [("function", fnMeta), ("argument", argMeta), ("context", ctxMeta)] +-/ +class Traceable (Reason: Type) (Metadata : Type) where + combine : List (Reason × Metadata) → Metadata + +/-- +Expected interface for pure expressions that can be used to specialize the +Imperative dialect. +-/ +structure LExprParams : Type 1 where + /-- The type of metadata allowed on expressions. -/ + Metadata: Type + /-- The type of metadata allowed on identifiers. -/ + IDMeta : Type + deriving Inhabited + +/-- +Extended LExprParams that includes TypeType parameter. +-/ +structure LExprParamsT : Type 1 where + /-- The base parameters, with the types for expression and identifier metadata. -/ + base : LExprParams + /-- The type of types used to annotate expressions. -/ + TypeType : Type + deriving Inhabited + +/-- +Dot notation syntax: T.mono transforms LExprParams into LExprParamsT with LMonoTy. +-/ +abbrev LExprParams.mono (T : LExprParams) : LExprParamsT := + ⟨T, LMonoTy⟩ + +abbrev LExprParams.Identifier (T : LExprParams) := Lambda.Identifier T.IDMeta + +structure Typed (T: Type) where + underlying: T + type: LMonoTy + +-- Metadata annotated with a type +abbrev LExprParams.typed (T: LExprParams): LExprParams := + ⟨ Typed T.Metadata, T.IDMeta ⟩ + +abbrev LExprParamsT.typed (T: LExprParamsT): LExprParamsT := + ⟨T.base.typed, LMonoTy⟩ + /-- -Lambda Expressions with Quantifiers. +Lambda constants. + +Constants are integers, strings, reals, bitvectors of a fixed length, or +booleans. +-/ +inductive LConst : Type where + /-- An unbounded integer constant. -/ + | intConst (i: Int) + + /-- A string constant, using Lean's `String` type for a sequence of Unicode + code points encoded with UTF-8. -/ + | strConst (s: String) + + /-- A real constant, represented as a rational number. -/ + | realConst (r: Rat) + + /-- A bit vector constant, represented using Lean's `BitVec` type. -/ + | bitvecConst (n: Nat) (b: BitVec n) + + /-- A Boolean constant. -/ + | boolConst (b: Bool) +deriving Repr, DecidableEq + +/-- +Lambda expressions with quantifiers. Like Lean's own expressions, we use the locally nameless representation for this abstract syntax. @@ -36,203 +113,366 @@ We leave placeholders for type annotations only for constants (`.const`), operations (`.op`), binders (`.abs`, `.quant`), and free variables (`.fvar`). -LExpr is parameterized by `TypeType`, which represents -user-allowed type annotations (optional), and `Identifier` for allowed -identifiers. For a fully annotated AST, see `LExprT` that is created after the -type inference transform. --/ -inductive LExpr (TypeType : Type) (IDMeta : Type) : Type where - /-- `.const c ty`: constants (in the sense of literals). -/ - | const (c : String) (ty : Option TypeType) - /-- `.op c ty`: operation names. -/ - | op (o : Identifier IDMeta) (ty : Option TypeType) - /-- `.bvar deBruijnIndex`: bound variable. -/ - | bvar (deBruijnIndex : Nat) - /-- `.fvar name ty`: free variable, with an option (mono)type annotation. -/ - | fvar (name : Identifier IDMeta) (ty : Option TypeType) - | mdata (info : Info) (e : LExpr TypeType IDMeta) - /-- `.abs ty e`: abstractions; `ty` the is type of bound variable. -/ - | abs (ty : Option TypeType) (e : LExpr TypeType IDMeta) - /-- `.quant k ty tr e`: quantified expressions; `ty` the is type of bound variable, and `tr` the trigger. -/ - | quant (k : QuantifierKind) (ty : Option TypeType) (trigger: LExpr TypeType IDMeta) (e : LExpr TypeType IDMeta) - /-- `.app fn e`: function application. -/ - | app (fn e : LExpr TypeType IDMeta) - /-- `.ite c t e`: if-then-else expression. -/ - | ite (c t e : LExpr TypeType IDMeta) - /-- `.eq e1 e2`: equality expression. -/ - | eq (e1 e2 : LExpr TypeType IDMeta) - deriving Repr, DecidableEq +LExpr is parameterized by `LExprParamsT`, which includes arbitrary metadata, +user-allowed type annotations (optional), and special metadata to attach to +`Identifier`s. Type inference adds any missing type annotations. +-/ +inductive LExpr (T : LExprParamsT) : Type where + /-- A constant (in the sense of literals). -/ + | const (m: T.base.Metadata) (c: LConst) + /-- A built-in operation, referred to by name. -/ + | op (m: T.base.Metadata) (o : Identifier T.base.IDMeta) (ty : Option T.TypeType) + /-- A bound variable, in de Bruijn form. -/ + | bvar (m: T.base.Metadata) (deBruijnIndex : Nat) + /-- A free variable, with an optional type annotation. -/ + | fvar (m: T.base.Metadata) (name : Identifier T.base.IDMeta) (ty : Option T.TypeType) + /-- An abstraction, where `ty` the is (optional) type of bound variable. -/ + | abs (m: T.base.Metadata) (ty : Option T.TypeType) (e : 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). -/ + | quant (m: T.base.Metadata) (k : QuantifierKind) (ty : Option T.TypeType) (trigger: LExpr T) (e : LExpr T) + /-- A function application. -/ + | app (m: T.base.Metadata) (fn e : LExpr T) + /-- A conditional expression. This is a constructor rather than a built-in + operation because it occurs so frequently. -/ + | ite (m: T.base.Metadata) (c t e : LExpr T) + /-- An equality expression. This is a constructor rather than a built-in + operation because it occurs so frequently. -/ + | eq (m: T.base.Metadata) (e1 e2 : LExpr T) + +instance [Repr T.base.Metadata] [Repr T.TypeType] [Repr T.base.IDMeta] : Repr (LExpr T) where + reprPrec e prec := + let rec go : LExpr T → Std.Format + | .const m c => + f!"LExpr.const {repr m} {repr c}" + | .op m o ty => + match ty with + | none => f!"LExpr.op {repr m} {repr o} none" + | some ty => f!"LExpr.op {repr m} {repr o} (some {repr ty})" + | .bvar m i => f!"LExpr.bvar {repr m} {repr i}" + | .fvar m name ty => + match ty with + | none => f!"LExpr.fvar {repr m} {repr name} none" + | some ty => f!"LExpr.fvar {repr m} {repr name} (some {repr ty})" + | .abs m ty e => + match ty with + | none => f!"LExpr.abs {repr m} none ({go e})" + | some ty => f!"LExpr.abs {repr m} (some {repr ty}) ({go e})" + | .quant m k ty tr e => + let kindStr := match k with | .all => "QuantifierKind.all" | .exist => "QuantifierKind.exist" + match ty with + | none => f!"LExpr.quant {repr m} {kindStr} none ({go tr}) ({go e})" + | some ty => f!"LExpr.quant {repr m} {kindStr} (some {repr ty}) ({go tr}) ({go e})" + | .app m fn e => f!"LExpr.app {repr m} ({go fn}) ({go e})" + | .ite m c t e => f!"LExpr.ite {repr m} ({go c}) ({go t}) ({go e})" + | .eq m e1 e2 => f!"LExpr.eq {repr m} ({go e1}) ({go e2})" + if prec > 0 then Std.Format.paren (go e) else go e + +-- Boolean equality function for LExpr +@[grind] +def LExpr.beq [BEq T.base.Metadata] [BEq T.TypeType] [BEq (Identifier T.base.IDMeta)] : LExpr T → LExpr T → Bool + | .const m1 c1, e2 => + match e2 with + | .const m2 c2 => m1 == m2 && c1 == c2 + | _ => false + | .op m1 o1 ty1, e2 => + match e2 with + | .op m2 o2 ty2 => m1 == m2 && o1 == o2 && ty1 == ty2 + | _ => false + | .bvar m1 i1, e2 => + match e2 with + | .bvar m2 i2 => m1 == m2 && i1 == i2 + | _ => false + | .fvar m1 n1 ty1, e2 => + match e2 with + | .fvar m2 n2 ty2 => m1 == m2 && n1 == n2 && ty1 == ty2 + | _ => false + | .abs m1 ty1 e1', e2 => + match e2 with + | .abs m2 ty2 e2' => m1 == m2 && ty1 == ty2 && LExpr.beq e1' e2' + | _ => false + | .quant m1 k1 ty1 tr1 e1', e2 => + match e2 with + | .quant m2 k2 ty2 tr2 e2' => + m1 == m2 && k1 == k2 && ty1 == ty2 && LExpr.beq tr1 tr2 && LExpr.beq e1' e2' + | _ => false + | .app m1 fn1 e1', e2 => + match e2 with + | .app m2 fn2 e2' => m1 == m2 && LExpr.beq fn1 fn2 && LExpr.beq e1' e2' + | _ => false + | .ite m1 c1 t1 e1', e2 => + match e2 with + | .ite m2 c2 t2 e2' => + m1 == m2 && LExpr.beq c1 c2 && LExpr.beq t1 t2 && LExpr.beq e1' e2' + | _ => false + | .eq m1 e1a e1b, e2 => + match e2 with + | .eq m2 e2a e2b => m1 == m2 && LExpr.beq e1a e2a && LExpr.beq e1b e2b + | _ => false + +instance [BEq T.base.Metadata] [BEq T.TypeType] [BEq (Identifier T.base.IDMeta)] : BEq (LExpr T) where + beq := LExpr.beq + +-- First, prove that beq is sound and complete +theorem LExpr.beq_eq {T : LExprParamsT} [DecidableEq T.base.Metadata] [DecidableEq T.TypeType] [DecidableEq T.base.IDMeta] + (e1 e2 : LExpr T) : LExpr.beq e1 e2 = true ↔ e1 = e2 := by + solve_beq e1 e2 + +-- Now use this theorem in DecidableEq +instance {T: LExprParamsT} [DecidableEq T.base.Metadata] [DecidableEq T.TypeType] [DecidableEq T.base.IDMeta] : DecidableEq (LExpr T) := + fun e1 e2 => + if h : LExpr.beq e1 e2 then + isTrue (LExpr.beq_eq e1 e2 |>.mp h) + else + isFalse (fun heq => h (LExpr.beq_eq e1 e2 |>.mpr heq)) + +def LExpr.noTrigger {T : LExprParamsT} (m : T.base.Metadata) : LExpr T := .bvar m 0 +def LExpr.allTr {T : LExprParamsT} (m : T.base.Metadata) (ty : Option T.TypeType) := @LExpr.quant T m .all ty +def LExpr.all {T : LExprParamsT} (m : T.base.Metadata) (ty : Option T.TypeType) := @LExpr.quant T m .all ty (LExpr.noTrigger m) +def LExpr.existTr {T : LExprParamsT} (m : T.base.Metadata) (ty : Option T.TypeType) := @LExpr.quant T m .exist ty +def LExpr.exist {T : LExprParamsT} (m : T.base.Metadata) (ty : Option T.TypeType) := @LExpr.quant T m .exist ty (LExpr.noTrigger m) -def LExpr.noTrigger {TypeType: Type} {IDMeta : Type} : LExpr TypeType IDMeta := .bvar 0 -def LExpr.allTr {TypeType: Type} {IDMeta : Type} (ty : Option TypeType) := @LExpr.quant TypeType IDMeta .all ty -def LExpr.all {TypeType: Type} {IDMeta : Type} (ty : Option TypeType) := @LExpr.quant TypeType IDMeta .all ty LExpr.noTrigger -def LExpr.existTr {TypeType: Type} {IDMeta : Type} (ty : Option TypeType) := @LExpr.quant TypeType IDMeta .exist ty -def LExpr.exist {TypeType: Type} {IDMeta : Type} (ty : Option TypeType) := @LExpr.quant TypeType IDMeta .exist ty LExpr.noTrigger - -abbrev LExpr.absUntyped {TypeType: Type} {IDMeta : Type} := @LExpr.abs TypeType IDMeta .none -abbrev LExpr.allUntypedTr {TypeType: Type} {IDMeta : Type} := @LExpr.quant TypeType IDMeta .all .none -abbrev LExpr.allUntyped {TypeType: Type} {IDMeta : Type} := @LExpr.quant TypeType IDMeta .all .none LExpr.noTrigger -abbrev LExpr.existUntypedTr {TypeType: Type} {IDMeta : Type} := @LExpr.quant TypeType IDMeta .exist .none -abbrev LExpr.existUntyped {TypeType: Type} {IDMeta : Type} := @LExpr.quant TypeType IDMeta .exist .none LExpr.noTrigger - - -def LExpr.sizeOf {TypeType: Type} [SizeOf IDMeta] - | LExpr.mdata (TypeType:=TypeType) (IDMeta:=IDMeta) _ e => 2 + sizeOf e - | LExpr.abs _ e => 2 + sizeOf e - | LExpr.quant _ _ tr e => 3 + sizeOf e + sizeOf tr - | LExpr.app fn e => 3 + sizeOf fn + sizeOf e - | LExpr.ite c t e => 4 + sizeOf c + sizeOf t + sizeOf e - | LExpr.eq e1 e2 => 3 + sizeOf e1 + sizeOf e2 +@[match_pattern] +def LExpr.intConst (m : T.base.Metadata) (n: Int) : LExpr T := .const m <| LConst.intConst n +@[match_pattern] +def LExpr.strConst (m : T.base.Metadata) (s: String) : LExpr T := .const m <| LConst.strConst s +@[match_pattern] +def LExpr.realConst (m : T.base.Metadata) (r: Rat) : LExpr T := .const m <| LConst.realConst r +@[match_pattern] +def LExpr.bitvecConst (m : T.base.Metadata) (n: Nat) (b: BitVec n) : LExpr T := .const m <| LConst.bitvecConst n b +@[match_pattern] +def LExpr.boolConst (m : T.base.Metadata) (b: Bool) : LExpr T := .const m <| LConst.boolConst b + +abbrev LExpr.absUntyped {T : LExprParamsT} (m : T.base.Metadata) := @LExpr.abs T m .none +abbrev LExpr.allUntypedTr {T : LExprParamsT} (m : T.base.Metadata) := @LExpr.quant T m .all .none +abbrev LExpr.allUntyped {T : LExprParamsT} (m : T.base.Metadata) := @LExpr.quant T m .all .none (LExpr.noTrigger m) +abbrev LExpr.existUntypedTr {T : LExprParamsT} (m : T.base.Metadata) := @LExpr.quant T m .exist .none +abbrev LExpr.existUntyped {T : LExprParamsT} (m : T.base.Metadata) := @LExpr.quant T m .exist .none (LExpr.noTrigger m) + +@[simp] +def LExpr.sizeOf: LExpr T → Nat + | LExpr.abs _ _ e => 2 + sizeOf e + | LExpr.quant _ _ _ tr e => 3 + sizeOf e + sizeOf tr + | LExpr.app _ fn e => 3 + sizeOf fn + sizeOf e + | LExpr.ite _ c t e => 4 + sizeOf c + sizeOf t + sizeOf e + | LExpr.eq _ e1 e2 => 3 + sizeOf e1 + sizeOf e2 | _ => 1 -instance : SizeOf (LExpr TypeType IDMeta) where +instance : SizeOf (LExpr T) where sizeOf := LExpr.sizeOf + +/-- +Get type of a constant `c` +-/ +def LConst.ty (c: LConst) : LMonoTy := + match c with + | .intConst _ => .int + | .strConst _ => .string + | .bitvecConst n _ => .bitvec n + | .realConst _ => .real + | .boolConst _ => .bool + +/-- +Get type name of a constant `c` (e.g. "int") +-/ +def LConst.tyName (c: LConst) : String := + match c with + | .intConst _ => "int" + | .strConst _ => "string" + | .bitvecConst _ _ => "bitvec" + | .realConst _ => "real" + | .boolConst _ => "bool" + +/-- +Get type name of a constant `c` as a Format (e.g. "Integers") +-/ +def LConst.tyNameFormat (c: LConst) : Format := + match c with + | .intConst _ => f!"Integers" + | .strConst _ => f!"Strings" + | .bitvecConst n _ => f!"Bit vectors of size {n}" + | .realConst _ => f!"Reals" + | .boolConst _ => f!"Booleans" + --------------------------------------------------------------------- namespace LExpr -instance : Inhabited (LExpr TypeType IDMeta) where - default := .const "false" none - -def LExpr.getVars (e : (LExpr TypeType IDMeta)) := match e with - | .const _ _ => [] | .bvar _ => [] | .op _ _ => [] - | .fvar y _ => [y] - | .mdata _ e' => LExpr.getVars e' - | .abs _ e' => LExpr.getVars e' - | .quant _ _ tr' e' => LExpr.getVars tr' ++ LExpr.getVars e' - | .app e1 e2 => LExpr.getVars e1 ++ LExpr.getVars e2 - | .ite c t e => LExpr.getVars c ++ LExpr.getVars t ++ LExpr.getVars e - | .eq e1 e2 => LExpr.getVars e1 ++ LExpr.getVars e2 - -def getOps (e : (LExpr TypeType IDMeta)) := match e with - | .op name _ => [name] - | .const _ _ => [] | .bvar _ => [] | .fvar _ _ => [] - | .mdata _ e' => getOps e' - | .abs _ e' => getOps e' - | .quant _ _ tr e' => +instance (T : LExprParamsT) [Inhabited T.base.Metadata] : Inhabited (LExpr T) where + default := .boolConst default false + +def LExpr.getVars (e : LExpr T) : List (Identifier T.base.IDMeta) := match e with + | .const _ _ => [] | .bvar _ _ => [] | .op _ _ _ => [] + | .fvar _ y _ => [y] + | .abs _ _ e' => LExpr.getVars e' + | .quant _ _ _ tr' e' => LExpr.getVars tr' ++ LExpr.getVars e' + | .app _ e1 e2 => LExpr.getVars e1 ++ LExpr.getVars e2 + | .ite _ c t e => LExpr.getVars c ++ LExpr.getVars t ++ LExpr.getVars e + | .eq _ e1 e2 => LExpr.getVars e1 ++ LExpr.getVars e2 + +def getOps (e : LExpr T) := match e with + | .op _ name _ => [name] + | .const _ _ => [] | .bvar _ _ => [] | .fvar _ _ _ => [] + | .abs _ _ e' => getOps e' + | .quant _ _ _ tr e' => -- NOTE: We also get all ops in the triggers here. getOps tr ++ getOps e' - | .app e1 e2 => getOps e1 ++ getOps e2 - | .ite c t e => getOps c ++ getOps t ++ getOps e - | .eq e1 e2 => getOps e1 ++ getOps e2 + | .app _ e1 e2 => getOps e1 ++ getOps e2 + | .ite _ c t e => getOps c ++ getOps t ++ getOps e + | .eq _ e1 e2 => getOps e1 ++ getOps e2 -def getFVarName? (e : (LExpr TypeType IDMeta)) : Option (Identifier IDMeta) := +def getFVarName? {T : LExprParamsT} (e : LExpr T) : Option (Identifier T.base.IDMeta) := match e with - | .fvar name _ => some name + | .fvar _ name _ => some name | _ => none -def isConst (e : (LExpr TypeType IDMeta)) : Bool := +def isConst {T : LExprParamsT} (e : LExpr T) : Bool := match e with | .const _ _ => true | _ => false -def isOp (e : (LExpr TypeType IDMeta)) : Bool := +def isOp (e : LExpr T) : Bool := match e with - | .op _ _ => true + | .op _ _ _ => true | _ => false @[match_pattern] -protected def true : (LExpr LMonoTy IDMeta) := .const "true" (some (.tcons "bool" [])) +protected def true {T : LExprParams} (m : T.Metadata) : LExpr T.mono := .boolConst m true @[match_pattern] -protected def false : (LExpr LMonoTy IDMeta) := .const "false" (some (.tcons "bool" [])) +protected def false {T : LExprParams} (m : T.Metadata) : LExpr T.mono := .boolConst m false -def isTrue (e : (LExpr TypeType IDMeta)) : Bool := +def isTrue (T : LExprParamsT) (e : LExpr T) : Bool := match e with - | .const "true" _ => true + | .boolConst _ true => true | _ => false -def isFalse (e : (LExpr TypeType IDMeta)) : Bool := +def isFalse (T : LExprParamsT) (e : LExpr T) : Bool := match e with - | .const "false" _ => true + | .boolConst _ false => true | _ => false +/-- An iterated/multi-argument lambda with arguments of types `tys` and body `body`-/ +def absMulti (m: Metadata) (tys: List TypeType) (body: LExpr ⟨⟨Metadata, IDMeta⟩, TypeType⟩) + : LExpr ⟨⟨Metadata, IDMeta⟩, TypeType⟩ := + List.foldr (fun ty e => .abs m (.some ty) e) body tys + +/-- An iterated/multi-argument lambda with n inferred arguments and body `body`-/ +def absMultiInfer (m: Metadata) (n: Nat) (body: LExpr ⟨⟨Metadata, IDMeta⟩, TypeType⟩) + : LExpr ⟨⟨Metadata, IDMeta⟩, TypeType⟩ := + List.foldr (fun _ e => .abs m .none e) body (List.range n) + /-- If `e` is an `LExpr` boolean, then denote that into a Lean `Bool`. -Note that we are type-agnostic here. -/ -def denoteBool (e : (LExpr LMonoTy IDMeta)) : Option Bool := +def denoteBool {T : LExprParams} (e : LExpr ⟨T, TypeType⟩) : Option Bool := match e with - | .const "true" (some (.tcons "bool" [])) => some true - | .const "true" none => some true - | .const "false" (some (.tcons "bool" [])) => some false - | .const "false" none => some false + | .boolConst _ b => some b | _ => none /-- If `e` is an `LExpr` integer, then denote that into a Lean `Int`. -Note that we are type-agnostic here. -/ -def denoteInt (e : (LExpr LMonoTy IDMeta)) : Option Int := +def denoteInt {T : LExprParams} (e : LExpr ⟨T, TypeType⟩) : Option Int := match e with - | .const x (some (.tcons "int" [])) => x.toInt? - | .const x none => x.toInt? + | .intConst _ x => x | _ => none /-- -If `e` is an `LExpr` real, then denote that into a Lean `String`. -Note that we are type-agnostic here. +If `e` is an `LExpr` real, then denote that into a Lean `Rat`. -/ -def denoteReal (e : (LExpr LMonoTy IDMeta)) : Option String := +def denoteReal {T : LExprParams} (e : LExpr ⟨T, TypeType⟩) : Option Rat := match e with - | .const x (some (.tcons "real" [])) => .some x - | .const x none => .some x + | .realConst _ r => some r | _ => none /-- If `e` is an `LExpr` bv, then denote that into a Lean `BitVec n`. -Note that we are type-agnostic here. -/ -def denoteBitVec (n : Nat) (e : (LExpr LMonoTy IDMeta)) : Option (BitVec n) := +def denoteBitVec {T : LExprParams} (n : Nat) (e : LExpr ⟨T, TypeType⟩) : Option (BitVec n) := match e with - | .const x (.some (.bitvec n')) => - if n == n' then .map (.ofNat n) x.toNat? else none - | .const x none => .map (.ofNat n) x.toNat? + | .bitvecConst _ n' b => if n == n' then some (BitVec.ofNat n b.toNat) else none | _ => none /-- -If `e` is an _annotated_ `LExpr` string, then denote that into a Lean `String`. -Note that unannotated strings are not denoted. +If `e` is an `LExpr` string, then denote that into a Lean `String`. -/ -def denoteString (e : (LExpr LMonoTy IDMeta)) : Option String := +def denoteString {T : LExprParams} (e : LExpr T.mono) : Option String := match e with - | .const c (some (.tcons "string" [])) => some c + | .strConst _ s => some s | _ => none -def mkApp (fn : (LExpr TypeType IDMeta)) (args : List (LExpr TypeType IDMeta)) : (LExpr TypeType IDMeta) := +def mkApp {T : LExprParamsT} (m : T.base.Metadata) (fn : LExpr T) (args : List (LExpr T)) : LExpr T := match args with | [] => fn | a :: rest => - mkApp (.app fn a) rest + mkApp m (.app m fn a) rest /-- -Does `e` have a metadata annotation? We don't check for nested metadata in `e`. +Returns the metadata of `e`. -/ -def isMData (e : (LExpr TypeType IDMeta)) : Bool := +def metadata {T : LExprParamsT} (e : LExpr T) : T.base.Metadata := match e with - | .mdata _ _ => true - | _ => false - -/-- -Remove the outermost metadata annotation in `e`, if any. --/ -def removeMData1 (e : (LExpr TypeType IDMeta)) : (LExpr TypeType IDMeta) := + | .const m _ => m + | .op m _ _ => m + | .bvar m _ => m + | .fvar m _ _ => m + | .abs m _ _ => m + | .quant m _ _ _ _ => m + | .app m _ _ => m + | .ite m _ _ _ => m + | .eq m _ _ => m + +def replaceMetadata1 {T : LExprParamsT} (r: T.base.Metadata) (e : LExpr T) : LExpr T := match e with - | .mdata _ e => e - | _ => e + | .const _ c => .const r c + | .op _ o ty => .op r o ty + | .bvar _ i => .bvar r i + | .fvar _ name ty => .fvar r name ty + | .abs _ ty e' => .abs r ty e' + | .quant _ qk ty tr e' => .quant r qk ty tr e' + | .app _ e1 e2 => .app r e1 e2 + | .ite _ c t e' => .ite r c t e' + | .eq _ e1 e2 => .eq r e1 e2 + /-- -Remove all metadata annotations in `e`, included nested ones. +Transform metadata in an expression using a callback function. -/ -def removeAllMData (e : (LExpr TypeType IDMeta)) : (LExpr TypeType IDMeta) := +def replaceMetadata {T : LExprParamsT} (e : LExpr T) (f : T.base.Metadata → NewMetadata) : LExpr ⟨⟨NewMetadata, T.base.IDMeta⟩, T.TypeType⟩ := match e with - | .const _ _ | .op _ _ | .fvar _ _ | .bvar _ => e - | .mdata _ e1 => removeAllMData e1 - | .abs ty e1 => .abs ty (removeAllMData e1) - | .quant qk ty tr e1 => .quant qk ty (removeAllMData tr) (removeAllMData e1) - | .app e1 e2 => .app (removeAllMData e1) (removeAllMData e2) - | .ite c t f => .ite (removeAllMData c) (removeAllMData t) (removeAllMData f) - | .eq e1 e2 => .eq (removeAllMData e1) (removeAllMData e2) + | .const m c => + .const (f m) c + | .op m o uty => + .op (f m) o uty + | .bvar m b => + .bvar (f m) b + | .fvar m x uty => + .fvar (f m) x uty + | .app m e1 e2 => + let e1 := replaceMetadata e1 f + let e2 := replaceMetadata e2 f + .app (f m) e1 e2 + | .abs m uty e => + let e := replaceMetadata e f + .abs (f m) uty e + | .quant m qk argTy tr e => + let e := replaceMetadata e f + let tr := replaceMetadata tr f + .quant (f m) qk argTy tr e + | .ite m c t f_expr => + let c := replaceMetadata c f + let t := replaceMetadata t f + let f_expr := replaceMetadata f_expr f + .ite (f m) c t f_expr + | .eq m e1 e2 => + let e1 := replaceMetadata e1 f + let e2 := replaceMetadata e2 f + .eq (f m) e1 e2 + +-- Replace all metadata by a unit, suitable for comparison +def eraseMetadata {T : LExprParamsT} (e : LExpr T) : LExpr ⟨⟨Unit, T.base.IDMeta⟩, T.TypeType⟩ := LExpr.replaceMetadata e (λ_ =>()) /-- Compute the size of `e` as a tree. @@ -240,71 +480,71 @@ Compute the size of `e` as a tree. Not optimized for execution efficiency, but can be used for termination arguments. -/ -def size (e : (LExpr TypeType IDMeta)) : Nat := +def size (T : LExprParamsT) (e : LExpr T) : Nat := match e with - | .const _ _ => 1 - | .op _ _ => 1 - | .bvar _ => 1 - | .fvar _ _ => 1 - | .abs _ e' => 1 + size e' - | .quant _ _ _ e' => 1 + size e' - | .mdata _ e' => 1 + size e' - | .app e1 e2 => 1 + size e1 + size e2 - | .ite c t f => 1 + size c + size t + size f - | .eq e1 e2 => 1 + size e1 + size e2 + | .const .. | .op .. | .bvar .. | .fvar .. => 1 + | .abs _ _ e' => 1 + size T e' + | .quant _ _ _ _ e' => 1 + size T e' + | .app _ e1 e2 => 1 + size T e1 + size T e2 + | .ite _ c t f => 1 + size T c + size T t + size T f + | .eq _ e1 e2 => 1 + size T e1 + size T e2 /-- -Erase all type annotations from `e`. +Erase all type annotations from `e` except the bound variables of abstractions +and quantified expressions. -/ -def eraseTypes (e : (LExpr TypeType IDMeta)) : (LExpr TypeType IDMeta) := +def eraseTypes {T : LExprParamsT} (e : LExpr T) : LExpr T := match e with - | .const c _ => .const c none - | .op o _ => .op o none - | .fvar x _ => .fvar x none - | .bvar _ => e - | .abs ty e => .abs ty (e.eraseTypes) - | .quant qk ty tr e => .quant qk ty (eraseTypes tr) (e.eraseTypes) - | .app e1 e2 => .app (e1.eraseTypes) (e2.eraseTypes) - | .ite c t f => .ite (c.eraseTypes) (t.eraseTypes) (f.eraseTypes) - | .eq e1 e2 => .eq (e1.eraseTypes) (e2.eraseTypes) - | .mdata m e => .mdata m (e.eraseTypes) + | .const m c => .const m c + | .op m o _ => .op m o none + | .fvar m x _ => .fvar m x none + | .bvar _ _ => e + | .abs m ty e => .abs m ty (eraseTypes e) + | .quant m qk _ tr e => .quant m qk .none (eraseTypes tr) (eraseTypes e) + | .app m e1 e2 => .app m (eraseTypes e1) (eraseTypes e2) + | .ite m c t f => .ite m (eraseTypes c) (eraseTypes t) (eraseTypes f) + | .eq m e1 e2 => .eq m (eraseTypes e1) (eraseTypes e2) --------------------------------------------------------------------- /- Formatting and Parsing of Lambda Expressions -/ - -instance (IDMeta : Type) [Repr IDMeta] [Repr TypeType] : ToString (LExpr TypeType IDMeta) where +instance : ToString LConst where + toString c := + match c with + | .intConst i => toString i + | .strConst s => s + | .realConst r => toString r + | .bitvecConst _ b => toString (b.toNat) + | .boolConst b => toString b + +instance (T : LExprParamsT) [Repr T.base.IDMeta] [Repr T.TypeType] [Repr T.base.Metadata] : ToString (LExpr T) where toString a := toString (repr a) -private def formatLExpr [ToFormat TypeType] (e : (LExpr TypeType IDMeta)) : +private def formatLExpr (T : LExprParamsT) [ToFormat T.base.IDMeta] [ToFormat T.TypeType] (e : LExpr T) : Format := match e with - | .const c ty => - match ty with - | none => f!"#{c}" - | some ty => f!"(#{c} : {ty})" - | .op c ty => + | .const _ c => f!"#{c}" + | .op _ c ty => match ty with | none => f!"~{c}" | some ty => f!"(~{c} : {ty})" - | .bvar i => f!"%{i}" - | .fvar x ty => + | .bvar _ i => f!"%{i}" + | .fvar _ x ty => match ty with | none => f!"{x}" | some ty => f!"({x} : {ty})" - | .mdata _info e => formatLExpr e - | .abs _ e1 => Format.paren (f!"λ {formatLExpr e1}") - | .quant .all _ _ e1 => Format.paren (f!"∀ {formatLExpr e1}") - | .quant .exist _ _ e1 => Format.paren (f!"∃ {formatLExpr e1}") - | .app e1 e2 => Format.paren (formatLExpr e1 ++ " " ++ formatLExpr e2) - | .ite c t e => Format.paren - ("if " ++ formatLExpr c ++ - " then " ++ formatLExpr t ++ " else " - ++ formatLExpr e) - | .eq e1 e2 => Format.paren (formatLExpr e1 ++ " == " ++ formatLExpr e2) - -instance [ToFormat TypeType] : ToFormat (LExpr TypeType IDMeta) where - format := formatLExpr + | .abs _ _ e1 => Format.paren (f!"λ {formatLExpr T e1}") + | .quant _ .all _ _ e1 => Format.paren (f!"∀ {formatLExpr T e1}") + | .quant _ .exist _ _ e1 => Format.paren (f!"∃ {formatLExpr T e1}") + | .app _ e1 e2 => Format.paren (formatLExpr T e1 ++ " " ++ formatLExpr T e2) + | .ite _ c t e => Format.paren + ("if " ++ formatLExpr T c ++ + " then " ++ formatLExpr T t ++ " else " + ++ formatLExpr T e) + | .eq _ e1 e2 => Format.paren (formatLExpr T e1 ++ " == " ++ formatLExpr T e2) + +instance (T : LExprParamsT) [ToFormat T.base.IDMeta] [ToFormat T.TypeType] : ToFormat (LExpr T) where + format := formatLExpr T /- Syntax for conveniently building `LExpr` terms with `LMonoTy`, scoped under the namespace @@ -313,7 +553,9 @@ Syntax for conveniently building `LExpr` terms with `LMonoTy`, scoped under the namespace SyntaxMono open Lean Elab Meta -class MkIdent (IDMeta : Type) where +-- Although T is not used in the class, it makes it possible to create instances +-- so that toExpr is meant to be typed +class MkLExprParams (T: LExprParams) where elabIdent : Lean.Syntax → MetaM Expr toExpr : Expr @@ -337,54 +579,38 @@ scoped syntax "#" noWs ident : lconstmono scoped syntax "(" lconstmono ":" lmonoty ")" : lconstmono scoped syntax lconstmono : lexprmono -def elabLConstMono (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def mkIntLit (n: NumLit) : Expr := Expr.app (.const ``Int.ofNat []) (mkNatLit n.getNat) +def mkNegLit (n: NumLit) := Expr.app (.const ``Int.neg []) (mkIntLit n) + +def elabLConstMono [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lconstmono| #$n:num) => do - let none ← mkNone (mkConst ``LMonoTy) - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit (toString n.getNat), none] - | `(lconstmono| (#$n:num : $ty:lmonoty)) => do - let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty - let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit (toString n.getNat), lmonoty] - | `(lconstmono| #-$n:num) => do - let none ← mkNone (mkConst ``LMonoTy) - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit ("-" ++ (toString n.getNat)), none] - | `(lconstmono| (#-$n:num : $ty:lmonoty)) => do - let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty - let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit ("-" ++ (toString n.getNat)), lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let intVal := mkIntLit n + let lconstVal ← mkAppM ``LConst.intConst #[intVal] + return mkAppN (.const ``LExpr.const []) #[tMono, metadata, lconstVal] + | `(lconstmono| #-$n:num) => do + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let intVal := mkNegLit n + let lconstVal ← mkAppM ``LConst.intConst #[intVal] + return mkAppN (.const ``LExpr.const []) #[tMono, metadata, lconstVal] | `(lconstmono| #true) => do - let none ← mkNone (mkConst ``LMonoTy) - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "true", none] - | `(lconstmono| (#true : $ty:lmonoty)) => do - let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty - let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "true", lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let lconstVal ← mkAppM ``LConst.boolConst #[toExpr true] + return mkAppN (.const ``LExpr.const []) #[tMono, metadata, lconstVal] | `(lconstmono| #false) => do - let none ← mkNone (mkConst ``LMonoTy) - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "false", none] - | `(lconstmono| (#false : $ty:lmonoty)) => do - let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty - let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "false", lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let lconstVal ← mkAppM ``LConst.boolConst #[toExpr false] + return mkAppN (.const ``LExpr.const []) #[tMono, metadata, lconstVal] | `(lconstmono| #$s:ident) => do - let none ← mkNone (mkConst ``LMonoTy) let s := toString s.getId - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit s, none] - | `(lconstmono| (#$s:ident : $ty:lmonoty)) => do - let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty - let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let s := toString s.getId - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit s, lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let lconstVal ← mkAppM ``LConst.strConst #[mkStrLit s] + return mkAppN (.const ``LExpr.const []) #[tMono, metadata, lconstVal] | _ => throwUnsupportedSyntax declare_syntax_cat lopmono @@ -392,24 +618,27 @@ scoped syntax "~" noWs lidentmono : lopmono scoped syntax "(" lopmono ":" lmonoty ")" : lopmono scoped syntax lopmono : lexprmono -def elabLOpMono (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def elabLOpMono [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lopmono| ~$s:lidentmono) => do let none ← mkNone (mkConst ``LMonoTy) - let ident ← MkIdent.elabIdent IDMeta s - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.op []) #[typeTypeExpr, MkIdent.toExpr IDMeta, ident, none] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.op []) #[tMono, metadata, ← MkLExprParams.elabIdent T s, none] | `(lopmono| (~$s:lidentmono : $ty:lmonoty)) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - mkAppM ``LExpr.op #[← MkIdent.elabIdent IDMeta s, lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.op []) #[tMono, metadata, ← MkLExprParams.elabIdent T s, lmonoty] | _ => throwUnsupportedSyntax declare_syntax_cat lbvarmono scoped syntax "%" noWs num : lbvarmono -def elabLBVarMono (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr - | `(lbvarmono| %$n:num) => - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.bvar []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkNatLit n.getNat] +def elabLBVarMono [MkLExprParams T] : Lean.Syntax → MetaM Expr + | `(lbvarmono| %$n:num) => do + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.bvar []) #[tMono, metadata, mkNatLit n.getNat] | _ => throwUnsupportedSyntax scoped syntax lbvarmono : lexprmono @@ -417,14 +646,18 @@ declare_syntax_cat lfvarmono scoped syntax lidentmono : lfvarmono scoped syntax "(" lidentmono ":" lmonoty ")" : lfvarmono -def elabLFVarMono (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def elabLFVarMono [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lfvarmono| $i:lidentmono) => do let none ← mkNone (mkConst ``LMonoTy) - mkAppM ``LExpr.fvar #[← MkIdent.elabIdent IDMeta i, none] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.fvar []) #[tMono, metadata, ← MkLExprParams.elabIdent T i, none] | `(lfvarmono| ($i:lidentmono : $ty:lmonoty)) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy ty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - mkAppM ``LExpr.fvar #[← MkIdent.elabIdent IDMeta i, lmonoty] + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.fvar []) #[tMono, metadata, ← MkLExprParams.elabIdent T i, lmonoty] | _ => throwUnsupportedSyntax scoped syntax lfvarmono : lexprmono @@ -465,80 +698,95 @@ All type annotations in `LExpr` are for monotypes, not polytypes. It's the user's responsibility to ensure correct usage of type variables (i.e., they're unique). -/ -partial def elabLExprMono (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr - | `(lexprmono| $c:lconstmono) => elabLConstMono IDMeta c - | `(lexprmono| $o:lopmono) => elabLOpMono IDMeta o - | `(lexprmono| $b:lbvarmono) => elabLBVarMono IDMeta b - | `(lexprmono| $f:lfvarmono) => elabLFVarMono IDMeta f +partial def elabLExprMono [MkLExprParams T] : Lean.Syntax → MetaM Expr + | `(lexprmono| $c:lconstmono) => elabLConstMono (T:=T) c + | `(lexprmono| $o:lopmono) => elabLOpMono (T:=T) o + | `(lexprmono| $b:lbvarmono) => elabLBVarMono (T:=T) b + | `(lexprmono| $f:lfvarmono) => elabLFVarMono (T:=T) f | `(lexprmono| λ $e:lexprmono) => do - let e' ← elabLExprMono IDMeta e - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.absUntyped []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e'] + let e' ← elabLExprMono (T:=T) e + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.absUntyped []) #[tMono, metadata, e'] | `(lexprmono| λ ($mty:lmonoty): $e:lexprmono) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy mty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let e' ← elabLExprMono IDMeta e - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.abs []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lmonoty, e'] + let e' ← elabLExprMono (T:=T) e + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.abs []) #[tMono, metadata, lmonoty, e'] | `(lexprmono| ∀ $e:lexprmono) => do - let e' ← elabLExprMono IDMeta e - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.allUntyped []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e'] + let e' ← elabLExprMono (T:=T) e + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.allUntyped []) #[tMono, metadata, e'] | `(lexprmono| ∀ {$tr}$e:lexprmono) => do - let e' ← elabLExprMono IDMeta e - let tr' ← elabLExprMono IDMeta tr - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.allUntypedTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, tr', e'] + let e' ← elabLExprMono (T:=T) e + let tr' ← elabLExprMono (T:=T) tr + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.allUntypedTr []) #[tMono, metadata, tr', e'] | `(lexprmono| ∀ ($mty:lmonoty): $e:lexprmono) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy mty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let e' ← elabLExprMono IDMeta e - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.all []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lmonoty, e'] + let e' ← elabLExprMono (T:=T) e + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.all []) #[tMono, metadata, lmonoty, e'] | `(lexprmono| ∀ ($mty:lmonoty):{$tr} $e:lexprmono) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy mty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let e' ← elabLExprMono IDMeta e - let tr' ← elabLExprMono IDMeta tr - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.allTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lmonoty, tr', e'] + let e' ← elabLExprMono (T:=T) e + let tr' ← elabLExprMono (T:=T) tr + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.allTr []) #[tMono, metadata, lmonoty, tr', e'] | `(lexprmono| ∃ ($mty:lmonoty): $e:lexprmono) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy mty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let e' ← elabLExprMono IDMeta e - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.exist []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lmonoty, e'] + let e' ← elabLExprMono (T:=T) e + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.exist []) #[tMono, metadata, lmonoty, e'] | `(lexprmono| ∃ ($mty:lmonoty):{$tr} $e:lexprmono) => do let lmonoty ← Lambda.LTy.Syntax.elabLMonoTy mty let lmonoty ← mkSome (mkConst ``LMonoTy) lmonoty - let e' ← elabLExprMono IDMeta e - let tr' ← elabLExprMono IDMeta tr - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.existTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lmonoty, tr', e'] + let e' ← elabLExprMono (T:=T) e + let tr' ← elabLExprMono (T:=T) tr + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + let metadata ← mkAppM ``Unit.unit #[] + return mkAppN (.const ``LExpr.existTr []) #[tMono, metadata, lmonoty, tr', e'] | `(lexprmono| ∃ $e:lexprmono) => do - let e' ← elabLExprMono IDMeta e - mkAppM ``LExpr.existUntyped #[e'] + let e' ← elabLExprMono (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.existUntyped []) #[tMono, metadata, e'] | `(lexprmono| ∃{$tr} $e:lexprmono) => do - let e' ← elabLExprMono IDMeta e - let tr' ← elabLExprMono IDMeta tr - mkAppM ``LExpr.existUntypedTr #[tr', e'] + let e' ← elabLExprMono (T:=T) e + let tr' ← elabLExprMono (T:=T) tr + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.existUntypedTr []) #[tMono, metadata, tr', e'] | `(lexprmono| ($e1:lexprmono $e2:lexprmono)) => do - let e1' ← elabLExprMono IDMeta e1 - let e2' ← elabLExprMono IDMeta e2 - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.app []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2'] + let e1' ← elabLExprMono (T:=T) e1 + let e2' ← elabLExprMono (T:=T) e2 + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.app []) #[tMono, metadata, e1', e2'] | `(lexprmono| $e1:lexprmono == $e2:lexprmono) => do - let e1' ← elabLExprMono IDMeta e1 - let e2' ← elabLExprMono IDMeta e2 - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.eq []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2'] + let e1' ← elabLExprMono (T:=T) e1 + let e2' ← elabLExprMono (T:=T) e2 + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.eq []) #[tMono, metadata, e1', e2'] | `(lexprmono| if $e1:lexprmono then $e2:lexprmono else $e3:lexprmono) => do - let e1' ← elabLExprMono IDMeta e1 - let e2' ← elabLExprMono IDMeta e2 - let e3' ← elabLExprMono IDMeta e3 - let typeTypeExpr := mkConst ``LMonoTy - return mkAppN (.const ``LExpr.ite []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2', e3'] - | `(lexprmono| ($e:lexprmono)) => elabLExprMono IDMeta e + let e1' ← elabLExprMono (T:=T) e1 + let e2' ← elabLExprMono (T:=T) e2 + let e3' ← elabLExprMono (T:=T) e3 + let metadata ← mkAppM ``Unit.unit #[] + let tMono ← mkAppM ``LExprParams.mono #[MkLExprParams.toExpr T] + return mkAppN (.const ``LExpr.ite []) #[tMono, metadata, e1', e2', e3'] + | `(lexprmono| ($e:lexprmono)) => elabLExprMono (T:=T) e | _ => throwUnsupportedSyntax scoped syntax ident : lidentmono @@ -549,65 +797,90 @@ def elabStrIdent : Lean.Syntax → MetaM Expr return mkAppN (.const `Lambda.Identifier.mk []) #[.const ``Unit [], mkStrLit s.toString, .const ``Unit.unit []] | _ => throwUnsupportedSyntax -instance : MkIdent Unit where +-- Unit metadata, Unit IDMeta +instance : MkLExprParams ⟨Unit, Unit⟩ where elabIdent := elabStrIdent - toExpr := .const ``Unit [] + toExpr := mkApp2 (mkConst ``LExprParams.mk) (mkConst ``Unit) (mkConst ``Unit) -elab "esM[" e:lexprmono "]" : term => elabLExprMono (IDMeta:=Unit) e +elab "esM[" e:lexprmono "]" : term => elabLExprMono (T:=⟨Unit, Unit⟩) e open LTy.Syntax -/-- info: (bvar 0).absUntyped.app (const "5" none) : LExpr LMonoTy Unit -/ +/-- +info: app () (absUntyped () (bvar () 0)) + (const () (LConst.intConst (Int.ofNat 5))) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[((λ %0) #5)] -/-- info: (abs (some (LMonoTy.tcons "bool" [])) (bvar 0)).app (const "true" none) : LExpr LMonoTy Unit -/ +/-- +info: app () (abs () (some (LMonoTy.tcons "bool" [])) (bvar () 0)) + (const () (LConst.boolConst true)) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[((λ (bool): %0) #true)] -/-- info: ((bvar 0).eq (const "5" none)).allUntyped : LExpr LMonoTy Unit -/ +/-- +info: allUntyped () + (eq () (bvar () 0) (const () (LConst.intConst (Int.ofNat 5)))) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[(∀ %0 == #5)] -/-- info: ((bvar 0).eq (const "5" none)).existUntyped : LExpr LMonoTy Unit -/ +/-- +info: existUntyped () + (eq () (bvar () 0) (const () (LConst.intConst (Int.ofNat 5)))) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[(∃ %0 == #5)] -/-- info: exist (some (LMonoTy.tcons "int" [])) ((bvar 0).eq (const "5" none)) : LExpr LMonoTy Unit -/ +/-- +info: exist () (some (LMonoTy.tcons "int" [])) + (eq () (bvar () 0) (const () (LConst.intConst (Int.ofNat 5)))) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[(∃ (int): %0 == #5)] -/-- info: fvar { name := "x", metadata := () } (some (LMonoTy.tcons "bool" [])) : LExpr LMonoTy Unit -/ +/-- +info: fvar () { name := "x", metadata := () } + (some (LMonoTy.tcons "bool" [])) : LExpr { Metadata := Unit, IDMeta := Unit }.mono +-/ #guard_msgs in #check esM[(x : bool)] -- axiom [updateSelect]: forall m: Map k v, kk: k, vv: v :: m[kk := vv][kk] == vv; /-- -info: all (some (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"])) - (all (some (LMonoTy.ftvar "k")) - (all (some (LMonoTy.ftvar "v")) - ((((op { name := "select", metadata := () } +info: all () (some (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"])) + (all () (some (LMonoTy.ftvar "k")) + (all () (some (LMonoTy.ftvar "v")) + (eq () + (app () + (app () + (op () { name := "select", metadata := () } + (some + (LMonoTy.tcons "Map" + [LMonoTy.ftvar "k", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "v", LMonoTy.tcons "arrow" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]))) + (app () + (app () + (app () + (op () { name := "update", metadata := () } (some (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", LMonoTy.tcons "arrow" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]))).app - ((((op { name := "update", metadata := () } - (some - (LMonoTy.tcons "Map" - [LMonoTy.ftvar "k", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "k", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", - LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]]]))).app - (bvar 2)).app - (bvar 1)).app - (bvar 0))).app - (bvar 1)).eq - (bvar 0)))) : LExpr LMonoTy Unit + [LMonoTy.ftvar "v", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "k", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "v", + LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]]]))) + (bvar () 2)) + (bvar () 1)) + (bvar () 0))) + (bvar () 1)) + (bvar () 0)))) : LExpr { Metadata := Unit, IDMeta := Unit }.mono -/ #guard_msgs in #check @@ -628,7 +901,9 @@ Syntax for conveniently building `LExpr` terms with `LTy`, scoped under the name namespace Syntax open Lean Elab Meta -class MkIdent (IDMeta : Type) where +-- Although T is not used in the class, it makes it possible to create instances +-- so that toExpr is meant to be typed +class MkLExprParams (T: LExprParams) where elabIdent : Lean.Syntax → MetaM Expr toExpr : Expr @@ -652,54 +927,38 @@ scoped syntax "#" noWs ident : lconst scoped syntax "(" lconst ":" lty ")" : lconst scoped syntax lconst : lexpr -def elabLConst (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def mkIntLit (n: NumLit) : Expr := Expr.app (.const ``Int.ofNat []) (mkNatLit n.getNat) +def mkNegLit (n: NumLit) := Expr.app (.const ``Int.neg []) (mkIntLit n) + +def elabLConst [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lconst| #$n:num) => do - let none ← mkNone (mkConst ``LTy) - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit (toString n.getNat), none] - | `(lconst| (#$n:num : $ty:lty)) => do - let lty ← Lambda.LTy.Syntax.elabLTy ty - let lty ← mkSome (mkConst ``LTy) lty - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit (toString n.getNat), lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + let lconstVal ← mkAppM ``LConst.intConst #[mkIntLit n] + return mkAppN (.const ``LExpr.const []) #[tParams, metadata, lconstVal] | `(lconst| #-$n:num) => do - let none ← mkNone (mkConst ``LTy) - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit ("-" ++ (toString n.getNat)), none] - | `(lconst| (#-$n:num : $ty:lty)) => do - let lty ← Lambda.LTy.Syntax.elabLTy ty - let lty ← mkSome (mkConst ``LTy) lty - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit ("-" ++ (toString n.getNat)), lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + let lconstVal ← mkAppM ``LConst.intConst #[mkNegLit n] + return mkAppN (.const ``LExpr.const []) #[tParams, metadata, lconstVal] | `(lconst| #true) => do - let none ← mkNone (mkConst ``LTy) - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "true", none] - | `(lconst| (#true : $ty:lty)) => do - let lty ← Lambda.LTy.Syntax.elabLTy ty - let lty ← mkSome (mkConst ``LTy) lty - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "true", lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.boolConst []) #[tParams, metadata, toExpr true] | `(lconst| #false) => do - let none ← mkNone (mkConst ``LTy) - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "false", none] - | `(lconst| (#false : $ty:lty)) => do - let lty ← Lambda.LTy.Syntax.elabLTy ty - let lty ← mkSome (mkConst ``LTy) lty - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit "false", lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.boolConst []) #[tParams, metadata, toExpr false] | `(lconst| #$s:ident) => do - let none ← mkNone (mkConst ``LTy) let s := toString s.getId - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit s, none] - | `(lconst| (#$s:ident : $ty:lty)) => do - let lty ← Lambda.LTy.Syntax.elabLTy ty - let lty ← mkSome (mkConst ``LTy) lty - let s := toString s.getId - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.const []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkStrLit s, lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.const []) #[tParams, metadata, mkStrLit s] | _ => throwUnsupportedSyntax declare_syntax_cat lop @@ -707,24 +966,32 @@ scoped syntax "~" noWs lident : lop scoped syntax "(" lop ":" lty ")" : lop scoped syntax lop : lexpr -def elabLOp (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def elabLOp [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lop| ~$s:lident) => do let none ← mkNone (mkConst ``LTy) - let ident ← MkIdent.elabIdent IDMeta s - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.op []) #[typeTypeExpr, MkIdent.toExpr IDMeta, ident, none] + let ident ← MkLExprParams.elabIdent T s + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.op []) #[tParams, metadata, ident, none] | `(lop| (~$s:lident : $ty:lty)) => do let lty ← Lambda.LTy.Syntax.elabLTy ty let lty ← mkSome (mkConst ``LTy) lty - mkAppM ``LExpr.op #[← MkIdent.elabIdent IDMeta s, lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.op []) #[tParams, metadata, ← MkLExprParams.elabIdent T s, lty] | _ => throwUnsupportedSyntax declare_syntax_cat lbvar scoped syntax "%" noWs num : lbvar -def elabLBVar (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr - | `(lbvar| %$n:num) => - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.bvar []) #[typeTypeExpr, MkIdent.toExpr IDMeta, mkNatLit n.getNat] + +def elabLBVar [MkLExprParams T] : Lean.Syntax → MetaM Expr + | `(lbvar| %$n:num) => do + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.bvar []) #[tParams, metadata, mkNatLit n.getNat] | _ => throwUnsupportedSyntax scoped syntax lbvar : lexpr @@ -732,14 +999,20 @@ declare_syntax_cat lfvar scoped syntax lident : lfvar scoped syntax "(" lident ":" lty ")" : lfvar -def elabLFVar (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr +def elabLFVar [MkLExprParams T] : Lean.Syntax → MetaM Expr | `(lfvar| $i:lident) => do let none ← mkNone (mkConst ``LTy) - mkAppM ``LExpr.fvar #[← MkIdent.elabIdent IDMeta i, none] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.fvar []) #[tParams, metadata, ← MkLExprParams.elabIdent T i, none] | `(lfvar| ($i:lident : $ty:lty)) => do let lty ← Lambda.LTy.Syntax.elabLTy ty let lty ← mkSome (mkConst ``LTy) lty - mkAppM ``LExpr.fvar #[← MkIdent.elabIdent IDMeta i, lty] + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.fvar []) #[tParams, metadata, ← MkLExprParams.elabIdent T i, lty] | _ => throwUnsupportedSyntax scoped syntax lfvar : lexpr @@ -779,80 +1052,108 @@ open LTy.Syntax in It's the user's responsibility to ensure correct usage of type variables (i.e., they're unique). -/ -partial def elabLExpr (IDMeta : Type) [MkIdent IDMeta] : Lean.Syntax → MetaM Expr - | `(lexpr| $c:lconst) => elabLConst IDMeta c - | `(lexpr| $o:lop) => elabLOp IDMeta o - | `(lexpr| $b:lbvar) => elabLBVar IDMeta b - | `(lexpr| $f:lfvar) => elabLFVar IDMeta f +partial def elabLExpr [MkLExprParams T] : Lean.Syntax → MetaM Expr + | `(lexpr| $c:lconst) => elabLConst (T:=T) c + | `(lexpr| $o:lop) => elabLOp (T:=T) o + | `(lexpr| $b:lbvar) => elabLBVar (T:=T) b + | `(lexpr| $f:lfvar) => elabLFVar (T:=T) f | `(lexpr| λ $e:lexpr) => do - let e' ← elabLExpr IDMeta e - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.absUntyped []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.absUntyped []) #[tParams, metadata, e'] | `(lexpr| λ ($mty:lty): $e:lexpr) => do let lty ← Lambda.LTy.Syntax.elabLTy mty let lty ← mkSome (mkConst ``LTy) lty - let e' ← elabLExpr IDMeta e - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.abs []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lty, e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.abs []) #[tParams, metadata, lty, e'] | `(lexpr| ∀ $e:lexpr) => do - let e' ← elabLExpr IDMeta e - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.allUntyped []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.allUntyped []) #[tParams, metadata, e'] | `(lexpr| ∀{$tr}$e:lexpr) => do - let e' ← elabLExpr IDMeta e - let tr' ← elabLExpr IDMeta tr - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.allUntypedTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, tr', e'] + let e' ← elabLExpr (T:=T) e + let tr' ← elabLExpr (T:=T) tr + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.allUntypedTr []) #[tParams, metadata, tr', e'] | `(lexpr| ∀ ($mty:lty): $e:lexpr) => do let lty ← Lambda.LTy.Syntax.elabLTy mty let lty ← mkSome (mkConst ``LTy) lty - let e' ← elabLExpr IDMeta e - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.all []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lty, e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.all []) #[tParams, metadata, lty, e'] | `(lexpr| ∀ ($mty:lty): {$tr}$e:lexpr) => do let lty ← Lambda.LTy.Syntax.elabLTy mty let lty ← mkSome (mkConst ``LTy) lty - let e' ← elabLExpr IDMeta e - let tr' ← elabLExpr IDMeta tr - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.allTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lty, tr', e'] + let e' ← elabLExpr (T:=T) e + let tr' ← elabLExpr (T:=T) tr + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.allTr []) #[tParams, metadata, lty, tr', e'] | `(lexpr| ∃ ($mty:lty): $e:lexpr) => do let lty ← Lambda.LTy.Syntax.elabLTy mty let lty ← mkSome (mkConst ``LTy) lty - let e' ← elabLExpr IDMeta e - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.exist []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lty, e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.exist []) #[tParams, metadata, lty, e'] | `(lexpr| ∃ ($mty:lty): {$tr}$e:lexpr) => do let lty ← Lambda.LTy.Syntax.elabLTy mty let lty ← mkSome (mkConst ``LTy) lty - let e' ← elabLExpr IDMeta e - let tr' ← elabLExpr IDMeta tr - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.existTr []) #[typeTypeExpr, MkIdent.toExpr IDMeta, lty, tr', e'] + let e' ← elabLExpr (T:=T) e + let tr' ← elabLExpr (T:=T) tr + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.existTr []) #[tParams, metadata, lty, tr', e'] | `(lexpr| ∃ $e:lexpr) => do - let e' ← elabLExpr IDMeta e - mkAppM ``LExpr.existUntyped #[e'] + let e' ← elabLExpr (T:=T) e + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.existUntyped []) #[tParams, metadata, e'] | `(lexpr| ∃ {$tr} $e:lexpr) => do - let e' ← elabLExpr IDMeta e - let tr' ← elabLExpr IDMeta tr - mkAppM ``LExpr.existUntypedTr #[tr', e'] + let e' ← elabLExpr (T:=T) e + let tr' ← elabLExpr (T:=T) tr + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.existUntypedTr []) #[tParams, metadata, tr', e'] | `(lexpr| ($e1:lexpr $e2:lexpr)) => do - let e1' ← elabLExpr IDMeta e1 - let e2' ← elabLExpr IDMeta e2 - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.app []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2'] + let e1' ← elabLExpr (T:=T) e1 + let e2' ← elabLExpr (T:=T) e2 + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.app []) #[tParams, metadata, e1', e2'] | `(lexpr| $e1:lexpr == $e2:lexpr) => do - let e1' ← elabLExpr IDMeta e1 - let e2' ← elabLExpr IDMeta e2 - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.eq []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2'] + let e1' ← elabLExpr (T:=T) e1 + let e2' ← elabLExpr (T:=T) e2 + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.eq []) #[tParams, metadata, e1', e2'] | `(lexpr| if $e1:lexpr then $e2:lexpr else $e3:lexpr) => do - let e1' ← elabLExpr IDMeta e1 - let e2' ← elabLExpr IDMeta e2 - let e3' ← elabLExpr IDMeta e3 - let typeTypeExpr := mkConst ``LTy - return mkAppN (.const ``LExpr.ite []) #[typeTypeExpr, MkIdent.toExpr IDMeta, e1', e2', e3'] - | `(lexpr| ($e:lexpr)) => elabLExpr IDMeta e + let e1' ← elabLExpr (T:=T) e1 + let e2' ← elabLExpr (T:=T) e2 + let e3' ← elabLExpr (T:=T) e3 + let metadata ← mkAppM ``Unit.unit #[] + let baseParams := MkLExprParams.toExpr T + let tParams := mkApp2 (mkConst ``LExprParamsT.mk) baseParams (mkConst ``LTy) + return mkAppN (.const ``LExpr.ite []) #[tParams, metadata, e1', e2', e3'] + | `(lexpr| ($e:lexpr)) => elabLExpr (T:=T) e | _ => throwUnsupportedSyntax scoped syntax ident : lident @@ -863,67 +1164,103 @@ def elabStrIdent : Lean.Syntax → MetaM Expr return mkAppN (.const `Lambda.Identifier.mk []) #[.const ``Unit [], mkStrLit s.toString, .const ``Unit.unit []] | _ => throwUnsupportedSyntax -instance : MkIdent Unit where +instance : MkLExprParams ⟨Unit, Unit⟩ where elabIdent := elabStrIdent - toExpr := .const ``Unit [] + toExpr := mkApp2 (mkConst ``LExprParams.mk) (mkConst ``Unit) (mkConst ``Unit) -elab "es[" e:lexpr "]" : term => elabLExpr (IDMeta:=Unit) e +elab "es[" e:lexpr "]" : term => elabLExpr (T:=⟨Unit, Unit⟩) e open LTy.Syntax +/-- +info: const () (LConst.intConst (Int.ofNat 5)) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ +#guard_msgs in +#check es[#5] -/-- info: (bvar 0).absUntyped.app (const "5" none) : LExpr LTy Unit -/ +/-- +info: app () (absUntyped () (bvar () 0)) + (const () (LConst.intConst (Int.ofNat 5))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[((λ %0) #5)] -/-- info: (abs (some (LTy.forAll [] (LMonoTy.tcons "bool" []))) (bvar 0)).app (const "true" none) : LExpr LTy Unit -/ +/-- +info: app () (abs () (some (LTy.forAll [] (LMonoTy.tcons "bool" []))) (bvar () 0)) + (boolConst () true) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[((λ (bool): %0) #true)] -/-- info: ((bvar 0).eq (const "5" none)).allUntyped : LExpr LTy Unit -/ +/-- +info: allUntyped () + (eq () (bvar () 0) + (const () + (LConst.intConst (Int.ofNat 5)))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[(∀ %0 == #5)] -/-- info: ((bvar 0).eq (const "5" none)).existUntyped : LExpr LTy Unit -/ +/-- +info: existUntyped () + (eq () (bvar () 0) + (const () + (LConst.intConst (Int.ofNat 5)))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[(∃ %0 == #5)] -/-- info: exist (some (LTy.forAll [] (LMonoTy.tcons "int" []))) ((bvar 0).eq (const "5" none)) : LExpr LTy Unit -/ +/-- +info: exist () (some (LTy.forAll [] (LMonoTy.tcons "int" []))) + (eq () (bvar () 0) + (const () + (LConst.intConst (Int.ofNat 5)))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[(∃ (int): %0 == #5)] -/-- info: fvar { name := "x", metadata := () } (some (LTy.forAll [] (LMonoTy.tcons "bool" []))) : LExpr LTy Unit -/ +/-- +info: fvar () { name := "x", metadata := () } + (some + (LTy.forAll [] (LMonoTy.tcons "bool" []))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } +-/ #guard_msgs in #check es[(x : bool)] -- axiom [updateSelect]: forall m: Map k v, kk: k, vv: v :: m[kk := vv][kk] == vv; /-- -info: all (some (LTy.forAll [] (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]))) - (all (some (LTy.forAll [] (LMonoTy.ftvar "k"))) - (all (some (LTy.forAll [] (LMonoTy.ftvar "v"))) - ((((op { name := "select", metadata := () } +info: all () (some (LTy.forAll [] (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]))) + (all () (some (LTy.forAll [] (LMonoTy.ftvar "k"))) + (all () (some (LTy.forAll [] (LMonoTy.ftvar "v"))) + (eq () + (app () + (app () + (op () { name := "select", metadata := () } + (some + (LTy.forAll [] + (LMonoTy.tcons "Map" + [LMonoTy.ftvar "k", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "v", LMonoTy.tcons "arrow" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]])))) + (app () + (app () + (app () + (op () { name := "update", metadata := () } (some (LTy.forAll [] (LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", LMonoTy.tcons "arrow" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]])))).app - ((((op { name := "update", metadata := () } - (some - (LTy.forAll [] - (LMonoTy.tcons "Map" - [LMonoTy.ftvar "k", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "k", - LMonoTy.tcons "arrow" - [LMonoTy.ftvar "v", - LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]]])))).app - (bvar 2)).app - (bvar 1)).app - (bvar 0))).app - (bvar 1)).eq - (bvar 0)))) : LExpr LTy Unit + [LMonoTy.ftvar "v", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "k", + LMonoTy.tcons "arrow" + [LMonoTy.ftvar "v", + LMonoTy.tcons "Map" [LMonoTy.ftvar "k", LMonoTy.ftvar "v"]]]]])))) + (bvar () 2)) + (bvar () 1)) + (bvar () 0))) + (bvar () 1)) + (bvar () 0)))) : LExpr { base := { Metadata := Unit, IDMeta := Unit }, TypeType := LTy } -/ #guard_msgs in #check diff --git a/Strata/DL/Lambda/LExprEval.lean b/Strata/DL/Lambda/LExprEval.lean index e68bb2669..73fb6f720 100644 --- a/Strata/DL/Lambda/LExprEval.lean +++ b/Strata/DL/Lambda/LExprEval.lean @@ -19,47 +19,72 @@ open Std (ToFormat Format format) namespace LExpr -variable {IDMeta : Type} [DecidableEq IDMeta] +variable {T : LExprParamsT} {TBase : LExprParams} [BEq T.TypeType] [DecidableEq T.base.Metadata] [DecidableEq TBase.IDMeta] [ToFormat T.base.Metadata] + [Inhabited T.base.IDMeta] [DecidableEq T.base.IDMeta] [ToFormat T.base.IDMeta] [Traceable EvalProvenance TBase.Metadata] + +inductive EvalProvenance + | Original -- The metadata of the original expression + | ReplacementVar -- The original bound variable that was replaced + | Abstraction -- The lambda that triggered the replacement + /-- Check for boolean equality of two expressions `e1` and `e2` after erasing any type annotations. -/ -def eqModuloTypes (e1 e2 : (LExpr LMonoTy IDMeta)) : Bool := - e1.eraseTypes == e2.eraseTypes +def eqModuloTypes (e1 e2 : LExpr T) : Bool := + e1.eraseMetadata.eraseTypes == e2.eraseMetadata.eraseTypes /-- Canonical values of `LExpr`s. Equality is simply `==` (or more accurately, `eqModuloTypes`) for these `LExpr`s. Also see `eql` for a version that can tolerate nested metadata. + +If `e:LExpr` is `.app`, say `e1 e2 .. en`, `e` is a canonical value if +(1) `e1` is a constructor and `e2 .. en` are all canonical values, or +(2) `e1` is a named function `f` (not abstraction) and `n` is less than the + number of arguments required to run the function `f`. + +The intuition of case (2) is as follows. Let's assume that we would like to +calculate `Int.Add 1 (2+3)`. According to the small step semantics, we would +like to calculate `2+3` to `5`, hence it becomes `Int.Add 1 5` and eventually 6. +Without (2), this is impossible because the `reduce_2` rule of small step +semantics only fires when `Int.Add 1` is a 'canonical value'. Therefore, without +(2), the semantics stuck and `2+3` can never be evaluated to `5`. -/ -def isCanonicalValue (e : (LExpr LMonoTy IDMeta)) : Bool := - match e with +def isCanonicalValue (F : @Factory T.base) (e : LExpr T) : Bool := + match he: e with | .const _ _ => true - | .abs _ _ => + | .abs _ _ _ | .quant _ _ _ _ _ => -- We're using the locally nameless representation, which guarantees that -- `closed (.abs e) = closed e` (see theorem `closed_abs`). -- So we could simplify the following to `closed e`, but leave it as is for -- clarity. LExpr.closed e - | .mdata _ e' => isCanonicalValue e' - | _ => false + | e' => + match h: Factory.callOfLFunc F e true with + | some (_, args, f) => + (f.isConstr || Nat.blt args.length f.inputs.length) && + List.all (args.attach.map (fun ⟨ x, _⟩ => + have : x.sizeOf < e'.sizeOf := by + have Hsmall := Factory.callOfLFunc_smaller h; grind + (isCanonicalValue F x))) id + | none => false + termination_by e.sizeOf /-- Equality of canonical values `e1` and `e2`. We can tolerate nested metadata here. -/ -def eql (e1 e2 : (LExpr LMonoTy IDMeta)) - (_h1 : isCanonicalValue e1) (_h2 : isCanonicalValue e2) : Bool := +def eql (F : @Factory T.base) (e1 e2 : LExpr T) + (_h1 : isCanonicalValue F e1) (_h2 : isCanonicalValue F e2) : Bool := if eqModuloTypes e1 e2 then true else - let e1' := removeAllMData e1 - let e2' := removeAllMData e2 - eqModuloTypes e1' e2' + eqModuloTypes e1 e2 -instance : ToFormat (Except Format (LExpr LMonoTy IDMeta)) where +instance [ToFormat T.TypeType]: ToFormat (Except Format (LExpr T)) where format x := match x with | .ok e => format e | .error err => err @@ -70,13 +95,24 @@ eta-expansion. E.g., `mkAbsOfArity 2 core` will give `λxλy ((core y) x)`. -/ -def mkAbsOfArity (arity : Nat) (core : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def mkAbsOfArity (arity : Nat) (core : LExpr T) : (LExpr T) := go 0 arity core - where go (bvarcount arity : Nat) (core : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := + where go (bvarcount arity : Nat) (core : LExpr T) : (LExpr T) := match arity with | 0 => core | n + 1 => - go (bvarcount + 1) n (.abs .none (.app core (.bvar bvarcount))) + go (bvarcount + 1) n (.abs core.metadata .none (.app core.metadata core (.bvar core.metadata bvarcount))) + +/-- +A metadata merger. It will be invoked 'subst s e' is invoked, to create a new +metadata. +-/ +def mergeMetadataForSubst (metaAbs metaE2 metaReplacementVar: TBase.Metadata) := + Traceable.combine + [(EvalProvenance.Original, metaE2), + (EvalProvenance.ReplacementVar, metaReplacementVar), + (EvalProvenance.Abstraction, metaAbs)] + mutual /-- @@ -89,32 +125,41 @@ can evaluate ill-typed terms w.r.t. a given type system here. We prefer Curry-style semantics because they separate the type system from evaluation, allowing us to potentially apply different type systems with our expressions, along with supporting dynamically-typed languages. + +Currently evaluator only supports LExpr with LMonoTy because LFuncs registered +at Factory must have LMonoTy. -/ -def eval (n : Nat) (σ : (LState IDMeta)) (e : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def eval (n : Nat) (σ : LState TBase) (e : (LExpr TBase.mono)) + : LExpr TBase.mono := match n with | 0 => e | n' + 1 => - if isCanonicalValue e then + if isCanonicalValue σ.config.factory e then e else -- Special handling for Factory functions. match σ.config.factory.callOfLFunc e with | some (op_expr, args, lfunc) => let args := args.map (fun a => eval n' σ a) - if h: "inline" ∈ lfunc.attr && lfunc.body.isSome then + if h: lfunc.body.isSome && ("inline" ∈ lfunc.attr || + ("inline_if_val" ∈ lfunc.attr && args.all (isCanonicalValue σ.config.factory))) then -- Inline a function only if it has a body. let body := lfunc.body.get (by simp_all) let input_map := lfunc.inputs.keys.zip args let new_e := substFvars body input_map eval n' σ new_e else - let new_e := mkApp op_expr args - if args.all isConst then + let new_e := @mkApp TBase.mono e.metadata op_expr args + if args.all (isCanonicalValue σ.config.factory) then -- All arguments in the function call are concrete. -- We can, provided a denotation function, evaluate this function -- call. match lfunc.concreteEval with - | none => new_e | some ceval => ceval new_e args + | none => new_e + | some ceval => + match ceval new_e.metadata args with + | .some e' => eval n' σ e' + | .none => new_e else -- At least one argument in the function call is symbolic. new_e @@ -122,27 +167,25 @@ def eval (n : Nat) (σ : (LState IDMeta)) (e : (LExpr LMonoTy IDMeta)) : (LExpr -- Not a call of a factory function. evalCore n' σ e -def evalCore (n' : Nat) (σ : (LState IDMeta)) (e : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def evalCore (n' : Nat) (σ : LState TBase) (e : LExpr TBase.mono) : LExpr TBase.mono := match e with | .const _ _ => e - | .op _ _ => e - | .bvar _ => e - | .fvar x ty => (σ.state.findD x (ty, e)).snd - -- (FIXME): Perform metadata transform instead of erasing it here. - | .mdata _ e' => eval n' σ e' + | .op _ _ _ => e + | .bvar _ _ => e + | .fvar _ x ty => (σ.state.findD x (ty, e)).snd -- Note: closed .abs terms are canonical values; we'll be here if .abs -- contains free variables. - | .abs _ _ => substFvarsFromState σ e - | .quant _ _ _ _ => substFvarsFromState σ e - | .app e1 e2 => evalApp n' σ e e1 e2 - | .eq e1 e2 => evalEq n' σ e1 e2 - | .ite c t f => evalIte n' σ c t f + | .abs _ _ _ => LExpr.substFvarsFromState σ e + | .quant _ _ _ _ _ => LExpr.substFvarsFromState σ e + | .app _ e1 e2 => evalApp n' σ e e1 e2 + | .eq m e1 e2 => evalEq n' σ m e1 e2 + | .ite m c t f => evalIte n' σ m c t f -def evalIte (n' : Nat) (σ : (LState IDMeta)) (c t f : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def evalIte (n' : Nat) (σ : LState TBase) (m: TBase.Metadata) (c t f : LExpr TBase.mono) : LExpr TBase.mono := let c' := eval n' σ c match c' with - | .const "true" _ => eval n' σ t - | .const "false" _ => eval n' σ f + | .true _ => eval n' σ t + | .false _ => eval n' σ f | _ => -- It's important to at least substitute `.fvar`s in both branches of the -- `ite` here so that we can replace the variables by the values in the @@ -152,41 +195,46 @@ def evalIte (n' : Nat) (σ : (LState IDMeta)) (c t f : (LExpr LMonoTy IDMeta)) : -- let f' := eval n' σ f let t' := substFvarsFromState σ t let f' := substFvarsFromState σ f - ite c' t' f' + .ite m c' t' f' -def evalEq (n' : Nat) (σ : (LState IDMeta)) (e1 e2 : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def evalEq (n' : Nat) (σ : LState TBase) (m: TBase.Metadata) (e1 e2 : LExpr TBase.mono) : LExpr TBase.mono := open LTy.Syntax in let e1' := eval n' σ e1 let e2' := eval n' σ e2 - if eqModuloTypes e1' e2' then + if eqModuloTypes e1'.eraseMetadata e2'.eraseMetadata then -- Short-circuit: e1' and e2' are syntactically the same after type erasure. - LExpr.true - else if h: isCanonicalValue e1' ∧ isCanonicalValue e2' then - if eql e1' e2' h.left h.right then - LExpr.true - else LExpr.false + LExpr.true m + else if h: isCanonicalValue σ.config.factory e1' ∧ + isCanonicalValue σ.config.factory e2' then + if eql σ.config.factory e1' e2' h.left h.right then + LExpr.true m + else LExpr.false m else - .eq e1' e2' + .eq m e1' e2' -def evalApp (n' : Nat) (σ : (LState IDMeta)) (e e1 e2 : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def evalApp (n' : Nat) (σ : LState TBase) (e e1 e2 : LExpr TBase.mono) : LExpr TBase.mono := let e1' := eval n' σ e1 let e2' := eval n' σ e2 match e1' with - | .abs _ e1' => - let e' := subst e2' e1' + | .abs mAbs _ e1' => + let e' := subst (fun metaReplacementVar => + let newMeta := mergeMetadataForSubst mAbs e2'.metadata metaReplacementVar + replaceMetadata1 newMeta e2') e1' if eqModuloTypes e e' then e else eval n' σ e' - | .op fn _ => - match σ.config.factory.getFactoryLFunc fn with - | none => LExpr.app e1' e2' + | .op m fn _ => + match σ.config.factory.getFactoryLFunc fn.name with + | none => LExpr.app m e1' e2' | some lfunc => - let e' := LExpr.app e1' e2' + let e' := LExpr.app m e1' e2' -- In `e'`, we have already supplied one input to `fn`. -- Note that we can't have 0-arity Factory functions at this point. - let e'' := mkAbsOfArity (lfunc.inputs.length - 1) e' + let e'' := @mkAbsOfArity TBase.mono (lfunc.inputs.length - 1) (e' : LExpr TBase.mono) eval n' σ e'' - | _ => LExpr.app e1' e2' - + | _ => .app e.metadata e1' e2' end +instance : Traceable EvalProvenance Unit where + combine _ := () + end LExpr end Lambda diff --git a/Strata/DL/Lambda/LExprT.lean b/Strata/DL/Lambda/LExprT.lean index a30add1f2..e988b09f9 100644 --- a/Strata/DL/Lambda/LExprT.lean +++ b/Strata/DL/Lambda/LExprT.lean @@ -21,175 +21,139 @@ namespace Lambda open Std (ToFormat Format format) open LTy -variable {IDMeta : Type} [DecidableEq IDMeta] [HasGen IDMeta] +variable {T : LExprParams} [ToString T.IDMeta] [DecidableEq T.IDMeta] [ToFormat T.IDMeta] [HasGen T.IDMeta] [ToFormat (LFunc T)] -/-- -Apply type substitution `S` to `LExpr e`. --/ -def LExpr.applySubst (e : (LExpr LMonoTy IDMeta)) (S : Subst) : (LExpr LMonoTy IDMeta) := - match e with - | .const c ty => - match ty with - | none => e - | some ty => - let ty := LMonoTy.subst S ty - .const c ty - | .op o ty => - match ty with - | none => e - | some ty => - let ty := LMonoTy.subst S ty - .op o ty - | .fvar x ty => - match ty with - | none => e - | some ty => - let ty := LMonoTy.subst S ty - .fvar x ty - | .bvar _ => e - | .abs ty e => .abs ty (e.applySubst S) - | .quant qk ty tr e => .quant qk ty (tr.applySubst S) (e.applySubst S) - | .app e1 e2 => .app (e1.applySubst S) (e2.applySubst S) - | .ite c t f => .ite (c.applySubst S) (t.applySubst S) (f.applySubst S) - | .eq e1 e2 => .eq (e1.applySubst S) (e2.applySubst S) - | .mdata m e => .mdata m (e.applySubst S) +abbrev LExprT (T : LExprParamsT) := + LExpr (LExprParamsT.typed T) -/-- -Monotype-annotated Lambda expressions, obtained after a type inference transform -from Lambda expressions `LExpr`. --/ -inductive LExprT (IDMeta : Type): Type where - | const (c : String) (ty : LMonoTy) - | op (c : Identifier IDMeta) (ty : LMonoTy) - | bvar (deBruijnIndex : Nat) (ty : LMonoTy) - | fvar (name : Identifier IDMeta) (ty : LMonoTy) - | mdata (info : Info) (e : LExprT IDMeta) - | abs (e : LExprT IDMeta) (ty : LMonoTy) - | quant (k : QuantifierKind) (argTy : LMonoTy) (triggers : LExprT IDMeta) (e : LExprT IDMeta) - | app (fn e : LExprT IDMeta) (ty : LMonoTy) - | ite (c t e : LExprT IDMeta) (ty : LMonoTy) - | eq (e1 e2 : LExprT IDMeta) (ty : LMonoTy) - deriving Repr, DecidableEq - -partial def LExprT.format (et : (LExprT IDMeta)) : Std.Format := +partial def LExprT.format {T : LExprParamsT} [ToFormat T.base.IDMeta] (et : LExprT T) : Std.Format := match et with - | .const c ty => f!"(#{c} : {ty})" - | .op o ty => f!"(~{o} : {ty})" - | .bvar i ty => f!"(%{i} : {ty})" - | .fvar x ty => f!"({x} : {ty})" - | .mdata m e => f!"(.mdata {repr m} {LExprT.format e})" - | .abs e ty => f!"((λ {LExprT.format e}) : {ty})" - | .quant .all ty _ e => f!"(∀({ty}) {LExprT.format e})" - | .quant .exist ty _ e => f!"(∃({ty}) {LExprT.format e})" - | .app e1 e2 ty => f!"({LExprT.format e1} {LExprT.format e2}) : {ty})" - | .ite c t f ty => f!"(if {LExprT.format c} then \ + | .const m c => f!"(#{c} : {m.type})" + | .op m o _ => f!"(~{o} : {m.type})" + | .bvar m i => f!"(%{i} : {m.type})" + | .fvar m x _ => f!"({x} : {m.type})" + | .abs m _ e => f!"((λ {LExprT.format e}) : {m.type})" + | .quant m .all _ _ e => f!"(∀({m.type}) {LExprT.format e})" + | .quant m .exist _ _ e => f!"(∃({m.type}) {LExprT.format e})" + | .app m e1 e2 => f!"({LExprT.format e1} {LExprT.format e2}) : {m.type})" + | .ite m c t f => f!"(if {LExprT.format c} then \ {LExprT.format t} else \ - {LExprT.format f}) : {ty})" - | .eq e1 e2 ty => f!"({LExprT.format e1} == {LExprT.format e2}) : {ty})" + {LExprT.format f}) : {m.type})" + | .eq m e1 e2 => f!"({LExprT.format e1} == {LExprT.format e2}) : {m.type})" -instance : ToFormat (LExprT IDMeta) where +instance (priority := high) {T : LExprParamsT} [ToFormat T.base.IDMeta] : ToFormat (LExprT T) where format := LExprT.format +-- More specific instance that matches when the metadata is explicitly Typed +instance (priority := high) {M : Type} {IDMeta : Type} [ToFormat IDMeta] : ToFormat (LExpr ⟨⟨Typed M, IDMeta⟩, LMonoTy⟩) where + format e := LExprT.format (T := ⟨⟨M, IDMeta⟩, LMonoTy⟩) e + --------------------------------------------------------------------- -namespace LExprT +namespace LExpr /-- Obtain the monotype from `LExprT e`. -/ -def toLMonoTy (e : (LExprT IDMeta)) : LMonoTy := +def toLMonoTy {T : LExprParamsT} (e : LExprT T) : LMonoTy := match e with - | .const _ ty | .op _ ty | .bvar _ ty | .fvar _ ty - | .app _ _ ty | .abs _ ty | .ite _ _ _ ty | .eq _ _ ty => ty - | .quant _ _ _ _ => LMonoTy.bool - | .mdata _ et => LExprT.toLMonoTy et + | .const m _ | .op m _ _ | .bvar m _ | .fvar m _ _ + | .app m _ _ | .abs m _ _ | .ite m _ _ _ | .eq m _ _ => m.type + | .quant _ _ _ _ _ => LMonoTy.bool /-- -Obtain an `LExpr` from an `LExprT`. We erase type annotations for all -expressions, except the constants `.const`s, `.op`s, and free variables +Remove any type annotation stored in metadata for all +expressions, except the `.op`s and free variables `.fvar`s. -/ -def toLExpr (e : (LExprT IDMeta)) : (LExpr LMonoTy IDMeta) := +def unresolved {T : LExprParamsT} (e : LExprT T) : LExpr T.base.mono := match e with - | .const c ty => .const c ty - | .op o ty => .op o ty - | .bvar b _ => .bvar b - | .fvar f ty => .fvar f ty - | .app e1 e2 _ => - .app e1.toLExpr e2.toLExpr - | .abs e (.arrow aty _) => .abs aty e.toLExpr - | .abs e _ => .abs .none e.toLExpr - | .quant qk ty tr e => .quant qk ty tr.toLExpr e.toLExpr - | .ite c t f _ => .ite c.toLExpr t.toLExpr f.toLExpr - | .eq e1 e2 _ => .eq e1.toLExpr e2.toLExpr - | .mdata m e => .mdata m e.toLExpr + | .const m c => .const m.underlying c + | .op m o _ => .op m.underlying o (some m.type) + | .bvar m b => .bvar m.underlying b + | .fvar m f _ => .fvar m.underlying f (some m.type) + | .app m e1 e2 => + .app m.underlying e1.unresolved e2.unresolved + | .abs ⟨underlying, .arrow aty _⟩ _ e => + .abs underlying (some aty) e.unresolved + | .abs m t e => .abs m.underlying t e.unresolved + -- Since quantifiers are bools, the type stored in their + -- metadata is the type of the argument + | .quant m qk _ tr e => .quant m.underlying qk (some m.type) tr.unresolved e.unresolved + | .ite m c t f => .ite m.underlying c.unresolved t.unresolved f.unresolved + | .eq m e1 e2 => .eq m.underlying e1.unresolved e2.unresolved + +def replaceUserProvidedType {T : LExprParamsT} (e : LExpr T) (f : T.TypeType → T.TypeType) : LExpr T := + match e with + | .const m c => + .const m c + | .op m o uty => + .op m o (uty.map f) + | .bvar m b => + .bvar m b + | .fvar m x uty => + .fvar m x (uty.map f) + | .app m e1 e2 => + let e1 := replaceUserProvidedType e1 f + let e2 := replaceUserProvidedType e2 f + .app m e1 e2 + | .abs m uty e => + let e := replaceUserProvidedType e f + .abs m (uty.map f) e + | .quant m qk argTy tr e => + let e := replaceUserProvidedType e f + let tr := replaceUserProvidedType tr f + .quant m qk (argTy.map f) tr e + | .ite m c t f_expr => + let c := replaceUserProvidedType c f + let t := replaceUserProvidedType t f + let f_expr := replaceUserProvidedType f_expr f + .ite m c t f_expr + | .eq m e1 e2 => + let e1 := replaceUserProvidedType e1 f + let e2 := replaceUserProvidedType e2 f + .eq m e1 e2 /-- -Apply type substitution `S` to `LExprT e`. +Apply type substitution `S` to `LExpr e`. +This is only for user-defined types, not metadata-stored resolved types +If e is an LExprT whose metadata contains type information, use applySubstT -/ -def applySubst (e : (LExprT IDMeta)) (S : Subst) : (LExprT IDMeta) := - match e with - | .const c ty => - let ty := LMonoTy.subst S ty - .const c ty - | .op o ty => - let ty := LMonoTy.subst S ty - .op o ty - | .bvar b ty => - let ty := LMonoTy.subst S ty - .bvar b ty - | .fvar x ty => - let ty := LMonoTy.subst S ty - .fvar x ty - | .app e1 e2 ty => - let e1 := LExprT.applySubst e1 S - let e2 := LExprT.applySubst e2 S - let ty := LMonoTy.subst S ty - .app e1 e2 ty - | .abs e ty => - let e := LExprT.applySubst e S - let ty := LMonoTy.subst S ty - .abs e ty - | .quant qk ty tr e => - let e := LExprT.applySubst e S - let tr := LExprT.applySubst tr S - .quant qk ty tr e - | .ite c t f ty => - let c := LExprT.applySubst c S - let t := LExprT.applySubst t S - let f := LExprT.applySubst f S - let ty := LMonoTy.subst S ty - .ite c t f ty - | .eq e1 e2 ty => - let e1 := LExprT.applySubst e1 S - let e2 := LExprT.applySubst e2 S - let ty := LMonoTy.subst S ty - .eq e1 e2 ty - | .mdata m e => - let e := LExprT.applySubst e S - .mdata m e +def applySubst {T : LExprParams} (e : LExpr T.mono) (S : Subst) : LExpr T.mono := + replaceUserProvidedType e (fun t: LMonoTy => LMonoTy.subst S t) + +/-- +Apply type substitution `S` to `LExpr e`. +This is for metadata-stored types. +To change user-defined types, use applySubst +-/ +def applySubstT (e : LExprT T.mono) (S : Subst) : LExprT T.mono := + LExpr.replaceMetadata (T:=T.mono.typed) (NewMetadata:=T.mono.typed.base.Metadata) e <| + fun ⟨m, ty⟩ => + let ty := LMonoTy.subst S ty + ⟨m, ty⟩ + /-- This function turns some free variables into bound variables to build an -abstraction, given its body. `varClose k x e` keeps track of the number `k` +abstraction, given its body. `varCloseT k x e` keeps track of the number `k` of abstractions that have passed by; it replaces all `(.fvar x)` with `(.bvar k)` in an `LExprT e`. Also see `LExpr.varClose` for an analogous function for `LExpr`s. -/ -protected def varClose (k : Nat) (x : Identifier IDMeta) (e : (LExprT IDMeta)) : (LExprT IDMeta) := +protected def varCloseT (k : Nat) (x : T.Identifier) (e : (LExprT T.mono)) : (LExprT T.mono) := match e with - | .const c ty => .const c ty - | .op o ty => .op o ty - | .bvar i ty => .bvar i ty - | .fvar y yty => if (x == y) then (.bvar k yty) - else (.fvar y yty) - | .mdata info e' => .mdata info (.varClose k x e') - | .abs e' ty => .abs (.varClose (k + 1) x e') ty - | .quant qk ty tr' e' => .quant qk ty (.varClose (k + 1) x tr') (.varClose (k + 1) x e') - | .app e1 e2 ty => .app (.varClose k x e1) (.varClose k x e2) ty - | .ite c t e ty => .ite (.varClose k x c) (.varClose k x t) (.varClose k x e) ty - | .eq e1 e2 ty => .eq (.varClose k x e1) (.varClose k x e2) ty + | .const m c => .const m c + | .op m o ty => .op m o ty + | .bvar m i => .bvar m i + | .fvar m y yty => if (x == y) then (.bvar m k) + else (.fvar m y yty) + | .abs m ty e' => .abs m ty (.varCloseT (k + 1) x e') + | .quant m qk ty tr' e' => .quant m qk ty (.varCloseT (k + 1) x tr') (.varCloseT (k + 1) x e') + | .app m e1 e2 => .app m (.varCloseT k x e1) (.varCloseT k x e2) + | .ite m c t e => .ite m (.varCloseT k x c) (.varCloseT k x t) (.varCloseT k x e) + | .eq m e1 e2 => .eq m (.varCloseT k x e1) (.varCloseT k x e2) --------------------------------------------------------------------- @@ -198,36 +162,36 @@ Generate a fresh identifier `xv` for a bound variable. Use the type annotation `ty` if present, otherwise generate a fresh type variable. Add `xv` along with its type to the type context. -/ -def typeBoundVar (C: LContext IDMeta) (T : TEnv IDMeta) (ty : Option LMonoTy) : - Except Format (Identifier IDMeta × LMonoTy × TEnv IDMeta) := do - let (xv, T) := liftGenEnv HasGen.genVar T - let (xty, T) ← match ty with +def typeBoundVar (C: LContext T) (Env : TEnv T.IDMeta) (ty : Option LMonoTy) : + Except Format (T.Identifier × LMonoTy × TEnv T.IDMeta) := do + let (xv, Env) := liftGenEnv HasGen.genVar Env + let (xty, Env) ← match ty with | some bty => - let ans := LMonoTy.instantiateWithCheck bty C T + let ans := LMonoTy.instantiateWithCheck bty C Env match ans with | .error e => .error e - | .ok (bty, T) => .ok (bty, T) + | .ok (bty, Env) => .ok (bty, Env) | none => - let (xtyid, T) := TEnv.genTyVar T + let (xtyid, Env) := TEnv.genTyVar Env let xty := (LMonoTy.ftvar xtyid) - .ok (xty, T) - let T := T.insertInContext xv (.forAll [] xty) - return (xv, xty, T) + .ok (xty, Env) + let Env := Env.insertInContext xv (.forAll [] xty) + return (xv, xty, Env) /-- Infer the type of `.fvar x fty`. -/ -def inferFVar (C: LContext IDMeta) (T : (TEnv IDMeta)) (x : Identifier IDMeta) (fty : Option LMonoTy) : - Except Format (LMonoTy × (TEnv IDMeta)) := - match T.context.types.find? x with +def inferFVar (C: LContext T) (Env : TEnv T.IDMeta) (x : T.Identifier) (fty : Option LMonoTy) : + Except Format (LMonoTy × (TEnv T.IDMeta)) := + match Env.context.types.find? x with | none => .error f!"Cannot find this fvar in the context! \ - {LExpr.fvar x fty}" + {x}" | some ty => do - let (ty, T) ← LTy.instantiateWithCheck ty C T + let (ty, Env) ← LTy.instantiateWithCheck ty C Env match fty with - | none => .ok (ty, T) + | none => .ok (ty, Env) | some fty => - let (fty, T) ← LMonoTy.instantiateWithCheck fty C T - let S ← Constraints.unify [(fty, ty)] T.stateSubstInfo - .ok (ty, TEnv.updateSubst T S) + let (fty, Env) ← LMonoTy.instantiateWithCheck fty C Env + let S ← Constraints.unify [(fty, ty)] Env.stateSubstInfo |>.mapError format + .ok (ty, TEnv.updateSubst Env S) /-- Infer the type of `.const c cty`. Here, we use the term "constant" in the same @@ -241,250 +205,187 @@ for some kinds of constants, especially for types with really large or infinite members (e.g., bitvectors, natural numbers, etc.). `.const` is the place to do that. -/ -def inferConst (C: LContext IDMeta) (T : (TEnv IDMeta)) (c : String) (cty : Option LMonoTy) : - Except Format (LMonoTy × (TEnv IDMeta)) := - open LTy.Syntax in - match c, cty with - -- Annotated Booleans - | "true", some LMonoTy.bool | "false", some LMonoTy.bool => .ok (mty[bool], T) - -- Unannotated Booleans: note that `(.const "true" none)` and - -- `(.const "false" none)` will be interpreted as booleans. - | "true", none | "false", none => - if C.knownTypes.containsName "bool" then - .ok (mty[bool], T) - else - .error f!"Booleans are not registered in the known types.\n\ - Don't know how to interpret the following constant:\n\ - {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - -- Annotated Integers - | c, some LMonoTy.int => - if C.knownTypes.containsName "int" then - if c.isInt then .ok (mty[int], T) - else .error f!"Constant annotated as an integer, but it is not.\n\ - {@LExpr.const LMonoTy IDMeta c cty}" - else - .error f!"Integers are not registered in the known types.\n\ - Don't know how to interpret the following constant:\n\ - {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - -- Annotated Reals - | c, some LMonoTy.real => - if C.knownTypes.containsName "real" then - .ok (mty[real], T) - else - .error f!"Reals are not registered in the known types.\n\ +def inferConst (C: LContext T) (Env : TEnv T.IDMeta) (c : LConst) : + Except Format (LMonoTy × (TEnv T.IDMeta)) := + if C.knownTypes.containsName c.tyName then + .ok (c.ty, Env) + else .error (c.tyNameFormat ++ f!" are not registered in the known types.\n\ Don't know how to interpret the following constant:\n\ - {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - -- Annotated BitVecs - | c, some (LMonoTy.bitvec n) => - let ty := LMonoTy.bitvec n - if C.knownTypes.containsName "bitvec" then - (.ok (ty, T)) - else - .error f!"Bit vectors of size {n} are not registered in the known types.\n\ - Don't know how to interpret the following constant:\n\ - {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - -- Annotated Strings - | c, some LMonoTy.string => - if C.knownTypes.containsName "string" then - .ok (mty[string], T) - else - .error f!"Strings are not registered in the known types.\n\ - Don't know how to interpret the following constant:\n\ - {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - | _, _ => - -- Unannotated Integers - if c.isInt then - if C.knownTypes.containsName "int" then - .ok (mty[int], T) - else - .error f!"Integers are not registered in the known types.\n\ - Constant {@LExpr.const LMonoTy IDMeta c cty}\n\ - Known Types: {C.knownTypes}" - else - .error f!"Cannot infer the type of this constant: \ - {@LExpr.const LMonoTy IDMeta c cty}" + {c}\n\ + Known Types: {C.knownTypes}") mutual -partial def fromLExprAux (C: LContext IDMeta) (T : (TEnv IDMeta)) (e : (LExpr LMonoTy IDMeta)) : - Except Format ((LExprT IDMeta) × (TEnv IDMeta)) := +partial def resolveAux (C: LContext T) (Env : TEnv T.IDMeta) (e : LExpr T.mono) : + Except Format (LExprT T.mono × TEnv T.IDMeta) := open LTy.Syntax in do match e with - | .mdata m e => - let (et, T) ← fromLExprAux C T e - .ok ((.mdata m et), T) - | .const c cty => - let (ty, T) ← inferConst C T c cty - .ok (.const c ty, T) - | .op o oty => - let (ty, T) ← inferOp C T o oty - .ok (.op o ty, T) - | .bvar _ => .error f!"Cannot infer the type of this bvar: {e}" - | .fvar x fty => - let (ty, T) ← inferFVar C T x fty - .ok (.fvar x ty, T) - | .app e1 e2 => fromLExprAux.app C T e1 e2 - | .abs ty e => fromLExprAux.abs C T ty e - | .quant qk ty tr e => fromLExprAux.quant C T qk ty tr e - | .eq e1 e2 => fromLExprAux.eq C T e1 e2 - | .ite c th el => fromLExprAux.ite C T c th el + | .const m c => + let (ty, Env) ← inferConst C Env c + .ok (.const ⟨m, ty⟩ c, Env) + | .op m o oty => + let (ty, Env) ← inferOp C Env o oty + .ok (.op ⟨m, ty⟩ o (.some ty), Env) + | .bvar _ _ => .error f!"Cannot infer the type of this bvar: {e}" + | .fvar m x fty => + let (ty, Env) ← inferFVar C Env x fty + .ok (.fvar ⟨m, ty⟩ x ty, Env) + | .app m e1 e2 => resolveAux.app C Env m e1 e2 + | .abs m ty e => resolveAux.abs C Env m ty e + | .quant m qk ty tr e => resolveAux.quant C Env m qk ty tr e + | .eq m e1 e2 => resolveAux.eq C Env m e1 e2 + | .ite m c th el => resolveAux.ite C Env m c th el /-- Infer the type of an operation `.op o oty`, where an operation is defined in the factory. -/ -partial def inferOp (C: LContext IDMeta) (T : (TEnv IDMeta)) (o : Identifier IDMeta) (oty : Option LMonoTy) : - Except Format (LMonoTy × (TEnv IDMeta)) := +partial def inferOp (C: LContext T) (Env : TEnv T.IDMeta) (o : T.Identifier) (oty : Option LMonoTy) : + Except Format (LMonoTy × (TEnv T.IDMeta)) := open LTy.Syntax in match C.functions.find? (fun fn => fn.name == o) with | none => .error f!"{toString $ C.functions.getFunctionNames} Cannot infer the type of this operation: \ - {LExpr.op o oty}" + {o}" | some func => do -- `LFunc.type` below will also catch any ill-formed functions (e.g., -- where there are duplicates in the formals, etc.). let type ← func.type - let (ty, T) ← LTy.instantiateWithCheck type C T - let T ← + let (ty, Env) ← LTy.instantiateWithCheck type C Env + let Env ← match func.body with - | none => .ok T + | none => .ok Env | some body => if body.freeVars.idents.all (fun k => k ∈ func.inputs.keys) then -- Temporarily add formals in the context. - let T := T.pushEmptyContext - let T := T.addToContext func.inputPolyTypes + let Env := Env.pushEmptyContext + let Env := Env.addToContext func.inputPolyTypes -- Type check the body and ensure that it unifies with the return type. - -- let (bodyty, T) ← infer T body - let (body_typed, T) ← fromLExprAux C T body + -- let (bodyty, Env) ← infer Env body + let (body_typed, Env) ← resolveAux C Env body let bodyty := body_typed.toLMonoTy - let (retty, T) ← func.outputPolyType.instantiateWithCheck C T - let S ← Constraints.unify [(retty, bodyty)] T.stateSubstInfo - let T := T.updateSubst S - let T := T.popContext - .ok T + let (retty, Env) ← func.outputPolyType.instantiateWithCheck C Env + let S ← Constraints.unify [(retty, bodyty)] Env.stateSubstInfo |>.mapError format + let Env := Env.updateSubst S + let Env := Env.popContext + .ok Env else .error f!"Function body contains free variables!\n\ {func}" match oty with - | none => .ok (ty, T) + | none => .ok (ty, Env) | some oty => - let (oty, T) ← LMonoTy.instantiateWithCheck oty C T - let S ← Constraints.unify [(ty, oty)] T.stateSubstInfo - .ok (ty, TEnv.updateSubst T S) - -partial def fromLExprAux.ite (C: LContext IDMeta) (T : (TEnv IDMeta)) (c th el : (LExpr LMonoTy IDMeta)) := do - let (ct, T) ← fromLExprAux C T c - let (tt, T) ← fromLExprAux C T th - let (et, T) ← fromLExprAux C T el + let (oty, Env) ← LMonoTy.instantiateWithCheck oty C Env + let S ← Constraints.unify [(ty, oty)] Env.stateSubstInfo |>.mapError format + .ok (ty, TEnv.updateSubst Env S) + +partial def resolveAux.ite (C: LContext T) (Env : TEnv T.IDMeta) (m : T.Metadata) (c th el : LExpr ⟨T, LMonoTy⟩) := do + let (ct, Env) ← resolveAux C Env c + let (tt, Env) ← resolveAux C Env th + let (et, Env) ← resolveAux C Env el let cty := ct.toLMonoTy let tty := tt.toLMonoTy let ety := et.toLMonoTy - let S ← Constraints.unify [(cty, LMonoTy.bool), (tty, ety)] T.stateSubstInfo - .ok (ite ct tt et tty, TEnv.updateSubst T S) + let S ← Constraints.unify [(cty, LMonoTy.bool), (tty, ety)] Env.stateSubstInfo |>.mapError format + .ok (.ite ⟨m, tty⟩ ct tt et, Env.updateSubst S) -partial def fromLExprAux.eq (C: LContext IDMeta) (T : (TEnv IDMeta)) (e1 e2 : (LExpr LMonoTy IDMeta)) := do +partial def resolveAux.eq (C: LContext T) (Env : TEnv T.IDMeta) (m: T.Metadata) (e1 e2 : LExpr T.mono) := do -- `.eq A B` is well-typed if there is some instantiation of -- type parameters in `A` and `B` that makes them have the same type. - let (e1t, T) ← fromLExprAux C T e1 - let (e2t, T) ← fromLExprAux C T e2 + let (e1t, Env) ← resolveAux C Env e1 + let (e2t, Env) ← resolveAux C Env e2 let ty1 := e1t.toLMonoTy let ty2 := e2t.toLMonoTy - let S ← Constraints.unify [(ty1, ty2)] T.stateSubstInfo - .ok (.eq e1t e2t LMonoTy.bool, TEnv.updateSubst T S) + let S ← Constraints.unify [(ty1, ty2)] Env.stateSubstInfo |>.mapError format + .ok (.eq ⟨m, LMonoTy.bool⟩ e1t e2t, TEnv.updateSubst Env S) -partial def fromLExprAux.abs (C: LContext IDMeta) (T : (TEnv IDMeta)) (bty : Option LMonoTy) (e : (LExpr LMonoTy IDMeta)) := do - -- Generate a fresh expression variable to stand in for the bound variable. +partial def resolveAux.abs (C: LContext T) (Env : TEnv T.IDMeta) (m: T.Metadata) (bty : Option LMonoTy) (e : LExpr T.mono): Except Format (LExprT T.mono × TEnv T.IDMeta) := do + -- Generate a fresh expression variable to stand in for the bound variable -- For the bound variable, use type annotation if present. Otherwise, -- generate a fresh type variable. - let (xv, xty, T) ← typeBoundVar C T bty + let (xv, xty, Env) ← typeBoundVar C Env bty -- Construct `e'` from `e`, where the bound variable has been replaced by -- `xv`. let e' := LExpr.varOpen 0 (xv, some xty) e - let (et, T) ← fromLExprAux C T e' - let etclosed := .varClose 0 xv et + let (et, Env) ← resolveAux C Env e' + let etclosed := .varCloseT 0 xv et let ety := etclosed.toLMonoTy - let mty := LMonoTy.subst T.stateSubstInfo.subst (.tcons "arrow" [xty, ety]) + let mty := LMonoTy.subst Env.stateSubstInfo.subst (.tcons "arrow" [xty, ety]) -- Safe to erase fresh variable from context after closing the expressions. -- Note that we don't erase `xty` (if it was a fresh type variable) from the substitution -- list because it may occur in `etclosed`, which hasn't undergone a -- substitution. We could, of course, substitute `xty` in `etclosed`, but -- that'd require crawling over that expression, which could be expensive. - let T := T.eraseFromContext xv - .ok ((.abs etclosed mty), T) + let Env := Env.eraseFromContext xv + .ok ((.abs ⟨m, mty⟩ bty etclosed), Env) -partial def fromLExprAux.quant (C: LContext IDMeta) (T : (TEnv IDMeta)) (qk : QuantifierKind) (bty : Option LMonoTy) - (triggers : LExpr LMonoTy IDMeta) (e : (LExpr LMonoTy IDMeta)) := do - let (xv, xty, T) ← typeBoundVar C T bty +partial def resolveAux.quant (C: LContext T) (Env : TEnv T.IDMeta) (m: T.Metadata) (qk : QuantifierKind) (bty : Option LMonoTy) + (triggers e : LExpr T.mono): Except Format (LExprT T.mono × TEnv T.IDMeta) := do + let (xv, xty, Env) ← typeBoundVar C Env bty let e' := LExpr.varOpen 0 (xv, some xty) e let triggers' := LExpr.varOpen 0 (xv, some xty) triggers - let (et, T) ← fromLExprAux C T e' - let (triggersT, T) ← fromLExprAux C T triggers' + let (et, Env) ← resolveAux C Env e' + let (triggersT, Env) ← resolveAux C Env triggers' let ety := et.toLMonoTy - let xty := LMonoTy.subst T.stateSubstInfo.subst xty - let etclosed := LExprT.varClose 0 xv et - let triggersClosed := LExprT.varClose 0 xv triggersT + let xty := LMonoTy.subst Env.stateSubstInfo.subst xty + let etclosed := Lambda.LExpr.varCloseT 0 xv et + let triggersClosed := Lambda.LExpr.varCloseT 0 xv triggersT -- Safe to erase fresh variable from context after closing the expressions. -- Again, as in `abs`, we do not erase `xty` (if it was a fresh variable) from the -- substitution list. - let T := T.eraseFromContext xv + let Env := Env.eraseFromContext xv if ety != LMonoTy.bool then do .error f!"Quantifier body has non-Boolean type: {ety}" else - .ok (.quant qk xty triggersClosed etclosed, T) + .ok (.quant ⟨m, xty⟩ qk xty triggersClosed etclosed, Env) -partial def fromLExprAux.app (C: LContext IDMeta) (T : (TEnv IDMeta)) (e1 e2 : (LExpr LMonoTy IDMeta)) := do - let (e1t, T) ← fromLExprAux C T e1 +partial def resolveAux.app (C: LContext T) (Env : TEnv T.IDMeta) (m: T.Metadata) (e1 e2 : LExpr T.mono) := do + let (e1t, Env) ← resolveAux C Env e1 let ty1 := e1t.toLMonoTy - let (e2t, T) ← fromLExprAux C T e2 + let (e2t, Env) ← resolveAux C Env e2 let ty2 := e2t.toLMonoTy -- `freshty` is the type variable whose identifier is `fresh_name`. It denotes -- the type of `(.app e1 e2)`. - let (fresh_name, T) := TEnv.genTyVar T + let (fresh_name, Env) := TEnv.genTyVar Env let freshty := (.ftvar fresh_name) -- `ty1` must be of the form `ty2 → freshty`. let constraints := [(ty1, (.tcons "arrow" [ty2, freshty]))] - let S ← Constraints.unify constraints T.stateSubstInfo - -- The type of `(.app e1 e2)` is `freshty`, with appropriate substitutions - -- applied. + let S ← Constraints.unify constraints Env.stateSubstInfo |>.mapError format let mty := LMonoTy.subst S.subst freshty -- `freshty` can now be safely removed from the substitution list. have hWF : SubstWF (Maps.remove S.subst fresh_name) := by apply @SubstWF_of_remove S.subst fresh_name S.isWF let S := { S with subst := S.subst.remove fresh_name, isWF := hWF } - .ok (.app e1t e2t mty, TEnv.updateSubst T S) + .ok (.app ⟨m, mty⟩ e1t e2t, TEnv.updateSubst Env S) + +protected partial def resolve (C: LContext T) (Env : TEnv T.IDMeta) (e : LExpr T.mono) : + Except Format (LExprT T.mono × TEnv T.IDMeta) := do + let (et, Env) ← resolveAux C Env e + .ok (LExpr.applySubstT et Env.stateSubstInfo.subst, Env) end -protected def fromLExpr (C: LContext IDMeta) (T : (TEnv IDMeta)) (e : (LExpr LMonoTy IDMeta)) : - Except Format ((LExprT IDMeta) × (TEnv IDMeta)) := do - let (et, T) ← fromLExprAux C T e - .ok (LExprT.applySubst et T.stateSubstInfo.subst, T) -protected def fromLExprs (C: LContext IDMeta) (T : (TEnv IDMeta)) (es : List (LExpr LMonoTy IDMeta)) : - Except Format (List (LExprT IDMeta) × (TEnv IDMeta)) := do - go T es [] +protected def resolves (C: LContext T) (Env : TEnv T.IDMeta) (es : List (LExpr T.mono)) : + Except Format (List (LExprT T.mono) × TEnv T.IDMeta) := do + go Env es [] where - go (T : TEnv IDMeta) (rest : List (LExpr LMonoTy IDMeta)) - (acc : List (LExprT IDMeta)) := do + go (Env : TEnv T.IDMeta) (rest : List (LExpr T.mono)) + (acc : List (LExprT T.mono)) := do match rest with - | [] => .ok (acc.reverse, T) + | [] => .ok (acc.reverse, Env) | e :: erest => - let (et, T) ← LExprT.fromLExpr C T e + let (et, T) ← LExpr.resolve C Env e go T erest (et :: acc) -end LExprT +end LExpr --------------------------------------------------------------------- /-- Annotate an `LExpr e` with inferred monotypes. -/ -def LExpr.annotate (C: LContext IDMeta) (T : (TEnv IDMeta)) (e : (LExpr LMonoTy IDMeta)) : - Except Format ((LExpr LMonoTy IDMeta) × (TEnv IDMeta)) := do - let (e_a, T) ← LExprT.fromLExpr C T e - return (LExprT.toLExpr e_a, T) +def LExpr.annotate (C: LContext T) (Env : TEnv T.IDMeta) (e : (LExpr T.mono)) : + Except Format (LExpr T.mono × TEnv T.IDMeta) := do + let (e_a, Env) ← LExpr.resolve C Env e + return (unresolved e_a, Env) --------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/LExprTypeEnv.lean b/Strata/DL/Lambda/LExprTypeEnv.lean index f6acf483d..3b0ad8d3a 100644 --- a/Strata/DL/Lambda/LExprTypeEnv.lean +++ b/Strata/DL/Lambda/LExprTypeEnv.lean @@ -7,6 +7,7 @@ import Strata.DL.Lambda.LExprWF import Strata.DL.Lambda.LTyUnify import Strata.DL.Lambda.Factory +import Strata.DL.Lambda.TypeFactory import Strata.DL.Util.Maps /-! ## Type Environment @@ -51,24 +52,26 @@ def TypeAlias.toAliasLTy (a : TypeAlias) : LTy := instance : ToFormat TypeAlias where format t := f!"{t.toAliasLTy} := {t.type}" -variable {IDMeta : Type} [DecidableEq IDMeta] +variable {T: LExprParams} [DecidableEq T.IDMeta] [ToFormat T.Metadata] [ToFormat T.IDMeta] /-- -A type context contains two maps: `types` and `aliases`. - -The `types` field maps free variables in expressions (i.e., `LExpr.fvar`s) to -their type schemes. This is essentially a stack to account for variable scopes. - -The `aliases` field maps type synonyms 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`). +A type context describing the types of free variables and the mappings of type +aliases. -/ structure TContext (IDMeta : Type) where + + /-- A map from free variables in expressions (i.e., `LExpr.fvar`s) to their + type schemes. This is essentially a stack to account for variable scopes. -/ types : Maps (Identifier IDMeta) LTy := [] + + /-- 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`). -/ aliases : List TypeAlias := [] + deriving DecidableEq, Repr, Inhabited -instance : ToFormat (TContext IDMeta) where +instance {IDMeta} [ToFormat IDMeta] : ToFormat (TContext IDMeta) where format ctx := f!"types: {ctx.types}\n\ aliases: {ctx.aliases}" @@ -100,14 +103,14 @@ def TContext.knownTypeVars (ctx : (TContext IDMeta)) : List TyIdentifier := /-- Is `x` is a fresh type variable w.r.t. the context? -/ -def TContext.isFresh (tx : TyIdentifier) (Γ : (TContext IDMeta)) : Prop := - ∀ (x : Identifier IDMeta) (ty : LTy), +def TContext.isFresh (tx : TyIdentifier) (Γ : TContext T.IDMeta) : Prop := + ∀ (x : T.Identifier) (ty : LTy), Γ.types.find? x = some ty → tx ∉ (LTy.freeVars ty) /-- Are `xs` fresh type variables w.r.t. the context? -/ -def TContext.allFreshVars (xs : List TyIdentifier) (Γ : (TContext IDMeta)) : Prop := +def TContext.allFreshVars (xs : List TyIdentifier) (Γ : (TContext T.IDMeta)) : Prop := match xs with | [] => True | x :: rest => (TContext.isFresh x Γ) ∧ (TContext.allFreshVars rest Γ) @@ -127,8 +130,8 @@ def TContext.types.subst (types : Maps (Identifier IDMeta) LTy) (S : Subst) : /-- Apply a substitution `S` to the context. -/ -def TContext.subst (T : TContext IDMeta) (S : Subst) : TContext IDMeta := - { T with types := types.subst T.types S } +def TContext.subst (ctx : TContext IDMeta) (S : Subst) : TContext IDMeta := + { ctx with types := types.subst ctx.types S } --------------------------------------------------------------------- @@ -212,6 +215,12 @@ structure TGenEnv (IDMeta : Type) where genState : TState deriving Inhabited +def LDatatype.toKnownType (d: LDatatype IDMeta) : KnownType := + { name := d.name, metadata := d.typeArgs.length} + +def TypeFactory.toKnownTypes (t: @TypeFactory IDMeta) : KnownTypes := + makeKnownTypes (t.foldr (fun t l => t.toKnownType :: l) []) + /-- A type environment `TEnv` contains - genEnv: `TGenEnv` to track the generator state as well as the typing context @@ -228,14 +237,27 @@ deriving Inhabited /-- Context data that does not change throughout type checking: a factory of user-specified functions and data structures for ensuring unique -names of types and functions --/ -structure LContext (IDMeta : Type) where - functions : @Factory IDMeta +names of types and functions. +Invariant: all functions defined in `TypeFactory.genFactory` +for `datatypes` should be in `functions`. +-/ +structure LContext (T: LExprParams) where + /-- Descriptions of all built-in functions. -/ + functions : @Factory T + /-- Descriptions of all built-in datatypes. -/ + datatypes : @TypeFactory T.IDMeta + /-- A list of known built-in types. -/ knownTypes : KnownTypes - idents : Identifiers IDMeta + /-- The set of identifiers that have been seen or generated so far. -/ + idents : Identifiers T.IDMeta deriving Inhabited +def LContext.empty {IDMeta} : LContext IDMeta := + ⟨#[], #[], {}, {}⟩ + +instance : EmptyCollection (LContext IDMeta) where + emptyCollection := LContext.empty + def TEnv.context (T: TEnv IDMeta) : TContext IDMeta := T.genEnv.context @@ -266,8 +288,9 @@ def TEnv.default : TEnv IDMeta := let g := {context := {}, genState := TState.init} { genEnv := g} -def LContext.default : LContext IDMeta := +def LContext.default : LContext T := { functions := #[], + datatypes := #[], knownTypes := KnownTypes.default, idents := Identifiers.default } @@ -288,31 +311,57 @@ instance [ToFormat IDMeta] : ToFormat (TEnv IDMeta) where exprPrefix: {g.exprPrefix}{Format.line}\ subst: {s.stateSubstInfo.subst}" -instance [ToFormat IDMeta] : ToFormat (LContext IDMeta) where +instance : ToFormat (LContext T) where format s := f!" known types:{Format.line}{s.knownTypes}\ identifiers:{Format.line}{s.idents}" -def LContext.addKnownTypeWithError (T : LContext IDMeta) (k : KnownType) (f: Format) : Except Format (LContext IDMeta) := do - .ok {T with knownTypes := (← T.knownTypes.addWithError k f)} +def LContext.addKnownTypeWithError (C : LContext T) (k : KnownType) (f: Format) : Except Format (LContext T) := do + .ok {C with knownTypes := (← C.knownTypes.addWithError k f)} + +def LContext.addKnownTypes (C : LContext T) (k : KnownTypes) : Except Format (LContext T) := do + k.foldM (fun T k n => T.addKnownTypeWithError ⟨k, n⟩ f!"Error: type {k} already known") C -def LContext.addIdentWithError (T : LContext IDMeta) (i: Identifier IDMeta) (f: Format) : Except Format (LContext IDMeta) := do - let i ← T.idents.addWithError i f - .ok {T with idents := i} +def LContext.addIdentWithError (C : LContext T) (i: T.Identifier) (f: Format) : Except Format (LContext T) := do + let i ← C.idents.addWithError i f + .ok {C with idents := i} -def LContext.addFactoryFunction (T : LContext IDMeta) (fn : LFunc IDMeta) : LContext IDMeta := - { T with functions := T.functions.push fn } +def LContext.addFactoryFunction (C : LContext T) (fn : LFunc T) : LContext T := + { C with functions := C.functions.push fn } -def LContext.addFactoryFunctions (T : LContext IDMeta) (fact : @Factory IDMeta) : LContext IDMeta := - { T with functions := T.functions.append fact } +def LContext.addFactoryFunctions (C : LContext T) (fact : @Factory T) : LContext T := + { C with functions := C.functions.append fact } + +/-- +Add a datatype `d` to an `LContext` `C`. +This adds `d` to `C.datatypes`, adds the derived functions +(e.g. eliminators, testers) to `C.functions`, and adds `d` to +`C.knownTypes`. It performs error checking for name clashes. +-/ +def LContext.addDatatype [Inhabited T.IDMeta] [Inhabited T.Metadata] (C: LContext T) (d: LDatatype T.IDMeta) : Except Format (LContext T) := do + -- Ensure not in known types + if C.knownTypes.containsName d.name then + .error f!"Cannot name datatype same as known type!\n\ + {d}\n\ + KnownTypes' names:\n\ + {C.knownTypes.keywords}" + let ds ← C.datatypes.addDatatype d + -- Add factory functions, checking for name clashes + let f ← d.genFactory + let fs ← C.functions.addFactory f + -- Add datatype names to knownTypes + let ks ← C.knownTypes.add d.toKnownType + .ok {C with datatypes := ds, functions := fs, knownTypes := ks} + +def LContext.addTypeFactory [Inhabited T.IDMeta] [Inhabited T.Metadata] (C: LContext T) (f: @TypeFactory T.IDMeta) : Except Format (LContext T) := + Array.foldlM (fun C d => C.addDatatype d) C f /-- Replace the global substitution in `T.state.subst` with `S`. -/ -def TEnv.updateSubst (T : (TEnv IDMeta)) (S : SubstInfo) : (TEnv IDMeta) := - { T with stateSubstInfo := S } +def TEnv.updateSubst (Env : TEnv IDMeta) (S : SubstInfo) : TEnv IDMeta := + { Env with stateSubstInfo := S } -omit [DecidableEq IDMeta] in theorem TEnv.SubstWF_of_pushemptySubstScope (T : TEnv IDMeta) : SubstWF (Maps.push T.stateSubstInfo.subst []) := by have h_SubstWF : SubstWF T.stateSubstInfo.subst := by @@ -326,7 +375,6 @@ def TEnv.pushEmptySubstScope (T : (TEnv IDMeta)) : (TEnv IDMeta) := let newS := { subst := new_subst, isWF := (by rw [TEnv.SubstWF_of_pushemptySubstScope]) } { T with stateSubstInfo := newS } -omit [DecidableEq IDMeta] in theorem TEnv.SubstWF_of_popSubstScope (T : TEnv IDMeta) : SubstWF (Maps.pop T.stateSubstInfo.subst) := by have h_SubstWF : SubstWF T.stateSubstInfo.subst := by @@ -347,43 +395,43 @@ def TEnv.pushEmptyContext (T : (TEnv IDMeta)) : (TEnv IDMeta) := let ctx' := { ctx with types := ctx.types.push [] } T.updateContext ctx' -def TEnv.popContext (T : (TEnv IDMeta)) : (TEnv IDMeta) := - let ctx := T.context +def TEnv.popContext (Env : (TEnv IDMeta)) : (TEnv IDMeta) := + let ctx := Env.context let ctx' := { ctx with types := ctx.types.pop } - T.updateContext ctx' + Env.updateContext ctx' -def TGenEnv.insertInContext (T : TGenEnv IDMeta) (x : Identifier IDMeta) (ty : LTy) : TGenEnv IDMeta := - let ctx := T.context +def TGenEnv.insertInContext [DecidableEq (Identifier IDMeta)] (Env : TGenEnv IDMeta) (x : Identifier IDMeta) (ty : LTy) : TGenEnv IDMeta := + let ctx := Env.context let ctx' := { ctx with types := ctx.types.insert x ty } - { T with context := ctx' } + { Env with context := ctx' } /-- Insert `(x, ty)` in `T.context`. -/ -def TEnv.insertInContext (T : (TEnv IDMeta)) (x : Identifier IDMeta) (ty : LTy) : (TEnv IDMeta) := - { T with genEnv := TGenEnv.insertInContext T.genEnv x ty} +def TEnv.insertInContext {T: LExprParams} [DecidableEq T.IDMeta] (Env : TEnv T.IDMeta) (x : T.Identifier) (ty : LTy) : TEnv T.IDMeta := + { Env with genEnv := TGenEnv.insertInContext Env.genEnv x ty} /-- Insert each element in `map` in `T.context`. -/ -def TEnv.addToContext (T : (TEnv IDMeta)) (map : Map (Identifier IDMeta) LTy) : (TEnv IDMeta) := - let ctx := T.context +def TEnv.addToContext (Env : TEnv T.IDMeta) (map : Map T.Identifier LTy) : TEnv T.IDMeta := + let ctx := Env.context let types := List.foldl (fun m (x, v) => m.insert x v) ctx.types map let ctx' := { ctx with types := types } - T.updateContext ctx' + Env.updateContext ctx' /-- Erase entry for `x` from `T.context`. -/ -def TEnv.eraseFromContext (T : (TEnv IDMeta)) (x : Identifier IDMeta) : (TEnv IDMeta) := - let ctx := T.context +def TEnv.eraseFromContext (Env : TEnv T.IDMeta) (x : T.Identifier) : TEnv T.IDMeta := + let ctx := Env.context let ctx' := { ctx with types := ctx.types.erase x } - T.updateContext ctx' + Env.updateContext ctx' -def TEnv.freeVarCheck (T : (TEnv IDMeta)) (e : LExpr LMonoTy IDMeta) (msg : Format) : +def TEnv.freeVarCheck [DecidableEq T.IDMeta] (Env : TEnv T.IDMeta) (e : LExpr T.mono) (msg : Format) : Except Format Unit := - let efv := e.freeVars.map (fun (x, _) => x) - let knownVars := T.context.knownVars + let efv := (@freeVars T LMonoTy e).map Prod.fst + let knownVars := Env.context.knownVars let freeVars := List.filter (fun v => v ∉ knownVars) efv match freeVars with | [] => .ok () @@ -392,22 +440,22 @@ def TEnv.freeVarCheck (T : (TEnv IDMeta)) (e : LExpr LMonoTy IDMeta) (msg : Form {Format.line}\ Free Variables: {freeVars}" -def TEnv.freeVarChecks (T : (TEnv IDMeta)) (es : List (LExpr LMonoTy IDMeta)) : Except Format Unit := +def TEnv.freeVarChecks [DecidableEq T.IDMeta] (Env : TEnv T.IDMeta) (es : List (LExpr T.mono)) : Except Format Unit := match es with | [] => .ok () | e :: erest => do - let _ ← freeVarCheck T e f!"[{e}]" - freeVarChecks T erest + let _ ← freeVarCheck Env e f!"[{e}]" + freeVarChecks Env erest -instance : Inhabited (TyIdentifier × TEnv IDMeta) where +instance : Inhabited (TyIdentifier × TEnv T.IDMeta) where default := ("$__ty0", TEnv.default) -instance [Inhabited IDMeta] : Inhabited (Identifier IDMeta × TEnv IDMeta) where +instance [Inhabited T.IDMeta] : Inhabited (T.Identifier × TEnv T.IDMeta) where default := ⟨⟨"$__ty0", Inhabited.default⟩, TEnv.default ⟩ /-- Variable Generator -/ -class HasGen (IDMeta : Type) where - genVar : TGenEnv IDMeta → Identifier IDMeta × TGenEnv IDMeta +class HasGen (IDMeta: Type) where + genVar : TGenEnv IDMeta → (Identifier IDMeta) × TGenEnv IDMeta /-- Generate a fresh variable (`LExpr.fvar`). This is needed to open the body of an @@ -422,15 +470,15 @@ checking. Also, we rely on the parser disallowing Lambda variables to begin with Together, these restrictions ensure that variables created using `TEnv.genExprVar` are fresh w.r.t. the Lambda expression. -/ -def TEnv.genExprVar (T: TGenEnv Unit) : (Identifier Unit × TGenEnv Unit) := - let (new_var, state) := T.genState.genExprSym - let T :={ T with genState := state } - let known_vars := TContext.knownVars T.context +def TEnv.genExprVar (Env: TGenEnv Unit) : (Identifier Unit × TGenEnv Unit) := + let (new_var, state) := Env.genState.genExprSym + let Env :={ Env with genState := state } + let known_vars := TContext.knownVars Env.context if ⟨new_var, ()⟩ ∈ known_vars then panic s!"[TEnv.genExprVar] Generated variable {new_var} is not fresh!\n\ - Context: {format T.context}" + Context: {format Env.context}" else - (new_var, T) + (new_var, Env) instance : HasGen Unit where genVar := TEnv.genExprVar @@ -443,45 +491,44 @@ along with the restriction that all `ftvar`s in an annotation are implicitly universally quantified -- ensures that we always get a fresh type variable when we use `TEnv.genTyVar`. -/ -def TGenEnv.genTyVar (T : TGenEnv IDMeta) : TyIdentifier × (TGenEnv IDMeta) := - let (new_var, state) := T.genState.genTySym - let T := {T with genState := state} - if new_var ∈ T.context.knownTypeVars then +def TGenEnv.genTyVar [ToFormat IDMeta] (Env : TGenEnv IDMeta) : TyIdentifier × (TGenEnv IDMeta) := + let (new_var, state) := Env.genState.genTySym + let Env := {Env with genState := state} + if new_var ∈ Env.context.knownTypeVars then panic s!"[TEnv.genTyVar] Generated type variable {new_var} is not fresh!\n\ - Context: {format T.context}" + Context: {format Env.context}" else - (new_var, T) + (new_var, Env) -def TEnv.genTyVar (T : TEnv IDMeta) : TyIdentifier × (TEnv IDMeta) := +def TEnv.genTyVar [ToFormat IDMeta] (T : TEnv IDMeta) : TyIdentifier × (TEnv IDMeta) := liftGenEnv TGenEnv.genTyVar T /-- Generate `n` fresh type variables (`ftvar`s). -/ -def TGenEnv.genTyVars (n : Nat) (T : (TGenEnv IDMeta)) : List TyIdentifier × (TGenEnv IDMeta) := +def TGenEnv.genTyVars [ToFormat IDMeta] (n : Nat) (Env : TGenEnv IDMeta) : List TyIdentifier × (TGenEnv IDMeta) := match n with - | 0 => ([], T) + | 0 => ([], Env) | n' + 1 => - let (ty, T) := TGenEnv.genTyVar T - let (rest_ty, T) := TGenEnv.genTyVars n' T - (ty :: rest_ty, T) + let (ty, Env) := TGenEnv.genTyVar Env + let (rest_ty, Env) := TGenEnv.genTyVars n' Env + (ty :: rest_ty, Env) /-- Consistently instantiate type variables `ids` in `mtys`. -/ -def LMonoTys.instantiate (ids : List TyIdentifier) (mtys : LMonoTys) (T : (TGenEnv IDMeta)) : +def LMonoTys.instantiate [ToFormat IDMeta] (ids : List TyIdentifier) (mtys : LMonoTys) (T : TGenEnv IDMeta) : LMonoTys × (TGenEnv IDMeta) := let (freshtvs, T) := TGenEnv.genTyVars ids.length T let S := List.zip ids (List.map (fun tv => (LMonoTy.ftvar tv)) freshtvs) (LMonoTys.subst [S] mtys, T) -def LMonoTys.instantiateEnv (ids : List TyIdentifier) (mtys : LMonoTys) (T : (TEnv IDMeta)) : +def LMonoTys.instantiateEnv [ToFormat IDMeta] (ids : List TyIdentifier) (mtys : LMonoTys) (T : (TEnv IDMeta)) : LMonoTys × (TEnv IDMeta) := liftGenEnv (LMonoTys.instantiate ids mtys) T -omit [DecidableEq IDMeta] in -theorem LMonoTys.instantiate_length : - (LMonoTys.instantiate (IDMeta:=IDMeta) ids mty T).fst.length == mty.length := by +theorem LMonoTys.instantiate_length [ToFormat IDMeta] : + (LMonoTys.instantiate (IDMeta:=IDMeta) ids mty Env).fst.length == mty.length := by simp [instantiate, LMonoTys.subst_eq_substLogic] induction mty <;> simp_all [substLogic] rename_i head tail ih @@ -496,13 +543,13 @@ Note: we do not check whether `ty` is a type alias here. See `LTy.resolveAliases`) as well as verifies whether the type is a previously registered one. -/ -def LTy.instantiate (ty : LTy) (T : (TGenEnv IDMeta)) : LMonoTy × (TGenEnv IDMeta) := +def LTy.instantiate [ToFormat IDMeta] (ty : LTy) (Env : TGenEnv IDMeta) : LMonoTy × (TGenEnv IDMeta) := match ty with - | .forAll [] mty' => (mty', T) + | .forAll [] mty' => (mty', Env) | .forAll xs lty' => - let (freshtvs, T) := TGenEnv.genTyVars xs.length T + let (freshtvs, Env) := TGenEnv.genTyVars xs.length Env let S := List.zip xs (List.map (fun tv => (.ftvar tv)) freshtvs) - (LMonoTy.subst [S] lty', T) + (LMonoTy.subst [S] lty', Env) instance : Inhabited (Option LMonoTy × TEnv IDMeta) where default := (none, TEnv.default) @@ -515,28 +562,29 @@ environment `T`. This function does not descend into the subtrees of `mty`, nor does it check whether the de-aliased types are registered/known. -/ -def LMonoTy.aliasDef? (mty : LMonoTy) (T : (TEnv IDMeta)) : (Option LMonoTy × TEnv IDMeta) := +def LMonoTy.aliasDef? [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta) : + (Option LMonoTy × TEnv IDMeta) := match mty with | .ftvar _ => -- We can't have a free variable be the LHS of an alias definition because -- then it will unify with every type. - (none, T) + (none, Env) | .bitvec _ => -- A bitvector cannot be a type alias. - (none, T) + (none, Env) | .tcons name args => - match T.context.aliases.find? (fun a => a.name == name && a.typeArgs.length == args.length) with - | none => (none, T) + match Env.context.aliases.find? (fun a => a.name == name && a.typeArgs.length == args.length) with + | none => (none, Env) | some alias => - let (lst, T) := LMonoTys.instantiateEnv alias.typeArgs [(.tcons name (alias.typeArgs.map (fun a => .ftvar a))), alias.type] T + let (lst, Env) := LMonoTys.instantiateEnv alias.typeArgs [(.tcons name (alias.typeArgs.map (fun a => .ftvar a))), alias.type] Env -- (FIXME): Use `LMonoTys.instantiate_length` to remove the `!` below. let alias_inst := lst[0]! let alias_def := lst[1]! - match Constraints.unify [(mty, alias_inst)] T.stateSubstInfo with + match Constraints.unify [(mty, alias_inst)] Env.stateSubstInfo with | .error e => - panic! s!"[LMonoTy.aliasDef?] {e}" + panic! s!"[LMonoTy.aliasDef?] {format e}" | .ok S => - (alias_def.subst S.subst, T.updateSubst S) + (alias_def.subst S.subst, Env.updateSubst S) -- Only `FooAlias` is dealiased, not `BarAlias`. Note that the type variables -- are instantiated appropriately and the global substitution is updated. @@ -548,7 +596,7 @@ Subst: -/ #guard_msgs in open LTy.Syntax in -#eval let (ans, T) := LMonoTy.aliasDef? +#eval let (ans, Env) := LMonoTy.aliasDef? mty[FooAlias %p (BarAlias %p %p)] ( (@TEnv.default String).updateContext { aliases := [{ typeArgs := ["x", "y"], @@ -560,7 +608,7 @@ open LTy.Syntax in } ]}) format f!"Ans: {ans}\n\ - Subst:\n{T.stateSubstInfo.subst}" + Subst:\n{Env.stateSubstInfo.subst}" /-- info: some (Foo $__ty0 (BarAlias q $__ty0)) -/ #guard_msgs in @@ -625,39 +673,40 @@ mutual /-- De-alias `mty`, including at the subtrees. -/ -partial def LMonoTy.resolveAliases (mty : LMonoTy) (T : TEnv IDMeta) : (Option LMonoTy × TEnv IDMeta) := - let (maybe_mty, T) := LMonoTy.aliasDef? mty T +partial def LMonoTy.resolveAliases [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta) : + (Option LMonoTy × TEnv IDMeta) := + let (maybe_mty, Env) := LMonoTy.aliasDef? mty Env match maybe_mty with | some (.tcons name args) => - let (args', T) := LMonoTys.resolveAliases args T.context.aliases T + let (args', Env) := LMonoTys.resolveAliases args Env.context.aliases Env match args' with - | none => (some (.tcons name args), T) - | some args' => (some (.tcons name args'), T) - | some mty' => (some mty', T) + | none => (some (.tcons name args), Env) + | some args' => (some (.tcons name args'), Env) + | some mty' => (some mty', Env) | none => match mty with - | .ftvar _ => (some mty, T) - | .bitvec _ => (some mty, T) + | .ftvar _ => (some mty, Env) + | .bitvec _ => (some mty, Env) | .tcons name mtys => - let (maybe_mtys, T) := LMonoTys.resolveAliases mtys T.context.aliases T + let (maybe_mtys, Env) := LMonoTys.resolveAliases mtys Env.context.aliases Env match maybe_mtys with - | none => (none, T) - | some mtys' => (some (.tcons name mtys'), T) + | none => (none, Env) + | some mtys' => (some (.tcons name mtys'), Env) /-- De-alias `mtys`, including at the subtrees. -/ -partial def LMonoTys.resolveAliases (mtys : LMonoTys) (aliases : List TypeAlias) (T : (TEnv IDMeta)) : +partial def LMonoTys.resolveAliases [ToFormat IDMeta] (mtys : LMonoTys) (aliases : List TypeAlias) (Env : (TEnv IDMeta)) : (Option LMonoTys × (TEnv IDMeta)) := match mtys with - | [] => (some [], T) + | [] => (some [], Env) | mty :: mrest => - let (mty', T) := LMonoTy.resolveAliases mty T - let (mrest', T) := LMonoTys.resolveAliases mrest aliases T + let (mty', Env) := LMonoTy.resolveAliases mty Env + let (mrest', Env) := LMonoTys.resolveAliases mrest aliases Env if h : mty'.isSome && mrest'.isSome then - ((mty'.get (by simp_all) :: mrest'.get (by simp_all)), T) + ((mty'.get (by simp_all) :: mrest'.get (by simp_all)), Env) else - (none, T) + (none, Env) end /-- @@ -667,7 +716,7 @@ Subst: -/ #guard_msgs in open LTy.Syntax in -#eval let (ty, T) := LMonoTy.resolveAliases +#eval let (ty, Env) := LMonoTy.resolveAliases mty[FooAlias %p (BarAlias %p %p)] ((@TEnv.default String).updateContext { aliases := [{ typeArgs := ["x", "y"], @@ -679,15 +728,15 @@ open LTy.Syntax in } ]}) format f!"De-aliased type: {ty}\n\ - Subst:\n{T.stateSubstInfo.subst}" + Subst:\n{Env.stateSubstInfo.subst}" /-- Instantiate and de-alias `ty`, including at the subtrees. -/ -def LTy.resolveAliases (ty : LTy) (T : (TEnv IDMeta)) : (Option LMonoTy × (TEnv IDMeta)) := - let (mty, T') := ty.instantiate T.genEnv - let T := {T with genEnv := T'} - LMonoTy.resolveAliases mty T +def LTy.resolveAliases [ToFormat IDMeta] (ty : LTy) (Env : TEnv IDMeta) : Option LMonoTy × TEnv IDMeta := + let (mty, Env') := ty.instantiate Env.genEnv + let Env := {Env with genEnv := Env'} + LMonoTy.resolveAliases mty Env /-- info: some (arrow bool $__ty0) -/ #guard_msgs in @@ -723,23 +772,23 @@ def LMonoTys.knownInstances (tys : LMonoTys) (ks : KnownTypes) : Bool := if LMonoTy.knownInstance ty ks then LMonoTys.knownInstances trest ks else false end -def isInstanceOfKnownType (ty : LMonoTy) (T : LContext IDMeta) : Bool := - LMonoTy.knownInstance ty T.knownTypes +def isInstanceOfKnownType (ty : LMonoTy) (C : LContext IDMeta) : Bool := + LMonoTy.knownInstance ty C.knownTypes /-- Instantiate `mty` by replacing all free type variables with fresh ones, and then perform resolution of type aliases and subsequent checks for registered types. -/ -def LMonoTy.instantiateWithCheck (mty : LMonoTy) (C: LContext IDMeta) (T : (TEnv IDMeta)) : - Except Format (LMonoTy × (TEnv IDMeta)) := do +def LMonoTy.instantiateWithCheck (mty : LMonoTy) (C: LContext T) (Env : TEnv T.IDMeta) : + Except Format (LMonoTy × (TEnv T.IDMeta)) := do let ftvs := mty.freeVars - let (mtys, T) := LMonoTys.instantiateEnv ftvs [mty] T + let (mtys, Env) := LMonoTys.instantiateEnv ftvs [mty] Env let mtyi := mtys[0]! - let (mtyi, T) := match mtyi.resolveAliases T with - | (some ty', T) => (ty', T) - | (none, T) => (mtyi, T) + let (mtyi, Env) := match mtyi.resolveAliases Env with + | (some ty', Env) => (ty', Env) + | (none, Env) => (mtyi, Env) if isInstanceOfKnownType mtyi C - then return (mtyi, T) + then return (mtyi, Env) else .error f!"Type {mty} is not an instance of a previously registered type!\n\ Known Types: {C.knownTypes}" @@ -747,14 +796,15 @@ def LMonoTy.instantiateWithCheck (mty : LMonoTy) (C: LContext IDMeta) (T : (TEnv Instantiate `ty`, with resolution of type aliases to type definitions and checks for registered types. -/ -def LTy.instantiateWithCheck (ty : LTy) (C: LContext IDMeta) (T : (TEnv IDMeta)) : Except Format (LMonoTy × (TEnv IDMeta)) := do - let (mty, T) := match ty.resolveAliases T with - | (some ty', T) => (ty', T) - | (none, T) => - let (ty, T') := ty.instantiate T.genEnv - (ty, {T with genEnv := T'}) +def LTy.instantiateWithCheck [ToFormat T.IDMeta] (ty : LTy) (C: LContext T) (Env : TEnv T.IDMeta) : + Except Format (LMonoTy × TEnv T.IDMeta) := do + let (mty, Env) := match ty.resolveAliases Env with + | (some ty', Env) => (ty', Env) + | (none, Env) => + let (ty, Env') := ty.instantiate Env.genEnv + (ty, {Env with genEnv := Env'}) if isInstanceOfKnownType mty C - then return (mty, T) + then return (mty, Env) else .error f!"Type {ty} is not an instance of a previously registered type!\n\ Known Types: {C.knownTypes}" @@ -765,69 +815,70 @@ open LTy.Syntax /-- info: false -/ #guard_msgs in #eval isInstanceOfKnownType mty[myTy (myTy)] - { @LContext.default String with + { @LContext.default ⟨Unit, String⟩ with knownTypes := makeKnownTypes [LTy.toKnownType! t[∀a. myTy %a], LTy.toKnownType! t[int]] } +abbrev TTyDefault: LExprParams := {Metadata := Unit, IDMeta := TyIdentifier} /-- info: false -/ #guard_msgs in -#eval isInstanceOfKnownType mty[Foo] (@LContext.default TyIdentifier) +#eval isInstanceOfKnownType mty[Foo] (@LContext.default TTyDefault) /-- info: error: Type (arrow int Foo) is not an instance of a previously registered type! Known Types: [∀[0, 1]. (arrow 0 1), string, int, bool] -/ #guard_msgs in -#eval do let ans ← t[int → Foo].instantiateWithCheck (@LContext.default TyIdentifier) (@TEnv.default TyIdentifier) +#eval do let ans ← t[int → Foo].instantiateWithCheck (@LContext.default TTyDefault) (@TEnv.default TyIdentifier) return format ans /-- info: ok: (arrow int bool) -/ #guard_msgs in -#eval do let ans ← t[int → bool].instantiateWithCheck (@LContext.default TyIdentifier) (@TEnv.default TyIdentifier) +#eval do let ans ← t[int → bool].instantiateWithCheck (@LContext.default TTyDefault) (@TEnv.default TyIdentifier) return format ans.fst end /-- -Instantiate the scheme `ty` and apply the global substitution `T.state.subst` to +Instantiate the scheme `ty` and apply the global substitution `Env.state.subst` to it. -/ -def LTy.instantiateAndSubst (ty : LTy) (C: LContext IDMeta) (T : (TEnv IDMeta)) : Except Format (LMonoTy × (TEnv IDMeta)) := do - let (mty, T) ← LTy.instantiateWithCheck ty C T - let mty := LMonoTy.subst T.stateSubstInfo.subst mty - return (mty, T) +def LTy.instantiateAndSubst (ty : LTy) (C: LContext T) (Env : TEnv T.IDMeta) : Except Format (LMonoTy × TEnv T.IDMeta) := do + let (mty, Env) ← LTy.instantiateWithCheck ty C Env + let mty := LMonoTy.subst Env.stateSubstInfo.subst mty + return (mty, Env) -def LTy.instantiateAndSubsts (tys : List LTy) (C: LContext IDMeta) (T : (TEnv IDMeta)) : - Except Format (List LMonoTy × (TEnv IDMeta)) := do +def LTy.instantiateAndSubsts (tys : List LTy) (C: LContext T) (Env : TEnv T.IDMeta) : + Except Format (List LMonoTy × TEnv T.IDMeta) := do match tys with - | [] => return ([], T) + | [] => return ([], Env) | ty :: tyrest => - let (mty, T) ← LTy.instantiateAndSubst ty C T - let (mtyrest, T) ← LTy.instantiateAndSubsts tyrest C T - return ((mty :: mtyrest), T) + let (mty, Env) ← LTy.instantiateAndSubst ty C Env + let (mtyrest, Env) ← LTy.instantiateAndSubsts tyrest C Env + return ((mty :: mtyrest), Env) /-- Get the monotype of variable corresponding to identifier `x` by instantiating the type and then applying the global substitution. -/ -def Identifier.instantiateAndSubst (x : Identifier IDMeta) (C: LContext IDMeta) (T : (TEnv IDMeta)) : - Except Format (Option (LMonoTy × (TEnv IDMeta))) := do - match T.context.types.find? x with - | some ty => LTy.instantiateAndSubst ty C T +def Identifier.instantiateAndSubst (x : T.Identifier) (C: LContext T) (Env : TEnv T.IDMeta) : + Except Format (Option (LMonoTy × TEnv T.IDMeta)) := do + match Env.context.types.find? x with + | some ty => LTy.instantiateAndSubst ty C Env | none => return none -def Identifier.instantiateAndSubsts (xs : List (Identifier IDMeta)) (C: LContext IDMeta) (T : (TEnv IDMeta)) : - Except Format (Option (List LMonoTy × (TEnv IDMeta))) := do +def Identifier.instantiateAndSubsts (xs : List T.Identifier) (C: LContext T) (Env :TEnv T.IDMeta) : + Except Format (Option (List LMonoTy × (TEnv T.IDMeta))) := do match xs with - | [] => return some ([], T) + | [] => return some ([], Env) | x :: xrest => - let ans ← instantiateAndSubst x C T + let ans ← instantiateAndSubst x C Env match ans with | none => return none - | some (xty, T) => - let ans ← Identifier.instantiateAndSubsts xrest C T + | some (xty, Env) => + let ans ← Identifier.instantiateAndSubsts xrest C Env match ans with | none => return none - | some (xtys, T) => return ((xty :: xtys), T) + | some (xtys, Env) => return ((xty :: xtys), Env) /-- info: (arrow $__ty0 b) -/ #guard_msgs in @@ -841,26 +892,26 @@ variables for all the variables bound by the universal quantifier. E.g., the instantiation of `∀a. (x : a) (y : int) (z : a)` must be something like `(x : _ty0) (y : int) (z : _ty0)`, and not `(x : _ty0) (y : int) (z : _ty1)`. -/ -def LMonoTySignature.instantiate (C: LContext IDMeta) (T : (TEnv IDMeta)) (tyArgs : List TyIdentifier) (sig : @LMonoTySignature IDMeta) : - Except Format ((@LMonoTySignature IDMeta) × (TEnv IDMeta)) := do - let (mtys, T) := LMonoTys.instantiateEnv tyArgs sig.values T +def LMonoTySignature.instantiate (C: LContext T) (Env : TEnv T.IDMeta) (tyArgs : List TyIdentifier) (sig : @LMonoTySignature T.IDMeta) : + Except Format ((@LMonoTySignature T.IDMeta) × TEnv T.IDMeta) := do + let (mtys, Env) := LMonoTys.instantiateEnv tyArgs sig.values Env let tys := mtys.map (fun mty => (LTy.forAll [] mty)) - let (newtys, T) ← go T tys - .ok ((sig.keys.zip newtys), T) - where go (T : (TEnv IDMeta)) (tys : LTys) : Except Format (LMonoTys × (TEnv IDMeta)) := + let (newtys, Env) ← go Env tys + .ok ((sig.keys.zip newtys), Env) + where go (Env : TEnv T.IDMeta) (tys : LTys) : Except Format (LMonoTys × TEnv T.IDMeta) := match tys with - | [] => .ok ([], T) + | [] => .ok ([], Env) | t :: trest => do - let (mt, T) ← LTy.instantiateWithCheck t C T - let (mtrest, T) ← go T trest - .ok (mt :: mtrest, T) + let (mt, Env) ← LTy.instantiateWithCheck t C Env + let (mtrest, Env) ← go Env trest + .ok (mt :: mtrest, Env) /-- info: ok: (x : $__ty0) (y : int) (z : $__ty0) -/ #guard_msgs in open LTy.Syntax in -#eval do let ans ← (LMonoTySignature.instantiate (@LContext.default Unit) +#eval do let ans ← (LMonoTySignature.instantiate (@LContext.default {Metadata := Unit, IDMeta := Unit}) ((@TEnv.default Unit).updateContext { aliases := [{ typeArgs := ["a", "b"], name := "myInt", @@ -877,40 +928,40 @@ def LMonoTySignature.toTrivialLTy (s : @LMonoTySignature IDMeta) : @LTySignature s.map (fun (x, ty) => (x, .forAll [] ty)) /-- -Generate fresh type variables only for unnannotated identifiers in `ids`, +Generate fresh type variables only for unannotated identifiers in `ids`, retaining any pre-existing type annotations. -/ -def TEnv.maybeGenMonoTypes (T : (TEnv IDMeta)) (ids : (IdentTs IDMeta)) : List LMonoTy × (TEnv IDMeta) := +def TEnv.maybeGenMonoTypes [ToFormat IDMeta] (Env : TEnv IDMeta) (ids : IdentTs LMonoTy IDMeta) : List LMonoTy × TEnv IDMeta := match ids with - | [] => ([], T) + | [] => ([], Env) | (_x, ty) :: irest => match ty with | none => - let (xty_id, T) := TEnv.genTyVar T + let (xty_id, Env) := TEnv.genTyVar Env let xty := .ftvar xty_id - let (ans, T) := maybeGenMonoTypes T irest - (xty :: ans, T) + let (ans, Env) := maybeGenMonoTypes Env irest + (xty :: ans, Env) | some xty => - let (ans, T) := maybeGenMonoTypes T irest - (xty :: ans, T) + let (ans, Env) := maybeGenMonoTypes Env irest + (xty :: ans, Env) /-- Insert `fvi` (where `fvi` is the `i`-th element of `fvs`) in the oldest context -in `T`, only if `fvi` doesn't already exist in some context in `T`. +in `Env`, only if `fvi` doesn't already exist in some context in `Env`. If `fvi` has no type annotation, a fresh type variable is put in the context. -/ -def TEnv.addInOldestContext (fvs : (IdentTs IDMeta)) (T : (TEnv IDMeta)) : (TEnv IDMeta) := - let (monotys, T) := maybeGenMonoTypes T fvs +def TEnv.addInOldestContext [ToFormat IDMeta] [DecidableEq IDMeta] (fvs : IdentTs LMonoTy IDMeta) (Env : TEnv IDMeta) : TEnv IDMeta := + let (monotys, Env) := maybeGenMonoTypes Env fvs let tys := monotys.map (fun mty => LTy.forAll [] mty) - let types := T.context.types.addInOldest fvs.idents tys - T.updateContext { T.genEnv.context with types := types } + let types := Env.context.types.addInOldest fvs.idents tys + Env.updateContext { Env.genEnv.context with types := types } /-- Add a well-formed `alias` to the context, where the type definition is first de-aliased. -/ -def TEnv.addTypeAlias (alias : TypeAlias) (C: LContext IDMeta) (T : TEnv IDMeta) : Except Format (TEnv IDMeta) := do +def TEnv.addTypeAlias (alias : TypeAlias) (C: LContext T) (Env : TEnv T.IDMeta) : Except Format (TEnv T.IDMeta) := do let alias_lty := alias.toAliasLTy if !alias.typeArgs.Nodup then .error f!"[TEnv.addTypeAlias] Duplicates found in the type arguments!\n\ @@ -929,7 +980,7 @@ def TEnv.addTypeAlias (alias : TypeAlias) (C: LContext IDMeta) (T : TEnv IDMeta) KnownTypes' names:\n\ {C.knownTypes.keywords}" else - let (mtys, T) := LMonoTys.instantiateEnv alias.typeArgs [alias_lty.toMonoTypeUnsafe, alias.type] T + let (mtys, Env) := LMonoTys.instantiateEnv alias.typeArgs [alias_lty.toMonoTypeUnsafe, alias.type] Env match mtys with | [lhs, rhs] => let newTyArgs := lhs.freeVars @@ -937,12 +988,12 @@ def TEnv.addTypeAlias (alias : TypeAlias) (C: LContext IDMeta) (T : TEnv IDMeta) -- `instantiateWithCheck` below. Note that we only store type -- declarations -- not synonyms -- as values in the alias table; -- i.e., we don't store a type alias mapped to another type alias. - let (rhsmty, _) ← (LTy.forAll [] rhs).instantiateWithCheck C T + let (rhsmty, _) ← (LTy.forAll [] rhs).instantiateWithCheck C Env let new_aliases := { typeArgs := newTyArgs, name := alias.name, - type := rhsmty } :: T.context.aliases - let context := { T.context with aliases := new_aliases } - .ok (T.updateContext context) + type := rhsmty } :: Env.context.aliases + let context := { Env.context with aliases := new_aliases } + .ok (Env.updateContext context) | _ => .error f!"[TEnv.addTypeAlias] Implementation error! \n\ {alias}" diff --git a/Strata/DL/Lambda/LExprTypeSpec.lean b/Strata/DL/Lambda/LExprTypeSpec.lean index ae2059766..289a40ac1 100644 --- a/Strata/DL/Lambda/LExprTypeSpec.lean +++ b/Strata/DL/Lambda/LExprTypeSpec.lean @@ -50,98 +50,173 @@ def LTy.open (x : TyIdentifier) (xty : LMonoTy) (ty : LTy) : LTy := ty /-- -Typing relation for `LExpr`s. - -(TODO) Add the introduction and elimination rules for `.tcons`. +Open `ty` by instantiating all its bound variables with `tys`, giving the +`LMonoTy` that results. `tys` should have length equal to the number of bound +variables in `ty`. -/ -inductive HasType {IDMeta : Type} [DecidableEq IDMeta]: - (TContext IDMeta) → (LExpr LMonoTy IDMeta) → LTy → Prop where - | tmdata : ∀ Γ info e ty, HasType Γ e ty → - HasType Γ (.mdata info e) ty - - | tbool_const_t : ∀ Γ, HasType Γ (.const "true" none) - (.forAll [] (.tcons "bool" [])) - | tbool_const_f : ∀ Γ, HasType Γ (.const "false" none) - (.forAll [] (.tcons "bool" [])) - | tint_const : ∀ Γ, n.isInt → HasType Γ (.const n none) - (.forAll [] (.tcons "int" [])) +def LTy.openFull (ty: LTy) (tys: List LMonoTy) : LMonoTy := + LMonoTy.subst [(List.zip (LTy.boundVars ty) tys)] (LTy.toMonoTypeUnsafe ty) - | tvar : ∀ Γ x ty, Γ.types.find? x = some ty → HasType Γ (.fvar x none) ty +/-- +Typing relation for `LExpr`s with respect to `LTy`. - | tabs : ∀ Γ x x_ty e e_ty, +The typing relation is parameterized by two contexts. An `LContext` contains +known types and functions while a `TContext` associates free variables with +their types. +-/ +inductive HasType {T: LExprParams} [DecidableEq T.IDMeta] (C: LContext T): + (TContext T.IDMeta) → LExpr T.mono → LTy → Prop where + + /-- A boolean constant has type `.bool` if `bool` is a known type in this + context. -/ + | tbool_const : ∀ Γ m b, + C.knownTypes.containsName "bool" → + HasType C Γ (.boolConst m b) (.forAll [] .bool) + + /-- An integer constant has type `.int` if `int` is a known type in this + context. -/ + | tint_const : ∀ Γ m n, + C.knownTypes.containsName "int" → + HasType C Γ (.intConst m n) (.forAll [] .int) + + /-- A real constant has type `.real` if `real` is a known type in this + context. -/ + | treal_const : ∀ Γ m r, + C.knownTypes.containsName "real" → + HasType C Γ (.realConst m r) (.forAll [] .real) + + /-- A string constant has type `.string` if `string` is a known type in this + context. -/ + | tstr_const : ∀ Γ m s, + C.knownTypes.containsName "string" → + HasType C Γ (.strConst m s) (.forAll [] .string) + + /-- A bit vector constant of size `n` has type `.bitvec n` if `bitvec` is a + known type in this context. -/ + | tbitvec_const : ∀ Γ m n b, + C.knownTypes.containsName "bitvec" → + HasType C Γ (.bitvecConst m n b) (.forAll [] (.bitvec n)) + + /-- An un-annotated variable has the type recorded for it in `Γ`, if any. -/ + | tvar : ∀ Γ m x ty, Γ.types.find? x = some ty → HasType C Γ (.fvar m x none) ty + + /-- + 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 `Γ`. + -/ + | tvar_annotated : ∀ Γ m x ty_o ty_s tys, + Γ.types.find? x = some ty_o → + tys.length = ty_o.boundVars.length → + LTy.openFull ty_o tys = ty_s → + HasType C Γ (.fvar m x (some ty_s)) (.forAll [] ty_s) + + /-- + 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)`. + -/ + | tabs : ∀ Γ m x x_ty e e_ty o, LExpr.fresh x e → (hx : LTy.isMonoType x_ty) → (he : LTy.isMonoType e_ty) → - HasType { Γ with types := Γ.types.insert x.fst x_ty} (LExpr.varOpen 0 x e) e_ty → - HasType Γ (.abs .none e) + HasType C { Γ with types := Γ.types.insert x.fst x_ty} (LExpr.varOpen 0 x e) e_ty → + o = none ∨ o = some (x_ty.toMonoType hx) → + HasType C Γ (.abs m o e) (.forAll [] (.tcons "arrow" [(LTy.toMonoType x_ty hx), (LTy.toMonoType e_ty he)])) --- | tcons_intro : ∀ Γ C args targs, --- args.length == targs.length → --- ∀ et ∈ (List.zip args targs), HasType Γ et.fst et.snd → --- HasType Γ (.app (.const C) args) (.tcons C targs) - --- | tcons_elim : --- HasType Γ (.app (.const C) args) (.tcons C targs) → --- (h : i < targs.length) → --- HasType Γ (.proj i args) (List.get targs i h) - - | tapp : ∀ Γ e1 e2 t1 t2, + /-- + An application `e₁e₂` has type `t1` if `e₁` has type `t2 → t1` and `e₂` has + type `t2`. + -/ + | tapp : ∀ Γ m e1 e2 t1 t2, (h1 : LTy.isMonoType t1) → (h2 : LTy.isMonoType t2) → - HasType Γ e1 (.forAll [] (.tcons "arrow" [(LTy.toMonoType t2 h2), + HasType C Γ e1 (.forAll [] (.tcons "arrow" [(LTy.toMonoType t2 h2), (LTy.toMonoType t1 h1)])) → - HasType Γ e2 t2 → - HasType Γ (.app e1 e2) t1 + HasType C Γ e2 t2 → + HasType C Γ (.app m e1 e2) t1 - -- `ty` is more general than `e_ty`, so we can instantiate `ty` with `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`). + -/ | tinst : ∀ Γ e ty e_ty x x_ty, - HasType Γ e ty → - e_ty = LTy.open x x_ty ty → - HasType Γ e e_ty - - -- The generalization rule will let us do things like the following: - -- `(·ftvar "a") → (.ftvar "a")` (or `a → a`) will be generalized to - -- `(.btvar 0) → (.btvar 0)` (or `∀a. a → a`), assuming `a` is not in the - -- context. + HasType C Γ e ty → + e_ty = LTy.open x x_ty ty → + HasType C Γ e e_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. + -/ | tgen : ∀ Γ e a ty, - HasType Γ e ty → - TContext.isFresh a Γ → - HasType Γ e (LTy.close a ty) - - | tif : ∀ Γ c e1 e2 ty, - HasType Γ c (.forAll [] (.tcons "bool" [])) → - HasType Γ e1 ty → - HasType Γ e2 ty → - HasType Γ (.ite c e1 e2) ty - - | teq : ∀ Γ e1 e2 ty, - HasType Γ e1 ty → - HasType Γ e2 ty → - HasType Γ (.eq e1 e2) (.forAll [] (.tcons "bool" [])) + HasType C Γ e ty → + TContext.isFresh a Γ → + HasType C Γ e (LTy.close a ty) + + /-- If `e1` and `e2` have the same type `ty`, and `c` has type `.bool`, then + `.ite c e1 e2` has type `ty`. -/ + | tif : ∀ Γ m c e1 e2 ty, + HasType C Γ c (.forAll [] .bool) → + HasType C Γ e1 ty → + HasType C Γ e2 ty → + HasType C Γ (.ite m c e1 e2) ty + + /-- If `e1` and `e2` have the same type `ty`, then `.eq e1 e2` has type + `.bool`. -/ + | teq : ∀ Γ m e1 e2 ty, + HasType C Γ e1 ty → + HasType C Γ e2 ty → + HasType C Γ (.eq m e1 e2) (.forAll [] .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. + -/ + | tquant: ∀ Γ m k tr tr_ty x x_ty e o, + LExpr.fresh x e → + (hx : LTy.isMonoType x_ty) → + HasType C { Γ with types := Γ.types.insert x.fst x_ty} (LExpr.varOpen 0 x e) (.forAll [] .bool) → + HasType C {Γ with types := Γ.types.insert x.fst x_ty} (LExpr.varOpen 0 x tr) tr_ty → + o = none ∨ o = some (x_ty.toMonoType hx) → + HasType C Γ (.quant m k o tr e) (.forAll [] .bool) + + /-- + An un-annotated operator has the type recorded for it in `C.functions`, if any. + -/ + | top: ∀ Γ m f op ty, + C.functions.find? (fun fn => fn.name == op) = some f → + f.type = .ok ty → + HasType C Γ (.op m op none) ty + /-- + 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`. + -/ + | top_annotated: ∀ Γ m f op ty_o ty_s tys, + C.functions.find? (fun fn => fn.name == op) = some f → + f.type = .ok ty_o → + tys.length = ty_o.boundVars.length → + LTy.openFull ty_o tys = ty_s → + HasType C Γ (.op m op (some ty_s)) (.forAll [] ty_s) /-- If `LExpr e` is well-typed, then it is well-formed, i.e., contains no dangling bound variables. -/ -theorem HasType.regularity (h : HasType (IDMeta:=IDMeta) Γ e ty) : +theorem HasType.regularity [DecidableEq T.IDMeta] (h : HasType (T := T) C Γ e ty) : LExpr.WF e := by open LExpr in - induction h - case tbool_const_t => simp [WF, lcAt] - case tbool_const_f => simp [WF, lcAt] - case tint_const => simp [WF, lcAt] - case tvar => simp [WF, lcAt] - case tmdata => simp_all [WF, lcAt] - case tabs T x x_ty e e_ty hx h_x_mono h_e_mono ht ih => + induction h <;> try (solve | simp_all[WF, lcAt]) + case tabs m x x_ty e e_ty hx h_x_mono h_e_mono ht ih => simp_all [WF] exact lcAt_varOpen_abs ih (by simp) - case tapp => simp_all [WF, lcAt] - case tif => simp_all [WF, lcAt] - case teq => simp_all [WF, lcAt] - case tgen => simp_all - case tinst => simp_all + case tquant m k tr tr_ty x x_ty e o h_x_mono hx htr ih ihtr => + simp_all [WF] + exact lcAt_varOpen_quant ih (by omega) ihtr done --------------------------------------------------------------------- @@ -152,47 +227,60 @@ section Tests open LExpr.SyntaxMono LTy.Syntax -example : LExpr.HasType {} esM[#true] t[bool] := by - apply LExpr.HasType.tbool_const_t +macro "solveKnownNames" : tactic => `(tactic | simp[KnownTypes.containsName, LTy.toKnownType!, makeKnownTypes, KnownTypes.default, LContext.default]) + +example : LExpr.HasType LContext.default {} esM[#true] t[bool] := by + apply LExpr.HasType.tbool_const; solveKnownNames -example : LExpr.HasType {} esM[#-1] t[int] := by - apply LExpr.HasType.tint_const - simp +ground +example : LExpr.HasType LContext.default {} esM[#-1] t[int] := by + apply LExpr.HasType.tint_const; solveKnownNames -example : LExpr.HasType { types := [[("x", t[∀a. %a])]]} esM[x] t[int] := by - have h_tinst := @LExpr.HasType.tinst (IDMeta := Unit) _ { types := [[("x", t[∀a. %a])]]} esM[x] t[∀a. %a] t[int] "a" mty[int] - have h_tvar := @LExpr.HasType.tvar (IDMeta := Unit) _ { types := [[("x", t[∀a. %a])]]} "x" t[∀a. %a] - simp +ground at h_tvar - simp [h_tvar] at h_tinst - simp +ground at h_tinst - exact h_tinst rfl +example : LExpr.HasType LContext.default { types := [[(⟨"x", ()⟩, t[∀a. %a])]]} esM[x] t[int] := by + have h_tinst := @LExpr.HasType.tinst (T := ⟨Unit, Unit⟩) _ LContext.default { types := [[("x", t[∀a. %a])]]} esM[x] t[∀a. %a] t[int] "a" mty[int] + have h_tvar := @LExpr.HasType.tvar (T := ⟨Unit, Unit⟩) _ LContext.default { types := [[("x", t[∀a. %a])]]} () "x" t[∀a. %a] + apply h_tinst; apply h_tvar; rfl + simp +ground; rfl -example : LExpr.HasType { types := [[("m", t[∀a. %a → int])]]} +example : LExpr.HasType LContext.default { types := [[(⟨"m", ()⟩, t[∀a. %a → int])]]} esM[(m #true)] t[int] := by - apply LExpr.HasType.tapp _ _ _ _ t[bool] <;> (try simp +ground) - <;> try apply LExpr.HasType.tbool_const_t + apply LExpr.HasType.tapp _ _ _ _ _ t[bool] <;> (try simp +ground) + <;> try apply LExpr.HasType.tbool_const <;> simp[KnownTypes.containsName] apply LExpr.HasType.tinst _ _ t[∀a. %a → int] t[bool → int] "a" mty[bool] · apply LExpr.HasType.tvar simp +ground · simp +ground exact rfl + solveKnownNames done -example : LExpr.HasType {} esM[λ %0] t[∀a. %a → %a] := by - have h_tabs := @LExpr.HasType.tabs (IDMeta := Unit) _ {} ("a", none) t[%a] esM[%0] t[%a] - simp +ground at h_tabs - have h_tvar := @LExpr.HasType.tvar (IDMeta := Unit) _ { types := [[("a", t[%a])]] } - "a" t[%a] +example : LExpr.HasType {} {} esM[λ %0] t[∀a. %a → %a] := by + have h_tabs := @LExpr.HasType.tabs (T := ⟨Unit, Unit⟩) _ {} {} () ("a", none) t[%a] esM[%0] t[%a] none + simp at h_tabs + have h_tvar := @LExpr.HasType.tvar (T := ⟨Unit, Unit⟩) _ {} { types := [[("a", t[%a])]] } + () "a" t[%a] simp [Maps.find?, Map.find?] at h_tvar - simp [h_tvar, LTy.toMonoType] at h_tabs - have h_tgen := @LExpr.HasType.tgen (IDMeta := Unit) _ {} esM[λ %0] "a" + specialize (h_tabs (by unfold fresh; unfold LExpr.freeVars; simp only [List.not_mem_nil, + not_false_eq_true]) rfl rfl h_tvar) + simp [LTy.toMonoType] at h_tabs + have h_tgen := @LExpr.HasType.tgen (T := ⟨Unit, Unit⟩) _ {} {} esM[λ %0] "a" t[%a → %a] h_tabs - simp +ground [Maps.find?] at h_tgen + simp[TContext.isFresh, Maps.find?] at h_tgen assumption done +def idFactory : LFunc ⟨Unit, Unit⟩ := {name := "id", typeArgs := ["a"], inputs := [⟨"x", .ftvar "a"⟩], output := .ftvar "a"} + +example : LExpr.HasType (LContext.default.addFactoryFunction idFactory) {} (.op () ⟨"id", ()⟩ none) t[∀a. %a → %a] := by + apply (LExpr.HasType.top _ _ idFactory) <;> rfl + +example : LExpr.HasType (LContext.default.addFactoryFunction idFactory) {} (.op () ⟨"id", ()⟩ mty[int → int]) t[int → int] := by + apply (LExpr.HasType.top_annotated _ _ idFactory _ t[∀a. %a → %a] _ [.int]) <;> try rfl + simp only[LTy.openFull, LTy.toMonoTypeUnsafe, List.zip, LTy.boundVars]; + unfold LMonoTy.subst ; + simp[Subst.hasEmptyScopes, Map.isEmpty, LMonoTys.subst, LMonoTys.subst.substAux, LMonoTy.subst, Maps.find?, Map.find?, LMonoTy.int] + end Tests --------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/LExprWF.lean b/Strata/DL/Lambda/LExprWF.lean index 1d0e8001c..0fbedf2cc 100644 --- a/Strata/DL/Lambda/LExprWF.lean +++ b/Strata/DL/Lambda/LExprWF.lean @@ -21,67 +21,50 @@ open Std (ToFormat Format format) namespace LExpr -variable {IDMeta : Type} [DecidableEq IDMeta] +variable {T : LExprParams} [DecidableEq T.IDMeta] /-- Compute the free variables in an `LExpr`, which are simply all the `LExpr.fvar`s in it. -/ -def freeVars (e : LExpr LMonoTy IDMeta) : IdentTs IDMeta := +def freeVars (e : LExpr ⟨T, GenericTy⟩) : IdentTs GenericTy T.IDMeta := match e with | .const _ _ => [] - | .op _ _ => [] - | .bvar _ => [] - | .fvar x ty => [(x, ty)] - | .mdata _ e1 => freeVars e1 - | .abs _ e1 => freeVars e1 - | .quant _ _ tr e1 => freeVars tr ++ freeVars e1 - | .app e1 e2 => freeVars e1 ++ freeVars e2 - | .ite c t e => freeVars c ++ freeVars t ++ freeVars e - | .eq e1 e2 => freeVars e1 ++ freeVars e2 + | .op _ _ _ => [] + | .bvar _ _ => [] + | .fvar _ x ty => [(x, ty)] + | .abs _ _ e1 => freeVars e1 + | .quant _ _ _ tr e1 => freeVars tr ++ freeVars e1 + | .app _ e1 e2 => freeVars e1 ++ freeVars e2 + | .ite _ c t e => freeVars c ++ freeVars t ++ freeVars e + | .eq _ e1 e2 => freeVars e1 ++ freeVars e2 /-- -Is `x` is a fresh variable w.r.t. `e`? +Is `x` a fresh variable w.r.t. `e`? -/ -def fresh (x : IdentT IDMeta) (e : LExpr LMonoTy IDMeta) : Bool := +def fresh (x : IdentT GenericTy T.IDMeta) (e : LExpr ⟨T, GenericTy⟩) : Prop := x ∉ (freeVars e) /-- An expression `e` is closed if has no free variables. -/ -def closed (e : LExpr LMonoTy IDMeta) : Bool := +def closed (e : LExpr ⟨T, GenericTy⟩) : Bool := freeVars e |>.isEmpty +omit [DecidableEq T.IDMeta] in @[simp] -theorem fresh_abs : - fresh (IDMeta:=IDMeta) x (.abs ty e) = fresh x e := by +theorem fresh_abs {x : IdentT GenericTy T.IDMeta} {m : T.Metadata} {ty : Option GenericTy} {e : LExpr ⟨T, GenericTy⟩} : + fresh x (.abs m ty e) = fresh x e := by simp [fresh, freeVars] +omit [DecidableEq T.IDMeta] in @[simp] -theorem fresh_mdata : - fresh (IDMeta:=IDMeta) x (.mdata info e) = fresh x e := by - simp [fresh, freeVars] - -omit [DecidableEq IDMeta] in -@[simp] -theorem freeVars_abs : - freeVars (IDMeta:=IDMeta) (.abs ty e) = freeVars e := by - simp [freeVars] - -omit [DecidableEq IDMeta] in -@[simp] -theorem freeVars_mdata : - freeVars (IDMeta:=IDMeta) (.mdata info e) = freeVars e := by +theorem freeVars_abs {m : T.Metadata} {ty : Option GenericTy} {e : LExpr ⟨T, GenericTy⟩} : + freeVars (.abs m ty e) = freeVars e := by simp [freeVars] -omit [DecidableEq IDMeta] in +omit [DecidableEq T.IDMeta] in @[simp] -theorem closed_abs : - closed (IDMeta:=IDMeta) (.abs ty e) = closed e := by - simp [closed] - -omit [DecidableEq IDMeta] in -@[simp] -theorem closed_mdata : - closed (IDMeta:=IDMeta) (.mdata info e) = closed e := by +theorem closed_abs {m : T.Metadata} {ty : Option GenericTy} {e : LExpr ⟨T, GenericTy⟩} : + closed (.abs m ty e) = closed e := by simp [closed] --------------------------------------------------------------------- @@ -95,18 +78,18 @@ This function replaces some bound variables in `e` by an arbitrary expression `substK k s e` keeps track of the number `k` of abstractions that have passed by; it replaces all leaves of the form `(.bvar k)` with `s`. -/ -def substK (k : Nat) (s : LExpr LMonoTy IDMeta) (e : LExpr LMonoTy IDMeta) : LExpr LMonoTy IDMeta := +def substK {T:LExprParamsT} (k : Nat) (s : T.base.Metadata → LExpr T) + (e : LExpr T) : LExpr T := match e with - | .const c ty => .const c ty - | .op o ty => .op o ty - | .bvar i => if (i == k) then s else .bvar i - | .fvar y ty => .fvar y ty - | .mdata info e' => .mdata info (substK k s e') - | .abs ty e' => .abs ty (substK (k + 1) s e') - | .quant qk ty tr' e' => .quant qk ty (substK (k + 1) s tr') (substK (k + 1) s e') - | .app e1 e2 => .app (substK k s e1) (substK k s e2) - | .ite c t e => .ite (substK k s c) (substK k s t) (substK k s e) - | .eq e1 e2 => .eq (substK k s e1) (substK k s e2) + | .const m c => .const m c + | .op m o ty => .op m o ty + | .bvar m i => if i == k then s m else .bvar m i + | .fvar m y ty => .fvar m y ty + | .abs m ty e' => .abs m ty (substK (k + 1) s e') + | .quant m qk ty tr' e' => .quant m qk ty (substK (k + 1) s tr') (substK (k + 1) s e') + | .app m e1 e2 => .app m (substK k s e1) (substK k s e2) + | .ite m c t e => .ite m (substK k s c) (substK k s t) (substK k s e) + | .eq m e1 e2 => .eq m (substK k s e1) (substK k s e2) /-- Substitute the outermost bound variable in `e` by an arbitrary expression `s`. @@ -129,7 +112,7 @@ to avoid such issues: `(λλ 1 0) (λ b) --β--> (λ (λ b) 0)` -/ -def subst (s : LExpr LMonoTy IDMeta) (e : LExpr LMonoTy IDMeta) : LExpr LMonoTy IDMeta := +def subst {T:LExprParamsT} (s : T.base.Metadata → LExpr T) (e : LExpr T) : LExpr T := substK 0 s e /-- @@ -140,8 +123,8 @@ with `(.fvar x)`. Note that `x` is expected to be a fresh variable w.r.t. `e`. -/ -def varOpen (k : Nat) (x : IdentT IDMeta) (e : LExpr LMonoTy IDMeta) : LExpr LMonoTy IDMeta := - substK k (.fvar x.fst x.snd) e +def varOpen (k : Nat) (x : IdentT GenericTy T.IDMeta) (e : LExpr ⟨T, GenericTy⟩) : LExpr ⟨T, GenericTy⟩ := + substK k (fun m => .fvar m x.fst x.snd) e /-- This function turns some free variables into bound variables to build an @@ -149,34 +132,34 @@ abstraction, given its body. `varClose k x e` keeps track of the number `k` of abstractions that have passed by; it replaces all `(.fvar x)` with `(.bvar k)`. -/ -def varClose (k : Nat) (x : IdentT IDMeta) (e : LExpr LMonoTy IDMeta) : LExpr LMonoTy IDMeta := +def varClose {T} {GenericTy} [BEq (Identifier T.IDMeta)] [BEq GenericTy] (k : Nat) (x : IdentT GenericTy T.IDMeta) (e : LExpr ⟨T, GenericTy⟩) : LExpr ⟨T, GenericTy⟩ := match e with - | .const c ty => .const c ty - | .op o ty => .op o ty - | .bvar i => .bvar i - | .fvar y yty => if (x.fst == y) && (yty == x.snd) then - (.bvar k) else (.fvar y yty) - | .mdata info e' => .mdata info (varClose k x e') - | .abs ty e' => .abs ty (varClose (k + 1) x e') - | .quant qk ty tr' e' => .quant qk ty (varClose (k + 1) x tr') (varClose (k + 1) x e') - | .app e1 e2 => .app (varClose k x e1) (varClose k x e2) - | .ite c t e => .ite (varClose k x c) (varClose k x t) (varClose k x e) - | .eq e1 e2 => .eq (varClose k x e1) (varClose k x e2) - - -theorem varClose_of_varOpen (h : fresh x e) : - varClose (IDMeta:=IDMeta) i x (varOpen i x e) = e := by + | .const m c => .const m c + | .op m o ty => .op m o ty + | .bvar m i => .bvar m i + | .fvar m y (yty: Option GenericTy) => if x.fst == y && (yty == x.snd) then + (.bvar m k) else (.fvar m y yty) + | .abs m ty e' => .abs m ty (varClose (k + 1) x e') + | .quant m qk ty tr' e' => .quant m qk ty (varClose (k + 1) x tr') (varClose (k + 1) x e') + | .app m e1 e2 => .app m (varClose k x e1) (varClose k x e2) + | .ite m c t e => .ite m (varClose k x c) (varClose k x t) (varClose k x e) + | .eq m e1 e2 => .eq m (varClose k x e1) (varClose k x e2) + + +theorem varClose_of_varOpen [LawfulBEq T.IDMeta] [BEq T.Metadata] [ReflBEq T.Metadata] [BEq GenericTy] [ReflBEq GenericTy] [LawfulBEq GenericTy] (h : fresh x e) : + varClose (T := T) (GenericTy := GenericTy) i x (varOpen i x e) = e := by induction e generalizing i x all_goals try simp_all [fresh, varOpen, LExpr.substK, varClose, freeVars] - case bvar j => + case bvar _ j => by_cases hi : j = i <;> simp_all [varClose] - case fvar name ty => + case fvar _ name ty => intro h1 have ⟨x1, x2⟩ := x simp at h h1 exact fun a => h h1 (id (Eq.symm a)) done + --------------------------------------------------------------------- /-! ### Well-formedness of `LExpr`s -/ @@ -187,22 +170,21 @@ variables. Example of a term that is not locally closed: `(.abs "x" (.bvar 1))`. -/ -def lcAt (k : Nat) (e : LExpr LMonoTy IDMeta) : Bool := +def lcAt (k : Nat) (e : LExpr ⟨T, GenericTy⟩) : Bool := match e with | .const _ _ => true - | .op _ _ => true - | .bvar i => i < k - | .fvar _ _ => true - | .mdata _ e1 => lcAt k e1 - | .abs _ e1 => lcAt (k + 1) e1 - | .quant _ _ tr e1 => lcAt (k + 1) tr && lcAt (k + 1) e1 - | .app e1 e2 => lcAt k e1 && lcAt k e2 - | .ite c t e' => lcAt k c && lcAt k t && lcAt k e' - | .eq e1 e2 => lcAt k e1 && lcAt k e2 - -theorem varOpen_varClose_when_lcAt + | .op _ _ _ => true + | .bvar _ i => i < k + | .fvar _ _ _ => true + | .abs _ _ e1 => lcAt (k + 1) e1 + | .quant _ _ _ tr e1 => lcAt (k + 1) tr && lcAt (k + 1) e1 + | .app _ e1 e2 => lcAt k e1 && lcAt k e2 + | .ite _ c t e' => lcAt k c && lcAt k t && lcAt k e' + | .eq _ e1 e2 => lcAt k e1 && lcAt k e2 + +theorem varOpen_varClose_when_lcAt [DecidableEq GenericTy] [BEq T.Metadata] [LawfulBEq T.Metadata] (h1 : lcAt k e) (h2 : k <= i) : - (varOpen i x (varClose (IDMeta:=IDMeta) i x e)) = e := by + (varOpen i x (varClose (T := T) (GenericTy := GenericTy) i x e)) = e := by induction e generalizing k i x case const c ty => simp! [lcAt, varOpen, substK] @@ -212,11 +194,8 @@ theorem varOpen_varClose_when_lcAt simp_all! [lcAt, varOpen, substK]; omega case fvar name ty => simp_all [lcAt, varOpen, varClose] - by_cases hx: x.fst = name <;> simp_all [substK] + by_cases hx: x.fst = name <;> simp_all[substK] by_cases ht: ty = x.snd <;> simp_all [substK] - case mdata info e ih => - simp_all [lcAt, varOpen, substK, varClose] - rw [@ih k i x.fst x.snd h1 h2] case abs e e_ih => simp_all [lcAt, varOpen, substK, varClose] simp_all [@e_ih (k + 1) (i + 1) x.fst] @@ -234,47 +213,26 @@ theorem varOpen_varClose_when_lcAt simp_all [@e1_ih k i x.fst, @e2_ih k i x.fst] done +theorem lcAt_substK_inv (he: lcAt k (substK i s e)) (hik: k ≤ i) : lcAt (i + 1) e := by + induction e generalizing i k s <;> simp_all[lcAt, substK] <;> try grind + case bvar id j => + by_cases j = i + case pos hji => omega + case neg hji => rw[if_neg hji] at he; simp[lcAt] at he; omega + +theorem lcAt_varOpen_inv (hs: lcAt k (varOpen i x e)) (hik: k ≤ i) : lcAt (i + 1) e := by + unfold varOpen at hs; exact (lcAt_substK_inv hs hik) + theorem lcAt_varOpen_abs (h1 : lcAt k (varOpen i x y)) (h2 : k <= i) : - lcAt i (abs ty y) := by - induction y generalizing i k - case const => simp_all [lcAt] - case op => simp_all [lcAt] - case bvar j => - simp_all [lcAt, varOpen, substK] - by_cases j = i <;> simp_all [lcAt]; try omega - case fvar => simp_all [lcAt] - case mdata info e ih => - simp_all [lcAt, varOpen, substK] - rw [@ih k i h1 h2] - case abs e e_ih => - simp_all [varOpen] - simp [substK, lcAt] at h1 - have e_ih' := @e_ih (k + 1) (i + 1) h1 (by omega) - simp_all [lcAt] - case quant tr e tr_ih e_ih => - simp_all [varOpen] - simp_all [substK, lcAt] - have e_ih' := @e_ih (k + 1) (i + 1) - have tr_ih' := @tr_ih (k + 1) (i + 1) - constructor - exact tr_ih' h1.left (by omega) - exact e_ih' h1.right (by omega) - case app fn e fn_ih e_ih => - simp_all [varOpen, lcAt, substK] - rw [@fn_ih k i h1.1 h2, @e_ih k i h1.2 h2]; simp - case ite c t e c_ih t_ih e_ih => - simp_all [varOpen, lcAt, substK] - rw [@c_ih k i h1.left.left h2, - @t_ih k i h1.left.right h2, - @e_ih k i h1.right h2]; - simp - case eq e1 e2 e1_ih e2_ih => - simp_all [varOpen, lcAt, substK] - rw [@e1_ih k i h1.left h2, - @e2_ih k i h1.right h2] - simp - done + lcAt i (abs m ty y) := by + simp[lcAt]; apply (@lcAt_varOpen_inv k i)<;> assumption + +theorem lcAt_varOpen_quant + (hy : lcAt k (varOpen i x y)) (hki : k <= i) + (htr: lcAt k (varOpen i x tr)) : + lcAt i (quant m qk ty tr y) := by + simp[lcAt]; constructor<;> apply (@lcAt_varOpen_inv k i) <;> assumption /-- An `LExpr e` is well-formed if it has no dangling bound variables. @@ -282,15 +240,16 @@ An `LExpr e` is well-formed if it has no dangling bound variables. We expect the type system to guarantee the well-formedness of an `LExpr`, i.e., we will prove a _regularity_ lemma; see lemma `HasType.regularity`. -/ -def WF (e : LExpr LMonoTy IDMeta) : Bool := +def WF {T} {GenericTy} (e : LExpr ⟨T, GenericTy⟩) : Bool := lcAt 0 e -theorem varOpen_of_varClose (h : LExpr.WF e) : - varOpen i x (varClose (IDMeta:=IDMeta) i x e) = e := by +theorem varOpen_of_varClose {T} {GenericTy} [BEq T.Metadata] [LawfulBEq T.Metadata] [DecidableEq T.IDMeta] [DecidableEq GenericTy] {i : Nat} {x : IdentT GenericTy T.IDMeta} {e : LExpr ⟨T, GenericTy⟩} (h : LExpr.WF e) : + varOpen i x (varClose i x e) = e := by simp_all [LExpr.WF] - rw [varOpen_varClose_when_lcAt (k:=0) h] - omega - done + rename_i r1 r2 r3 + have c := varOpen_varClose_when_lcAt (GenericTy:=GenericTy) (k:=0) (e:=e) (i:=i) (x:=x) h + simp at c + exact c --------------------------------------------------------------------- @@ -303,20 +262,19 @@ and `varOpen`, this function is agnostic of types. Also see function `subst`, where `subst s e` substitutes the outermost _bound_ variable in `e` with `s`. -/ -def substFvar {IDMeta: Type} [DecidableEq IDMeta] (e : LExpr LMonoTy IDMeta) (fr : Identifier IDMeta) (to : LExpr LMonoTy IDMeta) - : (LExpr LMonoTy IDMeta) := +def substFvar [BEq T.IDMeta] (e : LExpr ⟨T, GenericTy⟩) (fr : T.Identifier) (to : LExpr ⟨T, GenericTy⟩) + : (LExpr ⟨T, GenericTy⟩) := match e with - | .const _ _ => e | .bvar _ => e | .op _ _ => e - | .fvar name _ => if name == fr then to else e - | .mdata info e' => .mdata info (substFvar e' fr to) - | .abs ty e' => .abs ty (substFvar e' fr to) - | .quant qk ty tr' e' => .quant qk ty (substFvar tr' fr to) (substFvar e' fr to) - | .app fn e' => .app (substFvar fn fr to) (substFvar e' fr to) - | .ite c t e' => .ite (substFvar c fr to) (substFvar t fr to) (substFvar e' fr to) - | .eq e1 e2 => .eq (substFvar e1 fr to) (substFvar e2 fr to) - -def substFvars {IDMeta: Type} [DecidableEq IDMeta] (e : LExpr LMonoTy IDMeta) (sm : Map (Identifier IDMeta) (LExpr LMonoTy IDMeta)) - : LExpr LMonoTy IDMeta := + | .const _ _ => e | .bvar _ _ => e | .op _ _ _ => e + | .fvar _ name _ => if name == fr then to else e + | .abs m ty e' => .abs m ty (substFvar e' fr to) + | .quant m qk ty tr' e' => .quant m qk ty (substFvar tr' fr to) (substFvar e' fr to) + | .app m fn e' => .app m (substFvar fn fr to) (substFvar e' fr to) + | .ite m c t e' => .ite m (substFvar c fr to) (substFvar t fr to) (substFvar e' fr to) + | .eq m e1 e2 => .eq m (substFvar e1 fr to) (substFvar e2 fr to) + +def substFvars [BEq T.IDMeta] (e : LExpr ⟨T, GenericTy⟩) (sm : Map T.Identifier (LExpr ⟨T, GenericTy⟩)) + : LExpr ⟨T, GenericTy⟩ := List.foldl (fun e (var, s) => substFvar e var s) e sm --------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/LState.lean b/Strata/DL/Lambda/LState.lean index d5b43c093..bae92916b 100644 --- a/Strata/DL/Lambda/LState.lean +++ b/Strata/DL/Lambda/LState.lean @@ -16,7 +16,7 @@ namespace Lambda open Std (ToFormat Format format) -variable {IDMeta : Type} [DecidableEq IDMeta] +variable {T : LExprParams} [Inhabited T.Metadata] [BEq T.Metadata] [DecidableEq T.IDMeta] [BEq T.IDMeta] [ToFormat T.IDMeta] [ToFormat (LFunc T)] [ToFormat (Scopes T)] [Inhabited (LExpr T.mono)] --------------------------------------------------------------------- /- @@ -28,13 +28,13 @@ We rely on the parser disallowing Lambda variables to begin with `$__`, which is reserved for internal use. Also see `TEnv.genExprVar` used during type inference and `LState.genVar` used during evaluation. -/ -structure EvalConfig (IDMeta : Type) where - factory : @Factory IDMeta +structure EvalConfig (T : LExprParams) where + factory : @Factory T fuel : Nat := 200 varPrefix : String := "$__" gen : Nat := 0 -instance : ToFormat (EvalConfig IDMeta) where +instance : ToFormat (EvalConfig T) where format c := f!"Eval Depth: {(repr c.fuel)}" ++ Format.line ++ f!"Variable Prefix: {c.varPrefix}" ++ Format.line ++ @@ -42,15 +42,15 @@ instance : ToFormat (EvalConfig IDMeta) where f!"Factory Functions:" ++ Format.line ++ Std.Format.joinSep c.factory.toList f!"{Format.line}" -def EvalConfig.init : (EvalConfig IDMeta) := - { factory := @Factory.default IDMeta, +def EvalConfig.init : EvalConfig T := + { factory := @Factory.default T, fuel := 200, gen := 0 } -def EvalConfig.incGen (c : (EvalConfig IDMeta)) : (EvalConfig IDMeta) := +def EvalConfig.incGen (c : EvalConfig T) : EvalConfig T := { c with gen := c.gen + 1 } -def EvalConfig.genSym (x : String) (c : (EvalConfig Unit)) : String × (EvalConfig Unit) := +def EvalConfig.genSym (x : String) (c : EvalConfig T) : String × EvalConfig T := let new_idx := c.gen let c := c.incGen let new_var := c.varPrefix ++ x ++ toString new_idx @@ -59,24 +59,24 @@ def EvalConfig.genSym (x : String) (c : (EvalConfig Unit)) : String × (EvalConf --------------------------------------------------------------------- /-- The Lambda evaluation state. -/ -structure LState (IDMeta : Type) where - config : (EvalConfig IDMeta) - state : (Scopes IDMeta) +structure LState (T : LExprParams) where + config : EvalConfig T + state : Scopes T -- scoped notation (name := lstate) "Σ" => LState -def LState.init : (LState IDMeta) := +def LState.init : LState T := { state := [], config := EvalConfig.init } /-- An empty `LState` -/ -instance : EmptyCollection (LState IDMeta) where +instance : EmptyCollection (LState T) where emptyCollection := LState.init -instance : Inhabited (LState IDMeta) where +instance : Inhabited (LState T) where default := LState.init -instance : ToFormat (LState IDMeta) where +instance : ToFormat (LState T) where format s := let { state, config } := s format f!"State:{Format.line}{state}{Format.line}{Format.line}\ @@ -87,7 +87,7 @@ instance : ToFormat (LState IDMeta) where Add function `func` to the existing factory of functions in `σ`. Redefinitions are not allowed. -/ -def LState.addFactoryFunc (σ : LState IDMeta) (func : (LFunc IDMeta)) : Except Format (LState IDMeta) := do +def LState.addFactoryFunc (σ : LState T) (func : (LFunc T)) : Except Format (LState T) := do let F ← σ.config.factory.addFactoryFunc func .ok { σ with config := { σ.config with factory := F }} @@ -95,17 +95,24 @@ def LState.addFactoryFunc (σ : LState IDMeta) (func : (LFunc IDMeta)) : Except Append `Factory f` to the existing factory of functions in `σ`, checking for redefinitions. -/ -def LState.addFactory (σ : (LState IDMeta)) (F : @Factory IDMeta) : Except Format (LState IDMeta) := do +def LState.addFactory (σ : (LState T)) (F : @Factory T) : Except Format (LState T) := do let oldF := σ.config.factory let newF ← oldF.addFactory F .ok { σ with config := { σ.config with factory := newF } } +/-- +Replace the `factory` field of σ with F. +-/ +def LState.setFactory (σ : (LState IDMeta)) (F : @Factory IDMeta) + : (LState IDMeta) := + { σ with config := { σ.config with factory := F } } + /-- Get all the known variables from the scopes in state `σ`. -/ -def LState.knownVars (σ : (LState IDMeta)) : List (Identifier IDMeta) := +def LState.knownVars (σ : LState T) : List T.Identifier := go σ.state [] - where go (s : Scopes IDMeta) (acc : List (Identifier IDMeta)) := + where go (s : Scopes T) (acc : List T.Identifier) := match s with | [] => acc | m :: rest => go rest (acc ++ m.keys) @@ -114,31 +121,31 @@ def LState.knownVars (σ : (LState IDMeta)) : List (Identifier IDMeta) := Generate a fresh (internal) identifier with the base name `x`; i.e., `σ.config.varPrefix ++ x`. -/ -def LState.genVar (x : String) (σ : (LState Unit)) : (String × (LState Unit)) := +def LState.genVar {IDMeta} [Inhabited IDMeta] [DecidableEq IDMeta] (x : String) (σ : LState ⟨Unit, IDMeta⟩) : String × LState ⟨Unit, IDMeta⟩ := let (new_var, config) := σ.config.genSym x let σ := { σ with config := config } let known_vars := LState.knownVars σ - let new_var := ⟨ new_var, ()⟩ + let new_var := ⟨ new_var, Inhabited.default ⟩ if new_var ∈ known_vars then panic s!"[LState.genVar] Generated variable {new_var} is not fresh!\n\ - Known variables: {σ.knownVars}" + Known variables: {known_vars}" else (new_var.name, σ) /-- Generate fresh identifiers, each with the base name in `xs`. -/ -def LState.genVars (xs : List String) (σ : (LState Unit)) : (List String × (LState Unit)) := +def LState.genVars (xs : List String) (σ : (LState ⟨Unit, Unit⟩)) : (List String × (LState ⟨Unit, Unit⟩)) := let (vars, σ') := go xs σ [] (vars.reverse, σ') - where go (xs : List String) (σ : LState Unit) (acc : List String) := + where go (xs : List String) (σ : LState ⟨Unit, Unit⟩) (acc : List String) := match xs with | [] => (acc, σ) | x :: rest => let (x', σ) := LState.genVar x σ go rest σ (x' :: acc) -instance : ToFormat (Identifier IDMeta × LState IDMeta) where +instance : ToFormat (T.Identifier × LState T) where format im := f!"New Variable: {im.fst}{Format.line}\ Gen in EvalConfig: {im.snd.config.gen}{Format.line}\ @@ -149,7 +156,7 @@ instance : ToFormat (Identifier IDMeta × LState IDMeta) where /-- Substitute `.fvar`s in `e` by looking up their values in `σ`. -/ -def LExpr.substFvarsFromState (σ : (LState IDMeta)) (e : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := +def LExpr.substFvarsFromState (σ : (LState T)) (e : (LExpr T.mono)) : (LExpr T.mono) := let sm := σ.state.toSingleMap.map (fun (x, (_, v)) => (x, v)) Lambda.LExpr.substFvars e sm diff --git a/Strata/DL/Lambda/LTy.lean b/Strata/DL/Lambda/LTy.lean index f033f8c2c..296644d1a 100644 --- a/Strata/DL/Lambda/LTy.lean +++ b/Strata/DL/Lambda/LTy.lean @@ -20,21 +20,21 @@ namespace Lambda open Std (ToFormat Format format) +/-- Type identifiers. For now, these are just strings. -/ abbrev TyIdentifier := String instance : Coe String TyIdentifier where coe := id -/-- -Types in Lambda: these are mono-types. Note that all free type variables -(`.ftvar`) are implicitly universally quantified. --/ +/-- Monomorphic types in Lambda. Note that all free type variables (`.ftvar`) +are implicitly universally quantified. -/ inductive LMonoTy : Type where - /-- Type variable. -/ + /-- A type variable. -/ | ftvar (name : TyIdentifier) - /-- Type constructor. -/ + /-- A type constructor. -/ | tcons (name : String) (args : List LMonoTy) - /-- Special support for bitvector types of every size. -/ + /-- A bit vector type. This is a special case so that it can be parameterized + by a size. -/ | bitvec (size : Nat) deriving Inhabited, Repr @@ -86,6 +86,14 @@ def LMonoTy.mkArrow (mty : LMonoTy) (mtys : LMonoTys) : LMonoTy := let mrest' := LMonoTy.mkArrow m mrest .arrow mty mrest' +/-- +Create an iterated arrow type where `mty` is the return type +-/ +def LMonoTy.mkArrow' (mty : LMonoTy) (mtys : LMonoTys) : LMonoTy := + match mtys with + | [] => mty + | m :: mrest => .arrow m (LMonoTy.mkArrow' mty mrest) + mutual def LMonoTy.destructArrow (mty : LMonoTy) : LMonoTys := match mty with @@ -106,10 +114,16 @@ theorem LMonoTy.destructArrow_non_empty (mty : LMonoTy) : (mty.destructArrow) ≠ [] := by unfold destructArrow; split <;> simp_all +def LMonoTy.getArrowArgs (t: LMonoTy) : List LMonoTy := + match t with + | .arrow t1 t2 => t1 :: t2.getArrowArgs + | _ => [] + /-- -Type schemes (poly-types) in Lambda. +Polymorphic type schemes in Lambda. -/ inductive LTy : Type where + /-- A type containing universally quantified type variables. -/ | forAll (vars : List TyIdentifier) (ty : LMonoTy) deriving Inhabited, Repr diff --git a/Strata/DL/Lambda/LTyUnify.lean b/Strata/DL/Lambda/LTyUnify.lean index d411dd460..332f4b38b 100644 --- a/Strata/DL/Lambda/LTyUnify.lean +++ b/Strata/DL/Lambda/LTyUnify.lean @@ -1010,9 +1010,48 @@ private theorem Constraints.unify_termination_goal_2 omega done +/-- +Kinds of errors that can occur during type unification. Also includes the +failing constraint. +-/ +inductive UnifyError where + | ImpossibleToUnify (c : Constraint) (original : Option Constraint := .none) + | FailedOccursCheck (tyvar : TyIdentifier) (ty : LMonoTy) (c : Constraint) (original : Option Constraint := .none) + deriving Repr, Inhabited, DecidableEq + +def UnifyError.addOriginalConstraint (e : UnifyError) (o : Constraint) : UnifyError := + match e with + | ImpossibleToUnify c _ => ImpossibleToUnify c o + | FailedOccursCheck tyvar ty c _ => FailedOccursCheck tyvar ty c o + +instance : ToFormat UnifyError where + format u := match u with + | .ImpossibleToUnify c opt_original => + let msg_fn := fun (x : Constraint) => f!"Impossible to unify {x.fst} with {x.snd}." + match opt_original with + | none => msg_fn c + | some original => + if c == original then + msg_fn c + else + (msg_fn original) ++ f!"\nFirst mismatch: {c.fst} with {c.snd}." + | .FailedOccursCheck tyvar ty c opt_original => + let msg_fn := f!"Failed occurs check: \ + {tyvar} cannot be unified with {ty} because it would \ + create a circular dependency during unification." + match opt_original with + | none => msg_fn + | some original => + if original == c then msg_fn + else msg_fn ++ f!" Failure occurred when unifying {original.fst} with {original.snd}." + mutual +/-- +Type unification for a single constraint `c` w.r.t. a well-formed type +substitution `S`. See `Constraints.unify` for the top-level function. +-/ def Constraint.unifyOne (c : Constraint) (S : SubstInfo) : - Except Format (ValidSubstRelation [c] S) := + Except UnifyError (ValidSubstRelation [c] S) := let (t1, t2) := c if _h1: t1 == t2 then have h_sub : Subst.freeVars_subset_prop [(t1, t2)] S S := by @@ -1032,7 +1071,7 @@ def Constraint.unifyOne (c : Constraint) (S : SubstInfo) : else if _h4 : id ∈ lty.freeVars then -- Occurs check: `id` should not appear in the free type variables of -- `lty`. - .error f!"Ftvar {id} is in the free variables of {lty}!" + .error (.FailedOccursCheck id lty (t1, t2)) else -- At this point, `id` cannot be a free variable in `lty`. match _h5 : S.subst.find? id with @@ -1067,7 +1106,7 @@ def Constraint.unifyOne (c : Constraint) (S : SubstInfo) : if _h7 : n1 == n2 then .ok { newS := SubstInfo.mk [] (by simp [SubstWF]), goodSubset := by grind } else - .error f!"Cannot unify differently sized bitvector types {t1} and {t2}!" + .error (.ImpossibleToUnify (t1, t2)) | .tcons name1 args1, .tcons name2 args2 => do if _h6 : name1 == name2 && args1.length == args2.length then let new_constraints := List.zip args1 args2 @@ -1077,11 +1116,11 @@ def Constraint.unifyOne (c : Constraint) (S : SubstInfo) : exact Subst.freeVars_subset_prop_of_tcons S name1 name2 args1 args2 rfl relS .ok { newS := relS.newS, goodSubset := by simp [h_sub] } else - .error f!"Cannot unify differently named type constructors {t1} and {t2}!" + .error (.ImpossibleToUnify (t1, t2)) | .bitvec _, .tcons _ _ => - .error f!"Cannot unify bv type {t1} and type constructor {t2}!" + .error (.ImpossibleToUnify (t1, t2)) | .tcons _ _, .bitvec _ => - .error f!"Cannot unify type constructor {t1} and bv type {t2}!" + .error (.ImpossibleToUnify (t1, t2)) termination_by ((((Constraints.freeVars [c]) ++ S.subst.freeVars).dedup.length), Constraints.size [c], 0) @@ -1094,12 +1133,16 @@ def Constraint.unifyOne (c : Constraint) (S : SubstInfo) : -- Subgoal 3 · exact @Constraint.unify_termination_goal_3 S name1 name2 args1 args2 _h6 +/-- +Type unification for constraints `cs` w.r.t. a well-formed type +substitution `S`. See `Constraints.unify` for the top-level function. +-/ def Constraints.unifyCore (cs : Constraints) (S : SubstInfo) : - Except Format (ValidSubstRelation cs S) := do + Except UnifyError (ValidSubstRelation cs S) := do match _h0 : cs with | [] => .ok { newS := S, goodSubset := by simp [Subst.freeVars_subset_prop_of_empty] } | c :: c_rest => - let relS ← Constraint.unifyOne c S + let relS ← Constraint.unifyOne c S |> .mapError (fun e => UnifyError.addOriginalConstraint e c) let new_relS ← Constraints.unifyCore c_rest relS.newS .ok { newS := new_relS.newS, goodSubset := by simp [Subst.freeVars_subset_prop_mk_cons] } termination_by ((((Constraints.freeVars cs) ++ S.subst.freeVars).dedup.length), @@ -1119,17 +1162,48 @@ end bottom-up Hindley-Milner style algorithm that finds the most general type (principal type) of an expression by finding a substitution that makes all the types in the input constraints equal. + +On failure, returns the constraint that cannot be unified -- +note that this can be different from a constraint `c` in `cs` because it could +involve subterms of types in `c` (e.g., `Map int bool` and `Map int int` fail to +unify because `bool` and `int` can't be unified). The constraint returned on +failure would be the _first_ mismatching one, not necessarily the only one. + +Returns a well-formed `S` w.r.t. `cs` otherwise. -/ def Constraints.unify (constraints : Constraints) (S : SubstInfo) : - Except Format SubstInfo := do + Except UnifyError SubstInfo := do let relS ← Constraints.unifyCore constraints S .ok relS.newS -/-- info: ok: [(a, int) (b, (arrow c d))] -/ +/-- info: [(a, int) (b, (arrow c d))] -/ +#guard_msgs in +open LTy.Syntax in +#eval match Constraints.unify [(mty[%a → %b], mty[int → (%c → %d)])] SubstInfo.empty with + | .ok S => format S.subst + | .error e => format e + +/-- +info: Impossible to unify (Map int int) with (Map int bool). +First mismatch: int with bool. +-/ +#guard_msgs in +open LTy.Syntax in +#eval match Constraints.unify [(mty[Map int int], mty[Map int bool])] SubstInfo.empty with + | .ok S => format S.subst + | .error e => format e + +/-- +info: Impossible to unify (Map (Map bool int) int) with (Map int bool). +First mismatch: (Map bool int) with int. +-/ #guard_msgs in open LTy.Syntax in -#eval do let S ← Constraints.unify [(mty[%a → %b], mty[int → (%c → %d)])] SubstInfo.empty - return (format S.subst) +#eval match Constraints.unify [(mty[int], mty[int]), + (mty[Map (Map bool int) int], mty[Map int bool])] + SubstInfo.empty with + | .ok S => format S.subst + | .error e => format e --------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/Lambda.lean b/Strata/DL/Lambda/Lambda.lean index 63c723ada..6485f39bd 100644 --- a/Strata/DL/Lambda/Lambda.lean +++ b/Strata/DL/Lambda/Lambda.lean @@ -7,6 +7,9 @@ import Strata.DL.Lambda.LExprEval import Strata.DL.Lambda.LExprType import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.Semantics +import Strata.DL.Lambda.TypeFactory +import Strata.DL.Lambda.Reflect namespace Lambda @@ -26,21 +29,25 @@ See module `Strata.DL.Lambda.LExpr` for the formalization of expressions, `Strata.DL.Lambda.LExprEval` for the partial evaluator. -/ -variable {IDMeta : Type} [ToString IDMeta] [DecidableEq IDMeta] [HasGen IDMeta] +variable {T: LExprParams} [ToString T.IDMeta] [DecidableEq T.IDMeta] [ToFormat T.IDMeta] [HasGen T.IDMeta] [ToFormat (LFunc T)] [Inhabited (LExpr T.mono)] [BEq T.Metadata] [Traceable LExpr.EvalProvenance T.Metadata] /-- Top-level type checking and partial evaluation function for the Lambda dialect. -/ def typeCheckAndPartialEval - (f : Factory (IDMeta:=IDMeta) := Factory.default) - (e : (LExpr LMonoTy IDMeta)) : - Except Std.Format (LExpr LMonoTy IDMeta) := do - let T := TEnv.default + [Inhabited T.Metadata] + [Inhabited T.IDMeta] + (t: TypeFactory (IDMeta:=T.IDMeta) := TypeFactory.default) + (f : Factory (T:=T) := Factory.default) + (e : LExpr T.mono) : + Except Std.Format (LExpr T.mono) := do + let E := TEnv.default let C := LContext.default.addFactoryFunctions f - let (et, _T) ← LExpr.annotate C T e + let C ← C.addTypeFactory t + let (et, _T) ← LExpr.annotate C E e dbg_trace f!"Annotated expression:{Format.line}{et}{Format.line}" - let σ ← (LState.init).addFactory f + let σ ← (LState.init).addFactory C.functions return (LExpr.eval σ.config.fuel σ et) end Lambda diff --git a/Strata/DL/Lambda/PlausibleHelpers.lean b/Strata/DL/Lambda/PlausibleHelpers.lean new file mode 100644 index 000000000..037c469f9 --- /dev/null +++ b/Strata/DL/Lambda/PlausibleHelpers.lean @@ -0,0 +1,163 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Plausible.Sampleable +import Plausible.DeriveArbitrary +import Plausible.Attr + +/-! ## Helpers for using Plausible with Chamelean generated instances. + +This entire file may be removed, if a dependency is added on https://github.com/codyroux/plausible (or that fork is merged with upstream) + +-/ + +namespace TestGen + +open Plausible + +class ArbitrarySizedSuchThat (α : Type) (P : α → Prop) where + arbitrarySizedST : Nat → Gen α + +/-- The `DecOpt` class encodes partial decidability: + - It takes a `nat` argument as fuel + - It fails, if it can't decide (e.g. because it runs out of fuel) + - It returns `ok true/false` if it can. + - These are intended to be monotonic, in the + sense that if they ever return `ok b` for + some fuel, they will also do so for higher + fuel values. +-/ +class DecOpt (P : Prop) where + decOpt : Nat → Except GenError Bool + +/-- All `Prop`s that have a `Decidable` instance (this includes `DecidableEq`) + can be automatically given a `DecOpt` instance -/ +instance [Decidable P] : DecOpt P where + decOpt := fun _ => .ok (decide P) + +namespace DecOpt + +/-- `checkerBacktrack` takes a list of (thunked) sub-checkers and returns: + - `ok true` if *any* sub-checker does so + - `ok false` if *all* sub-checkers do so + - `error` otherwise + (see section 2 of "Computing Correctly with Inductive Relations") -/ +def checkerBacktrack (checkers : List (Unit → Except GenError Bool)) : Except GenError Bool := + let rec aux (l : List (Unit → Except GenError Bool)) (b : Bool) : Except GenError Bool := + let err := .genError "DecOpt.checkerBacktrack failure." + match l with + | c :: cs => + match c () with + | .ok true => .ok true + | .ok false => aux cs b + | .error _ => aux cs true + | [] => if b then throw err else .ok false + aux checkers false + +/-- Conjunction lifted to work over `Option Bool` + (corresponds to the `.&&` infix operator in section 2 of "Computing Correctly with Inductive Relations") -/ +def andOpt (a : Except GenError Bool) (b : Except GenError Bool) : Except GenError Bool := + match a with + | .ok true => b + | _ => a + +/-- Folds an optional conjunction operation `andOpt` over a list of `Except _ Bool`s, + returning the resultant `Except _ Bool` -/ +def andOptList (bs : List (Except GenError Bool)) : Except GenError Bool := + List.foldl andOpt (.ok true) bs + +end DecOpt + + +namespace GeneratorCombinators + +/-- `pick default xs n` chooses a weight & a generator `(k, gen)` from the list `xs` such that `n < k`. + If `xs` is empty, the `default` generator with weight 0 is returned. -/ +def pick (default : Gen α) (xs : List (Nat × Gen α)) (n : Nat) : Nat × Gen α := + match xs with + | [] => (0, default) + | (k, x) :: xs => + if n < k then + (k, x) + else + pick default xs (n - k) + + +/-- `pickDrop xs n` returns a weight & its generator `(k, gen)` from the list `xs` + such that `n < k`, and also returns the other elements of the list after `(k, gen)` -/ +def pickDrop (xs : List (Nat × Gen α)) (n : Nat) : Nat × Gen α × List (Nat × Gen α) := + let fail : GenError := .genError "Plausible.Chamelean.GeneratorCombinators: failure." + match xs with + | [] => (0, throw fail, []) + | (k, x) :: xs => + if n < k then + (k, x, xs) + else + let (k', x', xs') := pickDrop xs (n - k) + (k', x', (k, x)::xs') + +/-- Sums all the weights in an association list containing `Nat`s and `α`s -/ +def sumFst (gs : List (Nat × α)) : Nat := List.sum <| List.map Prod.fst gs + +/-- Picks one of the generators in `gs` at random, returning the `default` generator + if `gs` is empty. + + (This is a more ergonomic version of Plausible's `Gen.oneOf` which doesn't + require the caller to supply a proof that the list index is in bounds.) -/ +def oneOfWithDefault (default : Gen α) (gs : List (Gen α)) : Gen α := + match gs with + | [] => default + | _ => do + let idx ← Gen.choose Nat 0 (gs.length - 1) (by omega) + List.getD gs idx.val default + +/-- `frequency` picks a generator from the list `gs` according to the weights in `gs`. + If `gs` is empty, the `default` generator is returned. -/ +def frequency (default : Gen α) (gs : List (Nat × Gen α)) : Gen α := do + let total := sumFst gs + let n ← Gen.choose Nat 0 (total - 1) (by omega) + (pick default gs n).snd + +/-- `sized f` constructs a generator that depends on its `size` parameter -/ +def sized (f : Nat → Gen α) : Gen α := + Gen.getSize >>= f + +/-- Helper function for `backtrack` which picks one out of `total` generators with some initial amount of `fuel` -/ +def backtrackFuel (fuel : Nat) (total : Nat) (gs : List (Nat × Gen α)) : Gen α := + match fuel with + | .zero => throw Gen.outOfFuel + | .succ fuel' => do + let n ← Gen.choose Nat 0 (total - 1) (by omega) + let (k, g, gs') := pickDrop gs n + -- Try to generate a value using `g`, if it fails, backtrack with `fuel'` + -- and pick one out of the `total - k` remaining generators + tryCatch g (fun _ => backtrackFuel fuel' (total - k) gs') + +/-- Tries all generators until one returns a `Some` value or all the generators failed once with `None`. + The generators are picked at random according to their weights (like `frequency` in Haskell QuickCheck), + and each generator is run at most once. -/ +def backtrack (gs : List (Nat × Gen α)) : Gen α := + backtrackFuel (gs.length) (sumFst gs) gs + +/-- Delays the evaluation of a generator by taking in a function `f : Unit → Gen α` -/ +def thunkGen (f : Unit → Gen α) : Gen α := + f () + +/-- `elementsWithDefault` constructs a generator from a list `xs` and a `default` element. + If `xs` is non-empty, the generator picks an element from `xs` uniformly; otherwise it returns the `default` element. + + Remarks: + - this is a version of Plausible's `Gen.elements` where the caller doesn't have + to supply a proof that the list index is in bounds + - This is a version of QuickChick's `elems_` combinator -/ +def elementsWithDefault [Inhabited α] (default : α) (xs : List α) : Gen α := + match xs with + | [] => return default + | _ => do + let i ← Subtype.val <$> Gen.choose Nat 0 (xs.length - 1) (by omega) + return xs[i]! + +end GeneratorCombinators diff --git a/Strata/DL/Lambda/Reflect.lean b/Strata/DL/Lambda/Reflect.lean index d0d58224c..c467562f4 100644 --- a/Strata/DL/Lambda/Reflect.lean +++ b/Strata/DL/Lambda/Reflect.lean @@ -61,49 +61,39 @@ def toProp (e : Lean.Expr) : MetaM Lean.Expr := do else throwError f!"Cannot coerce to a Prop: {e}" -def LExpr.const.toExpr (c : String) (mty : Option LMonoTy) : MetaM Lean.Expr := do - match mty with - | none => throwError f!"Cannot reflect an untyped constant: {c}!" - | some mty => match mty with - | LMonoTy.bool => - match c with - | "true" => return (mkConst ``Bool.true) - | "false" => return (mkConst ``Bool.false) - | _ => throwError f!"Unexpected boolean: {c}" - | LMonoTy.int => - if c.isInt then - return (mkIntLit c.toInt!) - else - throwError f!"Unexpected integer: {c}" - | LMonoTy.string => - return (mkStrLit c) - | _ => throwError f!"Unexpected constant: {c}" - -def LExpr.toExprNoFVars (e : LExpr LMonoTy String) : MetaM Lean.Expr := do +def LExpr.const.toExpr (c : LConst) : MetaM Lean.Expr := do + match c with + | .boolConst .true => return (mkConst ``Bool.true) + | .boolConst .false => return (mkConst ``Bool.false) + | .intConst i => return (mkIntLit i) + | .strConst s => return (mkStrLit s) + | _ => throwError f!"Unexpected constant: {c}" + +abbrev MonoString: LExprParamsT := ⟨⟨Unit, String⟩, LMonoTy⟩ + +def LExpr.toExprNoFVars (e : LExpr MonoString) : MetaM Lean.Expr := do match e with - | .const c mty => - let expr ← LExpr.const.toExpr c mty + | .const _ c => + let expr ← LExpr.const.toExpr c return expr - | .op _ _ => + | .op _ _ _ => throwError f!"[LExpr.toExprNoFVars] Operations not yet supported: {e}" - | .bvar i => + | .bvar _ i => let lctx ← getLCtx let some decl := lctx.getAt? (lctx.decls.size - i - 1) | throwError f!"[LExpr {e}]: No local declaration found in the context!" let expr := .fvar decl.fvarId return expr - | .fvar f _ => + | .fvar _ f _ => let lctx ← getLCtx - match lctx.findFromUserName? (Lean.Name.mkSimple f) with + match lctx.findFromUserName? (Lean.Name.mkSimple f.name) with | none => throwError f!"[LExpr.toExprNoFVars] Cannot find free var in the local context: {e}" | some decl => return decl.toExpr - | .mdata _ e' => LExpr.toExprNoFVars e' - - | .abs mty e' => + | .abs _ mty e' => match mty with | none => throwError f!"[LExpr.toExprNoFVars] Cannot reflect untyped abstraction!" | some ty => do @@ -113,7 +103,7 @@ def LExpr.toExprNoFVars (e : LExpr LMonoTy String) : MetaM Lean.Expr := do let bodyExpr ← LExpr.toExprNoFVars e' mkLambdaFVars #[x] bodyExpr - | .quant qk mty tr e => + | .quant _ qk mty _ e => match mty with | none => throwError f!"[LExpr.toExprNoFVars] Cannot reflect untyped quantifier!" | some ty => @@ -130,12 +120,12 @@ def LExpr.toExprNoFVars (e : LExpr LMonoTy String) : MetaM Lean.Expr := do let lambdaExpr ← mkLambdaFVars #[x] bodyExpr mkAppM ``Exists #[lambdaExpr] - | .app fn arg => + | .app _ fn arg => let fnExpr ← LExpr.toExprNoFVars fn let argExpr ← LExpr.toExprNoFVars arg mkAppM' fnExpr #[argExpr] - | .ite c t e => + | .ite _ c t e => -- Lean's ite: -- _root_.ite.{u} {α : Sort u} (c : Prop) [h : Decidable c] (t e : α) : α let cExpr ← LExpr.toExprNoFVars c @@ -146,13 +136,13 @@ def LExpr.toExprNoFVars (e : LExpr LMonoTy String) : MetaM Lean.Expr := do let cProp ← mkAppM ``Eq #[cExpr, mkConst ``Bool.true] mkAppM ``_root_.ite #[cProp, tExpr, eExpr] - | .eq e1 e2 => + | .eq _ e1 e2 => let e1Expr ← LExpr.toExprNoFVars e1 let e2Expr ← LExpr.toExprNoFVars e2 let expr ← mkAppM ``BEq.beq #[e1Expr, e2Expr] return expr -def LExpr.toExpr (e : LExpr LMonoTy String) : MetaM Lean.Expr := do +def LExpr.toExpr (e : LExpr MonoString) : MetaM Lean.Expr := do let idTs := e.freeVars let decls : List (Name × (Array Lean.Expr → MetaM Lean.Expr)) ← idTs.mapM fun idT => do @@ -160,7 +150,7 @@ def LExpr.toExpr (e : LExpr LMonoTy String) : MetaM Lean.Expr := do | none => throwError f!"Untyped fvar encountered: {idT.fst}" | some ty => -- let name ← Lean.Core.mkFreshUserName (Lean.Name.mkSimple idT.fst) - let name := Lean.Name.mkSimple idT.fst + let name := Lean.Name.mkSimple idT.fst.name return (name, fun _ => LMonoTy.toExpr ty) withLocalDeclsD decls.toArray fun fvars => do let e ← LExpr.toExprNoFVars e @@ -175,14 +165,14 @@ open LTy.Syntax LExpr.Syntax def test1 : MetaM Lean.Expr := LExpr.toExpr - (.quant .all (some mty[int]) LExpr.noTrigger (.eq (.fvar "x" mty[int]) (.bvar 0))) + (.quant () .all (some mty[int]) (LExpr.noTrigger ()) (.eq () (.fvar () "x" mty[int]) (.bvar () 0))) /-- info: Lean.Expr.forallE `x (Lean.Expr.const `Int []) (Lean.Expr.forallE - (Lean.Name.mkNum `x._@.Strata.DL.Lambda.Reflect._hyg 1645) + (Lean.Name.mkNum (Lean.Name.mkStr (Lean.Name.mkStr (Lean.Name.mkNum `x.«_@».Strata.DL.Lambda.Reflect 1611904336) "_hygCtx") "_hyg") 8) (Lean.Expr.const `Int []) (Lean.Expr.app (Lean.Expr.app @@ -217,7 +207,7 @@ elab "test1" : term => do def test2 : MetaM Lean.Expr := LExpr.toExpr - (LExpr.app (.abs (some mty[bool]) (.bvar 0)) (.eq (.const "4" mty[int]) (.const "4" mty[int]))) + (LExpr.app () (.abs () (some mty[bool]) (.bvar () 0)) (.eq () (.const () (.intConst 4)) (.const () (.intConst 4)))) elab "test2" : term => do @@ -230,29 +220,22 @@ elab "test2" : term => do elab "elaborate_lexpr" "[" e:term "]" : term => unsafe do let expr ← Term.elabTerm e none - let lexpr ← Lean.Meta.evalExpr (LExpr LMonoTy String) (mkApp (mkConst ``LExpr) (mkConst ``String)) expr + let lexpr ← Lean.Meta.evalExpr (LExpr MonoString) + (mkApp (mkConst ``LExpr) (mkConst ``MonoString)) expr let result ← liftM (LExpr.toExpr lexpr) return result -/-- error: Cannot reflect an untyped constant: 5! -/ -#guard_msgs in -#check elaborate_lexpr [@LExpr.const String "5" Option.none] - -/-- error: Cannot coerce to a Prop: OfNat.ofNat.{0} Int 5 (instOfNat 5) -/ -#guard_msgs in -#check elaborate_lexpr [@LExpr.const String "5" (Option.some (LMonoTy.int))] - /-- info: true -/ #guard_msgs in -#eval elaborate_lexpr [@LExpr.eq String - (@LExpr.const String "5" (Option.some (LMonoTy.int))) - (@LExpr.const String "5" (Option.some (LMonoTy.int)))] +#eval elaborate_lexpr [@LExpr.eq MonoString () + (@LExpr.const MonoString () (.intConst 5)) + (@LExpr.const MonoString () (.intConst 5))] /-- info: ∀ (x : Int), (x == 5) = true : Prop -/ #guard_msgs in -#check elaborate_lexpr [@LExpr.eq String - (@LExpr.fvar String "x" (Option.some (LMonoTy.int))) - (@LExpr.const String "5" (Option.some (LMonoTy.int)))] +#check elaborate_lexpr [@LExpr.eq MonoString () + (@LExpr.fvar MonoString () "x" (Option.some (LMonoTy.int))) + (@LExpr.const MonoString () (.intConst 5))] end Tests diff --git a/Strata/DL/Lambda/Scopes.lean b/Strata/DL/Lambda/Scopes.lean index cc0f90d54..91d94f0d6 100644 --- a/Strata/DL/Lambda/Scopes.lean +++ b/Strata/DL/Lambda/Scopes.lean @@ -25,17 +25,20 @@ scopes, other dialects that include Lambda may need to do so. For the evaluation of Lambda expressions in isolation, the stack can contain a single scope. -/ -variable {IDMeta : Type} [DecidableEq IDMeta] +variable {T : LExprParams} [Inhabited T.Metadata] [BEq T.Metadata] [DecidableEq T.IDMeta] [BEq T.IDMeta] [ToFormat T.IDMeta] [BEq (LExpr T.mono)] [ToFormat (LExpr T.mono)] -abbrev Scope (IDMeta : Type) := (Map (Identifier IDMeta) (Option LMonoTy × (LExpr LMonoTy IDMeta))) +def Scope (T : LExprParams) : Type := Map T.Identifier (Option LMonoTy × (LExpr T.mono)) -instance : BEq (Scope IDMeta) where - beq m1 m2 := m1 == m2 +def Scope.ofMap (m : Map T.Identifier (Option LMonoTy × (LExpr T.mono))) : Scope T := m +def Scope.toMap (s : Scope T) : Map T.Identifier (Option LMonoTy × (LExpr T.mono)) := s -instance : Inhabited (Scope IDMeta) where - default := [] +instance : BEq (Scope T) where + beq m1 m2 := m1.toMap == m2.toMap -private def Scope.format (m : (Scope IDMeta)) : Std.Format := +instance : Inhabited (Scope T) where + default := Scope.ofMap [] + +private def Scope.format (m : Scope T) : Std.Format := match m with | [] => "" | [(k, (ty, v))] => go k ty v @@ -46,20 +49,20 @@ private def Scope.format (m : (Scope IDMeta)) : Std.Format := | some ty => f!"({k} : {ty}) → {v}" | none => f!"{k} → {v}" -instance : ToFormat (Scope IDMeta) where +instance (priority := high) : ToFormat (Scope T) where format := Scope.format /-- Merge two maps `m1` and `m2`, where `m1` is assumed to be the map if `cond` is `true` and `m2` when it is false. -/ -def Scope.merge (cond : (LExpr LMonoTy IDMeta)) (m1 m2 : (Scope IDMeta)) : (Scope IDMeta) := +def Scope.merge (cond : LExpr T.mono) (m1 m2 : Scope T) : Scope T := match m1 with - | [] => m2.map (fun (i, (ty, e)) => (i, (ty, mkIte cond (.fvar i ty) e))) + | [] => m2.map (fun (i, (ty, e)) => (i, (ty, mkIte cond (.fvar (default : T.Metadata) i ty) e))) | (k, (ty1, e1)) :: rest => match m2.find? k with | none => - (k, (ty1, mkIte cond e1 (.fvar k ty1))) :: + (k, (ty1, mkIte cond e1 (.fvar (default : T.Metadata) k ty1))) :: Scope.merge cond rest m2 | some (ty2, e2) => if ty1 ≠ ty2 then @@ -69,58 +72,63 @@ def Scope.merge (cond : (LExpr LMonoTy IDMeta)) (m1 m2 : (Scope IDMeta)) : (Scop else (k, (ty1, mkIte cond e1 e2)) :: Scope.merge cond rest (m2.erase k) - where mkIte (cond tru fals : (LExpr LMonoTy IDMeta)) : (LExpr LMonoTy IDMeta) := + where mkIte (cond tru fals : LExpr T.mono) : LExpr T.mono := if tru == fals then tru - else (LExpr.ite cond tru fals) + else (LExpr.ite (default : T.Metadata) cond tru fals) section Scope.merge.tests open LTy.Syntax LExpr.SyntaxMono +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ + +private instance : Coe String TestParams.Identifier where + coe s := Identifier.mk s () + /-- -info: (x : int) → (#8 : int) -(z : int) → (if (#true : bool) then (#100 : int) else (z : int)) +info: (x : int) → #8 +(z : int) → (if #true then #100 else (z : int)) -/ #guard_msgs in -#eval format $ Scope.merge (IDMeta:=Unit) (.const "true" mty[bool]) - [(("x"), (mty[int], .const "8" mty[int])), - (("z"), (mty[int], .const "100" mty[int]))] - [(("x"), (mty[int], .const "8" mty[int]))] +#eval format $ Scope.merge (T:=TestParams) (.boolConst () true) + [("x", (mty[int], .intConst () 8)), + ("z", (mty[int], .intConst () 100))] + [("x", (mty[int], .intConst () 8))] /-- -info: (x : int) → (if (#true : bool) then (#8 : int) else (x : int)) -(z : int) → (if (#true : bool) then (#100 : int) else (z : int)) -(y : int) → (if (#true : bool) then (y : int) else (#8 : int)) +info: (x : int) → (if #true then #8 else (x : int)) +(z : int) → (if #true then #100 else (z : int)) +(y : int) → (if #true then (y : int) else #8) -/ #guard_msgs in -#eval format $ Scope.merge (IDMeta:=Unit) (.const "true" mty[bool]) - [(("x"), (mty[int], .const "8" mty[int])), - (("z"), (mty[int], .const "100" mty[int]))] - [(("y"), (mty[int], .const "8" mty[int]))] +#eval format $ Scope.merge (T:=TestParams) (.boolConst () true) + [("x", (mty[int], .intConst () 8)), + ("z", (mty[int], .intConst () 100))] + [("y", (mty[int], .intConst () 8))] /-- -info: (y : int) → (if (#true : bool) then (#8 : int) else (y : int)) -(x : int) → (if (#true : bool) then (x : int) else (#8 : int)) -(z : int) → (if (#true : bool) then (z : int) else (#100 : int)) +info: (y : int) → (if #true then #8 else (y : int)) +(x : int) → (if #true then (x : int) else #8) +(z : int) → (if #true then (z : int) else #100) -/ #guard_msgs in -#eval format $ Scope.merge (IDMeta:=Unit) (.const "true" mty[bool]) - [(("y"), (mty[int], .const "8" mty[int]))] - [(("x"), (mty[int], .const "8" mty[int])), - (("z"), (mty[int], .const "100" mty[int]))] +#eval format $ Scope.merge (T:=TestParams) (.boolConst () true) + [("y", (mty[int], .intConst () 8 ))] + [("x", (mty[int], .intConst () 8)), + ("z", (mty[int], .intConst () 100))] /-- -info: (a : int) → (if (#true : bool) then (#8 : int) else (a : int)) -(x : int) → (if (#true : bool) then (#800 : int) else (#8 : int)) -(b : int) → (if (#true : bool) then (#900 : int) else (b : int)) -(z : int) → (if (#true : bool) then (z : int) else (#100 : int)) +info: (a : int) → (if #true then #8 else (a : int)) +(x : int) → (if #true then #800 else #8) +(b : int) → (if #true then #900 else (b : int)) +(z : int) → (if #true then (z : int) else #100) -/ #guard_msgs in -#eval format $ Scope.merge (IDMeta:=Unit) (.const "true" mty[bool]) - [(("a"), (mty[int], (.const "8" mty[int]))), - (("x"), (mty[int], (.const "800" mty[int]))), - (("b"), (mty[int], (.const "900" mty[int])))] - [(("x"), (mty[int], (.const "8" mty[int]))), - (("z"), (mty[int], (.const "100" mty[int])))] +#eval format $ Scope.merge (T:=TestParams) (.boolConst () true) + [("a", (mty[int], (.intConst () 8))), + ("x", (mty[int], (.intConst () 800))), + ("b", (mty[int], (.intConst () 900)))] + [("x", (mty[int], (.intConst () 8))), + ("z", (mty[int], (.intConst () 100)))] end Scope.merge.tests @@ -128,18 +136,18 @@ end Scope.merge.tests A stack of scopes, where each scope maps the free variables to their `LExpr` values. -/ -abbrev Scopes (IDMeta : Type) := Maps (Identifier IDMeta) (Option LMonoTy × LExpr LMonoTy IDMeta) +abbrev Scopes (T : LExprParams) := Maps T.Identifier (Option LMonoTy × LExpr T.mono) /-- Merge two scopes, where `s1` is assumed to be the scope if `cond` is true, and `s2` otherwise. -/ -def Scopes.merge (cond : LExpr LMonoTy IDMeta) (s1 s2 : Scopes IDMeta) : Scopes IDMeta := +def Scopes.merge (cond : LExpr T.mono) (s1 s2 : Scopes T) : Scopes T := match s1, s2 with | [], _ => s2 | _, [] => s1 | x :: xrest, y :: yrest => - Scope.merge (IDMeta := IDMeta) cond x y :: Scopes.merge cond xrest yrest + Scope.merge cond x y :: Scopes.merge cond xrest yrest -------------------------------------------------------------------- diff --git a/Strata/DL/Lambda/Semantics.lean b/Strata/DL/Lambda/Semantics.lean new file mode 100644 index 000000000..bb7261f68 --- /dev/null +++ b/Strata/DL/Lambda/Semantics.lean @@ -0,0 +1,158 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.LExprEval +import Strata.DL.Lambda.LExprWF +import Strata.DL.Lambda.LState +import Strata.DL.Util.Relations + +--------------------------------------------------------------------- + +namespace Lambda + +variable {Tbase : LExprParams} [DecidableEq Tbase.Metadata] + [DecidableEq Tbase.Identifier] [DecidableEq Tbase.IDMeta] + +open Lambda + +/-- +A free variable -> expression mapping. +-/ +abbrev Env (Tbase:LExprParams) := Tbase.Identifier → Option (LExpr Tbase.mono) + +def Scopes.toEnv (s:Scopes Tbase) : Env Tbase := + fun t => (s.find? t).map (·.snd) + +/-- +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. +-/ +inductive Step (F:@Factory Tbase) (rf:Env Tbase) + : LExpr Tbase.mono → LExpr Tbase.mono → Prop where +/-- A free variable. Stuck if `fvar` does not exist in `FreeVarMap`. -/ +| expand_fvar: + ∀ (x:Tbase.Identifier) (e:LExpr Tbase.mono), + rf x = .some e → + Step F rf (.fvar m x ty) e + +/-- Call-by-value semantics: beta reduction. -/ +| beta: + ∀ (e1 v2 eres:LExpr Tbase.mono), + LExpr.isCanonicalValue F v2 → + eres = LExpr.subst (fun _ => v2) e1 → + Step F rf (.app m1 (.abs m2 ty e1) v2) eres + +/-- Call-by-value semantics: argument evaluation. -/ +| reduce_2: + ∀ (v1 e2 e2':LExpr Tbase.mono), + LExpr.isCanonicalValue F v1 → + Step F rf e2 e2' → + Step F rf (.app m v1 e2) (.app m' v1 e2') + +/-- Call-by-value semantics: function evaluation. -/ +| reduce_1: + ∀ (e1 e1' e2:LExpr Tbase.mono), + Step F rf e1 e1' → + Step F rf (.app m e1 e2) (.app m' e1' e2) + +/-- 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_then: + ∀ (ethen eelse:LExpr Tbase.mono), + Step F rf (.ite m (.const mc (.boolConst true)) ethen eelse) ethen + +/-- 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_else: + ∀ (ethen eelse:LExpr Tbase.mono), + Step F rf (.ite m (.const mc (.boolConst false)) ethen eelse) eelse + +/-- Evaluation of `ite` condition. -/ +| ite_reduce_cond: + ∀ (econd econd' ethen eelse:LExpr Tbase.mono), + Step F rf econd econd' → + Step F rf (.ite m econd ethen eelse) (.ite m' econd' ethen eelse) + +/-- Evaluation of equality. Reduce after both operands evaluate to values. -/ +| eq_reduce: + ∀ (e1 e2 eres:LExpr Tbase.mono) + (H1:LExpr.isCanonicalValue F e1) + (H2:LExpr.isCanonicalValue F e2), + eres = .const mc (.boolConst (LExpr.eql F e1 e2 H1 H2)) → + Step F rf (.eq m e1 e2) eres + +/-- Evaluation of the left-hand side of an equality. -/ +| eq_reduce_lhs: + ∀ (e1 e1' e2:LExpr Tbase.mono), + Step F rf e1 e1' → + Step F rf (.eq m e1 e2) (.eq m' e1' e2) + +/-- Evaluation of the right-hand side of an equality. -/ +| eq_reduce_rhs: + ∀ (v1 e2 e2':LExpr Tbase.mono), + LExpr.isCanonicalValue F v1 → + Step F rf e2 e2' → + Step F rf (.eq m v1 e2) (.eq m' v1 e2') + +/-- 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 correctnes property is that doing so will yield the same +result. -/ +| expand_fn: + ∀ (e callee fnbody new_body:LExpr Tbase.mono) args fn, + F.callOfLFunc e = .some (callee,args,fn) → + args.all (LExpr.isCanonicalValue F) → + fn.body = .some fnbody → + new_body = LExpr.substFvars fnbody (fn.inputs.keys.zip args) → + Step F rf e new_body + +/-- 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 correctnes property is that doing so will yield +the same result. -/ +| eval_fn: + ∀ (e callee e':LExpr Tbase.mono) args fn denotefn, + F.callOfLFunc e = .some (callee,args,fn) → + args.all (LExpr.isCanonicalValue F) → + fn.concreteEval = .some denotefn → + .some e' = denotefn m args → + Step F rf e e' + + +omit [DecidableEq Tbase.Metadata] [DecidableEq Tbase.Identifier] in +theorem step_const_stuck: + ∀ (F:@Factory Tbase) r x e, + ¬ Step F r (.const m x) e := by + intros + intro H + contradiction + +/-- +Multi-step execution: reflexive transitive closure of single steps. +-/ +def StepStar (F:@Factory Tbase) (rf:Env Tbase) + : LExpr Tbase.mono → LExpr Tbase.mono → Prop := + ReflTrans (Step F rf) + +end Lambda diff --git a/Strata/DL/Lambda/TestGen.lean b/Strata/DL/Lambda/TestGen.lean new file mode 100644 index 000000000..187aa8f45 --- /dev/null +++ b/Strata/DL/Lambda/TestGen.lean @@ -0,0 +1,1294 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.LExprTypeSpec +import Strata.DL.Lambda.LExprTypeEnv +import Strata.DL.Lambda.LExprWF +import Strata.DL.Lambda.LExprT +import Strata.DL.Lambda.LExprEval +import Strata.DL.Lambda.IntBoolFactory +import Plausible.Sampleable +import Plausible.DeriveArbitrary +import Plausible.Attr +import Strata.DL.Lambda.PlausibleHelpers + +-- -- Add these if depending on Chamelean for instance generation. +-- import Plausible.Chamelean.ArbitrarySizedSuchThat +-- import Plausible.Chamelean.DecOpt +-- import Plausible.Chamelean.DeriveConstrainedProducer +-- import Plausible.Chamelean.DeriveChecker + +/-! ## Generators for Well-Typed Lambda expressions -/ + +-- Most of the instance definitions for `ArbitrarySizedSuchThat α P` could be replaced by a call to +-- `deriving_generator (fun ... => ∃ a : α, P)`, or `deriving_checker ...` if we had a dependency on https://github.com/codyroux/plausible +-- We avoid this for now, and simply inline the instance declaration. +-- We leave the relevant calls as comments, in case they need to be re-generated after a change. + +open Plausible + +deriving instance Arbitrary for Lambda.Identifier +deriving instance Arbitrary for Lambda.Info +deriving instance Arbitrary for Lambda.QuantifierKind + +instance instArbitraryRat : Arbitrary Rat where + arbitrary := do + let den ← Gen.chooseNat + let num : Int ← Arbitrary.arbitrary + return num / den + +deriving instance Arbitrary for Lambda.LConst + +-- This doesn't work because of bundled arguments +-- deriving instance Arbitrary for Lambda.LExpr + +def instArbitraryLExpr.arbitrary {T} + [Arbitrary T.base.Metadata] [Arbitrary T.base.IDMeta] [Arbitrary T.TypeType] + : Nat → Plausible.Gen (@Lambda.LExpr T) := + let rec aux_arb (fuel : Nat) : Plausible.Gen (@Lambda.LExpr T) := + (match fuel with + | Nat.zero => + Plausible.Gen.oneOfWithDefault + (do + let a ← Plausible.Arbitrary.arbitrary + let a_1 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.const a a_1) + [(do + let a ← Plausible.Arbitrary.arbitrary + let a_1 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.const a a_1), + (do + let a_2 ← Plausible.Arbitrary.arbitrary + let a_3 ← Plausible.Arbitrary.arbitrary + let a_4 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.op a_2 a_3 a_4), + (do + let a_5 ← Plausible.Arbitrary.arbitrary + let a_6 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.bvar a_5 a_6), + (do + let a_7 ← Plausible.Arbitrary.arbitrary + let a_8 ← Plausible.Arbitrary.arbitrary + -- let a_9 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.fvar a_7 a_8 none)] -- We don't annotate variables, those types will appear in context. + | fuel' + 1 => + Plausible.Gen.frequency + (do + let a ← Plausible.Arbitrary.arbitrary + let a_1 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.const a a_1) + [(1, + (do + let a ← Plausible.Arbitrary.arbitrary + let a_1 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.const a a_1)), + (1, + (do + let a_2 ← Plausible.Arbitrary.arbitrary + let a_3 ← Plausible.Arbitrary.arbitrary + let a_4 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.op a_2 a_3 a_4)), + (1, + (do + let a_5 ← Plausible.Arbitrary.arbitrary + let a_6 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.bvar a_5 a_6)), + (1, + (do + let a_7 ← Plausible.Arbitrary.arbitrary + let a_8 ← Plausible.Arbitrary.arbitrary + let a_9 ← Plausible.Arbitrary.arbitrary + return Lambda.LExpr.fvar a_7 a_8 a_9)), + (fuel' + 1, + (do + let a_10 ← Plausible.Arbitrary.arbitrary + let a_11 ← Plausible.Arbitrary.arbitrary + let a_12 ← aux_arb fuel' + return Lambda.LExpr.abs a_10 a_11 a_12)), + (fuel' + 1, + (do + let a_13 ← Plausible.Arbitrary.arbitrary + let a_14 ← Plausible.Arbitrary.arbitrary + let a_15 ← Plausible.Arbitrary.arbitrary + let a_16 ← aux_arb fuel' + let a_17 ← aux_arb fuel' + return Lambda.LExpr.quant a_13 a_14 a_15 a_16 a_17)), + (fuel' + 1, + (do + let a_18 ← Plausible.Arbitrary.arbitrary + let a_19 ← aux_arb fuel' + let a_20 ← aux_arb fuel' + return Lambda.LExpr.app a_18 a_19 a_20)), + (fuel' + 1, + (do + let a_21 ← Plausible.Arbitrary.arbitrary + let a_22 ← aux_arb fuel' + let a_23 ← aux_arb fuel' + let a_24 ← aux_arb fuel' + return Lambda.LExpr.ite a_21 a_22 a_23 a_24)), + (fuel' + 1, + (do + let a_25 ← Plausible.Arbitrary.arbitrary + let a_26 ← aux_arb fuel' + let a_27 ← aux_arb fuel' + return Lambda.LExpr.eq a_25 a_26 a_27))]) + fun fuel => aux_arb fuel + +instance {T} [Arbitrary T.base.Metadata] [Arbitrary T.base.IDMeta] [Arbitrary T.TypeType] : Plausible.ArbitraryFueled (@Lambda.LExpr T) := ⟨instArbitraryLExpr.arbitrary⟩ + +-- -- Prints a few examples of random expressions. +-- #eval Gen.printSamples (Arbitrary.arbitrary : Gen <| Lambda.LExpr ⟨⟨String, String⟩, String⟩) + +open Lambda +open LTy + +-- Comment this out when depending on Chamelean +open TestGen + +-- We make a bunch of functions inductive predicates to play nice with Chamelean. +inductive MapFind : Map α β → α → β → Prop where +| hd : MapFind ((x, y) :: m) x y +| tl : p.fst ≠ x → MapFind m x y → MapFind (p :: m) x y + +inductive MapNotFound : Map α β → α → Prop where +| nil : MapNotFound [] x +| cons : z ≠ x → MapNotFound m x → MapNotFound ((z, w) :: m) x + +inductive MapsFind : Maps α β → α → β → Prop where +| hd : MapFind m x y → MapsFind (m :: ms) x y +| tl : MapNotFound m x → MapsFind ms x y → MapsFind (m :: ms) x y + +-- Sadly, we need these versions as well for the time being, because +-- we can only generate one output at a time for a given inductive constraint. +-- Here we want to produce both the key and the value at once. +inductive MapFind₂ {α β : Type} : Map α β → α × β → Prop where +| hd : MapFind₂ ((x, y) :: m) (x, y) +| tl : MapFind₂ m q → MapFind₂ (p :: m) q + +inductive MapsFind₂ : Maps α β → α × β → Prop where +| hd : MapFind₂ m (x, y) → MapsFind₂ (m :: ms) (x, y) +| tl : MapsFind₂ ms (x, y) → MapsFind₂ (m :: ms) (x, y) + +inductive MapReplace : Map α β → α → β → Map α β → Prop where +| nil : MapReplace [] x y [] +| consFound : MapReplace ((x, z)::m) x y ((x, y)::m) +| consNotFound : x ≠ z → MapReplace m x y m' → MapReplace ((z, w) :: m) x y ((z, w) :: m') + +inductive MapsReplace : Maps α β → α → β → Maps α β → Prop where +| nil : MapsReplace [] x y [] +-- We do redundant work here but it's ok +| cons : MapReplace m x y m' → MapsReplace ms x y ms' → MapsReplace (m::ms) x y (m'::ms') + +inductive MapsNotFound : Maps α β → α → Prop where +| nil : MapsNotFound [] x +| cons : MapNotFound m x → MapsNotFound ms x → MapsNotFound (m::ms) x + +-- We tediously do what the functional implementation does but allowing shadowing would probably be ok +inductive MapsInsert : Maps α β → α → β → Maps α β → Prop where +| found : MapsFind ms x z → MapsReplace ms x y ms' → MapsInsert ms x y ms' +| notFound : MapsNotFound (m::ms) x → MapsInsert (m::ms) x y (((x,y)::m)::ms) +| empty : MapsInsert [] x y [[(x, y)]] + +-- -- We hand write this to avoid guessing and checking for strings. +instance instStringSuchThatIsInt : ArbitrarySizedSuchThat String (fun s => s.isInt) where + arbitrarySizedST _ := toString <$> (Arbitrary.arbitrary : Gen Int) + +#guard_msgs(drop info) in +#eval + let P : String → Prop := fun s => s.isInt + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +def ArrayFind (a : Array α) (x : α) := x ∈ a + +instance instArrayFindSuchThat {α} {a} : ArbitrarySizedSuchThat α (fun x => ArrayFind a x) where + arbitrarySizedST _ := do + if h:a.size = 0 then throw <| GenError.genError "Gen: cannot generate elements of empty array" else + let i ← Gen.chooseNatLt 0 a.size (by omega) + return a[i.val] + + +inductive IsUnaryArg : LTy → LTy → LTy → Prop where +| mk (ty₁ ty₂ : LMonoTy) : IsUnaryArg (.forAll [] (.tcons "arrow" [ty₁, ty₂])) (.forAll [] ty₁) (.forAll [] ty₂) + +inductive IsBinaryArg : LTy → (LTy × LTy) → LTy → Prop where +| mk (ty₁ ty₂ ty₃ : LMonoTy) : IsBinaryArg (.forAll [] (.tcons "arrow" [ty₁, .tcons "arrow" [ty₂, ty₃]])) ((.forAll [] ty₁), (.forAll [] ty₂)) (.forAll [] ty₃) + +-- Compare `LExpr.HasType` in `LExprTypeSpec.lean` + +-- Parameters for terms without metadata +abbrev TrivialParams : LExprParams := ⟨Unit, Unit⟩ + +def varClose (k : Nat) (x : IdentT LMonoTy TrivialParams.IDMeta) (e : LExpr TrivialParams.mono) : LExpr TrivialParams.mono := + match e with + | .const m c => .const m c + | .op m o ty => .op m o ty + | .bvar m i => .bvar m i + | .fvar m y yty => if x.fst == y && (yty == x.snd) then + (.bvar m k) else (.fvar m y yty) + | .abs m ty e' => .abs m ty (varClose (k + 1) x e') + | .quant m qk ty tr' e' => .quant m qk ty (varClose (k + 1) x tr') (varClose (k + 1) x e') + | .app m e1 e2 => .app m (varClose k x e1) (varClose k x e2) + | .ite m c t e => .ite m (varClose k x c) (varClose k x t) (varClose k x e) + | .eq m e1 e2 => .eq m (varClose k x e1) (varClose k x e2) + +def LFunc.type! (f : (LFunc T)) : LTy := + let input_tys := f.inputs.values + let output_tys := Lambda.LMonoTy.destructArrow f.output + match input_tys with + | [] => .forAll f.typeArgs f.output + | ity :: irest => + .forAll f.typeArgs (Lambda.LMonoTy.mkArrow ity (irest ++ output_tys)) + +-- We massage the `HasType` definition to be more amenable to generation. The main differences are that +-- polymorphism is not supported, and we tend to move function applications in the "output" position to the conclusion. +-- This avoids an additional costly check in the hypothesis. +inductive HasType {T: LExprParams} [DecidableEq T.IDMeta] (C : LContext T) : (TContext T.IDMeta) → LExpr T.mono → LTy → Prop where + | tbool_const : ∀ Γ m b, + C.knownTypes.containsName "bool" → + HasType C Γ (.boolConst m b) (.forAll [] .bool) + | tint_const : ∀ Γ m n, + C.knownTypes.containsName "int" → + HasType C Γ (.intConst m n) (.forAll [] .int) + | treal_const : ∀ Γ m r, + C.knownTypes.containsName "real" → + HasType C Γ (.realConst m r) (.forAll [] .real) + | tstr_const : ∀ Γ m s, + C.knownTypes.containsName "string" → + HasType C Γ (.strConst m s) (.forAll [] .string) + | tbitvec_const : ∀ Γ m n b, + C.knownTypes.containsName "bitvec" → + HasType C Γ (.bitvecConst m n b) (.forAll [] (.bitvec n)) + + | tvar : ∀ Γ m x ty, MapsFind Γ.types x ty → HasType C Γ (.fvar m x none) ty + + | tabs : ∀ Γ Γ' m x x_ty e e_ty, + MapsInsert Γ.types (id x) (.forAll [] x_ty : LTy) Γ' → + HasType C { Γ with types := Γ'} e (.forAll [] e_ty) → + HasType C Γ (.abs m .none <| LExpr.varClose 0 (x, none) e) -- We close in the conclusion rather than opening in the hyps. + (.forAll [] (.tcons "arrow" [x_ty, e_ty])) + + | tapp : ∀ Γ m e1 e2 t1 t2, + HasType C Γ e1 (.forAll [] (.tcons "arrow" [t2, t1])) → + HasType C Γ e2 (.forAll [] t2) → + HasType C Γ (.app m e1 e2) (.forAll [] t1) + + | tif : ∀ Γ m c e1 e2 ty, + HasType C Γ c (.forAll [] .bool) → + HasType C Γ e1 ty → + HasType C Γ e2 ty → + HasType C Γ (.ite m c e1 e2) ty + + | teq : ∀ Γ m e1 e2 ty, + HasType C Γ e1 ty → + HasType C Γ e2 ty → + HasType C Γ (.eq m e1 e2) (.forAll [] .bool) + + | top: ∀ Γ m f ty, + ty = (LFunc.type! f) → + ArrayFind C.functions f → + HasType C Γ (.op m f.name none) ty + + | top₁: ∀ Γ m f ty₁ ty₂, + ArrayFind C.functions f → + IsUnaryArg (LFunc.type! f) ty₁ ty₂ → + HasType C Γ t₁ ty₁ → + HasType C Γ (.app m (.op m f.name none) t₁) ty₂ + + | top₂: ∀ Γ m f ty₁ ty₂ ty₃, + ArrayFind C.functions f → + IsBinaryArg (LFunc.type! f) (ty₁, ty₂) ty₃ → + HasType C Γ t₁ ty₁ → + HasType C Γ t₂ ty₂ → + HasType C Γ (.app m (.app m (.op m f.name none) t₁) t₂) ty₃ + + -- -- We only generate monomorphic types for now + +-- -- We hand write this instance to control the base type names. +instance : Arbitrary LMonoTy where + arbitrary := + let rec aux (n : Nat) : Gen LMonoTy := + match n with + | 0 => Gen.oneOf #[return .tcons "int" [], return .tcons "bool" []] + | n'+1 => do + let choice ← Gen.chooseNatLt 0 3 (by simp) + if ↑choice = 0 then + Gen.oneOf #[return .tcons "int" [], return .tcons "bool" []] + else if ↑choice = 1 then + let ty1 ← aux n' + let ty2 ← aux n' + return .tcons "arrow" [ty1, ty2] + else + let n ← Gen.oneOf #[return 1, return 8, return 16, return 32, return 64] + return .bitvec n + do + let ⟨size⟩ ← read + aux size + +instance : Arbitrary LTy where + arbitrary := LTy.forAll [] <$> Arbitrary.arbitrary + +-- #eval Gen.printSamples (Arbitrary.arbitrary : Gen LMonoTy) + +-- -- This works +-- derive_generator fun α β m y => ∃ x, @MapFind α β m x y + +instance {α β m_1 y_1_1} [BEq β] : ArbitrarySizedSuchThat α (fun x_1_1 => @MapFind α β m_1 x_1_1 y_1_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α_1 : Type) (β_1 : Type) (m_1 : Map α β) (y_1_1 : β) : + Plausible.Gen α := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.cons (Prod.mk x y) _m => + match DecOpt.decOpt (BEq.beq y y_1_1) initSize with + | Except.ok Bool.true => return x + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.cons (Prod.mk x y) _m => + match DecOpt.decOpt (BEq.beq y y_1_1) initSize with + | Except.ok Bool.true => return x + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match m_1 with + | List.cons _p m => do + let (x_1_1 : α) ← aux_arb initSize size α_1 β_1 m y_1_1 + return x_1_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β m_1 y_1_1 + +/-- info: 2 -/ +#guard_msgs(info) in +#eval + let P : Nat → Prop := fun n : Nat => MapFind [((2 : Nat), "foo")] n "foo" + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +-- -- This works +-- derive_generator fun α β tys y => ∃ x, @MapsFind α β tys x y + +instance [DecidableEq β] : ArbitrarySizedSuchThat α (fun x_1 => @MapsFind α β tys_1 x_1 y_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq β] (tys_1 : Maps α β) (y_1 : β) : + Plausible.Gen α := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match tys_1 with + | List.cons m _ms => do + let (x_1 : α) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun (x_1 : α) => @MapFind α β m x_1 y_1) initSize; + return x_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match tys_1 with + | List.cons m _ms => do + let (x_1 : α) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun (x_1 : α) => MapFind m x_1 y_1) initSize; + return x_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match tys_1 with + | List.cons _m ms => do + let (x_1 : α) ← aux_arb initSize size α β ms y_1 -- Chamelean doesn't do the right thing here: it should call itself recursively! + return x_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β tys_1 y_1 + +/-- info: 2 -/ +#guard_msgs(info) in +#eval + let P : Nat → Prop := fun n : Nat => MapsFind [[((2 : Nat), "foo")]] n "foo" + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +-- -- This works +-- derive_generator fun α β m x_1 => ∃ y_1, @MapFind α β m x_1 y_1 +instance [DecidableEq α] : ArbitrarySizedSuchThat β (fun y_1_1 => @MapFind α β m_1 x_1_1 y_1_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] (m_1 : Map α β) (x_1_1 : α) : + Plausible.Gen β := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.cons (Prod.mk x y) _m => + match DecOpt.decOpt (BEq.beq x x_1_1) initSize with + | Except.ok Bool.true => return y + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.cons (Prod.mk x y) _m => + match DecOpt.decOpt (BEq.beq x x_1_1) initSize with + | Except.ok Bool.true => return y + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match m_1 with + | List.cons _p m => do + let (y_1_1 : β) ← aux_arb initSize size' α β m x_1_1 + return y_1_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β m_1 x_1_1 + +/-- info: "foo" -/ +#guard_msgs(info) in +#eval + let P : String → Prop := fun s : String => MapFind [((2 : Nat), "foo")] 2 s + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +/-- Creates a fresh identifier from a list -/ +def getFreshIdent (pre : String) (l : List TyIdentifier) : TyIdentifier := +if pre ∉ l then pre else +getFreshIdentSuffix l.length l +where + getFreshIdentSuffix n l := + match n with + | 0 => pre ++ "0" + | n'+1 => + let ty := pre ++ (toString (l.length - n)) + if ty ∉ l then ty + else getFreshIdentSuffix n' l + +-- -- We hand write this as well. We might be able to derive a reasonable version if we had an inductive relation, by guessing and checking. +instance instArbitrarySizedSuchThatFresh {T : LExprParams} [DecidableEq T.IDMeta] {ctx : TContext T.IDMeta} + : ArbitrarySizedSuchThat TyIdentifier + (fun a => TContext.isFresh a ctx) where + arbitrarySizedST _ := do + let allTypes := ctx.types.flatten.map Prod.snd + let allTyVars := allTypes.map LTy.freeVars |>.flatten + let pre ← Arbitrary.arbitrary + return getFreshIdent pre allTyVars + +#guard_msgs(drop info) in +#eval + let ty := .forAll [] (LMonoTy.bool) + let ctx : TContext TrivialParams.IDMeta := ⟨[[(⟨"foo", ()⟩, ty)]], []⟩ + let P : TyIdentifier → Prop := fun s : String => TContext.isFresh s ctx + Gen.runUntil .none (@ArbitrarySizedSuchThat.arbitrarySizedST _ P (@instArbitrarySizedSuchThatFresh _ _ ctx) 10) 10 + +-- -- This works +-- derive_checker fun α β m x => @MapNotFound α β m x +instance [DecidableEq α_1] : DecOpt (@MapNotFound α_1 β_1 m_1 x_1) where + decOpt := + let rec aux_dec (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] (m_1 : Map α β) (x_1 : α) : + Except Plausible.GenError Bool := + (match size with + | Nat.zero => + DecOpt.checkerBacktrack + [fun (_ : Unit) => + match m_1 with + | List.nil => Except.ok Bool.true + | _ => Except.ok Bool.false] + | Nat.succ size' => + DecOpt.checkerBacktrack + [fun (_ : Unit) => + match m_1 with + | List.nil => Except.ok Bool.true + | _ => Except.ok Bool.false, + fun (_ : Unit) => + match m_1 with + | List.cons (Prod.mk z _w) m => + DecOpt.andOptList [aux_dec initSize size' α β m x_1, DecOpt.decOpt (Ne x_1 z) initSize] + | _ => Except.ok Bool.false]) + fun size => aux_dec size size α_1 β_1 m_1 x_1 + +/-- info: false -/ +#guard_msgs(info) in +#eval DecOpt.decOpt (MapNotFound [("foo", 4)] "foo") 5 +/-- info: true -/ +#guard_msgs(info) in +#eval DecOpt.decOpt (MapNotFound [("foo", 4)] "bar") 5 + +-- -- This works +-- derive_generator fun α β m x_1_1 ty_1_1 => ∃ m', @MapReplace α β m x_1_1 ty_1_1 m' +instance [DecidableEq α] : ArbitrarySizedSuchThat (Map α β) (fun m'_1 => @MapReplace α β m_1 x_1_1_1 ty_1_1_1 m'_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] (m_1 : Map α β) (x_1_1_1 : α) + (ty_1_1_1 : β) : Plausible.Gen (Map α β) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.nil => return List.nil + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match m_1 with + | List.cons (Prod.mk x _z) m => + match DecOpt.decOpt (BEq.beq x x_1_1_1) initSize with + | Except.ok Bool.true => return List.cons (Prod.mk x_1_1_1 ty_1_1_1) m + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match m_1 with + | List.nil => return List.nil + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match m_1 with + | List.cons (Prod.mk x _z) m => + match DecOpt.decOpt (BEq.beq x x_1_1_1) initSize with + | Except.ok Bool.true => return List.cons (Prod.mk x_1_1_1 ty_1_1_1) m + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match m_1 with + | List.cons (Prod.mk z w) m => + match DecOpt.decOpt (Ne x_1_1_1 z) initSize with + | Except.ok Bool.true => do + let (m' : Map α β) ← aux_arb initSize size' α β m x_1_1_1 ty_1_1_1 + return List.cons (Prod.mk z w) m' + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β m_1 x_1_1_1 ty_1_1_1 + +/-- info: [(2, "new")] -/ +#guard_msgs(info) in +#eval + let P : Map Nat String → Prop := fun m' => MapReplace [((2 : Nat), "old")] 2 "new" m' + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +-- -- This works +-- derive_checker fun α β m x => @MapsNotFound α β m x + +instance [DecidableEq α_1] : DecOpt (@MapsNotFound α_1 β_1 m_1 x_1) where + decOpt := + let rec aux_dec (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] (m_1 : Maps α β) (x_1 : α) : + Except Plausible.GenError Bool := + (match size with + | Nat.zero => + DecOpt.checkerBacktrack + [fun (_ : Unit) => + match m_1 with + | List.nil => Except.ok Bool.true + | _ => Except.ok Bool.false] + | Nat.succ size' => + DecOpt.checkerBacktrack + [fun (_ : Unit) => + match m_1 with + | List.nil => Except.ok Bool.true + | _ => Except.ok Bool.false, + fun (_ : Unit) => + match m_1 with + | List.cons m ms => + DecOpt.andOptList [aux_dec initSize size' α β ms x_1, DecOpt.decOpt (MapNotFound m x_1) initSize] + | _ => Except.ok Bool.false]) + fun size => aux_dec size size α_1 β_1 m_1 x_1 + +/-- info: false -/ +#guard_msgs(info) in +#eval DecOpt.decOpt (MapsNotFound [[("foo", 4)]] "foo") 5 +/-- info: true -/ +#guard_msgs(info) in +#eval DecOpt.decOpt (MapsNotFound [[("foo", 4)]] "bar") 5 + +-- -- This works +-- derive_generator fun α β tys_1 x_1 ty_1 => ∃ (Γ_1 : Maps α β), @MapsReplace α β tys_1 x_1 ty_1 Γ_1 +instance [DecidableEq α] : ArbitrarySizedSuchThat (Maps α β) (fun Γ_1_1 => @MapsReplace α β tys_1_1 x_1_1 ty_1_1 Γ_1_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] (tys_1_1 : Maps α β) (x_1_1 : α) + (ty_1_1 : β) : Plausible.Gen (Maps α β) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match tys_1_1 with + | List.nil => return List.nil + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match tys_1_1 with + | List.nil => return List.nil + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match tys_1_1 with + | List.cons m ms => do + let (m' : Map α β) ← + ArbitrarySizedSuchThat.arbitrarySizedST (fun (m' : Map α β) => @MapReplace α β m x_1_1 ty_1_1 m') initSize; + do + let (ms' : Maps α β) ← + aux_arb initSize size α β ms x_1_1 ty_1_1 + return List.cons m' ms' + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β tys_1_1 x_1_1 ty_1_1 + +/-- info: [[(2, "new")]] -/ +#guard_msgs(info) in +#eval + let P : Maps Nat String → Prop := fun m' => MapsReplace [[((2 : Nat), "old")]] 2 "new" m' + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +-- -- This works +-- derive_generator (fun α β tys_1 x_1 => ∃ (z : β), @MapsFind α β tys_1 x_1 z) +instance [DecidableEq α][DecidableEq β] : ArbitrarySizedSuchThat β (fun z_1 => @MapsFind α β tys_1_1 x_1_1 z_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α : Type) (β : Type) [DecidableEq α] [DecidableEq β] (tys_1_1 : Maps α β) (x_1_1 : α) : + Plausible.Gen β := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match tys_1_1 with + | List.cons m _ms => do + let (z_1 : β) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun (z_1 : β) => MapFind m x_1_1 z_1) initSize; + return z_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match tys_1_1 with + | List.cons m _ms => do + let (z_1 : β) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun (z_1 : β) => MapFind m x_1_1 z_1) initSize; + return z_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match tys_1_1 with + | List.cons _m ms => do + let (z_1 : β) ← aux_arb initSize size' α β ms x_1_1 + return z_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α β tys_1_1 x_1_1 + +/-- info: "old" -/ +#guard_msgs(info) in +#eval + let P : _ → Prop := fun z => MapsFind [[((2 : Nat), "old")]] 2 z + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +-- -- This works +-- derive_generator (fun α β tys x ty => ∃ Γ, @MapsInsert α β tys x ty Γ) + +instance [DecidableEq α] [DecidableEq β] : ArbitrarySizedSuchThat (Maps α β) (fun Γ_1 => @MapsInsert α β tys_1 x_1 ty_1 Γ_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (tys_1 : Maps α β) (x_1 : α) + (ty_1 : β) : Plausible.Gen (Maps α β) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, do + let (_ : β) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun z_1 => @MapsFind α β tys_1 x_1 z_1) initSize; + let (Γ_1 : Maps α β) ← + ArbitrarySizedSuchThat.arbitrarySizedST (fun (Γ_1 : Maps α β) => MapsReplace tys_1 x_1 ty_1 Γ_1) + initSize; + return Γ_1), + (1, + match tys_1 with + | List.cons m ms => + match DecOpt.decOpt (MapsNotFound (List.cons m ms) x_1) initSize with + | Except.ok Bool.true => return List.cons (List.cons (Prod.mk x_1 ty_1) m) ms + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ _size' => + GeneratorCombinators.backtrack + [(1, do + let (_ : β) ← ArbitrarySizedSuchThat.arbitrarySizedST (fun z_1 => @MapsFind α β tys_1 x_1 z_1) initSize; + let (Γ_1 : Maps α β) ← + ArbitrarySizedSuchThat.arbitrarySizedST (fun (Γ_1 : Maps α β) => MapsReplace tys_1 x_1 ty_1 Γ_1) + initSize; + return Γ_1), + (1, + match tys_1 with + | List.cons m ms => + match DecOpt.decOpt (MapsNotFound (List.cons m ms) x_1) initSize with + | Except.ok Bool.true => return List.cons (List.cons (Prod.mk x_1 ty_1) m) ms + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + ]) + fun size => aux_arb size size tys_1 x_1 ty_1 + + +-- -- This works! +-- derive_generator fun (α β : Type) Γ => ∃ (p : α × β), @MapFind₂ α β Γ p + +instance [Plausible.Arbitrary α_1] [DecidableEq α_1] [Plausible.Arbitrary β_1] [DecidableEq β_1] : + ArbitrarySizedSuchThat (α_1 × β_1) (fun p_1 => @MapFind₂ α_1 β_1 Γ_1 p_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α_1 : Sort _) (β_1 : Sort _) (Γ_1 : Map α_1 β_1) + [Plausible.Arbitrary α_1] [DecidableEq α_1] [Plausible.Arbitrary β_1] [DecidableEq β_1] : + Plausible.Gen (α_1 × β_1) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match Γ_1 with + | List.cons (Prod.mk x y) _m => return Prod.mk x y + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ size' => + GeneratorCombinators.backtrack + [(1, + match Γ_1 with + | List.cons (Prod.mk x y) _m => return Prod.mk x y + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', + match Γ_1 with + | List.cons _p m => do + let (p_1 : Prod α_1 β_1) ← aux_arb initSize size' α_1 β_1 m; + return p_1 + | _ => MonadExcept.throw Plausible.Gen.genericFailure)]) + fun size => aux_arb size size α_1 β_1 Γ_1 + + +-- -- This does not work for silly reasons, a minor bug in matching on types with a single constructor. +-- derive_generator fun (α β : Type) Γ => ∃ (p : α × β), @MapsFind₂ α β Γ p + + +instance [Plausible.Arbitrary α_1] [DecidableEq α_1] [Plausible.Arbitrary β_1] [DecidableEq β_1] : + ArbitrarySizedSuchThat (α_1 × β_1) (fun p_1 => @MapsFind₂ α_1 β_1 Γ_1 p_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (α_1 : Sort _) (β_1 : Sort _) (Γ_1 : Maps α_1 β_1) + [Plausible.Arbitrary α_1] [DecidableEq α_1] [Plausible.Arbitrary β_1] [DecidableEq β_1] : + Plausible.Gen (α_1 × β_1) := + match size with + | 0 => + match Γ_1 with + | m :: _ => ArbitrarySizedSuchThat.arbitrarySizedST (fun p => MapFind₂ m p) initSize + | _ => throw Plausible.Gen.genericFailure + | size' + 1 => -- Slight hand optimization here, where we can match on Γ_1 directly + match Γ_1 with + | m :: ms => GeneratorCombinators.backtrack + [ + (1, ArbitrarySizedSuchThat.arbitrarySizedST (fun p => MapFind₂ m p) initSize), + (1, aux_arb initSize size' α_1 β_1 ms) + ] + | _ => throw Plausible.Gen.genericFailure + fun size => aux_arb size size α_1 β_1 Γ_1 + +/-- info: [[(2, "new")]] -/ +#guard_msgs(info) in +#eval + let P : Maps Nat String → Prop := fun m' => MapsInsert [[((2 : Nat), "old")]] 2 "new" m' + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +/-- info: [[], [(2, "new")]] -/ +#guard_msgs(info) in +#eval + let P : Maps Nat String → Prop := fun m' => MapsInsert [[], [((2 : Nat), "old")]] 2 "new" m' + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +/-- info: [[(2, "new")], [(3, "old")]] -/ +#guard_msgs(info) in +#eval + let P : Maps Nat String → Prop := fun m' => MapsInsert [[], [((3 : Nat), "old")]] 2 "new" m' + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +/-- info: (3, "old") -/ +#guard_msgs(info) in +#eval + let P : _ → Prop := fun m => MapsFind₂ [[], [((3 : Nat), "old")]] m + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + +/-- error: Generation failure:Gen.runUtil: Out of attempts +-/ +#guard_msgs(error) in +#eval + let P : String × Nat → Prop := fun m => MapsFind₂ [[], []] m + Gen.runUntil (.some 10) (ArbitrarySizedSuchThat.arbitrarySizedST P 10) 10 + + +-- -- This works +-- derive_generator fun ty ty₂ => ∃ ty₁, IsUnaryArg ty ty₁ ty₂ + +instance : ArbitrarySizedSuchThat LTy (fun ty₁_1 => @IsUnaryArg ty_1 ty₁_1 ty₂_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (ty_1 : LTy) (ty₂_1 : LTy) : Plausible.Gen LTy := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match ty₂_1 with + | Lambda.LTy.forAll (List.nil) ty₂ => + match ty_1 with + | + Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons unk_0 (List.cons ty₁ (List.cons ty₂_1_1 (List.nil)))) => + match @DecOpt.decOpt (@Eq (@Lambda.LMonoTy) ty₂_1_1 ty₂) _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_0 "arrow") _ initSize with + | Except.ok Bool.true => return Lambda.LTy.forAll (List.nil) ty₁ + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ _size' => + GeneratorCombinators.backtrack + [(1, + match ty₂_1 with + | Lambda.LTy.forAll (List.nil) ty₂ => + match ty_1 with + | + Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons unk_0 (List.cons ty₁ (List.cons ty₂_1_1 (List.nil)))) => + match @DecOpt.decOpt (@Eq (@Lambda.LMonoTy) ty₂_1_1 ty₂) _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_0 "arrow") _ initSize with + | Except.ok Bool.true => return Lambda.LTy.forAll (List.nil) ty₁ + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + ]) + fun size => aux_arb size size ty_1 ty₂_1 + + +-- -- This works +-- derive_generator fun ty ty₂ => ∃ ty₁, IsUnaryArg ty ty₁ ty₂ + +instance : ArbitrarySizedSuchThat (LTy × LTy) (fun ty_pair_1 => @IsBinaryArg ty_1 ty_pair_1 ty₃_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (ty_1 : LTy) (ty₃_1 : LTy) : Plausible.Gen (LTy × LTy) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match ty₃_1 with + | Lambda.LTy.forAll (List.nil) ty₃ => + match ty_1 with + | + Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons unk_0 + (List.cons ty₁ + (List.cons (Lambda.LMonoTy.tcons unk_1 (List.cons ty₂ (List.cons ty₃_1_1 (List.nil)))) + (List.nil)))) => + match @DecOpt.decOpt (@Eq (@Lambda.LMonoTy) ty₃_1_1 ty₃) _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_1 "arrow") _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_0 "arrow") _ initSize with + | Except.ok Bool.true => + return Prod.mk (Lambda.LTy.forAll (List.nil) ty₁) (Lambda.LTy.forAll (List.nil) ty₂) + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure)] + | Nat.succ _size' => + GeneratorCombinators.backtrack + [(1, + match ty₃_1 with + | Lambda.LTy.forAll (List.nil) ty₃ => + match ty_1 with + | + Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons unk_0 + (List.cons ty₁ + (List.cons (Lambda.LMonoTy.tcons unk_1 (List.cons ty₂ (List.cons ty₃_1_1 (List.nil)))) + (List.nil)))) => + match @DecOpt.decOpt (@Eq (@Lambda.LMonoTy) ty₃_1_1 ty₃) _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_1 "arrow") _ initSize with + | Except.ok Bool.true => + match @DecOpt.decOpt (@Eq (@String) unk_0 "arrow") _ initSize with + | Except.ok Bool.true => + return Prod.mk (Lambda.LTy.forAll (List.nil) ty₁) (Lambda.LTy.forAll (List.nil) ty₂) + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + ]) + fun size => aux_arb size size ty_1 ty₃_1 + + +-- We don't quite handle this case yet, if `α` is a type variable. +-- Monomorphising `α` and removing the `DecidableEq` constraint gives us an almost perfect generator! + +-- derive_generator (fun α eqdec fact ctx ty => ∃ t, @HasType α eqdec fact ctx t ty) + +-- For now though, we hand write a specialized version, without certain constants and without polymorphism. +instance {T : LExprParams} + {C : LContext T} + {ctx_1 : TContext T.IDMeta} + [Arbitrary T.mono.base.Metadata] + [Arbitrary T.IDMeta] + [DecidableEq T.IDMeta] : ArbitrarySizedSuchThat (LExpr T.mono) (fun t_1 => HasType C ctx_1 t_1 ty_1) where + arbitrarySizedST := + let rec aux_arb (initSize : Nat) (size : Nat) (ctx_1 : TContext T.IDMeta) (ty_1 : LTy) : + Plausible.Gen (LExpr T.mono) := + (match size with + | Nat.zero => + GeneratorCombinators.backtrack + [(1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .bool => do + if C.knownTypes.containsName "bool" then + let m ← Arbitrary.arbitrary + return .boolConst m true + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .bool => do + if C.knownTypes.containsName "bool" then + let m ← Arbitrary.arbitrary + return .boolConst m false + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .int => do + if C.knownTypes.containsName "int" then + let m ← Arbitrary.arbitrary + let n ← Arbitrary.arbitrary + return .intConst m n + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) (.bitvec n) => do + if C.knownTypes.containsName "bitvec" then + let m ← Arbitrary.arbitrary + let bv ← Arbitrary.arbitrary + return .bitvecConst m n bv + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .real => do + if C.knownTypes.containsName "real" then + let m ← Arbitrary.arbitrary + let r ← Arbitrary.arbitrary + return .realConst m r + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .string => do + if C.knownTypes.containsName "string" then + let m ← Arbitrary.arbitrary + let s ← Arbitrary.arbitrary + return .strConst m s + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, do + let (x : Identifier _ × LTy) ← + ArbitrarySizedSuchThat.arbitrarySizedST + (fun x => MapsFind₂ (Lambda.TContext.types ctx_1) x) initSize; + if x.snd = ty_1 then do + let m ← Arbitrary.arbitrary + return Lambda.LExpr.fvar m x.fst none + else + throw Gen.genericFailure + ) + ] + | Nat.succ size' => + GeneratorCombinators.backtrack + [ + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .bool => do + if C.knownTypes.containsName "bool" then + let m ← Arbitrary.arbitrary + return .boolConst m true + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .bool => do + if C.knownTypes.containsName "bool" then + let m ← Arbitrary.arbitrary + return .boolConst m false + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .int => do + if C.knownTypes.containsName "int" then + let m ← Arbitrary.arbitrary + let n ← Arbitrary.arbitrary + return .intConst m n + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) (.bitvec n) => do + if C.knownTypes.containsName "bitvec" then + let m ← Arbitrary.arbitrary + let bv ← Arbitrary.arbitrary + return .bitvecConst m n bv + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .real => do + if C.knownTypes.containsName "real" then + let m ← Arbitrary.arbitrary + let r ← Arbitrary.arbitrary + return .realConst m r + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, + match ty_1 with + | Lambda.LTy.forAll (List.nil) .string => do + if C.knownTypes.containsName "string" then + let m ← Arbitrary.arbitrary + let s ← Arbitrary.arbitrary + return .strConst m s + else MonadExcept.throw Plausible.Gen.genericFailure + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + + (size', do + let m ← Arbitrary.arbitrary + let (x : Identifier _ × LTy) ← + ArbitrarySizedSuchThat.arbitrarySizedST + (fun x_x_ty => MapsFind₂ (Lambda.TContext.types ctx_1) x_x_ty) initSize; + if x.snd = ty_1 then + return Lambda.LExpr.fvar m x.fst none + else + throw Gen.genericFailure), + (0, -- FIXME: for now we avoid generating lambdas for the boogie translator. + match ty_1 with + | + Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons "arrow" + (List.cons (x_ty) + (List.cons (e_ty) (List.nil)))) => do + let m ← Arbitrary.arbitrary + let x : Identifier _ ← Arbitrary.arbitrary + let x_ty' := LTy.forAll [] x_ty + let e_ty' := LTy.forAll [] e_ty + let Γ' : Maps (Identifier _) LTy ← + ArbitrarySizedSuchThat.arbitrarySizedST + (fun (Γ' : Maps (Identifier T.IDMeta) LTy) => + MapsInsert (Lambda.TContext.types ctx_1) x x_ty' Γ') initSize; + let e ← aux_arb initSize size' {ctx_1 with types := Γ'} e_ty' + let e := LExpr.varClose 0 (x, none) e + return .abs m x_ty e + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', do + let (t2 : LMonoTy) ← Plausible.Arbitrary.arbitrary; + do + let (e2 : LExpr _) ← aux_arb initSize size' ctx_1 (.forAll [] t2); + do + if h1 : isMonoType ty_1 then + let (e1 : LExpr _) ← + aux_arb initSize size' ctx_1 + (Lambda.LTy.forAll (List.nil) + (Lambda.LMonoTy.tcons "arrow" + (List.cons (t2) + (List.cons (Lambda.LTy.toMonoType ty_1 h1) (List.nil))))); + + let m ← Arbitrary.arbitrary + return Lambda.LExpr.app m e1 e2 + else MonadExcept.throw Plausible.Gen.genericFailure), + (Nat.succ size', do + let (c : LExpr _) ← + aux_arb initSize size' ctx_1 (Lambda.LTy.forAll (List.nil) (Lambda.LMonoTy.tcons "bool" (List.nil))); + do + let (e1 : LExpr _) ← aux_arb initSize size' ctx_1 ty_1; + do + let (e2 : LExpr _) ← aux_arb initSize size' ctx_1 ty_1; + let m ← Arbitrary.arbitrary + return Lambda.LExpr.ite m c e1 e2), + (Nat.succ size', + match ty_1 with + | Lambda.LTy.forAll (List.nil) (Lambda.LMonoTy.tcons "bool" (List.nil)) => do + let (ty : LTy) ← Plausible.Arbitrary.arbitrary; + do + let (e1 : LExpr _) ← aux_arb initSize size' ctx_1 ty; + do + let (e2 : LExpr _) ← aux_arb initSize size' ctx_1 ty; + let m ← Arbitrary.arbitrary + return Lambda.LExpr.eq m e1 e2 + | _ => MonadExcept.throw Plausible.Gen.genericFailure), + (1, do + let (f : LFunc _) ← + @ArbitrarySizedSuchThat.arbitrarySizedST _ + (fun (f : LFunc _) => + @ArrayFind (@Lambda.LFunc _) (@Lambda.LContext.functions _ C) f) + _ initSize; + do + match f.type with + | .ok f_ty => + if f_ty = ty_1 then do + let m ← Arbitrary.arbitrary + return Lambda.LExpr.op m f.name (Option.none) + else throw Plausible.Gen.genericFailure + | _ => throw Plausible.Gen.genericFailure + ), + (10, do + let (f : LFunc T) ← + @ArbitrarySizedSuchThat.arbitrarySizedST _ + (fun (f : LFunc T) => + @ArrayFind (@Lambda.LFunc T) (@Lambda.LContext.functions T C) + f) + _ initSize; + let (ty₁ : LTy) ← @ArbitrarySizedSuchThat.arbitrarySizedST _ (fun (ty₁ : LTy) => @IsUnaryArg (@LFunc.type! T f) ty₁ ty_1) _ initSize; + let (t₁ : LExpr (LExprParams.mono T)) ← aux_arb initSize size' ctx_1 ty₁; + let (m : _) ← Plausible.Arbitrary.arbitrary; + return Lambda.LExpr.app m (Lambda.LExpr.op m f.name (Option.none)) t₁), + (10, do + let (f : LFunc T) ← + @ArbitrarySizedSuchThat.arbitrarySizedST _ + (fun (f : LFunc T) => + @ArrayFind (@Lambda.LFunc T) + (@Lambda.LContext.functions T C) f) + _ initSize; + do + let vty₁_ty₂ ← + @ArbitrarySizedSuchThat.arbitrarySizedST _ + (fun vty₁_ty₂ => @IsBinaryArg (@LFunc.type! T f) vty₁_ty₂ ty_1) _ initSize; + match vty₁_ty₂ with + | @Prod.mk (@Lambda.LTy) (@Lambda.LTy) ty₁ ty₂ => do + let (t₂ : LExpr (LExprParams.mono T)) ← aux_arb initSize size' ctx_1 ty₂; + let (t₁ : LExpr (LExprParams.mono T)) ← aux_arb initSize size' ctx_1 ty₁; + let (m : _) ← Plausible.Arbitrary.arbitrary; + return Lambda.LExpr.app m (Lambda.LExpr.app m (Lambda.LExpr.op m f.name (Option.none)) t₁) t₂) + ]) + fun size => aux_arb size size ctx_1 ty_1 + + +#guard_msgs(drop info) in +#eval Gen.printSamples (Arbitrary.arbitrary : Gen LMonoTy) + +abbrev example_lctx : LContext TrivialParams := +{ LContext.empty with knownTypes := KnownTypes.default + functions := Lambda.IntBoolFactory +} + +abbrev example_ctx : TContext Unit := ⟨[[]], []⟩ +-- abbrev example_ty : LTy := .forAll [] <| .tcons "bool" [] +abbrev example_ty : LTy := .forAll [] <| .tcons "arrow" [.tcons "bool" [], .tcons "bool" []] + +/-- info: [[({ name := "y", metadata := () }, Lambda.LTy.forAll [] (Lambda.LMonoTy.tcons "int" []))]] -/ +#guard_msgs(info) in +#eval + let P : Maps (Identifier Unit) LTy → Prop := fun Γ => MapsInsert (example_ctx.types) "y" (.forAll [] (.tcons "int" [])) Γ + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 5) 5 + + +#guard_msgs(drop info) in +#time #eval + let P : LExpr TrivialParams.mono → Prop := fun t => HasType example_lctx example_ctx t example_ty + Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 4) 4 + +def example_lstate := + { LState.init (T := TrivialParams) with config := + { LState.init.config (T := TrivialParams) with + factory := Lambda.IntBoolFactory (T := TrivialParams)} + } + +/-- `Monad` instance for List. + Note that: + - The Lean standard library does not have a Monad instance for List (see https://leanprover-community.github.io/archive/stream/270676-lean4/topic/Option.20do.20notation.20regression.3F.html#231433226) + - MathLib4 does have a Monad instance for List, but we wish to avoid having Chamelean rely on Mathlib + as a dependency, so we reproduce instance here instead. -/ +private instance : Monad List where + pure x := [x] + bind xs f := xs.flatMap f + +instance [Inhabited T.base.IDMeta] : Shrinkable (LExpr T) where + shrink t := + let rec aux (t : LExpr T) : List (LExpr T) := + match t with + | .fvar _ _ _ + | .bvar _ _ + | .op _ _ _ + | .const _ _ -- We're being a bit lazy here for the time being + => [] + | .app m t u => + t :: u :: (.app m <$> aux t <*> aux u) + | .abs m ty t => (LExpr.varOpen 0 ⟨⟨"x", default⟩, ty⟩ t) :: (.abs m ty <$> aux t) -- IDK about the `"x"` + | .eq m t u => t :: u :: (.eq m <$> aux t <*> aux u) + | .ite m cond t u => cond :: t :: u :: (.ite m <$> aux cond <*> aux t <*> aux u) + | .quant m k ty tr t => (LExpr.varOpen 0 ⟨⟨"x", default⟩, ty⟩ t) :: (.quant m k ty tr <$> aux t) + aux t + +-- Shrinks an element of `α` recursively. +partial def shrinkFunAux [Shrinkable α] (f : α → Bool) (x : α) : Option α := do + let candidates := Shrinkable.shrink x + let y ← candidates.find? f + let z := shrinkFunAux f y + z <|> some y + +def shrinkFun [Shrinkable α] (f : α → Bool) (x : α) : α := +let shrinked := shrinkFunAux f x +match shrinked with +| .some y => y +| .none => x + +/-- info: [LExpr.fvar () { name := "x", metadata := () } none, LExpr.fvar () { name := "y", metadata := () } none] -/ +#guard_msgs(info) in +#eval Shrinkable.shrink (LExpr.eq (T := TrivialParams.mono) () (.fvar () "x" .none) (.fvar () "y" .none)) + +/-- info: 2 -/ +#guard_msgs(info) in +#eval shrinkFun (fun n : Nat => n % 3 == 2) 42 + +def annotate (t : LExpr TrivialParams.mono) := + let state : TState := {} + let env : TEnv Unit := { genEnv := ⟨example_ctx, state⟩ } + LExpr.annotate example_lctx env t + +def canAnnotate (t : LExpr TrivialParams.mono) : Bool := + (annotate t).isOk + + +#guard_msgs(drop info) in +#eval do + let P : LExpr TrivialParams.mono → Prop := fun t => HasType example_lctx example_ctx t example_ty + let t ← Gen.runUntil (.some 10) (ArbitrarySizedSuchThat.arbitrarySizedST P 5) 5 + IO.println s!"Generated {t}" + + +/-- info: Generating terms of type +Lambda.LTy.forAll [] (Lambda.LMonoTy.tcons "arrow" [Lambda.LMonoTy.tcons "bool" [], Lambda.LMonoTy.tcons "bool" []]) +in context +{ types := [[]], aliases := [] } +in factory +#[Int.Add, Int.Sub, Int.Mul, Int.Div, Int.Mod, Int.Neg, Int.Lt, Int.Le, Int.Gt, Int.Ge, Bool.And, Bool.Or, Bool.Implies, Bool.Equiv, Bool.Not] +-/ +#guard_msgs in +#eval do + IO.println s!"Generating terms of type\n{example_ty}\nin context\n{repr example_ctx}\nin \ + factory\n{example_lctx.functions.map (fun f : LFunc TrivialParams => f.name)}\n" + for i in List.range 100 do + let P : LExpr TrivialParams.mono → Prop := fun t => HasType example_lctx example_ctx t example_ty + let t ← Gen.runUntil (.some 1000) (ArbitrarySizedSuchThat.arbitrarySizedST P 5) 5 + -- IO.println s!"Generated {t}" + if !(canAnnotate t) then + let .error e := annotate t | throw <| IO.Error.userError "Unreachable" + IO.println s!"FAILED({i}): {e}\n{t}\n\nSHRUNK TO:\n{shrinkFun (not ∘ canAnnotate) t}\n\n" + +def isIntConst (t : LExpr TrivialParams.mono) : Bool := +match t with +| .const _ (.intConst _) => true +| _ => false + +def reduces (t : LExpr TrivialParams.mono) : Bool := + let t' := t.eval 1000 example_lstate + isIntConst t' + +/-- info: Generating terms of type +Lambda.LTy.forAll [] (Lambda.LMonoTy.tcons "arrow" [Lambda.LMonoTy.tcons "bool" [], Lambda.LMonoTy.tcons "bool" []]) +in context +{ types := [[]], aliases := [] } +in factory +#[Int.Add, Int.Sub, Int.Mul, Int.Div, Int.Mod, Int.Neg, Int.Lt, Int.Le, Int.Gt, Int.Ge, Bool.And, Bool.Or, Bool.Implies, Bool.Equiv, Bool.Not] +-/ +#guard_msgs(info, drop error) in +#eval do + IO.println s!"Generating terms of type\n{example_ty}\nin context\n{repr example_ctx}\nin \ + factory\n{example_lctx.functions.map (fun f : LFunc _ => f.name)}\n" + for _i in List.range 100 do + let P : LExpr TrivialParams.mono → Prop := fun t => HasType example_lctx example_ctx t (.forAll [] (.tcons "int" [])) + let t ← Gen.runUntil (.some 1000) (ArbitrarySizedSuchThat.arbitrarySizedST P 5) 5 + -- Unfortunately this *can* fail, if we compare two terms at arrow types, or try to take mod 0 etc. + if !(reduces t) then + -- IO.println s!"NOT A VALUE({i}): {t}\nREDUCES TO\n{t.eval 10000 example_lstate}\n\n" + continue diff --git a/Strata/DL/Lambda/TypeFactory.lean b/Strata/DL/Lambda/TypeFactory.lean new file mode 100644 index 000000000..e20cb53c5 --- /dev/null +++ b/Strata/DL/Lambda/TypeFactory.lean @@ -0,0 +1,434 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Lambda.LExprWF +import Strata.DL.Lambda.LTy +import Strata.DL.Lambda.Factory + +/-! +## Lambda's Type Factory + +This module contains Lambda's _type factory_, a mechanism for expressing +inductive datatypes (sum and product types). It synthesizes the corresponding +constructors and eliminators as `LFunc`. It currently does not allow +non-uniform or mutually inductive types. +-/ + + +namespace Lambda + +open Std (ToFormat Format format) + +--------------------------------------------------------------------- + +open LTy.Syntax + +variable {IDMeta : Type} [DecidableEq IDMeta] [Inhabited IDMeta] + +/- +Prefixes for newly generated type and term variables. +See comment for `TEnv.genExprVar` for naming. +Note that `exprPrefix` is designed to avoid clashes with `exprPrefix` +in `LExprTypeEnv.lean`. +-/ +def tyPrefix : String := "$__ty" +def exprPrefix : String := "$__tvar" + +/-- +A type constructor description. The free type variables in `args` must be a +subset of the `typeArgs` of the corresponding datatype. +-/ +structure LConstr (IDMeta : Type) where + name : Identifier IDMeta + args : List (Identifier IDMeta × LMonoTy) + testerName : String := "is" ++ name.name +deriving Repr, DecidableEq + +instance: ToFormat (LConstr IDMeta) where + format c := f!"Name:{Format.line}{c.name}{Format.line}\ + Args:{Format.line}{c.args}{Format.line}\ + Tester:{Format.line}{c.testerName}{Format.line}" + +/-- +A datatype description. `typeArgs` contains the free type variables of the +given datatype. +-/ +structure LDatatype (IDMeta : Type) where + name : String + typeArgs : List TyIdentifier + constrs: List (@LConstr IDMeta) + constrs_ne : constrs.length != 0 +deriving Repr, DecidableEq + +instance : ToFormat (LDatatype IDMeta) where + format d := f!"type:{Format.line}{d.name}{Format.line}\ + Type Arguments:{Format.line}{d.typeArgs}{Format.line}\ + Constructors:{Format.line}{d.constrs}{Format.line}" + +/-- +A datatype applied to arguments +-/ +def data (d: LDatatype IDMeta) (args: List LMonoTy) : LMonoTy := + .tcons d.name args + +/-- +The default type application for a datatype. E.g. for datatype +`type List α = | Nil | Cons α (List α)`, produces LMonoTy `List α`. +-/ +def dataDefault (d: LDatatype IDMeta) : LMonoTy := + data d (d.typeArgs.map .ftvar) + +--------------------------------------------------------------------- + +-- Typechecking + +/-- +Determines whether type name `n` appear in type `t` +-/ +def tyNameAppearsIn (n: String) (t: LMonoTy) : Bool := + match t with + | .tcons n1 args => n == n1 || List.any (List.map (tyNameAppearsIn n) args) id + | _ => false + +/-- +Determines whether all occurences of type name `n` within type `t` have +arguments `args`. The string `c` appears only for error message information. +-/ +def checkUniform (c: String) (n: String) (args: List LMonoTy) (t: LMonoTy) : Except Format Unit := + match t with + | .tcons n1 args1 => if n == n1 && args == args1 then .ok () + else if n == n1 then .error f!"Error in constructor {c}: Non-uniform occurrence of {n}, which is applied to {args1} when it should be applied to {args}" + else List.foldrM (fun t u => do + let _ ← checkUniform c n args t + .ok u + ) () args1 + | _ => .ok () + + +/-- +Check for strict positivity and uniformity of datatype `d` in type `ty`. The +string `c` appears only for error message information. +-/ +def checkStrictPosUnifTy (c: String) (d: LDatatype IDMeta) (ty: LMonoTy) : Except Format Unit := + match ty with + | .arrow t1 t2 => + if tyNameAppearsIn d.name t1 then + .error f!"Error in constructor {c}: Non-strictly positive occurrence of {d.name} in type {ty}" + else checkStrictPosUnifTy c d t2 + | _ => checkUniform c d.name (d.typeArgs.map .ftvar) ty + +/-- +Check for strict positivity and uniformity of a datatype +-/ +def checkStrictPosUnif (d: LDatatype IDMeta) : Except Format Unit := + List.foldrM (fun ⟨name, args, _⟩ _ => + List.foldrM (fun ⟨ _, ty ⟩ _ => + checkStrictPosUnifTy name.name d ty + ) () args + ) () d.constrs + +--------------------------------------------------------------------- + +-- Generating constructors and eliminators + +/-- +The `LFunc` corresponding to constructor `c` of datatype `d`. Constructor +functions do not have bodies or `concreteEval` functions, as they are values +when applied to value arguments. +-/ +def constrFunc (c: LConstr T.IDMeta) (d: LDatatype T.IDMeta) : LFunc T := + { name := c.name, typeArgs := d.typeArgs, inputs := c.args, output := dataDefault d, isConstr := true } + +/-- +Generate `n` strings for argument names for the eliminator. Since there is no +body, these strings do not need to be used. +-/ +private def genArgNames (n: Nat) : List (Identifier IDMeta) := + (List.range n).map (fun i => ⟨exprPrefix ++ toString i, Inhabited.default⟩) + +private def genArgName : Identifier IDMeta := + have H: genArgNames 1 ≠ [] := by unfold genArgNames; grind + List.head (genArgNames 1) H + +/-- +Find `n` type arguments (string) not present in list by enumeration. +Inefficient on large inputs. +-/ +def freshTypeArgs (n: Nat) (l: List TyIdentifier) : List TyIdentifier := + -- Generate n + |l| names to ensure enough unique ones + let candidates := List.map (fun n => tyPrefix ++ toString n) (List.range (l.length + n)); + List.take n (List.filter (fun t => ¬ t ∈ l) candidates) + +/-- +Find a fresh type argument not present in `l` by enumeration. Relies on the fact +that `freshTypeArgs n` gives a list of exactly `n` fresh type arguments. +-/ +def freshTypeArg (l: List TyIdentifier) : TyIdentifier := + match freshTypeArgs 1 l with + | t :: _ => t + | _ => "" + +/-- +Construct a recursive type argument for the eliminator. +Specifically, determine if a type `ty` contains a strictly positive, uniform +occurrence of `t`, if so, replace this occurence with `retTy`. + +For example, given `ty` (int -> (int -> List α)), datatype List, and `retTy` β, +gives (int -> (int -> β)) +-/ +def genRecTy (t: LDatatype IDMeta) (retTy: LMonoTy) (ty: LMonoTy) : Option LMonoTy := + if ty == dataDefault t then .some retTy else + match ty with + | .arrow t1 t2 => (genRecTy t retTy t2).map (fun r => .arrow t1 r) + | _ => .none + +def isRecTy (t: LDatatype IDMeta) (ty: LMonoTy) : Bool := + (genRecTy t .int ty).isSome + +/-- +Generate types for eliminator arguments. +The types are functions taking in 1) each argument of constructor `c` of +datatype `d` and 2) recursive results for each recursive argument of `c` and +returning an element of type `outputType`. + +For example, the eliminator type argument for `cons` is α → List α → β → β +-/ +def elimTy (outputType : LMonoTy) (t: LDatatype IDMeta) (c: LConstr IDMeta): LMonoTy := + match c.args with + | [] => outputType + | _ :: _ => LMonoTy.mkArrow' outputType (c.args.map Prod.snd ++ (c.args.map (fun x => (genRecTy t outputType x.2).toList)).flatten) + +/-- +Simulates pattern matching on operator o. +-/ +def LExpr.matchOp {T: LExprParams} [BEq T.Identifier] (e: LExpr T.mono) (o: T.Identifier) : Option (List (LExpr T.mono)) := + match getLFuncCall e with + | (.op _ o1 _, args) => if o == o1 then .some args else .none + | _ => .none + +/-- +Determine which constructor, if any, a datatype instance belongs to and get the +arguments. Also gives the index in the constructor list as well as the +recursive arguments (somewhat redundantly) + +For example, expression `cons x l` gives constructor `cons`, index `1` (cons is +the second constructor), arguments `[x, l]`, and recursive argument +`[(l, List α)]` +-/ +def datatypeGetConstr {T: LExprParams} [BEq T.Identifier] (d: LDatatype T.IDMeta) (x: LExpr T.mono) : Option (LConstr T.IDMeta × Nat × List (LExpr T.mono) × List (LExpr T.mono × LMonoTy)) := + List.foldr (fun (c, i) acc => + match x.matchOp c.name with + | .some args => + -- Get the elements of args corresponding to recursive calls, in order + let recs := (List.zip args (c.args.map Prod.snd)).filter (fun (_, ty) => isRecTy d ty) + + .some (c, i, args, recs) + | .none => acc) .none (List.zip d.constrs (List.range d.constrs.length)) + +/-- +Determines which category a recursive type argument falls in: either `d +(typeArgs)` or `τ₁ → ... → τₙ → d(typeArgs)`. +In the later case, returns the `τ` list +-/ +def recTyStructure (d: LDatatype IDMeta) (recTy: LMonoTy) : Unit ⊕ (List LMonoTy) := + if recTy == dataDefault d then .inl () else .inr (recTy.getArrowArgs) + +/-- +Finds the lambda `bvar` arguments, in order, given an iterated lambda with `n` +binders +-/ +private def getBVars {T: LExprParams} (m: T.Metadata) (n: Nat) : List (LExpr T.mono) := + (List.range n).reverse.map (.bvar m) + +/-- +Construct recursive call of eliminator. Specifically, `recs` are the recursive +arguments, in order, while `elimArgs` are the eliminator cases (e.g. for a +binary tree with constructor `Node x l r`, where `l` and `r` are subtrees, +`recs` is `[l, r]`) + +Invariant: `recTy` must either have the form `d(typeArgs)` or `τ₁ → ... → τₙ → d +(typeArgs)`. This is enforced by `dataTypeGetConstr` + +-/ +def elimRecCall {T: LExprParams} (d: LDatatype T.IDMeta) (recArg: LExpr T.mono) (recTy: LMonoTy) (elimArgs: List (LExpr T.mono)) (m: T.Metadata) (elimName : Identifier T.IDMeta) : LExpr T.mono := + match recTyStructure d recTy with + | .inl _ => -- Generate eliminator call directly + (LExpr.op m elimName .none).mkApp m (recArg :: elimArgs) + | .inr funArgs => + /- Construct lambda, first arg of eliminator is recArg applied to lambda + arguments -/ + LExpr.absMulti m funArgs ((LExpr.op m elimName .none).mkApp m (recArg.mkApp m (getBVars m funArgs.length) :: elimArgs)) + +/-- +Generate eliminator concrete evaluator. Idea: match on 1st argument (e.g. +`x : List α`) to determine constructor and corresponding arguments. If it +matches the `n`th constructor, return `n+1`st element of input list applied to +constructor arguments and recursive calls. + +Examples: +1. For `List α`, the generated function behaves as follows: +`List$Elim Nil e1 e2 = e1` and +`List$Elim (x :: xs) e1 e2 = e2 x xs (List$Elim xs e1 e2)` +2. For `tree = | T (int -> tree)`, the generated function is: +`Tree$Elim (T f) e = e f (fun (x: int) => Tree$Elim (f x) e)` + +-/ +def elimConcreteEval {T: LExprParams} [BEq T.Identifier] (d: LDatatype T.IDMeta) (m: T.Metadata) (elimName : Identifier T.IDMeta) : + T.Metadata → List (LExpr T.mono) → Option (LExpr T.mono) := + fun _ args => + match args with + | x :: xs => + match datatypeGetConstr d x with + | .some (_, i, a, recs) => + match xs[i]? with + | .some f => f.mkApp m (a ++ recs.map (fun (r, rty) => elimRecCall d r rty xs m elimName)) + | .none => .none + | .none => .none + | _ => .none + +def elimFuncName (d: LDatatype IDMeta) : Identifier IDMeta := + d.name ++ "$Elim" + +/-- +The `LFunc` corresponding to the eliminator for datatype `d`, called e.g. +`List$Elim` for type `List`. +-/ +def elimFunc [Inhabited T.IDMeta] [BEq T.Identifier] (d: LDatatype T.IDMeta) (m: T.Metadata) : LFunc T := + let outTyId := freshTypeArg d.typeArgs + { name := elimFuncName d, typeArgs := outTyId :: d.typeArgs, inputs := List.zip (genArgNames (d.constrs.length + 1)) (dataDefault d :: d.constrs.map (elimTy (.ftvar outTyId) d)), output := .ftvar outTyId, concreteEval := elimConcreteEval d m (elimFuncName d)} + +--------------------------------------------------------------------- + +-- Generating testers and destructors + +/-- +Generate tester body (see `testerFunc`). The body assigns each argument of the +eliminator (fun _ ... _ => b), where b is true for the constructor's index and +false otherwise. This requires knowledge of the number of arguments for each +argument to the eliminator.-/ +def testerFuncBody {T : LExprParams} [Inhabited T.IDMeta] (d: LDatatype T.IDMeta) (c: LConstr T.IDMeta) (input: LExpr T.mono) (m: T.Metadata) : LExpr T.mono := + -- Number of arguments is number of constr args + number of recursive args + let numargs (c: LConstr T.IDMeta) := c.args.length + ((c.args.map Prod.snd).filter (isRecTy d)).length + let args := List.map (fun c1 => LExpr.absMultiInfer m (numargs c1) (.boolConst m (c.name.name == c1.name.name))) d.constrs + .mkApp m (.op m (elimFuncName d) .none) (input :: args) + +/-- +Generate tester function for a constructor (e.g. `List$isCons` and +`List$isNil`). The semantics of the testers are given via a body, +and they are defined in terms of eliminators. For example: +`List$isNil l := List$Elim l true (fun _ _ _ => false)` +`List$isCons l := List$Elim l false (fun _ _ _ => true)` +-/ +def testerFunc {T} [Inhabited T.IDMeta] (d: LDatatype T.IDMeta) (c: LConstr T.IDMeta) (m: T.Metadata) : LFunc T := + let arg := genArgName + {name := c.testerName, + typeArgs := d.typeArgs, + inputs := [(arg, dataDefault d)], + output := .bool, + body := testerFuncBody d c (.fvar m arg .none) m, + attr := #["inline_if_val"] + } + +/-- +Concrete evaluator for destructor: if given instance of the constructor, +the `i`th projection retrieves the `i`th argument of the application +-/ +def destructorConcreteEval {T: LExprParams} [BEq T.Identifier] (d: LDatatype T.IDMeta) (c: LConstr T.IDMeta) (idx: Nat) : + List (LExpr T.mono) → Option (LExpr T.mono) := + fun args => + match args with + | [x] => + (datatypeGetConstr d x).bind (fun (c1, _, a, _) => + if c1.name.name == c.name.name + then a[idx]? else none) + | _ => none + +/-- +Generate destructor functions for a constructor, which extract the +constructor components, e.g. +`List$ConsProj0 (Cons h t) = h` +`List$ConsProj1 (Cons h t) = t` +These functions are partial, `List@ConsProj0 Nil` is undefined. +-/ +def destructorFuncs {T} [BEq T.Identifier] [Inhabited T.IDMeta] (d: LDatatype T.IDMeta) (c: LConstr T.IDMeta) : List (LFunc T) := + c.args.mapIdx (fun i (name, ty) => + let arg := genArgName + { + name := name, + typeArgs := d.typeArgs, + inputs := [(arg, dataDefault d)], + output := ty, + concreteEval := some (fun _ => destructorConcreteEval d c i)}) + + +--------------------------------------------------------------------- + +-- Type Factories + +def TypeFactory := Array (LDatatype IDMeta) + +instance: ToFormat (@TypeFactory IDMeta) where + format f := Std.Format.joinSep f.toList f!"{Format.line}" + +instance : Inhabited (@TypeFactory IDMeta) where + default := #[] + +def TypeFactory.default : @TypeFactory IDMeta := #[] + +/-- +Generates the Factory (containing the eliminator, constructors, testers, +and destructors) for a single datatype. +-/ +def LDatatype.genFactory {T: LExprParams} [inst: Inhabited T.Metadata] [Inhabited T.IDMeta] [ToFormat T.IDMeta] [BEq T.Identifier] (d: LDatatype T.IDMeta): Except Format (@Lambda.Factory T) := do + _ ← checkStrictPosUnif d + Factory.default.addFactory ( + elimFunc d inst.default :: + d.constrs.map (fun c => constrFunc c d) ++ + d.constrs.map (fun c => testerFunc d c inst.default) ++ + (d.constrs.map (fun c => destructorFuncs d c)).flatten).toArray + +/-- +Constructs maps of generated functions for datatype `d`: map of +constructors, testers, and destructors in order. Each maps names to +the datatype and constructor AST. +-/ +def LDatatype.genFunctionMaps {T: LExprParams} [Inhabited T.IDMeta] [BEq T.Identifier] (d: LDatatype T.IDMeta) : + Map String (LDatatype T.IDMeta × LConstr T.IDMeta) × + Map String (LDatatype T.IDMeta × LConstr T.IDMeta) × + Map String (LDatatype T.IDMeta × LConstr T.IDMeta) := + (Map.ofList (d.constrs.map (fun c => (c.name.name, (d, c)))), + Map.ofList (d.constrs.map (fun c => (c.testerName, (d, c)))), + Map.ofList (d.constrs.map (fun c => + (destructorFuncs d c).map (fun f => (f.name.name, (d, c))))).flatten) + +/-- +Generates the Factory (containing all constructor and eliminator functions) for the given `TypeFactory` +-/ +def TypeFactory.genFactory {T: LExprParams} [inst: Inhabited T.Metadata] [Inhabited T.IDMeta] [ToFormat T.IDMeta] [BEq T.Identifier] (t: @TypeFactory T.IDMeta) : Except Format (@Lambda.Factory T) := + t.foldlM (fun f d => do + let f' ← d.genFactory + f.addFactory f') Factory.default + +def TypeFactory.getType (F : @TypeFactory IDMeta) (name : String) : Option (LDatatype IDMeta) := + F.find? (fun d => d.name == name) + +/-- +Add an `LDatatype` to an existing `TypeFactory`, checking that no +types are duplicated. +-/ +def TypeFactory.addDatatype (t: @TypeFactory IDMeta) (d: LDatatype IDMeta) : Except Format (@TypeFactory IDMeta) := + -- Check that type is not redeclared + match t.getType d.name with + | none => .ok (t.push d) + | some d' => .error f!"A datatype of name {d.name} already exists! \ + Redefinitions are not allowed.\n\ + Existing Type: {d'}\n\ + New Type:{d}" + + +--------------------------------------------------------------------- + +end Lambda diff --git a/Strata/DL/SMT/CexParser.lean b/Strata/DL/SMT/CexParser.lean index 871edb666..23c1fd08f 100644 --- a/Strata/DL/SMT/CexParser.lean +++ b/Strata/DL/SMT/CexParser.lean @@ -51,7 +51,7 @@ abbrev Parser := Std.Internal.Parsec.String.Parser def varToken : Parser String := do let chars ← many1 (satisfy (fun c => !c.isWhitespace && c ≠ '(' && c ≠ ')')) - return String.mk chars.toList + return String.ofList chars.toList def valToken : Parser String := do (attempt (do @@ -59,7 +59,7 @@ def valToken : Parser String := do let _open_paren ← pchar '(' let content ← many (satisfy (fun c => c ≠ ')')) let _close_paren ← pchar ')' - return s!"({String.mk content.toList})")) <|> + return s!"({String.ofList content.toList})")) <|> -- Handle regular token. varToken @@ -91,9 +91,9 @@ def parseCEx1 : Parser CEx := do return { pairs := [] })) def parseCEx (cex : String) : Except Format CEx := - match parseCEx1 (String.mkIterator cex) with + match parseCEx1 ⟨cex, cex.startValidPos⟩ with | Std.Internal.Parsec.ParseResult.success _ result => Except.ok result - | Std.Internal.Parsec.ParseResult.error pos msg => Except.error s!"Parse error at {pos}: {msg}" + | Std.Internal.Parsec.ParseResult.error ⟨_, pos⟩ msg => Except.error s!"Parse error at {pos.offset}: {msg}" /-- info: -/ #guard_msgs in diff --git a/Strata/DL/SMT/DDMTransform/Parse.lean b/Strata/DL/SMT/DDMTransform/Parse.lean index 50f117b2e..e573c74f3 100644 --- a/Strata/DL/SMT/DDMTransform/Parse.lean +++ b/Strata/DL/SMT/DDMTransform/Parse.lean @@ -8,6 +8,13 @@ import Strata.DDM.BuiltinDialects.BuiltinM import Strata.DDM.Integration.Lean import Strata.DDM.Util.Format +/-! # The SMTLib syntax + +This file defines the syntax of SMTLib 2.7, which is defined at +https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-07-07.pdf, +Appendix B. Concrete Syntax. +-/ + namespace Strata open Elab @@ -35,6 +42,7 @@ private def reservedKeywords := [ ("check_sat_assuming", "check-sat-assuming"), ("declare_const", "declare-const"), ("declare_datatype", "declare-datatype"), + ("declare_datatypes", "declare-datatypes"), ("declare_fun", "declare-fun"), ("declare_sort", "declare-sort"), ("declare_sort_parameter", "declare-sort-parameter"), @@ -72,7 +80,7 @@ private def reservedKeywords := [ This makes the below list exclude "_" and "!" because it is already in reservedKeywords. -/ -private def specialCharsInSimpleSymbol := [ +def specialCharsInSimpleSymbol := [ ("plus", "+"), ("minus", "-"), -- ("slash", "/"), -- This causes an error in the SMT dialect definition @@ -95,14 +103,14 @@ private def specialCharsInSimpleSymbol := [ -- https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-07-07.pdf -- Appendix B. Concrete Syntax -- Prepare reserved keywords and simple symbols in advance. -private def smtReservedKeywordsDialect : Dialect := +def smtReservedKeywordsDialect : Dialect := BuiltinM.create! "SMTReservedKeywords" #[] do declareAtomicCat q`SMTReservedKeywords.Reserved for (name, s) in reservedKeywords do declareOp { name := s!"reserved_{name}", - argDecls := #[], + argDecls := ArgDecls.empty, category := q`SMTReservedKeywords.Reserved, syntaxDef := .ofList [.str s] } @@ -113,7 +121,7 @@ private def smtReservedKeywordsDialect : Dialect := for (name, s) in specialCharsInSimpleSymbol do declareOp { name := s!"simple_symbol_{name}", - argDecls := #[], + argDecls := ArgDecls.empty, category := q`SMTReservedKeywords.SimpleSymbol, syntaxDef := .ofList [.str s] } @@ -136,30 +144,46 @@ import SMTReservedKeywords; // is Str. // is QualifiedIdent. -op simple_symbol_qid (s:QualifiedIdent) : SimpleSymbol => s; -// The two symbols "true" and "false" are not parsed as QualifiedIdent. +op simple_symbol_qid (@[unwrap] s:QualifiedIdent) : SimpleSymbol => s; +// These strings are not parsed as QualifiedIdent. // This is because they are currently used as keywords in the Init dialect // (see Strata/DDM/BuiltinDialects/Init.lean) op simple_symbol_tt () : SimpleSymbol => "true"; op simple_symbol_ff () : SimpleSymbol => "false"; +op simple_symbol_none () : SimpleSymbol => "none"; +op simple_symbol_some () : SimpleSymbol => "some"; // is simplified to . // - TODO: // * Support quoted symbols // * Support symbols with non-ascii characters (&, ., !, etc) category Symbol; -op symbol (s:SimpleSymbol) : Symbol => s; +op symbol (@[unwrap] s:SimpleSymbol) : Symbol => s; + +category SymbolList; // For spacing, has at least one element +op symbol_list_one (se:Symbol) : SymbolList => se; +op symbol_list_cons (se:Symbol, sse:SymbolList) : SymbolList => se " " sse; category Keyword; -op kw_symbol (s:SimpleSymbol) : Keyword => ":" s; +op kw_symbol (@[unwrap] s:SimpleSymbol) : Keyword => ":" s; // 2. S-expressions -// Special constants +// Special constants. category SpecConstant; -op sc_numeral (n:Num) : SpecConstant => n; -op sc_decimal (d:Decimal) : SpecConstant => d; -op sc_str (s:Str) : SpecConstant => s; +op sc_numeral (@[unwrap] n:Num) : SpecConstant => n; +op sc_decimal (@[unwrap] d:Decimal) : SpecConstant => d; +op sc_str (@[unwrap] s:Str) : SpecConstant => s; + +// sign is not a part of the standard, but it seems CVC5 and Z3 +// support this for convenience. +// Note that negative integers like '-1231' are symbols in Std! (Sec 3.1. Lexicon) +// The only way to create a unary symbol is through identifiers, but this +// makes its DDM format wrapped with pipes, like '|-1231|`. Since such +// representation cannot be recognized by Z3, make a workaround which is to have +// separate `*_neg` categories for sc_numeral. +op sc_numeral_neg (@[unwrap] n:Num) : SpecConstant => "-" n:0; +op sc_decimal_neg (@[unwrap] n:Decimal) : SpecConstant => "-" n:0; category SExpr; op se_spec_const (s:SpecConstant) : SExpr => s; @@ -167,26 +191,40 @@ op se_symbol (s:Symbol) : SExpr => s; op se_reserved (s:Reserved) : SExpr => s; op se_keyword (s:Keyword) : SExpr => s; -op se_ls (s:Seq SExpr) : SExpr => "(" s ")"; +category SExprList; // For spacing, has at least one element +op sexpr_list_one (se:SExpr) : SExprList => se; +op sexpr_list_cons (se:SExpr, sse:SExprList) : SExprList => se " " sse; + +op se_ls (s:Option SExprList) : SExpr => "(" s ")"; -// 3. Identifier +category SMTIdentifier; +// 3. Identifier. Use 'SMTIdentifier' because the 'Identifier' category is +// already defined in DDM category Index; -op ind_numeral (n:Num) : Index => n; -op ind_symbol (s:Symbol) : Index => s; +op ind_numeral (@[unwrap] n:Num) : Index => n; +op ind_symbol (@[unwrap] s:Symbol) : Index => s; -category Identifier; -op iden_simple (s:Symbol) : Identifier => s; -op iden_indexed (s:Symbol, i0:Index, il:Seq Index) : Identifier => - "(" "_" s i0 il ")"; +op iden_simple (s:Symbol) : SMTIdentifier => s; + +category IndexList; // For spacing; has at least one element +op index_list_one (i:Index) : IndexList => i; +op index_list_cons (i:Index, spi:IndexList) : IndexList => i " " spi; + +op iden_indexed (s:Symbol, si:IndexList) : SMTIdentifier => + "(" "_ " s " " si ")"; // 4. Sorts category SMTSort; -op smtsort_ident (s:Identifier) : SMTSort => s; +op smtsort_ident (s:SMTIdentifier) : SMTSort => s; + +category SMTSortList; // For spacing; has at least one element +op smtsort_list_one (i:SMTSort) : SMTSortList => i; +op smtsort_list_cons (i:SMTSort, spi:SMTSortList) : SMTSortList => i " " spi; -op smtsort_param (s:Identifier, s0:SMTSort, sl:Seq SMTSort) : SMTSort - => "(" s s0 sl ")"; +op smtsort_param (s:SMTIdentifier, sl:SMTSortList) : SMTSort + => "(" s " " sl ")"; // 5. Attributes @@ -198,78 +236,149 @@ op av_sel (s:Seq SExpr) : AttributeValue => "(" s ")"; category Attribute; op att_kw (k:Keyword, av:Option AttributeValue) : Attribute => k av; +category AttributeList; // For spacing; has at least one element +op att_list_one (i:Attribute) : AttributeList => i; +op att_list_cons (i:Attribute, spi:AttributeList) : AttributeList => i " " spi; + // 6. Terms category QualIdentifier; -op qi_ident (i:Identifier) : QualIdentifier => i; -op qi_isort (i:Identifier, s:SMTSort) : QualIdentifier => "(" "as" i s ")"; +op qi_ident (i:SMTIdentifier) : QualIdentifier => i; +op qi_isort (i:SMTIdentifier, s:SMTSort) : QualIdentifier => + "(" "as " i " " s ")"; category Term; // Forward declaration +category TermList; // For Spacing +op term_list_one (i:Term) : TermList => i:0; +op term_list_cons (i:Term, spi:TermList) : TermList => i:0 " " spi:0; + category ValBinding; -op val_binding (s:Symbol, t:Term) : ValBinding => "(" s t ")"; +op val_binding (s:Symbol, t:Term) : ValBinding => "(" s " " t ")"; + +category ValBindingList; // For spacing; has at least one element +op val_binding_list_one (i:ValBinding) : ValBindingList => i; +op val_binding_list_cons (i:ValBinding, spi:ValBindingList) : ValBindingList => i " " spi; category SortedVar; -op sorted_var (s:Symbol, so:SMTSort) : SortedVar => "(" s so ")"; +op sorted_var (s:Symbol, so:SMTSort) : SortedVar => "(" s " " so ")"; + +category SortedVarList; // For spacing; has at least one element +op sorted_var_list_one (i:SortedVar) : SortedVarList => i; +op sorted_var_list_cons (i:SortedVar, spi:SortedVarList) : SortedVarList => i " " spi; // TODO: support the match statement // category Pattern; op spec_constant_term (sc:SpecConstant) : Term => sc; op qual_identifier (qi:QualIdentifier) : Term => qi; -op qual_identifier_args (qi:QualIdentifier, t0:Term, ts:Seq Term) : Term => - "(" qi t0 ts ")"; +op qual_identifier_args (qi:QualIdentifier, ts:TermList) : Term => + "(" qi " " ts ")"; -op let_smt (vb:ValBinding, vbps: Seq ValBinding, t:Term) : Term => - "(" "let" "(" vb vbps ")" t ")"; -op lambda_smt (sv: SortedVar, svs: Seq SortedVar, t:Term) : Term => - "(" "lambda" "(" sv svs ")" t ")"; -op forall_smt (sv: SortedVar, svs: Seq SortedVar, t:Term) : Term => - "(" "forall" "(" sv svs ")" t ")"; -op exists_smt (sv: SortedVar, svs: Seq SortedVar, t:Term) : Term => - "(" "exists" "(" sv svs ")" t ")"; -op bang (t:Term, attr0: Attribute, attrs:Seq Attribute) : Term => - "(" "!" t attr0 attrs ")"; +op let_smt (vbps: ValBindingList, t:Term) : Term => + "(" "let" "(" vbps ")" t ")"; +op lambda_smt (svs: SortedVarList, t:Term) : Term => + "(" "lambda" "(" svs ")" t ")"; +op forall_smt (svs: SortedVarList, t:Term) : Term => + "(" "forall" "(" svs ")" t ")"; +op exists_smt (svs: SortedVarList, t:Term) : Term => + "(" "exists" "(" svs ")" t ")"; +op bang (t:Term, attrs:AttributeList) : Term => + "(" "!" t " " attrs ")"; // 7. Theories category TheoryDecl; // TODO: theory_attribute -op theory_decl (s:Symbol) : TheoryDecl => "(" "theory" s ")"; +op theory_decl (s:Symbol) : TheoryDecl => "(" "theory " s ")"; // 8. Logic category Logic; // TODO: logic_attribute -op logic (s:Symbol) : Logic => "(" "logic" s ")"; +op logic (s:Symbol) : Logic => "(" "logic " s ")"; + +// 9. Info flags -// 9. Info flags: TODO +category InfoFlag; +op info_flag_stat () : InfoFlag => ":all-statistics"; +op info_flag_stlvl () : InfoFlag => ":assertion-stack-levels"; +op info_flag_authors () : InfoFlag => ":authors"; +op info_flag_errb () : InfoFlag => ":error-behavior"; +// This collides with the ':named' term attribute. This file has the example. +// op info_flag_name () : InfoFlag => ":name"; +op info_flag_reasonu () : InfoFlag => ":reason-unknown"; +op info_flag_version () : InfoFlag => ":version"; -// 10. Command options: TODO +// 10. Command options category BValue; op bvalue_true () : BValue => "true"; op bvalue_false () : BValue => "false"; +category SMTOption; +// NOTE: "Solver-specific option names are allowed and indeed expected." +// A set of standard options is presented here. +op smtoption_diagoc (s:Str) : SMTOption => ":diagnostic-output-channel " s; +op smtoption_globald (b:BValue) : SMTOption => ":global-declarations " b; +op smtoption_interm (b:BValue) : SMTOption => ":interactive-mode " b; +op smtoption_prints (b:BValue) : SMTOption => ":print-success " b; +op smtoption_produceasr (b:BValue) : SMTOption => ":produce-assertions " b; +op smtoption_produceasn (b:BValue) : SMTOption => ":produce-assignments " b; +op smtoption_producem (b:BValue) : SMTOption => ":produce-models " b; +op smtoption_producep (b:BValue) : SMTOption => ":produce-proofs " b; +op smtoption_produceua (b:BValue) : SMTOption => + ":produce-unsat-assumptions " b; +op smtoption_produceuc (b:BValue) : SMTOption => ":produce-unsat-cores " b; +op smtoption_rseed (n:Num) : SMTOption => ":random-seed " n; +op smtoption_regularoc (s:Str) : SMTOption => ":regular-output-channel " s; +op smtoption_reproduciblerl (n:Num) : SMTOption => + ":reproducible-resource-limit " n; +op smtoption_verbosity (n:Num) : SMTOption => ":verbosity " n; +op smtoption_attr (a:Attribute) : SMTOption => a; + // 11. Commands category SortDec; op sort_dec (s:Symbol, n:Num) : SortDec => "(" s n ")"; +category SortDecList; // For spacing; has at least one element +op sort_dec_list_one (i:SortDec) : SortDecList => i; +op sort_dec_list_cons (i:SortDec, spi:SortDecList) : SortDecList => + i " " spi; + category SelectorDec; op selector_dec (s:Symbol, so:SMTSort) : SelectorDec => "(" s so ")"; +category SelectorDecList; // For spacing; has at least one element +op selector_dec_list_one (i:SelectorDec) : SelectorDecList => i; +op selector_dec_list_cons (i:SelectorDec, spi:SelectorDecList) : SelectorDecList => + i " " spi; + category ConstructorDec; -op constructor_dec (s:Symbol, sdl:Seq SelectorDec) : ConstructorDec => - "(" s sdl ")"; +op constructor_dec (s:Symbol, sdl:Option SelectorDecList) : ConstructorDec => + "(" s " " sdl ")"; + +category ConstructorDecList; // For spacing; has at least one element +op constructor_list_one (i:ConstructorDec) : ConstructorDecList => i; +op constructor_list_cons (i:ConstructorDec, spi:ConstructorDecList) + : ConstructorDecList => + i " " spi; category DatatypeDec; -op datatype_dec (c0:ConstructorDec, cs:Seq ConstructorDec) : DatatypeDec - => "(" c0 cs ")"; -// TODO: ( par ( + ) ( + )) +op datatype_dec (cs:ConstructorDecList) : DatatypeDec + => "(" cs ")"; +op datatype_dec_par (symbols: SymbolList, cs:ConstructorDecList) : DatatypeDec + => "(" "par " "(" symbols ")" "(" cs ")" ")"; + +category DatatypeDecList; // For spacing; has at least one element +op datatype_dec_list_one (i:DatatypeDec) : DatatypeDecList => i; +op datatype_dec_list_cons (i:DatatypeDec, spi:DatatypeDecList) + : DatatypeDecList => + i " " spi; category FunctionDec; op function_dec (s:Symbol, sv:Seq SortedVar, so:SMTSort) : FunctionDec @@ -279,6 +388,12 @@ category FunctionDef; op function_def (s:Symbol, sv:Seq SortedVar, so:SMTSort, t:Term) : FunctionDef => s "(" sv ")" so t; +category FunctionDefList; // For spacing; has at least one element +op function_def_list_one (i:FunctionDef) : FunctionDefList => i; +op function_def_list_cons (i:FunctionDef, spi:FunctionDefList) + : FunctionDefList => + i " " spi; + #end @@ -290,48 +405,54 @@ import SMTCore; // 11. Commands (cont.) -// 'the_' is necessary, otherwise it raises "unexpected token 'assert'; expected identifier" -op the_assert (t:Term) : Command => "(" "assert" t ")"; +// cmd_' is necessary, otherwise it raises "unexpected token 'assert'; expected identifier" +op cmd_assert (t:Term) : Command => "(" "assert " t ")"; op check_sat () : Command => "(" "check-sat" ")"; -op check_sat_assuming (ts:Seq Term) : Command => "(" "check-sat-assuming" ts ")"; +op check_sat_assuming (ts:Option TermList) : Command => + "(" "check-sat-assuming " ts ")"; op declare_const (s:Symbol, so:SMTSort) : Command => - "(" "declare-const" s so ")"; -op declare_datatype (s:Symbol, so:SMTSort) : Command => - "(" "declare-datatype" s so ")"; -// TODO: declare-datatypes; what is ^(n+1)? -op declare_fun (s:Symbol, sol:Seq SMTSort, range:SMTSort) : Command => - "(" "declare-fun" s "(" sol ")" range ")"; + "(" "declare-const " s so ")"; +op declare_datatype (s:Symbol, so:DatatypeDec) : Command => + "(" "declare-datatype " s so ")"; +// The size of SortDec and DatatypeDec must be equal, but omit the check in +// this DDM definition because its representation can be quite ugly. +op declare_datatypes (s:SortDecList, so:DatatypeDecList) : Command => + "(" "declare-datatypes" "(" s ")" "(" so ")" ")"; +op declare_fun (s:Symbol, sol:Option SMTSortList, range:SMTSort) : Command => + "(" "declare-fun " s "(" sol ")" range ")"; op declare_sort (s:Symbol, n:Num) : Command => - "(" "declare-sort" s n ")"; + "(" "declare-sort " s n ")"; op declare_sort_parameter (s:Symbol) : Command => - "(" "declare-sort-parameter" s ")"; + "(" "declare-sort-parameter " s ")"; op define_const (s:Symbol, so:SMTSort, t:Term) : Command => - "(" "define-const" s so t ")"; + "(" "define-const " s so t ")"; op define_fun (fdef:FunctionDef) : Command => - "(" "define-fun" fdef ")"; + "(" "define-fun " fdef ")"; op define_fun_rec (fdef:FunctionDef) : Command => - "(" "define-fun-rec" fdef ")"; + "(" "define-fun-rec " fdef ")"; +op define_funs_rec (fdefs:FunctionDefList, terms:TermList) : Command => + "(" "define-funs-rec" "(" fdefs ")" "(" terms ")" ")"; op define_sort (s:Symbol, sl:Seq Symbol, so:SMTSort) : Command => - "(" "define-sort" s "(" sl ")" so ")"; -op the_echo (s:Str) : Command => "(" "echo" s ")"; -op the_exit () : Command => "(" "exit" ")"; + "(" "define-sort " s "(" sl ")" so ")"; +op cmd_echo (s:Str) : Command => "(" "echo " s ")"; +op cmd_exit () : Command => "(" "exit" ")"; op get_assertions () : Command => "(" "get-assertions" ")"; -op get_assignments () : Command => "(" "get-assignments" ")"; -// TODO: get-info +op get_assignment () : Command => "(" "get-assignment" ")"; +op get_info (x:InfoFlag) : Command => "(" "get-info " x ")"; op get_model () : Command => "(" "get-model" ")"; -op get_option (kw:Keyword) : Command => "(" "get-option" kw ")"; +op get_option (kw:Keyword) : Command => "(" "get-option " kw ")"; op get_proof () : Command => "(" "get-proof" ")"; op get_unsat_assumptions () : Command => "(" "get-unsat-assumptions" ")"; op get_unsat_core () : Command => "(" "get-unsat-core" ")"; -op get_value (t0:Term, tl:Seq Term) : Command => - "(" "get-value" "(" t0 tl ")" ")"; -op the_pop (n:Num) : Command => "(" "pop" n ")"; -op the_push (n:Num) : Command => "(" "push" n ")"; -op the_reset () : Command => "(" "reset" ")"; +op get_value (tl:TermList) : Command => + "(" "get-value" "(" tl ")" ")"; +op cmd_pop (n:Num) : Command => "(" "pop " n ")"; +op cmd_push (n:Num) : Command => "(" "push " n ")"; +op cmd_reset () : Command => "(" "reset" ")"; op reset_assertions () : Command => "(" "reset-assertions" ")"; -op set_info (a:Attribute) : Command => "(" "set-info" a ")"; -op set_logic (s:Symbol) : Command => "(" "set-logic" s ")"; -// TODO: set-option +op set_info (a:Attribute) : Command => "(" "set-info " a ")"; +op set_logic (s:Symbol) : Command => "(" "set-logic " s ")"; +op set_option (s:SMTOption) : Command => "(" "set-option " s ")"; #end @@ -354,25 +475,49 @@ op ru_incomplete () : ReasonUnknown => "incomplete"; op ru_other (s:SExpr) : ReasonUnknown => s; category ModelResponse; -op mr_deffun (fdef:FunctionDef) : ModelResponse => "(" "define-fun" fdef ")"; +op mr_deffun (fdef:FunctionDef) : ModelResponse => + "(" "define-fun " fdef ")"; op mr_deffunrec (fdef:FunctionDef) : ModelResponse => - "(" "define-fun-rec" fdef ")"; + "(" "define-fun-rec " fdef ")"; // TODO: define-funs-rec +category SeqModelResponse; +op seqmr_nil () : SeqModelResponse => ; +op seqmr_one (i: ModelResponse) : SeqModelResponse => i; +op seqmr_cons (i: ModelResponse, is: SeqModelResponse) : SeqModelResponse + => i is; + category InfoResponse; -op ir_stack_levels (n:Num) : InfoResponse => ":assertion-stack-response" n; -op ir_authors (s:Str) : InfoResponse => ":authors" s; -op ir_eb (eb:ErrorBehavior) : InfoResponse => ":error-behavior" eb; -op ir_name (n:Str) : InfoResponse => ":name" n; -op ir_unknown (r:ReasonUnknown) : InfoResponse => ":reason-unknown" r; -op ir_ver (s:Str) : InfoResponse => ":version" s; +op ir_stack_levels (n:Num) : InfoResponse => ":assertion-stack-response " n; +op ir_authors (s:Str) : InfoResponse => ":authors " s; +op ir_eb (eb:ErrorBehavior) : InfoResponse => ":error-behavior " eb; +op ir_name (n:Str) : InfoResponse => ":name " n; +op ir_unknown (r:ReasonUnknown) : InfoResponse => ":reason-unknown " r; +op ir_ver (s:Str) : InfoResponse => ":version " s; op ir_attr (a:Attribute) : InfoResponse => a; +category InfoResponseList; +op ir_list_one (i: InfoResponse) : InfoResponseList => i; +op ir_list_cons (i: InfoResponse, is: InfoResponseList) : InfoResponseList + => i is; + category ValuationPair; -op valuation_pair (t1:Term, t2:Term) : ValuationPair => "(" t1 t2 ")"; +op valuation_pair (t1:Term, t2:Term) : ValuationPair => "(" t1 " " t2 ")"; + +category ValuationPairList; +op valuation_pair_list_one (i: ValuationPair) : ValuationPairList => i; +op valuation_pair_list_cons (i: ValuationPair, is: ValuationPairList) + : ValuationPairList + => i is; category TValuationPair; -op t_valuation_pair (t1:Symbol, t2:BValue) : TValuationPair => "(" t1 t2 ")"; +op t_valuation_pair (t1:Symbol, t2:BValue) : TValuationPair => "(" t1 " " t2 ")"; + +category TValuationPairList; +op t_valuation_pair_list_one (i: TValuationPair) : TValuationPairList => i; +op t_valuation_pair_list_cons (i: TValuationPair, is: TValuationPairList) + : TValuationPairList + => i is; category CheckSatResponse; op csr_sat () : CheckSatResponse => "sat"; @@ -383,18 +528,20 @@ category EchoResponse; op echo_response (s:Str) : EchoResponse => s; category GetAssertionsResponse; -op get_assertions_response (t:Seq Term) : GetAssertionsResponse => "(" t ")"; +op get_assertions_response (t:Option TermList) : GetAssertionsResponse => + "(" t ")"; category GetAssignmentResponse; -op get_assignment_response (t:Seq TValuationPair) : GetAssignmentResponse => +op get_assignment_response (t:Option TValuationPairList) + : GetAssignmentResponse => "(" t ")"; category GetInfoResponse; -op get_info_response (i:InfoResponse, i2:Seq InfoResponse) : GetInfoResponse => - "(" i i2 ")"; +op get_info_response (i2:InfoResponseList) : GetInfoResponse => + "(" i2 ")"; category GetModelResponse; -op get_model_response (mr:Seq ModelResponse) : GetModelResponse => +op get_model_response (mr:SeqModelResponse) : GetModelResponse => "(" mr ")"; category GetOptionResponse; @@ -404,16 +551,16 @@ category GetProofResponse; op get_proof_response (s:SExpr) : GetProofResponse => s; category GetUnsatAssumpResponse; -op get_unsat_assump_response (ts:Seq Term) : GetUnsatAssumpResponse => +op get_unsat_assump_response (ts:Option TermList) : GetUnsatAssumpResponse => "(" ts ")"; category GetUnsatCoreResponse; -op get_unsat_core_response (ss:Seq Symbol) : GetUnsatCoreResponse => +op get_unsat_core_response (ss:Option SymbolList) : GetUnsatCoreResponse => "(" ss ")"; category GetValueResponse; -op get_value_response (vp:ValuationPair, vps:Seq ValuationPair) - : GetValueResponse => "(" vp vps ")"; +op get_value_response (vps:Option ValuationPairList) + : GetValueResponse => "(" vps ")"; category SpecificSuccessResponse; op ssr_check_sat (r:CheckSatResponse) : SpecificSuccessResponse => r; @@ -432,10 +579,14 @@ op ssr_get_value (r:GetValueResponse) : SpecificSuccessResponse => r; op success () : Command => "success"; op unsupported () : Command => "unsupported"; op specific_success_response (ssr:SpecificSuccessResponse) : Command => ssr; -op error (msg:Str) : Command => "(" "error" msg ")"; +op error (msg:Str) : Command => "(" "error " msg ")"; #end +/-- +info: Strata.SMT : Dialect +-/ +#guard_msgs in #check SMT namespace Test @@ -449,7 +600,7 @@ op parse_keyword (x:Keyword): Command => "parse_keyword" x ";"; op parse_spec_constant (x:SpecConstant): Command => "parse_spec_constant" x ";"; op parse_sexpr (x:SExpr): Command => "parse_sexpr" x ";"; op parse_index (x:Index): Command => "parse_index" x ";"; -op parse_identifier (x:Identifier): Command => "parse_identifier" x ";"; +op parse_identifier (x:SMTIdentifier): Command => "parse_identifier" x ";"; op parse_sort (x:SMTSort): Command => "parse_sort" x ";"; op parse_attribute_value (x:AttributeValue): Command => "parse_attribute_value" x ";"; @@ -492,7 +643,9 @@ parse_symbol + ; parse_keyword :aaa ; parse_spec_constant 1; +parse_spec_constant -1; parse_spec_constant 1.5; +parse_spec_constant -1.5; parse_spec_constant "test"; parse_sexpr 1; @@ -503,6 +656,7 @@ parse_sexpr (+ a b) ; parse_identifier x ; parse_identifier ( _ move up) ; parse_identifier ( _ BitVec 32) ; +parse_identifier ( _ bv32 12345) ; parse_sort Int ; parse_sort ( _ BitVec 32 ); @@ -524,6 +678,12 @@ parse_term (- 1 (+ 2 3)) ; // Attribute parse_term (=> (! (> x y) :named p1) (! (= x z) :named p2 )) ; +// page 34, 3.6.5. Term attributes +parse_term (forall ((x0 A) (x1 A) (x2 A)) + (! (=> (and (r x0 x1) (r x1 x2)) (r x0 x2)) + :pattern ((r x0 x1) (r x1 x2)) + :pattern ((p x0 a)) + )); // Let parse_term (let ((x (+ 1 2))) x) ; @@ -553,7 +713,40 @@ program SMT; (check-sat) (check-sat-assuming x y) (declare-const x Int) -(declare-datatype X Int) + +// declare-datatype examples at page 66 +(declare-datatype Color ((red) (green) (blue))) +(declare-datatype IntList ( + (empty) + (insert (head Int) (tail IntList)))) +(declare-datatype List + (par (E) ( + (nil) + (cons (car E) (cdr (List E)))))) +(declare-datatype Option + (par (X) ( + (none) + (some (val X))))) +(declare-datatype Pair + (par (X Y) + ((pair (first X) (second Y))))) + +// declare-datatypes examples at page 65 +(declare-datatypes ((Color 0)) ( + ((red) (green) (blue)))) +(declare-datatypes ((IntList 0)) ( + ((empty) (insert (head Int) (tail IntList))))) +(declare-datatypes ((List 1)) ( + (par (T) ((nil) (cons (car T) (cdr (List T))))))) +(declare-datatypes ((Option 1)) ( + (par (X) ((none) (some (val X)))))) +(declare-datatypes ((Pair 2)) ( + (par (X Y) ((pair (first X) (second Y)))))) +(declare-datatypes ((Tree 1) (TreeList 1)) ( + (par (X) ((node (value X) (children (TreeList X))))) + (par (Y) ((empty) + (insert (head (Tree Y)) (tail (TreeList Y))))))) + (declare-fun f (Int Int) Int) (declare-sort Int 10) (declare-sort-parameter X) @@ -563,7 +756,7 @@ program SMT; (echo "x") (exit) (get-assertions) -(get-assignments) +(get-assignment) (get-model) (get-option :h) (get-proof) @@ -576,6 +769,7 @@ program SMT; (reset-assertions) (set-info :t 1) (set-logic MY_LOGIC) +(set-option :global-declarations true) #end @@ -609,7 +803,46 @@ unknown // ) #end - end Test + +-- The ASTs generated by strata_gen. + +namespace SMTDDM + +-- set_option trace.Strata.generator trueß +#strata_gen SMT + +deriving instance BEq for + -- Sequences + SpecConstant, QualifiedIdent, SimpleSymbol, + Symbol, SymbolList, + SortDec, SortDecList, + Reserved, + Keyword, SExpr, AttributeValue, BValue, + Attribute, AttributeList, + SMTOption, + Index, IndexList, + SMTIdentifier, + SMTSort, SMTSortList, + SortedVar, SortedVarList, + QualIdentifier, ValBinding, + Term, TermList, + InfoFlag, + SelectorDec, SelectorDecList, + ConstructorDec, ConstructorDecList, + DatatypeDec, DatatypeDecList, + FunctionDef, FunctionDefList, + Command + +end SMTDDM + + +namespace SMTResponseDDM + +--set_option trace.Strata.generator true +#strata_gen SMTResponse + +end SMTResponseDDM + end Strata diff --git a/Strata/DL/SMT/DDMTransform/Translate.lean b/Strata/DL/SMT/DDMTransform/Translate.lean new file mode 100644 index 000000000..ec5a7f853 --- /dev/null +++ b/Strata/DL/SMT/DDMTransform/Translate.lean @@ -0,0 +1,216 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.SMT.DDMTransform.Parse +import Strata.DL.SMT.Term +import Strata.DDM.Format +import Strata.DDM.Util.DecimalRat + +namespace Strata + +namespace SMTDDM + +private def mkQualifiedIdent (s:String):QualifiedIdent SourceRange := + .qualifiedIdentImplicit SourceRange.none (Ann.mk SourceRange.none s) + +private def mkSimpleSymbol (s:String):SimpleSymbol SourceRange := + match List.find? (fun (_,sym) => sym = s) specialCharsInSimpleSymbol with + | .some (name,_) => + -- This needs hard-coded for now. + (match name with + | "plus" => .simple_symbol_plus SourceRange.none + | "minus" => .simple_symbol_minus SourceRange.none + | "star" => .simple_symbol_star SourceRange.none + | "eq" => .simple_symbol_eq SourceRange.none + | "percent" => .simple_symbol_percent SourceRange.none + | "questionmark" => .simple_symbol_questionmark SourceRange.none + | "period" => .simple_symbol_period SourceRange.none + | "dollar" => .simple_symbol_dollar SourceRange.none + | "tilde" => .simple_symbol_tilde SourceRange.none + | "amp" => .simple_symbol_amp SourceRange.none + | "caret" => .simple_symbol_caret SourceRange.none + | "lt" => .simple_symbol_lt SourceRange.none + | "gt" => .simple_symbol_gt SourceRange.none + | "at" => .simple_symbol_at SourceRange.none + | _ => panic! s!"Unknown simple symbol: {name}") + | .none => + .simple_symbol_qid SourceRange.none (mkQualifiedIdent s) + +private def mkSymbol (s:String):Symbol SourceRange := + .symbol SourceRange.none (mkSimpleSymbol s) + +private def mkIdentifier (s:String):SMTIdentifier SourceRange := + .iden_simple SourceRange.none (mkSymbol s) + +private def translateFromTermPrim (t:SMT.TermPrim): + Except String (SMTDDM.Term SourceRange) := do + let srnone := SourceRange.none + match t with + | .bool b => + let ss:SimpleSymbol SourceRange := + if b then .simple_symbol_tt srnone else .simple_symbol_ff srnone + return (.qual_identifier srnone + (.qi_ident srnone (.iden_simple srnone (.symbol srnone ss)))) + | .int i => + let abs_i := if i < 0 then -i else i + if i >= 0 then + return .spec_constant_term srnone (.sc_numeral srnone abs_i.toNat) + else + -- Note that negative integers like '-1231' are symbols in Std! (Sec 3.1. Lexicon) + -- The only way to create a unary symbol is through idenitifers, but this + -- makes its DDM format wrapped with pipes, like '|-1231|`. Since such + -- representation cannot be recognized by Z3, make a workaround which is to have + -- separate `*_neg` categories for sc_numeral/decimal. + return .spec_constant_term srnone (.sc_numeral_neg srnone abs_i.toNat) + | .real dec => + return .spec_constant_term srnone (.sc_decimal srnone dec) + | .bitvec bv => + let bvty := mkSymbol (s!"bv{bv.toNat}") + let val:Index SourceRange := .ind_numeral srnone bv.width + return (.qual_identifier srnone + (.qi_ident srnone (.iden_indexed srnone bvty (.index_list_one srnone val)))) + | .string s => + return .spec_constant_term srnone (.sc_str srnone s) + +-- List of SMTSort to SeqPSMTSort. +-- Hope this could be elided away later. :( +private def translateFromSMTSortList (l: List (SMTSort SourceRange)): + Option (SMTSortList SourceRange) := + let srnone := SourceRange.none + match l with + | [] => .none + | h::[] => .some (.smtsort_list_one srnone h) + | h1::h2::t => .some ( + match translateFromSMTSortList t with + | .none => .smtsort_list_cons srnone h1 (.smtsort_list_one srnone h2) + | .some t => .smtsort_list_cons srnone h1 (.smtsort_list_cons srnone h2 t)) + +private def translateFromTermType (t:SMT.TermType): + Except String (SMTDDM.SMTSort SourceRange) := do + let srnone := SourceRange.none + match t with + | .prim tp => + match tp with + | .bitvec n => + return (.smtsort_ident srnone + (.iden_indexed srnone + (mkSymbol "BitVec") + (.index_list_one srnone (.ind_numeral srnone n)))) + | .trigger => + throw "don't know how to translate a trigger type" + | _ => + return .smtsort_ident srnone (mkIdentifier + (match tp with + | .bool => "Bool" + | .int => "Int" + | .real => "Real" + | .string => "String" + | .regex => "RegLan" + | _ => panic! "unreachable")) + | .option _ => + throw "don't know how to translate an option type" + | .constr id args => + let argtys <- args.mapM translateFromTermType + match translateFromSMTSortList argtys with + | .none => throw "empty argument to type constructor" + | .some argtys => + return .smtsort_param srnone (mkIdentifier id) argtys + +-- List of SortedVar to SeqPSortedVar. +-- Hope this could be elided away later. :( +private def translateFromSortedVarList (l: List (SortedVar SourceRange)): + Option (SortedVarList SourceRange) := + let srnone := SourceRange.none + match l with + | [] => .none + | h::[] => .some (.sorted_var_list_one srnone h) + | h1::h2::t => .some ( + match translateFromSortedVarList t with + | .none => .sorted_var_list_cons srnone h1 (.sorted_var_list_one srnone h2) + | .some t => + .sorted_var_list_cons srnone h1 (.sorted_var_list_cons srnone h2 t)) + +-- List of SortedVar to SeqPSortedVar. +-- Hope this could be elided away later. :( +private def translateFromTermList (l: List (Term SourceRange)): + Option (TermList SourceRange) := + let srnone := SourceRange.none + match l with + | [] => .none + | h::[] => .some (.term_list_one srnone h) + | h1::h2::t => .some ( + match translateFromTermList t with + | .none => .term_list_cons srnone h1 (.term_list_one srnone h2) + | .some t => .term_list_cons srnone h1 (.term_list_cons srnone h2 t)) + +def translateFromTerm (t:SMT.Term): Except String (SMTDDM.Term SourceRange) := do + let srnone := SourceRange.none + match t with + | .prim p => translateFromTermPrim p + | .var v => + return .qual_identifier srnone (.qi_ident srnone (.iden_simple srnone + (.symbol srnone (mkSimpleSymbol v.id)))) + | .none _ | .some _ => throw "don't know how to translate none and some" + | .app op args _ => + let args' <- args.mapM translateFromTerm + match translateFromTermList args' with + | .some args => + return (.qual_identifier_args srnone + (.qi_ident srnone (mkIdentifier op.mkName)) args) + | .none => + return (.qual_identifier srnone (.qi_ident srnone (mkIdentifier op.mkName))) + | .quant qkind args _tr body => + let args_sorted:List (SMTDDM.SortedVar SourceRange) <- + args.mapM + (fun ⟨name,ty⟩ => do + let ty' <- translateFromTermType ty + return .sorted_var srnone (mkSymbol name) ty') + match translateFromSortedVarList args_sorted with + | .none => throw "empty quantifier" + | .some args_sorted => + let body <- translateFromTerm body + match qkind with + | .all => + return .forall_smt srnone args_sorted body + | .exist => + return .exists_smt srnone args_sorted body + + +private def dummy_prg_for_toString := + let dialect_map := DialectMap.ofList! + [Strata.initDialect, Strata.smtReservedKeywordsDialect, Strata.SMTCore, + Strata.SMT] + Program.create dialect_map "SMT" #[] + +def toString (t:SMT.Term): Except String String := do + let ddm_term <- translateFromTerm t + let ddm_ast := SMTDDM.Term.toAst ddm_term + let fmt := Operation.instToStrataFormat.mformat ddm_ast + (dummy_prg_for_toString.formatContext {}) + dummy_prg_for_toString.formatState + return fmt.format |>.render + + +/-- info: Except.ok "(+ 10 20)" -/ +#guard_msgs in #eval (toString + (.app SMT.Op.add [(.prim (.int 10)), (.prim (.int 20))] .int)) + +/-- info: Except.ok "(+ 10 -20)" -/ +#guard_msgs in #eval (toString + (.app SMT.Op.add [(.prim (.int 10)), (.prim (.int (-20)))] .int)) + +/-- info: Except.ok "(+ 0.1 0.2)" -/ +#guard_msgs in #eval (toString + (.app SMT.Op.add [(.prim (.real (Decimal.mk 1 (-1)))), + (.prim (.real (Decimal.mk 2 (-2))))] .int)) + +/-- info: Except.ok "(_ bv1 32)" -/ +#guard_msgs in #eval (toString + (.prim (.bitvec (BitVec.ofNat 32/-width-/ 1/-value-/)))) + +end SMTDDM + +end Strata diff --git a/Strata/DL/SMT/Encoder.lean b/Strata/DL/SMT/Encoder.lean index b9937c67e..8a8b74e02 100644 --- a/Strata/DL/SMT/Encoder.lean +++ b/Strata/DL/SMT/Encoder.lean @@ -4,6 +4,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +import Strata.DL.SMT.DDMTransform.Translate import Strata.DL.SMT.Factory import Strata.DL.SMT.Op import Strata.DL.SMT.Solver @@ -195,8 +196,20 @@ def defineApp (inBinder : Bool) (tyEnc : String) (op : Op) (tEncs : List String) defineTerm inBinder tyEnc s!"{← encodeUF f}" else defineTerm inBinder tyEnc s!"({← encodeUF f} {args})" - | _ => + | .datatype_op .constructor name => + -- Zero-argument constructors are constants in SMT-LIB, not function applications + -- For parametric datatypes, we need to cast the constructor to the concrete type + if tEncs.isEmpty then + defineTerm inBinder tyEnc s!"(as {name} {tyEnc})" + else + defineTerm inBinder tyEnc s!"({name} {args})" + | .datatype_op _ _ => defineTerm inBinder tyEnc s!"({encodeOp op} {args})" + | _ => + if tEncs.isEmpty then + defineTerm inBinder tyEnc s!"({encodeOp op})" + else + defineTerm inBinder tyEnc s!"({encodeOp op} {args})" -- Helper function for quantifier generation def defineQuantifierHelper (inBinder : Bool) (quantKind : String) (varDecls : String) (trEncs: List (List String)) (tEnc : String) : EncoderM String := @@ -241,13 +254,10 @@ def encodeTerm (inBinder : Bool) (t : Term) : EncoderM String := do let enc ← match t with | .var v => return v.id - | .prim p => - match p with - | .bool b => return if b then "true" else "false" - | .int i => return encodeInt i - | .real r => return r - | .bitvec bv => return encodeBitVec bv - | .string s => return encodeString s + | .prim _ => + match SMTDDM.toString t with + | .ok s => return s + | .error _ => return "" | .none _ => defineTerm inBinder tyEnc s!"(as none {tyEnc})" | .some t₁ => defineTerm inBinder tyEnc s!"(some {← encodeTerm inBinder t₁})" | .app .re_allchar [] .regex => return encodeReAllChar @@ -294,7 +304,7 @@ def termToString (e : Term) : IO String := do let solver ← Solver.bufferWriter b let _ ← ((Encoder.encodeTerm False e).run EncoderState.init).run solver let contents ← b.get - if h: String.validateUTF8 contents.data + if h: contents.data.IsValidUTF8 then pure (String.fromUTF8 contents.data h) else pure "Converting SMT Term to bytes produced an invalid UTF-8 sequence." diff --git a/Strata/DL/SMT/Op.lean b/Strata/DL/SMT/Op.lean index 2aa4dc19f..8b53f7198 100644 --- a/Strata/DL/SMT/Op.lean +++ b/Strata/DL/SMT/Op.lean @@ -158,6 +158,12 @@ inductive Op.Strings : Type where | re_index : Nat → Op.Strings deriving Repr, DecidableEq, Inhabited, Hashable +inductive Op.DatatypeFuncs : Type where + | constructor : Op.DatatypeFuncs + | tester : Op.DatatypeFuncs + | selector : Op.DatatypeFuncs +deriving Repr, DecidableEq, Inhabited, Hashable + inductive Op : Type where -- SMTLib core theory of equality with uninterpreted functions (`UF`) | core : Op.Core → Op @@ -171,6 +177,8 @@ inductive Op : Type where | triggers -- Core ADT operators with a trusted mapping to SMT | option_get + -- Datatype ops (for user-defined algebraic datatypes) + | datatype_op : Op.DatatypeFuncs → String → Op deriving Repr, DecidableEq, Inhabited, Hashable -- Generate abbreviations like `Op.not` for `Op.core Op.Core.not` for @@ -182,7 +190,7 @@ elab "#genOpAbbrevs" : command => do if let some (.inductInfo coreInfo) := env.find? `Strata.SMT.Op.Core then for ctor in coreInfo.ctors do - let ctorName := ctor.toString.split (· == '.') |>.getLast! + let ctorName := ctor.toString.splitToList (· == '.') |>.getLast! let name := Lean.Name.mkStr2 "Op" ctorName if ctorName == "uf" then let abbrevCmd ← `(command| abbrev $(mkIdent name) (arg : UF) := Op.core (Op.Core.uf arg)) @@ -193,14 +201,14 @@ elab "#genOpAbbrevs" : command => do if let some (.inductInfo numInfo) := env.find? `Strata.SMT.Op.Num then for ctor in numInfo.ctors do - let ctorName := ctor.toString.split (· == '.') |>.getLast! + let ctorName := ctor.toString.splitToList (· == '.') |>.getLast! let name := Lean.Name.mkStr2 "Op" ctorName let abbrevCmd ← `(command| abbrev $(mkIdent name) := Op.num $(mkIdent ctor)) abbrevs := abbrevs.push (name, abbrevCmd) if let some (.inductInfo bvInfo) := env.find? `Strata.SMT.Op.BV then for ctor in bvInfo.ctors do - let ctorName := ctor.toString.split (· == '.') |>.getLast! + let ctorName := ctor.toString.splitToList (· == '.') |>.getLast! let name := Lean.Name.mkStr2 "Op" ctorName if ctorName == "zero_extend" then let abbrevCmd ← `(command| abbrev $(mkIdent name) (n : Nat) := Op.bv (Op.BV.zero_extend n)) @@ -211,7 +219,7 @@ elab "#genOpAbbrevs" : command => do if let some (.inductInfo strInfo) := env.find? `Strata.SMT.Op.Strings then for ctor in strInfo.ctors do - let ctorName := ctor.toString.split (· == '.') |>.getLast! + let ctorName := ctor.toString.splitToList (· == '.') |>.getLast! let name := Lean.Name.mkStr2 "Op" ctorName if ctorName == "re_index" then let abbrevCmd ← `(command| abbrev $(mkIdent name) (n : Nat) := Op.str (Op.Strings.re_index n)) @@ -285,6 +293,8 @@ def Op.mkName : Op → String | .zero_extend _ => "zero_extend" | .triggers => "triggers" | .option_get => "option.get" + | .datatype_op .tester name => s!"is-{name}" + | .datatype_op _ name => name | .str_length => "str.len" | .str_concat => "str.++" | .str_lt => "str.<" diff --git a/Strata/DL/SMT/Term.lean b/Strata/DL/SMT/Term.lean index 96203e77d..c856807c9 100644 --- a/Strata/DL/SMT/Term.lean +++ b/Strata/DL/SMT/Term.lean @@ -7,6 +7,8 @@ import Strata.DL.SMT.TermType import Strata.DL.SMT.Basic import Strata.DL.SMT.Op +import Strata.DDM.Util.Decimal +import Strata.DDM.Util.DecimalRat /-! Based on Cedar's Term language. @@ -32,7 +34,7 @@ namespace Strata.SMT inductive TermPrim : Type where | bool : Bool → TermPrim | int : Int → TermPrim - | real : String → TermPrim + | real : Decimal → TermPrim | bitvec : ∀ {n}, BitVec n → TermPrim | string : String → TermPrim deriving instance Repr, Inhabited, DecidableEq for TermPrim @@ -47,7 +49,7 @@ def TermPrim.mkName : TermPrim → String def TermPrim.lt : TermPrim → TermPrim → Bool | .bool b₁, .bool b₂ => b₁ < b₂ | .int i₁, .int i₂ => i₁ < i₂ - | .real r₁, .real r₂ => r₁ < r₂ -- TODO + | .real r₁, .real r₂ => r₁.toRat < r₂.toRat | @TermPrim.bitvec n₁ bv₁, @TermPrim.bitvec n₂ bv₂ => n₁ < n₂ || (n₁ = n₂ && bv₁.toNat < bv₂.toNat) | .string s₁, .string s₂ => s₁ < s₂ @@ -166,7 +168,7 @@ instance Term.decLt (x y : Term) : Decidable (x < y) := abbrev Term.bool (b : Bool) : Term := .prim (.bool b) abbrev Term.int (i : Int) : Term := .prim (.int i) -abbrev Term.real (r : String) : Term := .prim (.real r) +abbrev Term.real (r : Decimal) : Term := .prim (.real r) abbrev Term.bitvec {n : Nat} (bv : BitVec n) : Term := .prim (.bitvec bv) abbrev Term.string (s : String) : Term := .prim (.string s) diff --git a/Strata/DL/Util/DecidableEq.lean b/Strata/DL/Util/DecidableEq.lean new file mode 100644 index 000000000..f74ae5e33 --- /dev/null +++ b/Strata/DL/Util/DecidableEq.lean @@ -0,0 +1,30 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +def beq_eq_DecidableEq + {T : Type} + (beq : T → T → Bool) + (beq_eq : (x1 x2 : T) → beq x1 x2 = true ↔ x1 = x2) : + DecidableEq T := + fun x1 x2 => + let eq := beq_eq x1 x2 + if h: beq x1 x2 then + isTrue (eq.mp h) + else + isFalse (fun heq => h (eq.mpr heq)) + +/-- +Solves goals of the form `beq e1 e2 = true ↔ e1 = e2` if `beq` is +marked with `@[grind]`. +-/ +syntax "solve_beq" ident ident : tactic +macro_rules + | `(tactic|solve_beq $e1:ident $e2:ident) => + `(tactic| + constructor <;> intro h <;> + try (induction $e1:ident generalizing $e2 <;> cases $e2:ident <;> grind) <;> + (subst_vars; induction $e2:ident <;> grind) + ) diff --git a/Strata/DL/Util/Map.lean b/Strata/DL/Util/Map.lean index 2e4f65129..35d5f1a69 100644 --- a/Strata/DL/Util/Map.lean +++ b/Strata/DL/Util/Map.lean @@ -111,6 +111,9 @@ def Map.values (m : Map α β) : List β := def Map.disjointp [DecidableEq α] (m1 m2 : Map α β) : Prop := ∀ k, (m1.find? k) = none ∨ (m2.find? k = none) +def Map.fmap (f: β → γ) (m: Map α β) : Map α γ := + List.map (fun (x, y) => (x, f y)) m + --------------------------------------------------------------------- theorem Map.find?_mem_keys [DecidableEq α] (m : Map α β) diff --git a/Strata/DL/Util/Relations.lean b/Strata/DL/Util/Relations.lean new file mode 100644 index 000000000..1ea9af812 --- /dev/null +++ b/Strata/DL/Util/Relations.lean @@ -0,0 +1,28 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +section Relation + +def Relation (A: Type) := A → A → Prop +def Reflexive (r: Relation A) : Prop := ∀ x, r x x +def Transitive (r: Relation A) : Prop := ∀ x y z, r x y → r y z → r x z + +inductive ReflTrans {A: Type} (r: Relation A) : Relation A where + | refl : ∀ x, ReflTrans r x x + | step: ∀ x y z, r x y → ReflTrans r y z → ReflTrans r x z + +theorem ReflTrans_Reflexive {A: Type} (r: Relation A): + Reflexive (ReflTrans r) := by apply ReflTrans.refl + +theorem ReflTrans_Transitive {A: Type} (r: Relation A): + Transitive (ReflTrans r) := by + unfold Transitive; intros x y z rxy + induction rxy generalizing z + case refl => simp + case step x1 y1 z1 rxy1 ryz1 IH => + intros rzz1; + apply (ReflTrans.step _ y1 _ rxy1 (IH _ rzz1)) + +end Relation diff --git a/Strata/DL/Util/StringGen.lean b/Strata/DL/Util/StringGen.lean index 2f11f1b64..5aa0b207b 100644 --- a/Strata/DL/Util/StringGen.lean +++ b/Strata/DL/Util/StringGen.lean @@ -17,8 +17,9 @@ import Strata.DL.Util.Counter /-- `s.IsSuffix t` checks if the string `s` is a suffix of the string `t`. from mathlib https://github.com/leanprover-community/mathlib4/blob/f3c56c29d5c787d62f66c207e097a159ff66318a/Mathlib/Data/String/Defs.lean#L37-L39 -/ -def String.IsSuffix : String → String → Prop - | ⟨d1⟩, ⟨d2⟩ => List.IsSuffix d1 d2 +abbrev String.IsSuffix (s1 s2 : String) : Prop := List.IsSuffix s1.toList s2.toList + +local infixl:50 " <:+ " => String.IsSuffix /-- Wrapper around CounterState to allow a prefix -/ structure StringGenState where @@ -55,32 +56,21 @@ theorem String.append_eq_suffix (as bs bs' : String): by_cases bs = bs' <;> simp_all next Hne => have Heq' := String.ext_iff.mp Heq - have Hne' : ¬ bs.data = bs'.data := by + have Hne' : ¬ bs.toList = bs'.toList := by intros Heq have HH := String.ext_iff.mpr Heq contradiction - simp [String.data_append] at * + simp at * contradiction theorem String.append_eq_prefix (as as' bs : String): (as ++ bs = as' ++ bs) → as = as' := by intros Heq by_cases as = as' <;> simp_all - next Hne => - have Heq' := String.ext_iff.mp Heq - have Hne' : ¬ as.data = as'.data := by - intros Heq - have HH := String.ext_iff.mpr Heq - contradiction - simp [String.data_append] at * - contradiction theorem List.reverse_injective : List.reverse l₁ = List.reverse l₂ → l₁ = l₂ := List.reverse_inj.mp -theorem String.data_wrap : pf = { data:= pf : String}.data := rfl -theorem String.data_wrap_eq (a b : String) : a.data = b.data → a = b := String.ext - theorem StringGenState.contains : StringGenState.gen pf σ = (s, σ') → s ∈ σ'.generated.unzip.2 := by @@ -109,21 +99,21 @@ theorem Nat_digitchar_neq_underscore {x: Nat}: ¬ '_' = Nat.digitChar x := by unfold Nat.digitChar repeat (cases x; simp; rename_i x; simp [*]) -theorem Nat_toDigitsCore_not_contain_underscore: ¬'_' ∈ l → ¬'_' ∈ (Nat.toDigitsCore 10 n m l).asString.data := by +theorem Nat_toDigitsCore_not_contain_underscore {n m l} : '_' ∉ l → '_' ∉ Nat.toDigitsCore 10 n m l := by intro Hnin induction n using Nat.strongRecOn generalizing m l rename_i n ind cases n - simp [Nat.toDigitsCore, List.asString, Hnin] + simp [Nat.toDigitsCore, Hnin] rename_i n - simp [Nat.toDigitsCore, List.asString] + simp [Nat.toDigitsCore] split simp [Nat_digitchar_neq_underscore, Hnin] apply ind <;> simp [*, Nat_digitchar_neq_underscore] -theorem Nat_toString_not_contain_underscore {x: Nat} : ¬ '_' ∈ (toString x).data := by +theorem Nat_toString_not_contain_underscore {x: Nat} : '_' ∉ (toString x).toList := by simp [toString, Nat.repr, Nat.toDigits] - exact Nat_toDigitsCore_not_contain_underscore (by simp) + exact Nat_toDigitsCore_not_contain_underscore (l := []) (by simp) theorem Nat_digitChar_index: x.digitChar = #['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','*'][min x 16]'(by simp; omega) := by @@ -132,21 +122,14 @@ theorem Nat_digitChar_index: x.digitChar = repeat (cases x; simp; rename_i x) any_goals simp -theorem neq_elem_of_neq_index_of_nodup (H: List.Nodup a) (Hl1: x < a.length) (Hl2: y < a.length) (Hneq: ¬ x = y): ¬ a[x]'Hl1 = a[y]'Hl2 := by +theorem nodup_implies_injective (H: List.Nodup a) (Hl1: x < a.length) (Hl2: y < a.length) (eq : a[x]'Hl1 = a[y]'Hl2) : x = y := by unfold List.Nodup at H induction a generalizing x y - simp at Hl1 - rename_i h t ind - simp at H - cases x; cases y - contradiction - simp - apply H.left _ (by simp) - cases y <;> simp - rename_i y - simp [Eq.comm, H.left (t[y]'(by simp at Hl1; omega)) (by simp)] - rename_i x y - simp_all + case nil => + simp at Hl1 + case cons h t ind => + simp only [List.pairwise_cons] at H + grind theorem Nat_eq_of_digitChar_eq : n < 16 → m < 16 → n.digitChar = m.digitChar → n = m := by intro H1 H2 @@ -156,11 +139,7 @@ theorem Nat_eq_of_digitChar_eq : n < 16 → m < 16 → n.digitChar = m.digitChar have: min m 16 = m := by omega simp [this] intro H - false_or_by_contra - have : ¬ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '*'][n]'(by simp; omega) = - ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '*'][m]'(by simp; omega) :=by - apply neq_elem_of_neq_index_of_nodup (by simp) (by simp; omega) (by simp; omega) (by assumption) - contradiction + apply nodup_implies_injective (by simp) _ _ H theorem Nat_toDigitsCore_list_suffix : l <:+ Nat.toDigitsCore 10 x n l := by induction x generalizing n l <;> simp [Nat.toDigitsCore] @@ -250,67 +229,44 @@ theorem Nat_eq_of_toDigitsCore_eq : x > n → y > m theorem Nat_eq_of_toString_eq {x y: Nat}: (toString x) = (toString y) → x = y := by intro H - simp [toString, Nat.repr, Nat.toDigits, List.asString] at H - apply Nat_eq_of_toDigitsCore_eq (by simp) (by simp) H + simp only [toString, Nat.repr] at H + apply Nat_eq_of_toDigitsCore_eq (by simp) (by simp) (String.ofList_injective H) + +private theorem under_toList : "_".toList = ['_'] := rfl theorem Nat_eq_of_StringGen_suffix {x y: Nat}: ("_" ++ toString x).IsSuffix (s ++ "_" ++ toString y) → x = y := by intro Hsuf apply Nat_eq_of_toString_eq - simp only [String.IsSuffix] at Hsuf - by_cases Hc: (toString x).length < (toString y).length - have Hsuf': (toString y).data <:+ ((s ++ "_").append (toString y)).data := by - simp only [String.append, List.append_assoc, List.cons_append, List.nil_append, toString] - apply List.suffix_append_of_suffix - simp - have : ("_".append (toString x)).data <:+ (toString y).data := by - apply List.suffix_of_suffix_length_le Hsuf Hsuf' - simp [String.append, String.length, toString] at * - omega - have : ¬ ("_".append (toString x)).data <:+ (toString y).data := by - intro h; - simp [String.append, List.IsSuffix] at h - obtain ⟨t, h⟩ := h - have : '_' ∈ (toString y).data := by simp [← h] + if x_lt : (toString x).length < (toString y).length then + simp only [String.IsSuffix, String.toList_append, under_toList] at Hsuf + have Hsuf': (toString y).toList <:+ s.toList ++ ['_'] ++ (toString y).toList := + List.suffix_append_of_suffix (List.suffix_refl _) + have ⟨t, h⟩ : ['_'] ++ (toString x).toList <:+ (toString y).toList := + List.suffix_of_suffix_length_le Hsuf Hsuf' (by simp; exact x_lt) + have : '_' ∈ (toString y).toList := by simp [← h] have := @Nat_toString_not_contain_underscore y contradiction - contradiction - --case 2 - by_cases Hc: (toString x).length > (toString y).length - have Hsuf : (toString x).data <:+ ((s ++ "_").append (toString y)).data := by - simp [String.append, toString, List.IsSuffix] at * - obtain ⟨t, H⟩ := Hsuf - exists t ++ ['_'] - simp [← H] - have Hsuf': ("_".append (toString y)).data <:+ ((s ++ "_").append (toString y)).data := by - simp only [String.append, List.append_assoc, List.cons_append, List.nil_append] - apply List.suffix_append_of_suffix - simp - have H: ("_".append (toString y)).data <:+ (toString x).data := by - apply List.suffix_of_suffix_length_le Hsuf' Hsuf - simp [String.append, String.length, toString] at * - omega - have : ¬ ("_".append (toString y)).data <:+ (toString x).data := by - intro h; - simp [String.append, List.IsSuffix] at h - obtain ⟨t, h⟩ := h - have : '_' ∈ (toString x).data := by simp [← h] + else if x_gt : (toString x).length > (toString y).length then + have Hsuf : (toString x).toList <:+ s.toList ++ ['_'] ++ (toString y).toList := by + obtain ⟨t, H⟩ := Hsuf + exists t ++ ['_'] + simp only [String.toList_append, under_toList, List.append_assoc] at H + simp only [List.append_assoc] + exact H + have Hsuf': ['_'] ++ (toString y).toList <:+ s.toList ++ ['_'] ++ (toString y).toList := by + simp only [List.append_assoc] + exact List.suffix_append_of_suffix (List.suffix_refl _) + have ⟨t, h⟩ : ['_'] ++ (toString y).toList <:+ (toString x).toList := + List.suffix_of_suffix_length_le Hsuf' Hsuf (by simp; omega) + have : '_' ∈ (toString x).toList := by simp [← h] have := @Nat_toString_not_contain_underscore x contradiction - contradiction - -- case 3 - have Hc: (toString x).data.length = (toString y).data.length := by simp [String.length, toString] at *; omega - have Hsuf : (toString x).data <:+ ((s ++ "_").append (toString y)).data := by - simp [String.append, toString, List.IsSuffix] at * - obtain ⟨t, H⟩ := Hsuf - exists t ++ ['_'] - simp [← H] - have Hsuf': (toString y).data <:+ ((s ++ "_").append (toString y)).data := by - simp only [String.append, List.append_assoc, List.cons_append, List.nil_append, toString] - apply List.suffix_append_of_suffix - simp - simp [List.suffix_iff_eq_drop, Hc] at * - rw [← Hsuf] at Hsuf' - simp [String.ext_iff, Hsuf'] + else + have eq_len: (toString x).length = (toString y).length := by omega + obtain ⟨cs, H⟩ := Hsuf + simp only [String.toList_append, ← List.append_assoc] at H + have this := List.append_inj_right' H eq_len + exact String.toList_inj.mp this /-- The uniqueness of the generated string follows from the following: given that the numbers at the end of all strings are unique, then the strings themselves must be unique. @@ -334,7 +290,7 @@ theorem StringGenState.WFMono : simp at Hcontra intro c s H cases H - rename_i H - simp [H.right, H.left, String.IsSuffix, String.append] - apply List.suffix_append - apply Hwf.right.right.right <;> assumption + · rename_i H + simp only [H.right, H.left, String.IsSuffix, String.toList_append, List.append_assoc] + apply List.suffix_append + · apply Hwf.right.right.right <;> assumption diff --git a/Strata/Languages/B3/DDMTransform/Conversion.lean b/Strata/Languages/B3/DDMTransform/Conversion.lean new file mode 100644 index 000000000..16d5d466e --- /dev/null +++ b/Strata/Languages/B3/DDMTransform/Conversion.lean @@ -0,0 +1,1007 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.B3.DDMTransform.ParseCST +import Strata.Languages.B3.DDMTransform.DefinitionAST + +/-! +# B3 ↔ DDM Bidirectional Conversion + +This module provides bidirectional conversion between B3 AST types and B3 CST types. + +## B3AST → B3CST Conversion +Converts abstract syntax (de Bruijn indices) to concrete syntax (named identifiers). +Used for formatting and pretty-printing B3 constructs using DDM's formatting system. + +## B3CST → B3AST Conversion +Converts concrete syntax (named identifiers) to abstract syntax (de Bruijn indices). +Used for parsing B3 syntax via DDM and converting it back to B3 AST. + +## Context Management +A list of variable names is maintained to convert between indices and names. +-/ + +namespace B3 + +open Strata +open Strata.B3CST +open Strata.B3AST + +--------------------------------------------------------------------- +-- Helper Instances +--------------------------------------------------------------------- + +instance : ToString SourceRange where + toString _sr := "" + +--------------------------------------------------------------------- +-- Conversion Errors +--------------------------------------------------------------------- + +/-- Errors that can occur during CST→AST conversion (parsing) -/ +inductive CSTToASTError (M : Type) where + | unresolvedIdentifier (name : String) (metadata : M) : CSTToASTError M + deriving Inhabited + +namespace CSTToASTError + +def toString [ToString M] : CSTToASTError M → String + | unresolvedIdentifier name _m => s!"Unresolved identifier '{name}'" + +instance [ToString M] : ToString (CSTToASTError M) where + toString := CSTToASTError.toString + +def toFormat [ToString M] : CSTToASTError M → Std.Format + | unresolvedIdentifier name _m => f!"Unresolved identifier '{name}'" + +instance [ToString M] : Std.ToFormat (CSTToASTError M) where + format := CSTToASTError.toFormat + +end CSTToASTError + +/-- Errors that can occur during AST→CST conversion (formatting) -/ +inductive ASTToCSTError (M : Type) where + | variableOutOfBounds (index : Nat) (contextSize : Nat) (metadata : M) : ASTToCSTError M + | unsupportedVariableReference (index : Nat) (metadata : M) : ASTToCSTError M + deriving Inhabited + +namespace ASTToCSTError + +def toString [ToString M] : ASTToCSTError M → String + | variableOutOfBounds idx size _m => + s!"Variable index @{idx} is out of bounds (context has {size} variables)" + | unsupportedVariableReference idx _m => + s!"Variable reference @{idx} not yet supported in concrete syntax. " ++ + s!"B3 concrete syntax currently only supports referencing the most recent variable " ++ + s!"or 'old' values in procedure contexts." + +instance [ToString M] : ToString (ASTToCSTError M) where + toString := ASTToCSTError.toString + +def toFormat [ToString M] : ASTToCSTError M → Std.Format + | variableOutOfBounds idx size _m => + f!"Variable index @{idx} is out of bounds (context has {size} variables)" + | unsupportedVariableReference idx _m => + f!"Variable reference @{idx} not yet supported in concrete syntax. " ++ + f!"B3 concrete syntax currently only supports referencing the most recent variable " ++ + f!"or 'old' values in procedure contexts." + +instance [ToString M] : Std.ToFormat (ASTToCSTError M) where + format := ASTToCSTError.toFormat + +end ASTToCSTError + +--------------------------------------------------------------------- +-- B3AnnFromCST Typeclass +--------------------------------------------------------------------- + +/-- +Typeclass for creating annotations when converting CST → AST. +Methods extract multiple metadata from a single CST metadata when AST needs more. +-/ +class B3AnnFromCST (α : Type) where + -- Literals: AST needs one extra metadata for the literal wrapper + annForLiteral : α → α + -- Literals: AST needs one extra metadata for the literal type (.intLit/.stringLit/.boolLit) + annForLiteralType : α → α + -- Unary ops: AST needs one extra metadata for the .unaryOp wrapper + annForUnaryOp : α → α + -- Unary ops: AST needs one extra metadata for the op type (.not/.neg) + annForUnaryOpType : α → α + -- Binary ops: AST needs one extra metadata for the .binaryOp wrapper + annForBinaryOp : α → α + -- Binary ops: AST needs one extra metadata for the op type (.add/.sub/etc) + annForBinaryOpType : α → α + -- Function calls: AST needs two extra metadata for fn and args Anns + annForFunctionCall : α → α + annForFunctionCallName : α → α + annForFunctionCallArgs : α → α + -- Labeled expressions: AST needs one extra metadata for the label Ann + annForLabeledExpr : α → α + annForLabeledExprLabel : α → α + -- Let expressions: AST needs one extra metadata for the var Ann + annForLetExpr : α → α + annForLetExprVar : α → α + -- If-then-else: AST has same metadata count (passthrough) + annForIte : α → α + -- Quantifiers: AST needs three extra metadata for kind, vars (Seq), and patterns Anns + annForQuantifierExpr : α → α + annForQuantifierKind : α → α + annForQuantifierVars : α → α + annForQuantifierPatterns : α → α + -- Patterns: AST needs one extra metadata for the exprs Ann + annForPattern : α → α + annForPatternExprs : α → α + +instance : B3AnnFromCST Unit where + annForLiteral _ := () + annForLiteralType _ := () + annForUnaryOp _ := () + annForUnaryOpType _ := () + annForBinaryOp _ := () + annForBinaryOpType _ := () + annForFunctionCall _ := () + annForFunctionCallName _ := () + annForFunctionCallArgs _ := () + annForLabeledExpr _ := () + annForLabeledExprLabel _ := () + annForLetExpr _ := () + annForLetExprVar _ := () + annForIte _ := () + annForQuantifierExpr _ := () + annForQuantifierKind _ := () + annForQuantifierVars _ := () + annForQuantifierPatterns _ := () + annForPattern _ := () + annForPatternExprs _ := () + +instance : B3AnnFromCST M where + annForLiteral := id + annForLiteralType := id + annForUnaryOp := id + annForUnaryOpType := id + annForBinaryOp := id + annForBinaryOpType := id + annForFunctionCall := id + annForFunctionCallName := id + annForFunctionCallArgs := id + annForLabeledExpr := id + annForLabeledExprLabel := id + annForLetExpr := id + annForLetExprVar := id + annForIte := id + annForQuantifierExpr := id + annForQuantifierKind := id + annForQuantifierVars := id + annForQuantifierPatterns := id + annForPattern := id + annForPatternExprs := id + +-- Helpers for common Ann operations +private def mkAnn {α M: Type} (m: M) (x : α) : Ann α M := ⟨m, x⟩ +private def mapAnn {α β M : Type} (f : α → β) (a : Ann α M) : Ann β M := mkAnn a.ann (f a.val) + +--------------------------------------------------------------------- +-- B3AST → B3CST Conversion (Abstract to Concrete) +--------------------------------------------------------------------- + +section ToCST + +structure ToCSTContext where + vars : List String + inProcedure : Bool := false -- Track if we're in a procedure context (for "old" value support) + +namespace ToCSTContext + +/-- +Check if a variable reference is supported in concrete syntax. +Supported cases: +- Index 0 (most recent variable) +- Variables with unique names (appear only once in context) +- Last occurrence of a variable (for "old" values in inout parameters) +NOT supported: +- Middle occurrences of shadowed variables +- "old" references outside procedure context (not yet implemented in B3) +-/ +def isSupported (ctx : ToCSTContext) (idx : Nat) : Bool := + match ctx.vars[idx]? with + | .none => false + | .some name => + if idx == 0 then true -- Most recent variable always supported + else if name == "" then false -- Anonymous variable + else + -- Check if this is the last (oldest) occurrence of the name + let isLastOccurrence := !(ctx.vars.drop (idx + 1)).any (· == name) + -- Check if this is a middle occurrence (has both earlier and later occurrences) + let hasEarlierOccurrence := (ctx.vars.take idx).any (· == name) + + if hasEarlierOccurrence && !isLastOccurrence then + false -- Middle occurrence - not supported + else if isLastOccurrence && hasEarlierOccurrence then + ctx.inProcedure -- Last occurrence with shadowing - supported only in procedure context + else + true -- Unique name - supported + +/-- Helper to resolve variable name disambiguation -/ +private def resolveVarName (vars : List String) (name : String) (idx : Nat) : String := + let rec go (vars: List String) (pastIndex: Nat) (idx: Nat): String := + let default := fun _: Unit => if pastIndex == 0 then + name -- No ambiguity + else + s!"name@{pastIndex}" + if idx == 0 then + default () + else + match vars with + | [] => default () + | otherName :: tail => + if name == otherName then + go tail (pastIndex + 1) (idx - 1) + else + go tail pastIndex (idx - 1) + go vars 0 idx + +def lookup (ctx : ToCSTContext) (idx : Nat) (m : M) : String × Bool × List (ASTToCSTError M) := + match ctx.vars[idx]? with + | .some name => + if name == "" then (s!"@{idx}", false, []) else + -- Determine if this is an old value: first occurrence with shadowing + let isOld := + -- Check if there's a later occurrence (lower index) with the same name + ctx.vars.take idx |>.any (· == name) + -- Old values in procedure contexts are always supported + if isOld && ctx.inProcedure then + (name, true, []) + else + -- Check if this reference is supported in concrete syntax + if !ctx.isSupported idx then + -- Not supported - return error + let resolvedName := if isOld then name else resolveVarName ctx.vars name idx + (resolvedName, isOld, [.unsupportedVariableReference idx m]) + else + -- Supported - return without error + if isOld then + (name, true, []) + else + (resolveVarName ctx.vars name idx, false, []) + | .none => + (s!"@{idx}", false, [.variableOutOfBounds idx ctx.vars.length m]) + +def push (ctx : ToCSTContext) (name : String) : ToCSTContext := + { vars := name :: ctx.vars, inProcedure := ctx.inProcedure } + +def empty : ToCSTContext := { vars := [] } + +end ToCSTContext + +mutual + +partial def binaryOpToCST [Inhabited (B3CST.Expression M)] : B3AST.BinaryOp M → + (M → B3CST.Expression M → B3CST.Expression M → B3CST.Expression M) + | .iff _ => B3CST.Expression.iff + | .implies _ => B3CST.Expression.implies + | .impliedBy _ => B3CST.Expression.impliedBy + | .and _ => B3CST.Expression.and + | .or _ => B3CST.Expression.or + | .eq _ => B3CST.Expression.equal + | .neq _ => B3CST.Expression.not_equal + | .lt _ => B3CST.Expression.lt + | .le _ => B3CST.Expression.le + | .ge _ => B3CST.Expression.ge + | .gt _ => B3CST.Expression.gt + | .add _ => B3CST.Expression.add + | .sub _ => B3CST.Expression.sub + | .mul _ => B3CST.Expression.mul + | .div _ => B3CST.Expression.div + | .mod _ => B3CST.Expression.mod + +partial def unaryOpToCST [Inhabited (B3CST.Expression M)] : B3AST.UnaryOp M → + (M → B3CST.Expression M → B3CST.Expression M) + | .not _ => B3CST.Expression.not + | .neg _ => B3CST.Expression.neg + +partial def literalToCST [Inhabited (B3CST.Expression M)] : B3AST.Literal M → B3CST.Expression M + | .intLit m n => B3CST.Expression.natLit m n + | .boolLit m b => if b then B3CST.Expression.btrue m else B3CST.Expression.bfalse m + | .stringLit m s => B3CST.Expression.strLit m s + +partial def expressionToCST [Inhabited (B3CST.Expression M)] (ctx : ToCSTContext) : B3AST.Expression M → B3CST.Expression M × List (ASTToCSTError M) + | .literal _m lit => + (literalToCST lit, []) + | .id m idx => + let (name, isOld, errors) := ctx.lookup idx m + let cstExpr := if isOld then + B3CST.Expression.old_id m (mkAnn m name) + else + B3CST.Expression.id m name + (cstExpr, errors) + | .ite m cond thn els => + let (cond', e1) := expressionToCST ctx cond + let (thn', e2) := expressionToCST ctx thn + let (els', e3) := expressionToCST ctx els + (B3CST.Expression.ite m cond' thn' els', e1 ++ e2 ++ e3) + | .binaryOp m op lhs rhs => + let (lhs', e1) := expressionToCST ctx lhs + let (rhs', e2) := expressionToCST ctx rhs + ((binaryOpToCST op) m lhs' rhs', e1 ++ e2) + | .unaryOp m op arg => + let (arg', errs) := expressionToCST ctx arg + ((unaryOpToCST op) m arg', errs) + | .functionCall m fnName args => + let (argsConverted, errors) := args.val.toList.foldl (fun (acc, errs) arg => + let (arg', e) := expressionToCST ctx arg + (acc ++ [arg'], errs ++ e) + ) ([], []) + (B3CST.Expression.functionCall m (mapAnn (fun x => x) fnName) (mapAnn (fun _ => argsConverted.toArray) args), errors) + | .labeledExpr m label expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Expression.labeledExpr m (mapAnn (fun x => x) label) expr', errs) + | .letExpr m var value body => + let ctx' := ctx.push var.val + let (value', e1) := expressionToCST ctx value + let (body', e2) := expressionToCST ctx' body + (B3CST.Expression.letExpr m (mapAnn (fun x => x) var) value' body', e1 ++ e2) + | .quantifierExpr m qkind vars patterns body => + -- Build context with all variables + let varList := vars.val.toList + let ctx' := varList.foldl (fun acc v => + match v with + | .quantVarDecl _ name _ => acc.push name.val + ) ctx + let convertPattern (p : Strata.B3AST.Pattern M) : B3CST.Pattern M × List (ASTToCSTError M) := + match p with + | .pattern pm exprs => + let (exprsConverted, errors) := exprs.val.toList.foldl (fun (acc, errs) e => + let (e', err) := expressionToCST ctx' e + (acc ++ [e'], errs ++ err) + ) ([], []) + (B3CST.Pattern.pattern pm (mkAnn pm exprsConverted.toArray), errors) + let (patternsConverted, patternErrors) := patterns.val.toList.foldl (fun (acc, errs) p => + let (p', e) := convertPattern p + (acc ++ [p'], errs ++ e) + ) ([], []) + let patternsDDM : Ann (Array (B3CST.Pattern M)) M := mkAnn m patternsConverted.toArray + let (body', bodyErrs) := expressionToCST ctx' body + -- Convert VarDecl list to CST VarDecl list + let varDeclsCST := varList.map (fun v => + match v with + | .quantVarDecl vm name ty => B3CST.VarDecl.var_decl vm (mkAnn vm name.val) (mkAnn vm ty.val) + ) + let result := match qkind with + | .forall _qm => + B3CST.Expression.forall_expr m (mkAnn m varDeclsCST.toArray) patternsDDM body' + | .exists _qm => + B3CST.Expression.exists_expr m (mkAnn m varDeclsCST.toArray) patternsDDM body' + (result, patternErrors ++ bodyErrs) + +partial def callArgToCST [Inhabited (B3CST.Expression M)] (ctx : ToCSTContext) : Strata.B3AST.CallArg M → B3CST.CallArg M × List (ASTToCSTError M) + | .callArgExpr m e => + let (e', errs) := expressionToCST ctx e + (B3CST.CallArg.call_arg_expr m e', errs) + | .callArgOut m id => (B3CST.CallArg.call_arg_out m (mapAnn (fun x => x) id), []) + | .callArgInout m id => (B3CST.CallArg.call_arg_inout m (mapAnn (fun x => x) id), []) + +partial def buildChoiceBranches [Inhabited (B3CST.Expression M)] : M → List (B3CST.ChoiceBranch M) → B3CST.ChoiceBranches M + | m, [] => ChoiceBranches.choiceAtom m (ChoiceBranch.choice_branch m (B3CST.Statement.return_statement m)) + | m, [b] => ChoiceBranches.choiceAtom m b + | m, b :: bs => ChoiceBranches.choicePush m (buildChoiceBranches m bs) b + +partial def stmtToCST [Inhabited (B3CST.Expression M)] [Inhabited (B3CST.Statement M)] (ctx : ToCSTContext) : Strata.B3AST.Statement M → B3CST.Statement M × List (ASTToCSTError M) + | .varDecl m name ty autoinv init => + let ctx' := ctx.push name.val + match ty.val, autoinv.val, init.val with + | some t, some ai, some i => + let (ai', e1) := expressionToCST ctx' ai + let (i', e2) := expressionToCST ctx' i + (B3CST.Statement.var_decl_full m (mapAnn (fun x => x) name) (mkAnn m t.val) ai' i', e1 ++ e2) + | some t, some ai, none => + let (ai', errs) := expressionToCST ctx' ai + (B3CST.Statement.var_decl_with_autoinv m (mapAnn (fun x => x) name) (mkAnn m t.val) ai', errs) + | some t, none, some i => + let (i', errs) := expressionToCST ctx' i + (B3CST.Statement.var_decl_with_init m (mapAnn (fun x => x) name) (mkAnn m t.val) i', errs) + | some t, none, none => + (B3CST.Statement.var_decl_typed m (mapAnn (fun x => x) name) (mkAnn m t.val), []) + | none, _, some i => + let (i', errs) := expressionToCST ctx' i + (B3CST.Statement.var_decl_inferred m (mapAnn (fun x => x) name) i', errs) + | none, _, none => + (B3CST.Statement.var_decl_typed m (mapAnn (fun x => x) name) (mkAnn m "unknown"), []) + | .assign m lhs rhs => + let (name, _, e1) := ctx.lookup lhs.val m + let (rhs', e2) := expressionToCST ctx rhs + (B3CST.Statement.assign m (mkAnn m name) rhs', e1 ++ e2) + | .reinit m idx => + let (name, _, errs) := ctx.lookup idx.val m + (B3CST.Statement.reinit_statement m (mkAnn m name), errs) + | .blockStmt m stmts => + let (stmts', _, errors) := stmts.val.toList.foldl (fun (acc, ctx, errs) stmt => + let (stmt', e) := stmtToCST ctx stmt + let ctx' := match stmt with + | .varDecl _ name _ _ _ => ctx.push name.val + | _ => ctx + (acc ++ [stmt'], ctx', errs ++ e) + ) ([], ctx, []) + (B3CST.Statement.block m (mkAnn m stmts'.toArray), errors) + | .call m procName args => + let (argsConverted, errors) := args.val.toList.foldl (fun (acc, errs) arg => + let (arg', e) := callArgToCST ctx arg + (acc ++ [arg'], errs ++ e) + ) ([], []) + (B3CST.Statement.call_statement m (mapAnn (fun x => x) procName) (mapAnn (fun _ => argsConverted.toArray) args), errors) + | .check m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Statement.check m expr', errs) + | .assume m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Statement.assume m expr', errs) + | .reach m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Statement.reach m expr', errs) + | .assert m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Statement.assert m expr', errs) + | .aForall m var ty body => + let ctx' := ctx.push var.val + let (body', errs) := stmtToCST ctx' body + (B3CST.Statement.aForall_statement m (mapAnn (fun x => x) var) (mapAnn (fun x => x) ty) body', errs) + | .choose m branches => + let (choiceBranches, errors) := branches.val.toList.foldl (fun (acc, errs) s => + let (s', e) := stmtToCST ctx s + (acc ++ [ChoiceBranch.choice_branch m s'], errs ++ e) + ) ([], []) + (B3CST.Statement.choose_statement m (buildChoiceBranches m choiceBranches), errors) + | .ifStmt m cond thenB elseB => + let (cond', e1) := expressionToCST ctx cond + let (then', e2) := stmtToCST ctx thenB + let (elseBranch, e3) := match elseB.val with + | some e => + let (e', err) := stmtToCST ctx e + (some (Else.else_some m e'), err) + | none => (none, []) + (B3CST.Statement.if_statement m cond' then' (mapAnn (fun _ => elseBranch) elseB), e1 ++ e2 ++ e3) + | .ifCase m cases => + let (casesConverted, errors) := cases.val.toList.foldl (fun (acc, errs) c => + match c with + | .oneIfCase cm cond body => + let (cond', e1) := expressionToCST ctx cond + let (body', e2) := stmtToCST ctx body + (acc ++ [IfCaseBranch.if_case_branch cm cond' body'], errs ++ e1 ++ e2) + ) ([], []) + (B3CST.Statement.if_case_statement m (mapAnn (fun _ => casesConverted.toArray) cases), errors) + | .loop m invariants body => + let (invs, invErrors) := invariants.val.toList.foldl (fun (acc, errs) e => + let (e', err) := expressionToCST ctx e + (acc ++ [Invariant.invariant m e'], errs ++ err) + ) ([], []) + let (body', bodyErrs) := stmtToCST ctx body + (B3CST.Statement.loop_statement m (mkAnn m invs.toArray) body', invErrors ++ bodyErrs) + | .labeledStmt m label stmt => + let (stmt', errs) := stmtToCST ctx stmt + (B3CST.Statement.labeled_statement m (mapAnn (fun x => x) label) stmt', errs) + | .exit m label => + (B3CST.Statement.exit_statement m (mapAnn (fun opt => opt.map (fun l => l)) label), []) + | .returnStmt m => (B3CST.Statement.return_statement m, []) + | .probe m label => (B3CST.Statement.probe m (mapAnn (fun x => x) label), []) + +end + +def fParameterToCST : Strata.B3AST.FParameter M → B3CST.FParam M + | .fParameter m injective name ty => + let inj := mapAnn (fun b => if b then some (B3CST.Injective.injective_some m) else none) injective + B3CST.FParam.fparam m inj (mkAnn m name.val) (mkAnn m ty.val) + +def pParameterToCST [Inhabited (B3CST.Expression M)] (ctx : ToCSTContext) : Strata.B3AST.PParameter M → B3CST.PParam M × List (ASTToCSTError M) + | .pParameter m mode name ty autoinv => + let modeCST := match mode with + | .paramModeIn _ => mkAnn m none + | .paramModeOut _ => mkAnn m (some (B3CST.PParamMode.pmode_out m)) + | .paramModeInout _ => mkAnn m (some (B3CST.PParamMode.pmode_inout m)) + match autoinv.val with + | some ai => + let (ai', errs) := expressionToCST ctx ai + (B3CST.PParam.pparam_with_autoinv m modeCST (mkAnn m name.val) (mkAnn m ty.val) ai', errs) + | none => (B3CST.PParam.pparam m modeCST (mkAnn m name.val) (mkAnn m ty.val), []) + +def specToCST [Inhabited (B3CST.Expression M)] (ctx : ToCSTContext) : Strata.B3AST.Spec M → B3CST.Spec M × List (ASTToCSTError M) + | .specRequires m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Spec.spec_requires m expr', errs) + | .specEnsures m expr => + let (expr', errs) := expressionToCST ctx expr + (B3CST.Spec.spec_ensures m expr', errs) + +def declToCST [Inhabited M] [Inhabited (B3CST.Expression M)] [Inhabited (B3CST.Statement M)] (ctx : ToCSTContext) : Strata.B3AST.Decl M → B3CST.Decl M × List (ASTToCSTError M) + | .typeDecl m name => + (B3CST.Decl.type_decl m (mkAnn m name.val), []) + | .tagger m name forType => + (B3CST.Decl.tagger_decl m (mkAnn m name.val) (mkAnn m forType.val), []) + | .function m name params resultType tag body => + let paramNames := params.val.toList.map (fun p => match p with | .fParameter _ _ n _ => n.val) + let ctx' := paramNames.foldl (fun acc n => acc.push n) ctx + let paramsCST := mkAnn m (params.val.toList.map fParameterToCST |>.toArray) + let tagClause := mapAnn (fun opt => opt.map (fun t => B3CST.TagClause.tag_some m (mkAnn m t.val))) tag + let (bodyCST, errors) := match body.val with + | some (.functionBody bm whens expr) => + let (whensConverted, whenErrors) := whens.val.toList.foldl (fun (acc, errs) w => + match w with + | .when wm e => + let (e', err) := expressionToCST ctx' e + (acc ++ [B3CST.WhenClause.when_clause wm e'], errs ++ err) + ) ([], []) + let (expr', exprErrs) := expressionToCST ctx' expr + (some (B3CST.FunctionBody.function_body_some bm (mkAnn bm whensConverted.toArray) expr'), whenErrors ++ exprErrs) + | none => (none, []) + (B3CST.Decl.function_decl m (mkAnn m name.val) paramsCST (mkAnn m resultType.val) tagClause (mapAnn (fun _ => bodyCST) body), errors) + | .axiom m explains expr => + let explainsCST := mkAnn m (explains.val.toList.map (fun id => mkAnn m id.val) |>.toArray) + let (expr', errs) := expressionToCST ctx expr + if explains.val.isEmpty then + (B3CST.Decl.axiom_decl m (B3CST.AxiomBody.axiom m expr'), errs) + else + (B3CST.Decl.axiom_decl m (B3CST.AxiomBody.explain_axiom m explainsCST expr'), errs) + | .procedure m name params specs body => + -- Build context: inout parameters need two entries (old and current) + let ctx' := params.val.toList.foldl (fun acc p => + match p with + | .pParameter _ mode pname _ _ => + match mode with + | .paramModeInout _ => acc.push pname.val |>.push pname.val -- Push twice for inout + | _ => acc.push pname.val + ) {ctx with inProcedure := true} -- Set inProcedure flag for procedure context + let (paramsConverted, paramErrors) := params.val.toList.foldl (fun (acc, errs) p => + let (p', e) := pParameterToCST ctx' p + (acc ++ [p'], errs ++ e) + ) ([], []) + let (specsConverted, specErrors) := specs.val.toList.foldl (fun (acc, errs) s => + let (s', e) := specToCST ctx' s + (acc ++ [s'], errs ++ e) + ) ([], []) + let (bodyCST, bodyErrors) := match body.val with + | some s => + let (s', e) := stmtToCST ctx' s + (some (B3CST.ProcBody.proc_body_some m s'), e) + | none => (none, []) + (B3CST.Decl.procedure_decl m (mkAnn m name.val) (mkAnn m paramsConverted.toArray) (mkAnn m specsConverted.toArray) (mapAnn (fun _ => bodyCST) body), paramErrors ++ specErrors ++ bodyErrors) + +def programToCST [Inhabited M] [Inhabited (B3CST.Expression M)] [Inhabited (B3CST.Statement M)] (ctx : ToCSTContext) : Strata.B3AST.Program M → B3CST.Program M × List (ASTToCSTError M) + | .program m decls => + let (declsConverted, errors) := decls.val.toList.foldl (fun (acc, errs) d => + let (d', e) := declToCST ctx d + (acc ++ [d'], errs ++ e) + ) ([], []) + (.program m (mkAnn m declsConverted.toArray), errors) + +end ToCST + +--------------------------------------------------------------------- +-- B3CST → B3AST Conversion (Concrete to Abstract) +--------------------------------------------------------------------- + +section FromCST + +structure FromCSTContext where + vars : List String + +namespace FromCSTContext + +def lookup (ctx : FromCSTContext) (name : String) (m : M) : Nat × List (CSTToASTError M) := + match ctx.vars.findIdx? (· == name) with + | .some idx => + (idx, []) + | .none => + (ctx.vars.length, [.unresolvedIdentifier name m]) + +def lookupLast (ctx : FromCSTContext) (name : String) (m : M) : Nat × List (CSTToASTError M) := + let rec findLast (vars : List String) (idx : Nat) (acc : Option Nat) : Option Nat := + match vars with + | [] => acc + | v :: vs => + let newAcc := if v == name then some idx else acc + findLast vs (idx + 1) newAcc + match findLast ctx.vars 0 none with + | some idx => (idx, []) + | none => (ctx.vars.length, [.unresolvedIdentifier name m]) + +def push (ctx : FromCSTContext) (name : String) : FromCSTContext := + { ctx with vars := name :: ctx.vars } + +def empty : FromCSTContext := { vars := [] } + +end FromCSTContext + +partial def expressionFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.Expression M → Strata.B3AST.Expression M × List (CSTToASTError M) + | .natLit ann n => (.literal (B3AnnFromCST.annForLiteral ann) (.intLit (B3AnnFromCST.annForLiteralType ann) n), []) + | .strLit ann s => (.literal (B3AnnFromCST.annForLiteral ann) (.stringLit (B3AnnFromCST.annForLiteralType ann) s), []) + | .btrue ann => (.literal (B3AnnFromCST.annForLiteral ann) (.boolLit (B3AnnFromCST.annForLiteralType ann) true), []) + | .bfalse ann => (.literal (B3AnnFromCST.annForLiteral ann) (.boolLit (B3AnnFromCST.annForLiteralType ann) false), []) + | .id ann name => + let (idx, errs) := ctx.lookup name ann + (.id ann idx, errs) + | .old_id ann name => + let (idx, errs) := ctx.lookupLast name.val ann + (.id ann idx, errs) + | .not ann arg => + let (arg', errs) := expressionFromCST ctx arg + (.unaryOp (B3AnnFromCST.annForUnaryOp ann) (.not (B3AnnFromCST.annForUnaryOpType ann)) arg', errs) + | .neg ann arg => + let (arg', errs) := expressionFromCST ctx arg + (.unaryOp (B3AnnFromCST.annForUnaryOp ann) (.neg (B3AnnFromCST.annForUnaryOpType ann)) arg', errs) + | .iff ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.iff (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .implies ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.implies (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .impliedBy ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.impliedBy (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .and ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.and (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .or ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.or (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .equal ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.eq (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .not_equal ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.neq (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .lt ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.lt (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .le ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.le (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .ge ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.ge (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .gt ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.gt (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .add ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.add (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .sub ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.sub (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .mul ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.mul (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .div ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.div (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .mod ann lhs rhs => + let (lhs', e1) := expressionFromCST ctx lhs + let (rhs', e2) := expressionFromCST ctx rhs + (.binaryOp (B3AnnFromCST.annForBinaryOp ann) (.mod (B3AnnFromCST.annForBinaryOpType ann)) lhs' rhs', e1 ++ e2) + | .functionCall ann fn args => + let (argsExprs, errors) := args.val.toList.foldl (fun (acc, errs) arg => + let (arg', e) := expressionFromCST ctx arg + (acc ++ [arg'], errs ++ e) + ) ([], []) + (.functionCall (B3AnnFromCST.annForFunctionCall ann) ⟨B3AnnFromCST.annForFunctionCallName ann, fn.val⟩ ⟨B3AnnFromCST.annForFunctionCallArgs ann, argsExprs.toArray⟩, errors) + | .labeledExpr ann label expr => + let (expr', errs) := expressionFromCST ctx expr + (.labeledExpr (B3AnnFromCST.annForLabeledExpr ann) ⟨B3AnnFromCST.annForLabeledExprLabel ann, label.val⟩ expr', errs) + | .letExpr ann var value body => + let ctx' := ctx.push var.val + let (value', e1) := expressionFromCST ctx value + let (body', e2) := expressionFromCST ctx' body + (.letExpr (B3AnnFromCST.annForLetExpr ann) ⟨B3AnnFromCST.annForLetExprVar ann, var.val⟩ value' body', e1 ++ e2) + | .ite ann cond thenExpr elseExpr => + let (cond', e1) := expressionFromCST ctx cond + let (then', e2) := expressionFromCST ctx thenExpr + let (else', e3) := expressionFromCST ctx elseExpr + (.ite (B3AnnFromCST.annForIte ann) cond' then' else', e1 ++ e2 ++ e3) + | .forall_expr ann vars patterns body => + -- Convert VarDecl array to AST VarDecl array and build context + let varList := vars.val.toList + let ctx' := varList.foldl (fun acc v => + match v with + | .var_decl _ name ty => acc.push name.val + ) ctx + let convertPattern (p : B3CST.Pattern M) : Strata.B3AST.Pattern M × List (CSTToASTError M) := + match p with + | .pattern pann exprs => + let (exprsConverted, errors) := exprs.val.toList.foldl (fun (acc, errs) e => + let (e', err) := expressionFromCST ctx' e + (acc ++ [e'], errs ++ err) + ) ([], []) + (.pattern (B3AnnFromCST.annForPattern pann) ⟨B3AnnFromCST.annForPatternExprs pann, exprsConverted.toArray⟩, errors) + let (patternsConverted, patternErrors) := patterns.val.toList.foldl (fun (acc, errs) p => + let (p', e) := convertPattern p + (acc ++ [p'], errs ++ e) + ) ([], []) + let (body', bodyErrs) := expressionFromCST ctx' body + -- Convert CST VarDecls to AST VarDecls + let varDeclsAST := varList.map (fun v => + match v with + | .var_decl vann name ty => + Strata.B3AST.VarDecl.quantVarDecl vann (mkAnn vann name.val) (mkAnn vann ty.val) + ) + (.quantifierExpr (B3AnnFromCST.annForQuantifierExpr ann) + (.forall (B3AnnFromCST.annForQuantifierKind ann)) + ⟨B3AnnFromCST.annForQuantifierVars ann, varDeclsAST.toArray⟩ + ⟨B3AnnFromCST.annForQuantifierPatterns ann, patternsConverted.toArray⟩ + body', patternErrors ++ bodyErrs) + | .exists_expr ann vars patterns body => + -- Convert VarDecl array to AST VarDecl array and build context + let varList := vars.val.toList + let ctx' := varList.foldl (fun acc v => + match v with + | .var_decl _ name ty => acc.push name.val + ) ctx + let convertPattern (p : B3CST.Pattern M) : Strata.B3AST.Pattern M × List (CSTToASTError M) := + match p with + | .pattern pann exprs => + let (exprsConverted, errors) := exprs.val.toList.foldl (fun (acc, errs) e => + let (e', err) := expressionFromCST ctx' e + (acc ++ [e'], errs ++ err) + ) ([], []) + (.pattern (B3AnnFromCST.annForPattern pann) ⟨B3AnnFromCST.annForPatternExprs pann, exprsConverted.toArray⟩, errors) + let (patternsConverted, patternErrors) := patterns.val.toList.foldl (fun (acc, errs) p => + let (p', e) := convertPattern p + (acc ++ [p'], errs ++ e) + ) ([], []) + let (body', bodyErrs) := expressionFromCST ctx' body + -- Convert CST VarDecls to AST VarDecls + let varDeclsAST := varList.map (fun v => + match v with + | .var_decl vann name ty => + Strata.B3AST.VarDecl.quantVarDecl vann (mkAnn vann name.val) (mkAnn vann ty.val) + ) + (.quantifierExpr (B3AnnFromCST.annForQuantifierExpr ann) + (.exists (B3AnnFromCST.annForQuantifierKind ann)) + ⟨B3AnnFromCST.annForQuantifierVars ann, varDeclsAST.toArray⟩ + ⟨B3AnnFromCST.annForQuantifierPatterns ann, patternsConverted.toArray⟩ + body', patternErrors ++ bodyErrs) + | .paren _ expr => expressionFromCST ctx expr + +partial def callArgFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.CallArg M → Strata.B3AST.CallArg M × List (CSTToASTError M) + | .call_arg_expr m expr => + let (expr', errs) := expressionFromCST ctx expr + (.callArgExpr m expr', errs) + | .call_arg_out m id => (.callArgOut m (mapAnn (fun x => x) id), []) + | .call_arg_inout m id => (.callArgInout m (mapAnn (fun x => x) id), []) + +partial def choiceBranchesToList [Inhabited M] : B3CST.ChoiceBranches M → List (B3CST.Statement M) + | .choiceAtom _ branch => + match branch with + | .choice_branch _ stmt => [stmt] + | .choicePush _ branches branch => + match branch with + | .choice_branch _ stmt => stmt :: choiceBranchesToList branches + +partial def stmtFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.Statement M → Strata.B3AST.Statement M × List (CSTToASTError M) + | .var_decl_full m name ty autoinv init => + let ctx' := ctx.push name.val + let (autoinv', e1) := expressionFromCST ctx' autoinv + let (init', e2) := expressionFromCST ctx' init + (.varDecl m (mapAnn (fun x => x) name) (mapAnn (fun t => some (mkAnn m t)) ty) (mkAnn m (some autoinv')) (mkAnn m (some init')), e1 ++ e2) + | .var_decl_with_autoinv m name ty autoinv => + let ctx' := ctx.push name.val + let (autoinv', errs) := expressionFromCST ctx' autoinv + (.varDecl m (mapAnn (fun x => x) name) (mapAnn (fun t => some (mkAnn m t)) ty) (mkAnn m (some autoinv')) (mkAnn m none), errs) + | .var_decl_with_init m name ty init => + let ctx' := ctx.push name.val + let (init', errs) := expressionFromCST ctx' init + (.varDecl m (mapAnn (fun x => x) name) (mapAnn (fun t => some (mkAnn m t)) ty) (mkAnn m none) (mkAnn m (some init')), errs) + | .var_decl_typed m name ty => + (.varDecl m (mapAnn (fun x => x) name) (mapAnn (fun t => some (mkAnn m t)) ty) (mkAnn m none) (mkAnn m none), []) + | .var_decl_inferred m name init => + let ctx' := ctx.push name.val + let (init', errs) := expressionFromCST ctx' init + (.varDecl m (mapAnn (fun x => x) name) (mkAnn m none) (mkAnn m none) (mkAnn m (some init')), errs) + | .val_decl m name ty init => + let ctx' := ctx.push name.val + let (init', errs) := expressionFromCST ctx' init + (.varDecl m (mapAnn (fun x => x) name) (mapAnn (fun t => some (mkAnn m t)) ty) (mkAnn m none) (mkAnn m (some init')), errs) + | .val_decl_inferred m name init => + let ctx' := ctx.push name.val + let (init', errs) := expressionFromCST ctx' init + (.varDecl m (mapAnn (fun x => x) name) (mkAnn m none) (mkAnn m none) (mkAnn m (some init')), errs) + | .assign m lhs rhs => + let (idx, e1) := ctx.lookup lhs.val m + let (rhs', e2) := expressionFromCST ctx rhs + (.assign m (mkAnn m idx) rhs', e1 ++ e2) + | .reinit_statement m v => + let (idx, errs) := ctx.lookup v.val m + (.reinit m (mkAnn m idx), errs) + | .check m expr => + let (expr', errs) := expressionFromCST ctx expr + (.check m expr', errs) + | .assume m expr => + let (expr', errs) := expressionFromCST ctx expr + (.assume m expr', errs) + | .reach m expr => + let (expr', errs) := expressionFromCST ctx expr + (.reach m expr', errs) + | .assert m expr => + let (expr', errs) := expressionFromCST ctx expr + (.assert m expr', errs) + | .return_statement m => + (.returnStmt m, []) + | .block m stmts => + let (stmts', _, errors) := stmts.val.toList.foldl (fun (acc, ctx, errs) stmt => + let (stmt', e) := stmtFromCST ctx stmt + let ctx' := match stmt with + | .var_decl_full _ name _ _ _ => ctx.push name.val + | .var_decl_with_autoinv _ name _ _ => ctx.push name.val + | .var_decl_with_init _ name _ _ => ctx.push name.val + | .var_decl_typed _ name _ => ctx.push name.val + | .var_decl_inferred _ name _ => ctx.push name.val + | .val_decl _ name _ _ => ctx.push name.val + | .val_decl_inferred _ name _ => ctx.push name.val + | _ => ctx + (acc ++ [stmt'], ctx', errs ++ e) + ) ([], ctx, []) + (.blockStmt m (mkAnn m stmts'.toArray), errors) + | .if_statement m cond thenB elseB => + let (cond', e1) := expressionFromCST ctx cond + let (then', e2) := stmtFromCST ctx thenB + let (elseBranch, e3) := match elseB.val with + | some (.else_some _ stmt) => + let (stmt', e) := stmtFromCST ctx stmt + (some stmt', e) + | none => (none, []) + (.ifStmt m cond' then' (mapAnn (fun _ => elseBranch) elseB), e1 ++ e2 ++ e3) + | .loop_statement m invs body => + let (invariants, invErrors) := invs.val.toList.foldl (fun (acc, errs) inv => + match inv with + | .invariant _ expr => + let (expr', e) := expressionFromCST ctx expr + (acc ++ [expr'], errs ++ e) + ) ([], []) + let (body', bodyErrs) := stmtFromCST ctx body + (.loop m (mkAnn m invariants.toArray) body', invErrors ++ bodyErrs) + | .exit_statement m label => + (.exit m (mapAnn (fun opt => opt.map (fun l => mkAnn m l.val)) label), []) + | .labeled_statement m label stmt => + let (stmt', errs) := stmtFromCST ctx stmt + (.labeledStmt m (mapAnn (fun x => x) label) stmt', errs) + | .probe m label => + (.probe m (mapAnn (fun x => x) label), []) + | .aForall_statement m var ty body => + let ctx' := ctx.push var.val + let (body', errs) := stmtFromCST ctx' body + (.aForall m (mapAnn (fun x => x) var) (mapAnn (fun x => x) ty) body', errs) + | .choose_statement m branches => + let (stmts, errors) := (choiceBranchesToList branches).foldl (fun (acc, errs) stmt => + let (stmt', e) := stmtFromCST ctx stmt + (acc ++ [stmt'], errs ++ e) + ) ([], []) + (.choose m (mkAnn m stmts.toArray), errors) + | .if_case_statement m cases => + let (casesConverted, errors) := cases.val.toList.foldl (fun (acc, errs) case => + match case with + | .if_case_branch cm cond stmt => + let (cond', e1) := expressionFromCST ctx cond + let (stmt', e2) := stmtFromCST ctx stmt + (acc ++ [.oneIfCase cm cond' stmt'], errs ++ e1 ++ e2) + ) ([], []) + (.ifCase m (mapAnn (fun _ => casesConverted.toArray) cases), errors) + | .call_statement m procName args => + let (argsConverted, errors) := args.val.toList.foldl (fun (acc, errs) arg => + let (arg', e) := callArgFromCST ctx arg + (acc ++ [arg'], errs ++ e) + ) ([], []) + (.call m (mapAnn (fun x => x) procName) (mapAnn (fun _ => argsConverted.toArray) args), errors) + +def paramModeFromCST [Inhabited M] : Ann (Option (B3CST.PParamMode M)) M → Strata.B3AST.ParamMode M + | ⟨m, none⟩ => .paramModeIn m + | ⟨m, some (.pmode_out _)⟩ => .paramModeOut m + | ⟨m, some (.pmode_inout _)⟩ => .paramModeInout m + +def fParameterFromCST [Inhabited M] : B3CST.FParam M → Strata.B3AST.FParameter M + | .fparam m injective name ty => + let inj := match injective.val with + | some (.injective_some _) => true + | none => false + .fParameter m (mkAnn m inj) (mapAnn (fun x => x) name) (mapAnn (fun x => x) ty) + +def pParameterFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.PParam M → Strata.B3AST.PParameter M × List (CSTToASTError M) + | .pparam m mode name ty => + (.pParameter m (paramModeFromCST mode) (mapAnn (fun x => x) name) (mapAnn (fun x => x) ty) (mkAnn m none), []) + | .pparam_with_autoinv m mode name ty autoinv => + let (autoinv', errs) := expressionFromCST ctx autoinv + (.pParameter m (paramModeFromCST mode) (mapAnn (fun x => x) name) (mapAnn (fun x => x) ty) (mkAnn m (some autoinv')), errs) + +def specFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.Spec M → Strata.B3AST.Spec M × List (CSTToASTError M) + | .spec_requires m expr => + let (expr', errs) := expressionFromCST ctx expr + (.specRequires m expr', errs) + | .spec_ensures m expr => + let (expr', errs) := expressionFromCST ctx expr + (.specEnsures m expr', errs) + +def fparamsToList : Ann (Array (B3CST.FParam M)) M → List (B3CST.FParam M) + | ⟨_, arr⟩ => arr.toList + +def declFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.Decl M → Strata.B3AST.Decl M × List (CSTToASTError M) + | .type_decl m name => + (.typeDecl m (mapAnn (fun x => x) name), []) + | .tagger_decl m name forType => + (.tagger m (mapAnn (fun x => x) name) (mapAnn (fun x => x) forType), []) + | .function_decl m name params resultType tag body => + let paramsAST := fparamsToList params |>.map fParameterFromCST + let paramNames := paramsAST.map (fun p => match p with | .fParameter _ _ n _ => n.val) + let ctx' := paramNames.foldl (fun acc n => acc.push n) ctx + let tagAST := tag.val.map (fun t => match t with | .tag_some _ id => mkAnn m id.val) + let (bodyAST, errors) := match body.val with + | some (.function_body_some bm whens expr) => + let (whensConverted, whenErrors) := whens.val.toList.foldl (fun (acc, errs) w => + match w with + | .when_clause wm e => + let (e', err) := expressionFromCST ctx' e + (acc ++ [B3AST.When.when wm e'], errs ++ err) + ) ([], []) + let (expr', exprErrs) := expressionFromCST ctx' expr + (some (B3AST.FunctionBody.functionBody bm (mkAnn bm whensConverted.toArray) expr'), whenErrors ++ exprErrs) + | none => (none, []) + (.function m (mapAnn (fun x => x) name) (mkAnn m paramsAST.toArray) (mapAnn (fun x => x) resultType) (mkAnn m tagAST) (mapAnn (fun _ => bodyAST) body), errors) + | .axiom_decl m axiomBody => + match axiomBody with + | .axiom _ expr => + let (expr', errs) := expressionFromCST ctx expr + (.axiom m (mkAnn m #[]) expr', errs) + | .explain_axiom _ names expr => + let namesAST := names.val.toList.map (fun n => mkAnn m n.val) + let (expr', errs) := expressionFromCST ctx expr + (.axiom m (mkAnn m namesAST.toArray) expr', errs) + | .procedure_decl m name params specs body => + -- Build context for parameters: inout parameters need two entries (old and current) + let ctx' := params.val.toList.foldl (fun acc p => + let (pname, mode) := match p with + | .pparam _ mode n _ => (n.val, mode.val) + | .pparam_with_autoinv _ mode n _ _ => (n.val, mode.val) + match mode with + | some (.pmode_inout _) => acc.push pname |>.push pname -- Push twice: old value, then current value + | _ => acc.push pname -- Push once for in/out parameters + ) ctx + -- Now convert all parameters with the full context (so autoinv can reference all params) + let (paramsConverted, paramErrors) := params.val.toList.foldl (fun (acc, errs) p => + let (p', e) := pParameterFromCST ctx' p + (acc ++ [p'], errs ++ e) + ) ([], []) + let (specsConverted, specErrors) := specs.val.toList.foldl (fun (acc, errs) s => + let (s', e) := specFromCST ctx' s + (acc ++ [s'], errs ++ e) + ) ([], []) + let (bodyAST, bodyErrors) := match body.val with + | some (.proc_body_some _ s) => + let (s', e) := stmtFromCST ctx' s + (some s', e) + | none => (none, []) + (.procedure m (mapAnn (fun x => x) name) (mkAnn m paramsConverted.toArray) (mkAnn m specsConverted.toArray) (mapAnn (fun _ => bodyAST) body), paramErrors ++ specErrors ++ bodyErrors) + +def programFromCST [Inhabited M] [B3AnnFromCST M] (ctx : FromCSTContext) : B3CST.Program M → Strata.B3AST.Program M × List (CSTToASTError M) + | .program m decls => + let (declsConverted, errors) := decls.val.toList.foldl (fun (acc, errs) d => + let (d', e) := declFromCST ctx d + (acc ++ [d'], errs ++ e) + ) ([], []) + (.program m (mkAnn m declsConverted.toArray), errors) + +end FromCST + +end B3 diff --git a/Strata/Languages/B3/DDMTransform/DefinitionAST.lean b/Strata/Languages/B3/DDMTransform/DefinitionAST.lean new file mode 100644 index 000000000..8ab2de8a3 --- /dev/null +++ b/Strata/Languages/B3/DDMTransform/DefinitionAST.lean @@ -0,0 +1,377 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean +import Strata.DDM.Util.Format + +--------------------------------------------------------------------- + +namespace Strata + +--------------------------------------------------------------------- +-- B3AST DDM Dialect for Abstract Syntax Tree +--------------------------------------------------------------------- + +/-! +# B3 Abstract Syntax Tree (AST) + +The B3 AST differs from the B3 CST in two ways. First, the AST uses de Bruijn indices for +variable references instead of identifier names. Where the CST parses `i` and `old i` as +distinct identifiers, the AST represents both as de Bruijn bound variables. Second, where +the CST has multiple syntactic forms for the same semantic construct, the AST has a single +canonical representation. + +The CST is suitable for parsing and pretty-printing the B3 language, while the AST is +suitable as a target for encoding Strata Core. The bidirectional conversion in +`Conversion.lean` handles name resolution, de Bruijn index assignment, and special cases +like shadowed variables and `inout` parameters (modeled as two context values). Conversions +return a list of errors for issues like unresolved identifiers or out-of-bounds references. +-/ + +#dialect +dialect B3AST; + +category Literal; +category Expression; +category Pattern; +category BinaryOp; +category UnaryOp; +category QuantifierKind; + +op intLit (@[unwrap] n : Num) : Literal => n; +op boolLit (@[unwrap] b : Bool) : Literal => b; +op stringLit (@[unwrap] s : Str) : Literal => s; + +op iff : BinaryOp => "iff"; +op implies : BinaryOp => "implies"; +op impliedBy : BinaryOp => "impliedBy"; +op and : BinaryOp => "and"; +op or : BinaryOp => "or"; +op eq : BinaryOp => "eq"; +op neq : BinaryOp => "neq"; +op lt : BinaryOp => "lt"; +op le : BinaryOp => "le"; +op ge : BinaryOp => "ge"; +op gt : BinaryOp => "gt"; +op add : BinaryOp => "add"; +op sub : BinaryOp => "sub"; +op mul : BinaryOp => "mul"; +op div : BinaryOp => "div"; +op mod : BinaryOp => "mod"; + +op not : UnaryOp => "not"; +op neg : UnaryOp => "neg"; + +op forall : QuantifierKind => "forall"; +op exists : QuantifierKind => "exists"; + +op literal (val : Literal) : Expression => "#" val; +op id (@[unwrap] index : Num) : Expression => index; +op ite (cond : Expression, thn : Expression, els : Expression) : Expression => + "ite " cond " " thn " " els; +op binaryOp (binOp : BinaryOp, lhs : Expression, rhs : Expression) : Expression => + "binop " binOp " " lhs " " rhs; +op unaryOp (unOp : UnaryOp, arg : Expression) : Expression => + "unop " unOp " " arg; +op functionCall (fnName : Ident, args : CommaSepBy Expression) : Expression => + "call " fnName " (" args ")"; +op labeledExpr (label : Ident, expr : Expression) : Expression => + "labeled " label " " expr; +op letExpr (var : Ident, value : Expression, body : Expression) : Expression => + "let " var " = " value " in " body; +category VarDecl; +op quantVarDecl (name : Ident, ty : Ident) : VarDecl => + name " : " ty; + +op quantifierExpr (quantifier : QuantifierKind, vars : Seq VarDecl, patterns : Seq Pattern, body : Expression) : Expression => + "quant " quantifier " [" vars "] [" patterns "] " body; + +op pattern (exprs : CommaSepBy Expression) : Pattern => + "pattern (" exprs ")"; + +category Statement; +category CallArg; +category OneIfCase; + +op varDecl (name : Ident, ty : Option Ident, autoinv : Option Expression, init : Option Expression) : Statement => + "varDecl " name " : " ty " autoinv " autoinv " := " init; +op assign (lhs : Num, rhs : Expression) : Statement => + "assign @" lhs " := " rhs; +op reinit (name : Num) : Statement => + "reinit @" name; +op blockStmt (stmts : Seq Statement) : Statement => + "block {" stmts "}"; +op call (procName : Ident, args : Seq CallArg) : Statement => + "call " procName "(" args ")"; +op check (expr : Expression) : Statement => + "check " expr; +op assume (expr : Expression) : Statement => + "assume " expr; +op reach (expr : Expression) : Statement => + "reach " expr; +op assert (expr : Expression) : Statement => + "assert " expr; +op aForall (var : Ident, ty : Ident, body : Statement) : Statement => + "forall " var " : " ty " " body; +op choose (branches : Seq Statement) : Statement => + "choose " branches; +op ifStmt (cond : Expression, thenBranch : Statement, elseBranch : Option Statement) : Statement => + "if " cond " then " thenBranch " else " elseBranch; +op oneIfCase (cond : Expression, body : Statement): OneIfCase => + "oneIfCase " cond body; +op ifCase (cases : Seq OneIfCase) : Statement => + "ifcase " cases; +op loop (invariants : Seq Expression, body : Statement) : Statement => + "loop invariants " invariants " {" body "}"; +op labeledStmt (label : Ident, stmt : Statement) : Statement => + "labelStmt " label " " stmt; +op exit (label : Option Ident) : Statement => + "exit " label; +op returnStmt : Statement => + "return"; +op probe (label : Ident) : Statement => + "probe " label; + +op callArgExpr (e : Expression) : CallArg => + "expr " e; +op callArgOut (id : Ident) : CallArg => + "out " id; +op callArgInout (id : Ident) : CallArg => + "inout " id; + +category ParamMode; +category FParameter; +category PParameter; +category Spec; +category Decl; + +op paramModeIn : ParamMode => "in"; +op paramModeOut : ParamMode => "out"; +op paramModeInout : ParamMode => "inout"; + +op fParameter (injective : Bool, name : Ident, ty : Ident) : FParameter => + "fparam " injective " " name " : " ty; + +op pParameter (mode : ParamMode, name : Ident, ty : Ident, autoinv : Option Expression) : PParameter => + "pparam " mode " " name " : " ty " autoinv " autoinv; + +op specRequires (expr : Expression) : Spec => + "requires " expr; +op specEnsures (expr : Expression) : Spec => + "ensures " expr; + +op typeDecl (name : Ident) : Decl => + "type " name; +op tagger (name : Ident, forType : Ident) : Decl => + "tagger " name " for " forType; + +category When; +op when (cond: Expression): When => + "when " cond; + +category FunctionBody; +op functionBody (whens: Seq When, body: Expression): FunctionBody => + whens "{" body "}"; + +op function (name : Ident, params : Seq FParameter, resultType : Ident, tag : Option Ident, body : Option FunctionBody) : Decl => + "\nfunction " name " (" params ") : " resultType " tag " tag " body " body; + +op axiom (explains : Seq Ident, expr : Expression) : Decl => + "\naxiom explains " explains "," expr; + +op procedure (name : Ident, params : Seq PParameter, specs : Seq Spec, body : Option Statement) : Decl => + "\nprocedure " name " (" params ") specs " specs " body " body; + +category Program; +op program (decls : Seq Decl) : Program => + decls; + +#end + +namespace B3AST + +#strata_gen B3AST + +end B3AST + +--------------------------------------------------------------------- +-- Metadata Transformation +--------------------------------------------------------------------- + +namespace B3AST + +open Strata.B3AST + +private def mapAnn {α M N : Type} (f : M → N) (a : Ann α M) : Ann α N := + ⟨f a.ann, a.val⟩ + +def Literal.mapMetadata [Inhabited N] (f : M → N) : Literal M → Literal N + | .intLit m n => .intLit (f m) n + | .boolLit m b => .boolLit (f m) b + | .stringLit m s => .stringLit (f m) s + +def BinaryOp.mapMetadata [Inhabited N] (f : M → N) : BinaryOp M → BinaryOp N + | .iff m => .iff (f m) + | .implies m => .implies (f m) + | .impliedBy m => .impliedBy (f m) + | .and m => .and (f m) + | .or m => .or (f m) + | .eq m => .eq (f m) + | .neq m => .neq (f m) + | .lt m => .lt (f m) + | .le m => .le (f m) + | .ge m => .ge (f m) + | .gt m => .gt (f m) + | .add m => .add (f m) + | .sub m => .sub (f m) + | .mul m => .mul (f m) + | .div m => .div (f m) + | .mod m => .mod (f m) + +def UnaryOp.mapMetadata [Inhabited N] (f : M → N) : UnaryOp M → UnaryOp N + | .not m => .not (f m) + | .neg m => .neg (f m) + +def QuantifierKind.mapMetadata [Inhabited N] (f : M → N) : QuantifierKind M → QuantifierKind N + | .forall m => .forall (f m) + | .exists m => .exists (f m) + +def VarDecl.mapMetadata [Inhabited N] (f : M → N) : VarDecl M → VarDecl N + | .quantVarDecl m name ty => .quantVarDecl (f m) (mapAnn f name) (mapAnn f ty) + +mutual + +def Expression.mapMetadata [Inhabited N] (f : M → N) (e: Expression M) :Expression N := + match e with + | .literal m lit => .literal (f m) (Literal.mapMetadata f lit) + | .id m idx => .id (f m) idx + | .ite m cond thn els => .ite (f m) (Expression.mapMetadata f cond) (Expression.mapMetadata f thn) (Expression.mapMetadata f els) + | .binaryOp m op lhs rhs => .binaryOp (f m) (BinaryOp.mapMetadata f op) (Expression.mapMetadata f lhs) (Expression.mapMetadata f rhs) + | .unaryOp m op arg => .unaryOp (f m) (UnaryOp.mapMetadata f op) (Expression.mapMetadata f arg) + | .functionCall m fnName args => .functionCall (f m) (mapAnn f fnName) ⟨f args.ann, args.val.map (Expression.mapMetadata f)⟩ + | .labeledExpr m label expr => .labeledExpr (f m) (mapAnn f label) (Expression.mapMetadata f expr) + | .letExpr m var value body => .letExpr (f m) (mapAnn f var) (Expression.mapMetadata f value) (Expression.mapMetadata f body) + | .quantifierExpr m qkind vars patterns body => + .quantifierExpr (f m) (QuantifierKind.mapMetadata f qkind) + ⟨f vars.ann, vars.val.map (VarDecl.mapMetadata f)⟩ + ⟨f patterns.ann, patterns.val.map (fun p => + match _: p with + | .pattern m exprs => .pattern (f m) ⟨f exprs.ann, exprs.val.map (Expression.mapMetadata f)⟩)⟩ + (Expression.mapMetadata f body) + termination_by SizeOf.sizeOf e + decreasing_by + all_goals (simp_wf <;> try omega) + . cases args ; simp_all + rename_i h; have := Array.sizeOf_lt_of_mem h; omega + . cases exprs; cases patterns; simp_all; subst_vars + rename_i h1 h2 + have := Array.sizeOf_lt_of_mem h1 + have Hpsz := Array.sizeOf_lt_of_mem h2 + simp at Hpsz; omega + +def CallArg.mapMetadata [Inhabited N] (f : M → N) : CallArg M → CallArg N + | .callArgExpr m e => .callArgExpr (f m) (Expression.mapMetadata f e) + | .callArgOut m id => .callArgOut (f m) (mapAnn f id) + | .callArgInout m id => .callArgInout (f m) (mapAnn f id) + +def Statement.mapMetadata [Inhabited N] (f : M → N) (s: Statement M) : Statement N := + match s with + | .varDecl m name ty autoinv init => + .varDecl (f m) (mapAnn f name) + ⟨f ty.ann, ty.val.map (mapAnn f)⟩ + ⟨f autoinv.ann, autoinv.val.map (Expression.mapMetadata f)⟩ + ⟨f init.ann, init.val.map (Expression.mapMetadata f)⟩ + | .assign m lhs rhs => .assign (f m) (mapAnn f lhs) (Expression.mapMetadata f rhs) + | .reinit m idx => .reinit (f m) (mapAnn f idx) + | .blockStmt m stmts => .blockStmt (f m) ⟨f stmts.ann, stmts.val.map (Statement.mapMetadata f)⟩ + | .call m procName args => .call (f m) (mapAnn f procName) ⟨f args.ann, args.val.map (CallArg.mapMetadata f)⟩ + | .check m expr => .check (f m) (Expression.mapMetadata f expr) + | .assume m expr => .assume (f m) (Expression.mapMetadata f expr) + | .reach m expr => .reach (f m) (Expression.mapMetadata f expr) + | .assert m expr => .assert (f m) (Expression.mapMetadata f expr) + | .aForall m var ty body => .aForall (f m) (mapAnn f var) (mapAnn f ty) (Statement.mapMetadata f body) + | .choose m branches => .choose (f m) ⟨f branches.ann, branches.val.map (Statement.mapMetadata f)⟩ + | .ifStmt m cond thenB elseB => + .ifStmt (f m) (Expression.mapMetadata f cond) (Statement.mapMetadata f thenB) + -- Unlike List and Array, Option.map does not use `attach` by default for wf proofs + ⟨f elseB.ann, elseB.val.attach.map (fun x => Statement.mapMetadata f x.1)⟩ + | .ifCase m cases => .ifCase (f m) ⟨f cases.ann, cases.val.map (fun o => + match ho: o with + | .oneIfCase m cond body => .oneIfCase (f m) (Expression.mapMetadata f cond) (Statement.mapMetadata f body))⟩ + | .loop m invariants body => + .loop (f m) ⟨f invariants.ann, invariants.val.map (Expression.mapMetadata f)⟩ (Statement.mapMetadata f body) + | .labeledStmt m label stmt => .labeledStmt (f m) (mapAnn f label) (Statement.mapMetadata f stmt) + | .exit m label => .exit (f m) ⟨f label.ann, label.val.map (mapAnn f)⟩ + | .returnStmt m => .returnStmt (f m) + | .probe m label => .probe (f m) (mapAnn f label) + decreasing_by + all_goals (simp_wf; try omega) + . cases stmts; simp_all; subst_vars + rename_i h; have :=Array.sizeOf_lt_of_mem h; omega + . cases branches; simp_all; subst_vars + rename_i h; have :=Array.sizeOf_lt_of_mem h; omega + . cases elseB; cases x + case mk x xin => + simp_all; subst_vars; simp; omega + . cases cases; simp_all; subst_vars + rename_i h; have :=Array.sizeOf_lt_of_mem h; simp_all; omega + +def ParamMode.mapMetadata [Inhabited N] (f : M → N) : ParamMode M → ParamMode N + | .paramModeIn m => .paramModeIn (f m) + | .paramModeOut m => .paramModeOut (f m) + | .paramModeInout m => .paramModeInout (f m) + +def FParameter.mapMetadata [Inhabited N] (f : M → N) : FParameter M → FParameter N + | .fParameter m injective name ty => .fParameter (f m) (mapAnn f injective) (mapAnn f name) (mapAnn f ty) + +def PParameter.mapMetadata [Inhabited N] (f : M → N) : PParameter M → PParameter N + | .pParameter m mode name ty autoinv => + .pParameter (f m) (ParamMode.mapMetadata f mode) (mapAnn f name) (mapAnn f ty) + ⟨f autoinv.ann, autoinv.val.map (Expression.mapMetadata f)⟩ + +def Spec.mapMetadata [Inhabited N] (f : M → N) : Spec M → Spec N + | .specRequires m expr => .specRequires (f m) (Expression.mapMetadata f expr) + | .specEnsures m expr => .specEnsures (f m) (Expression.mapMetadata f expr) + +def When.mapMetadata [Inhabited N] (f : M → N) : When M → When N + | .when m cond => .when (f m) (Expression.mapMetadata f cond) + +def FunctionBody.mapMetadata [Inhabited N] (f : M → N) : FunctionBody M → FunctionBody N + | .functionBody m whens body => + .functionBody (f m) ⟨f whens.ann, whens.val.map (When.mapMetadata f)⟩ (Expression.mapMetadata f body) + +def Decl.mapMetadata [Inhabited N] (f : M → N) : Decl M → Decl N + | .typeDecl m name => .typeDecl (f m) (mapAnn f name) + | .tagger m name forType => .tagger (f m) (mapAnn f name) (mapAnn f forType) + | .function m name params resultType tag body => + .function (f m) (mapAnn f name) ⟨f params.ann, params.val.map (FParameter.mapMetadata f)⟩ + (mapAnn f resultType) ⟨f tag.ann, tag.val.map (mapAnn f)⟩ + ⟨f body.ann, body.val.map (FunctionBody.mapMetadata f)⟩ + | .axiom m explains expr => + .axiom (f m) ⟨f explains.ann, explains.val.map (mapAnn f)⟩ (Expression.mapMetadata f expr) + | .procedure m name params specs body => + .procedure (f m) (mapAnn f name) ⟨f params.ann, params.val.map (PParameter.mapMetadata f)⟩ + ⟨f specs.ann, specs.val.map (Spec.mapMetadata f)⟩ + ⟨f body.ann, body.val.map (Statement.mapMetadata f)⟩ + +def Program.mapMetadata [Inhabited N] (f : M → N) : Program M → Program N + | .program m decls => .program (f m) ⟨f decls.ann, decls.val.map (Decl.mapMetadata f)⟩ + +end + +def Expression.toUnit [Inhabited (Expression Unit)] (e : Expression M) : Expression Unit := + e.mapMetadata (fun _ => ()) + +def Statement.toUnit [Inhabited (Expression Unit)] (s : Statement M) : Statement Unit := + s.mapMetadata (fun _ => ()) + +def Decl.toUnit [Inhabited (Expression Unit)] (d : Decl M) : Decl Unit := + d.mapMetadata (fun _ => ()) + +def Program.toUnit [Inhabited (Expression Unit)] (p : Program M) : Program Unit := + p.mapMetadata (fun _ => ()) + +end B3AST diff --git a/Strata/Languages/B3/DDMTransform/ParseCST.lean b/Strata/Languages/B3/DDMTransform/ParseCST.lean new file mode 100644 index 000000000..bfdebad6a --- /dev/null +++ b/Strata/Languages/B3/DDMTransform/ParseCST.lean @@ -0,0 +1,233 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean +import Strata.DDM.Util.Format + +--------------------------------------------------------------------- + +namespace Strata + +--------------------------------------------------------------------- +-- B3CST DDM Dialect for Concrete Syntax Tree +--------------------------------------------------------------------- +-- B3CST represents the concrete syntax with named identifiers (e.g., "x", "y"). +-- Used for parsing user-written code and formatting/pretty-printing. +-- Variables are referenced by name, which must be resolved to indices. +-- Supports "old x" syntax for referencing previous values of inout parameters. +--------------------------------------------------------------------- + +#dialect +dialect B3CST; + +category Expression; + +op not (e : Expression) : Expression => @[prec(35)] "!" e; + +op natLit (@[unwrap] n : Num) : Expression => n; +op strLit (@[unwrap] s : Str) : Expression => s; + +op btrue : Expression => "true"; +op bfalse : Expression => "false"; + +op old_id (name : Ident) : Expression => "old " name:0; +op id (@[unwrap] name : Ident) : Expression => name; + +op letExpr (name : Ident, value : Expression, body : Expression) : Expression => + @[prec(2)] "val " name " := " value:0 " " body:2; + +op labeledExpr (label : Ident, e : Expression) : Expression => @[prec(1)] label ": " e:1; + +op ite (c : Expression, t : Expression, f : Expression) : Expression => @[prec(3)] "if " c:0 " then " indent(2, t:3) " else " indent(2, f:3); +op iff (a : Expression, b : Expression) : Expression => @[prec(4)] a " <==> " b; +op implies (a : Expression, b : Expression) : Expression => @[prec(5), rightassoc] a " ==> " b; +op impliedBy (a : Expression, b : Expression) : Expression => @[prec(5), rightassoc] a " <== " b; +op and (a : Expression, b : Expression) : Expression => @[prec(10), leftassoc] a " && " b; +op or (a : Expression, b : Expression) : Expression => @[prec(8), leftassoc] a " || " b; + +op equal (a : Expression, b : Expression) : Expression => @[prec(15)] a " == " b; +op not_equal (a : Expression, b : Expression) : Expression => @[prec(15)] a " != " b; +op le (a : Expression, b : Expression) : Expression => @[prec(15)] a " <= " b; +op lt (a : Expression, b : Expression) : Expression => @[prec(15)] a " < " b; +op ge (a : Expression, b : Expression) : Expression => @[prec(15)] a " >= " b; +op gt (a : Expression, b : Expression) : Expression => @[prec(15)] a " > " b; + +op neg (e : Expression) : Expression => "-" e; +op add (a : Expression, b : Expression) : Expression => @[prec(25), leftassoc] a " + " b; +op sub (a : Expression, b : Expression) : Expression => @[prec(25), leftassoc] a " - " b; +op mul (a : Expression, b : Expression) : Expression => @[prec(30), leftassoc] a " * " b; +op div (a : Expression, b : Expression) : Expression => @[prec(30), leftassoc] a " div " b; +op mod (a : Expression, b : Expression) : Expression => @[prec(30), leftassoc] a " mod " b; +op paren (a : Expression) : Expression => @[prec(30)] "(" a ")"; + +op functionCall (name : Ident, args : CommaSepBy Expression) : Expression => @[prec(40)] name "(" args ")"; + +category Pattern; +op pattern (e : CommaSepBy Expression) : Pattern => " pattern " e:0; + +category VarDecl; +op var_decl (name : Ident, ty : Ident) : VarDecl => name:0 " : " ty:0; + +op forall_expr (vars : CommaSepBy VarDecl, patterns : Seq Pattern, body : Expression) : Expression => + @[prec(1)] "forall " vars:0 patterns " " body:1; + +op exists_expr (vars : CommaSepBy VarDecl, patterns : Seq Pattern, body : Expression) : Expression => + @[prec(1)] "exists " vars:0 patterns " " body:1; + +category Statement; + +op assign (v : Ident, e : Expression) : Statement => "\n" v:0 " := " e:0; +op reinit_statement (v : Ident) : Statement => "\nreinit " v:0; + +category CallArg; +op call_arg_expr (e : Expression) : CallArg => e:0; +op call_arg_out (id : Ident) : CallArg => "out " id:0; +op call_arg_inout (id : Ident) : CallArg => "inout " id:0; + +op call_statement (proc : Ident, args : CommaSepBy CallArg) : Statement => + "\n" proc "(" args ")"; + +op check (c : Expression) : Statement => "\ncheck " c:0; +op assume (c : Expression) : Statement => "\nassume " c:0; +op reach (c : Expression) : Statement => "\nreach " c:0; +op assert (c : Expression) : Statement => "\nassert " c:0; + +category Else; +op else_some (s : Statement) : Else => @[prec(0)] "\nelse " indent(2, s:0); + +op if_statement (c : Expression, t : Statement, f : Option Else) : Statement => + "\nif " c:0 " " indent(2, t:0) f:0; + +category Invariant; +op invariant (e : Expression) : Invariant => "\n invariant " e:0; + +op loop_statement (invs : Seq Invariant, body : Statement) : Statement => + "\nloop" invs " " body:40; + +op exit_statement (label : Option Ident) : Statement => "\nexit " label:0 ; +op return_statement () : Statement => "\nreturn"; + +op labeled_statement (label : Ident, s : Statement) : Statement => label:0 ": " s:0; + +op probe (name : Ident) : Statement => "\nprobe " name:0 ; + +op var_decl_full (name : Ident, ty : Ident, autoinv : Expression, init : Expression) : Statement => + "\nvar " name:0 " : " ty:0 " autoinv " autoinv:0 " := " init:0 ; + +op var_decl_with_autoinv (name : Ident, ty : Ident, autoinv : Expression) : Statement => + "\nvar " name:0 " : " ty:0 " autoinv " autoinv:0 ; + +op var_decl_with_init (name : Ident, ty : Ident, init : Expression) : Statement => + "\nvar " name:0 " : " ty:0 " := " init:0 ; + +op var_decl_typed (name : Ident, ty : Ident) : Statement => + "\nvar " name:0 " : " ty:0 ; + +op var_decl_inferred (name : Ident, init : Expression) : Statement => + "\nvar " name:0 " := " init:0 ; + +op val_decl (name : Ident, ty : Ident, init : Expression) : Statement => + "\nval " name:0 " : " ty:0 " := " init:0 ; + +op val_decl_inferred (name : Ident, init : Expression) : Statement => + "\nval " name:0 " := " init:0 ; + +category ChoiceBranch; +op choice_branch (s : Statement) : ChoiceBranch => s:40; + +category ChoiceBranches; +op choiceAtom (b : ChoiceBranch) : ChoiceBranches => b:0; +op choicePush (bs : ChoiceBranches, b : ChoiceBranch) : ChoiceBranches => bs:0 " or " b:0; + +op choose_statement (branches : ChoiceBranches) : Statement => + "\nchoose " branches:0; + +category IfCaseBranch; +op if_case_branch (cond : Expression, body : Statement) : IfCaseBranch => + "\ncase " cond:0 " " body:40; + +op if_case_statement (branches : Seq IfCaseBranch) : Statement => + "\nif" branches:0; + +op aForall_statement (var : Ident, ty : Ident, body : Statement) : Statement => + "\nforall " var:0 " : " ty:0 " " body:40; + +op block (c : Seq Statement) : Statement => "\n{" indent(2, c:0) "\n}"; + +category Decl; + +op type_decl (name : Ident) : Decl => "\ntype " name:0; + +op tagger_decl (name : Ident, forType : Ident) : Decl => "\ntagger " name:0 " for " forType:0; + +category Injective; +op injective_some () : Injective => "injective "; + +category FParam; +op fparam (injective : Option Injective, name : Ident, ty : Ident) : FParam => + injective:0 name:0 " : " ty:0; + +category TagClause; +op tag_some (t : Ident) : TagClause => " tag " t:0; + +category WhenClause; +op when_clause (e : Expression) : WhenClause => "\n when " e:0; + +category FunctionBody; +op function_body_some (whens : Seq WhenClause, e : Expression) : FunctionBody => whens:0 " {" indent(2, "\n" e:0) "\n}"; + +op function_decl (name : Ident, params : CommaSepBy FParam, resultType : Ident, tag : Option TagClause, body : Option FunctionBody) : Decl => + "\nfunction " name:0 "(" params:0 ")" " : " resultType:0 tag:0 body:0; + +category AxiomBody; + +op explain_axiom (names: CommaSepBy Ident, expr : Expression) : AxiomBody => + "explains " names:0 indent(2, "\n" expr:0); + +op axiom (expr : Expression) : AxiomBody => + expr; + +op axiom_decl (expr : AxiomBody) : Decl => + "\naxiom " expr:0; + +category PParamMode; +op pmode_out () : PParamMode => "out "; +op pmode_inout () : PParamMode => "inout "; + +category PParam; +op pparam (mode : Option PParamMode, name : Ident, ty : Ident) : PParam => + mode:0 name:0 " : " ty:0; + +op pparam_with_autoinv (mode : Option PParamMode, name : Ident, ty : Ident, autoinv : Expression) : PParam => + mode:0 name:0 " : " ty:0 " autoinv " autoinv:0; + +category Spec; +op spec_requires (e : Expression) : Spec => "\n requires " e:0; +op spec_ensures (e : Expression) : Spec => "\n ensures " e:0; + +category ProcBody; +op proc_body_some (s : Statement) : ProcBody => s:40; + +op procedure_decl (name : Ident, params : CommaSepBy PParam, specs : Seq Spec, body : Option ProcBody) : Decl => + "\nprocedure " name "(" params ")" specs body:0; + +category Program; +op program (decls : Seq Decl) : Program => + decls; + +op command_stmt (s : Statement) : Command => s; +op command_program (p : Program) : Command => p; +#end + +namespace B3CST + +#strata_gen B3CST + +end B3CST + +--------------------------------------------------------------------- + +end Strata diff --git a/Strata/Languages/Boogie/Axiom.lean b/Strata/Languages/Boogie/Axiom.lean index 28513e119..ea113002b 100644 --- a/Strata/Languages/Boogie/Axiom.lean +++ b/Strata/Languages/Boogie/Axiom.lean @@ -8,6 +8,8 @@ import Strata.Languages.Boogie.Statement +import Strata.DL.Lambda.LTy +import Strata.DL.Lambda.LExpr namespace Boogie --------------------------------------------------------------------- @@ -25,8 +27,10 @@ the responsibility of the user to ensure that they are consistent. structure Axiom where name : BoogieLabel - e : LExpr LMonoTy Visibility - deriving DecidableEq, Inhabited + e : LExpr BoogieLParams.mono + +instance : ToFormat (BoogieLParams.mono : LExprParamsT).base.Identifier := + show ToFormat BoogieIdent from inferInstance instance : ToFormat Axiom where format a := f!"axiom {a.name}: {a.e};" diff --git a/Strata/Languages/Boogie/Boogie.lean b/Strata/Languages/Boogie/Boogie.lean index f4917b818..b8f609e59 100644 --- a/Strata/Languages/Boogie/Boogie.lean +++ b/Strata/Languages/Boogie/Boogie.lean @@ -34,21 +34,34 @@ namespace Boogie types. -/ -def typeCheck (options : Options) (program : Program) : Except Std.Format Program := do +def typeCheck (options : Options) (program : Program) + (moreFns : @Lambda.Factory BoogieLParams := Lambda.Factory.default) : + Except Std.Format Program := do let T := Lambda.TEnv.default - let C := { Lambda.LContext.default with functions := Boogie.Factory, knownTypes := Boogie.KnownTypes } + let factory ← Boogie.Factory.addFactory moreFns + let C := { Lambda.LContext.default with + functions := factory, + knownTypes := Boogie.KnownTypes } let (program, _T) ← Program.typeCheck C T program -- dbg_trace f!"[Strata.Boogie] Type variables:\n{T.state.substInfo.subst.length}" -- dbg_trace f!"[Strata.Boogie] Annotated program:\n{program}" if options.verbose then dbg_trace f!"[Strata.Boogie] Type checking succeeded.\n" return program -def typeCheckAndPartialEval (options : Options) (program : Program) : - Except Std.Format (List (Program × Env)) := do - let program ← typeCheck options program +def typeCheckAndPartialEval (options : Options) (program : Program) + (moreFns : @Lambda.Factory BoogieLParams := Lambda.Factory.default) : + Except Std.Format (List (Program × Env)) := do + let program ← typeCheck options program moreFns + -- Extract datatypes from program declarations and add to environment + let datatypes := program.decls.filterMap fun decl => + match decl with + | .type (.data d) _ => some d + | _ => none let σ ← (Lambda.LState.init).addFactory Boogie.Factory + let σ ← σ.addFactory moreFns let E := { Env.init with exprEnv := σ, program := program } + let E ← E.addDatatypes datatypes let pEs := Program.eval E if options.verbose then do dbg_trace f!"{Std.Format.line}VCs:" diff --git a/Strata/Languages/Boogie/BoogieGen.lean b/Strata/Languages/Boogie/BoogieGen.lean index 52704ed45..58f845b91 100644 --- a/Strata/Languages/Boogie/BoogieGen.lean +++ b/Strata/Languages/Boogie/BoogieGen.lean @@ -20,7 +20,7 @@ open Boogie Lambda Imperative namespace Names def initVarValue (id : BoogieIdent) : Expression.Expr := - .fvar ("init_" ++ id.name) none + .fvar () (BoogieIdent.unres ("init_" ++ id.name)) none end Names diff --git a/Strata/Languages/Boogie/CallGraph.lean b/Strata/Languages/Boogie/CallGraph.lean index 7a15c59ff..8e60228d4 100644 --- a/Strata/Languages/Boogie/CallGraph.lean +++ b/Strata/Languages/Boogie/CallGraph.lean @@ -73,18 +73,17 @@ Extract function calls from an expression. We ignore Boogie's builtin functions -/ def extractFunctionCallsFromExpr (expr : Expression.Expr) : List String := match expr with - | .fvar _ _ => [] - | .bvar _ => [] - | .mdata _ e => extractFunctionCallsFromExpr e - | .op fname _ => + | .fvar _ _ _ => [] + | .bvar _ _ => [] + | .op _ fname _ => let fname := BoogieIdent.toPretty fname if builtinFunctions.contains fname then [] else [fname] | .const _ _ => [] - | .app fn arg => extractFunctionCallsFromExpr fn ++ extractFunctionCallsFromExpr arg - | .ite c t e => extractFunctionCallsFromExpr c ++ extractFunctionCallsFromExpr t ++ extractFunctionCallsFromExpr e - | .eq e1 e2 => extractFunctionCallsFromExpr e1 ++ extractFunctionCallsFromExpr e2 - | .abs _ body => extractFunctionCallsFromExpr body - | .quant _ _ trigger body => extractFunctionCallsFromExpr trigger ++ extractFunctionCallsFromExpr body + | .app _ fn arg => extractFunctionCallsFromExpr fn ++ extractFunctionCallsFromExpr arg + | .ite _ c t e => extractFunctionCallsFromExpr c ++ extractFunctionCallsFromExpr t ++ extractFunctionCallsFromExpr e + | .eq _ e1 e2 => extractFunctionCallsFromExpr e1 ++ extractFunctionCallsFromExpr e2 + | .abs _ _ body => extractFunctionCallsFromExpr body + | .quant _ _ _ trigger body => extractFunctionCallsFromExpr trigger ++ extractFunctionCallsFromExpr body def extractCallsFromFunction (func : Function) : List String := match func.body with @@ -97,11 +96,11 @@ partial def extractCallsFromStatement (stmt : Statement) : List String := match stmt with | .cmd (.call _ procName _ _) => [procName] | .cmd _ => [] - | .block _ body _ => extractCallsFromStatements body.ss + | .block _ body _ => extractCallsFromStatements body | .ite _ thenBody elseBody _ => - extractCallsFromStatements thenBody.ss ++ - extractCallsFromStatements elseBody.ss - | .loop _ _ _ body _ => extractCallsFromStatements body.ss + extractCallsFromStatements thenBody ++ + extractCallsFromStatements elseBody + | .loop _ _ _ body _ => extractCallsFromStatements body | .goto _ _ => [] /-- Extract procedure calls from a list of statements -/ diff --git a/Strata/Languages/Boogie/CmdEval.lean b/Strata/Languages/Boogie/CmdEval.lean index caf5e2852..0249f4097 100644 --- a/Strata/Languages/Boogie/CmdEval.lean +++ b/Strata/Languages/Boogie/CmdEval.lean @@ -135,7 +135,7 @@ private def testProgram1 : Cmds Expression := info: Commands: init (x : int) := #0 x := #10 -assert [x_value_eq] (#true : bool) +assert [x_value_eq] #true State: Error: @@ -154,6 +154,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -163,10 +165,10 @@ Deferred Proof Obligations: Label: x_value_eq Assumptions: Proof Obligation: -(#true : bool) +#true -/ #guard_msgs in -#eval format $ Imperative.Cmds.eval Env.init testProgram1 +#eval format $ Imperative.Cmds.eval (Env.init (empty_factory := true)) testProgram1 private def testProgram2 : Cmds Expression := [.init "x" t[int] eb[(y : int)], @@ -195,6 +197,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -207,7 +211,7 @@ Proof Obligation: ((y : int) == #12) -/ #guard_msgs in -#eval format $ Imperative.Cmds.eval Env.init testProgram2 +#eval format $ Imperative.Cmds.eval (Env.init (empty_factory := true)) testProgram2 end CmdEval --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/CmdType.lean b/Strata/Languages/Boogie/CmdType.lean index 79db19199..5b6fcb6e2 100644 --- a/Strata/Languages/Boogie/CmdType.lean +++ b/Strata/Languages/Boogie/CmdType.lean @@ -9,10 +9,12 @@ import Strata.Languages.Boogie.OldExpressions import Strata.Languages.Boogie.Expressions import Strata.DL.Imperative.TypeContext +import Strata.DL.Lambda.Factory namespace Boogie open Lambda Imperative open Std (ToFormat Format format) + --------------------------------------------------------------------- namespace CmdType @@ -22,13 +24,13 @@ def isBoolType (ty : LTy) : Bool := | .forAll [] LMonoTy.bool => true | _ => false -def lookup (T : (TEnv Visibility)) (x : BoogieIdent) : Option LTy := - T.context.types.find? x +def lookup (Env : TEnv Visibility) (x : BoogieIdent) : Option LTy := + Env.context.types.find? x -def update (T : TEnv Visibility) (x : BoogieIdent) (ty : LTy) : TEnv Visibility := - T.insertInContext x ty +def update (Env : TEnv Visibility) (x : BoogieIdent) (ty : LTy) : TEnv Visibility := + Env.insertInContext (T := BoogieLParams) x ty -def freeVars (e : (LExpr LMonoTy Visibility)) : List BoogieIdent := +def freeVars (e : (LExpr BoogieLParams.mono)) : List BoogieIdent := (LExpr.freeVars e).map (fun (i, _) => i) /-- @@ -36,14 +38,16 @@ Preprocess a user-facing type in Boogie amounts to converting a poly-type (i.e., `LTy`) to a mono-type (i.e., `LMonoTy`) via instantiation. We still return an `LTy`, with no bound variables. -/ -def preprocess (C: LContext Visibility) (T : TEnv Visibility) (ty : LTy) : Except Format (LTy × TEnv Visibility) := do - let (mty, T) ← ty.instantiateWithCheck C T - return (.forAll [] mty, T) +def preprocess (C: LContext BoogieLParams) (Env : TEnv Visibility) (ty : LTy) : + Except Format (LTy × TEnv Visibility) := do + let (mty, Env) ← ty.instantiateWithCheck C Env + return (.forAll [] mty, Env) -def postprocess (_: LContext Visibility) (T : TEnv Visibility) (ty : LTy) : Except Format (LTy × TEnv Visibility) := do +def postprocess (_: LContext BoogieLParams) (Env: TEnv Visibility) (ty : LTy) : + Except Format (LTy × TEnv Visibility) := do if h: ty.isMonoType then - let ty := LMonoTy.subst T.stateSubstInfo.subst (ty.toMonoType h) - .ok (.forAll [] ty, T) + let ty := LMonoTy.subst Env.stateSubstInfo.subst (ty.toMonoType h) + .ok (.forAll [] ty, Env) else .error f!"[postprocess] Expected mono-type; instead got {ty}" @@ -51,28 +55,29 @@ def postprocess (_: LContext Visibility) (T : TEnv Visibility) (ty : LTy) : Exce The inferred type of `e` will be an `LMonoTy`, but we return an `LTy` with no bound variables. -/ -def inferType (C: LContext Visibility) (T : TEnv Visibility) (c : Cmd Expression) (e : (LExpr LMonoTy Visibility)) : - Except Format ((LExpr LMonoTy Visibility) × LTy × TEnv Visibility) := do +def inferType (C: LContext BoogieLParams) (Env: TEnv Visibility) (c : Cmd Expression) (e : LExpr BoogieLParams.mono) : + Except Format ((LExpr BoogieLParams.mono) × LTy × TEnv Visibility) := do -- We only allow free variables to appear in `init` statements. Any other -- occurrence leads to an error. let T ← match c with | .init _ _ _ _ => let efv := LExpr.freeVars e - .ok (T.addInOldestContext efv) + .ok (Env.addInOldestContext efv) | _ => - let _ ← T.freeVarCheck e f!"[{c}]" - .ok T + let _ ← Env.freeVarCheck e f!"[{c}]" + .ok Env let e := OldExpressions.normalizeOldExpr e - let (ea, T) ← LExprT.fromLExpr C T e + let (ea, T) ← LExpr.resolve C T e let ety := ea.toLMonoTy - return (ea.toLExpr, (.forAll [] ety), T) + return (ea.unresolved, (.forAll [] ety), T) /-- Type constraints come from functions `inferType` and `preprocess`, both of which are expected to return `LTy`s with no bound variables which can be safely converted to `LMonoTy`s. -/ -def canonicalizeConstraints (constraints : List (LTy × LTy)) : Except Format Constraints := do +def canonicalizeConstraints (constraints : List (LTy × LTy)) : + Except Format Constraints := do match constraints with | [] => .ok [] | (t1, t2) :: c_rest => @@ -86,23 +91,28 @@ def canonicalizeConstraints (constraints : List (LTy × LTy)) : Except Format Co type constraints, but found the following instead:\n\ t1: {t1}\nt2: {t2}\n" -def unifyTypes (T : TEnv Visibility) (constraints : List (LTy × LTy)) : Except Format (TEnv Visibility) := do +def unifyTypes (Env: TEnv Visibility) (constraints : List (LTy × LTy)) : + Except Format (TEnv Visibility) := do let constraints ← canonicalizeConstraints constraints - let S ← Constraints.unify constraints T.stateSubstInfo - let T := T.updateSubst S - return T + let S ← Constraints.unify constraints Env.stateSubstInfo |> .mapError format + let Env := Env.updateSubst S + return Env + +def typeErrorFmt (e : Format) : Format := + e --------------------------------------------------------------------- -instance : Imperative.TypeContext Expression (LContext Visibility) (TEnv Visibility) where - isBoolType := CmdType.isBoolType - freeVars := CmdType.freeVars - preprocess := CmdType.preprocess - postprocess := CmdType.postprocess - update := CmdType.update - lookup := CmdType.lookup - inferType := CmdType.inferType - unifyTypes := CmdType.unifyTypes +instance : Imperative.TypeContext Expression (LContext BoogieLParams) (TEnv Visibility) Format where + isBoolType := CmdType.isBoolType + freeVars := CmdType.freeVars + preprocess := CmdType.preprocess + postprocess := CmdType.postprocess + update := CmdType.update + lookup := CmdType.lookup + inferType := CmdType.inferType + unifyTypes := CmdType.unifyTypes + typeErrorFmt := CmdType.typeErrorFmt end CmdType --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/DDMTransform/Parse.lean b/Strata/Languages/Boogie/DDMTransform/Parse.lean index e5e271362..43f60e314 100644 --- a/Strata/Languages/Boogie/DDMTransform/Parse.lean +++ b/Strata/Languages/Boogie/DDMTransform/Parse.lean @@ -81,6 +81,7 @@ fn map_set (K : Type, V : Type, m : Map K V, k : K, v : V) : Map K V => // FIXME: Define polymorphic length and concat functions? fn str_len (a : string) : int => "str.len" "(" a ")"; fn str_concat (a : string, b : string) : string => "str.concat" "(" a "," b ")"; +fn str_substr (a : string, i : int, n : int) : string => "str.substr" "(" a "," i "," n ")"; fn str_toregex (a : string) : regex => "str.to.re" "(" a ")"; fn str_inregex (s : string, a : regex) : bool => "str.in.re" "(" s "," a ")"; fn re_allchar () : regex => "re.allchar" "(" ")"; @@ -93,6 +94,7 @@ fn re_loop (r : regex, i : int, j : int) : regex => "re.loop" "(" r "," i "," j" fn re_union (r1 : regex, r2 : regex) : regex => "re.union" "(" r1 "," r2 ")"; fn re_inter (r1 : regex, r2 : regex) : regex => "re.inter" "(" r1 "," r2 ")"; fn re_comp (r : regex) : regex => "re.comp" "(" r ")"; +fn re_none () : regex => "re.none" "(" ")"; fn btrue : bool => "true"; fn bfalse : bool => "false"; diff --git a/Strata/Languages/Boogie/DDMTransform/Translate.lean b/Strata/Languages/Boogie/DDMTransform/Translate.lean index cc08092de..ffb6e670b 100644 --- a/Strata/Languages/Boogie/DDMTransform/Translate.lean +++ b/Strata/Languages/Boogie/DDMTransform/Translate.lean @@ -7,6 +7,7 @@ import Strata.DDM.AST import Strata.Languages.Boogie.DDMTransform.Parse import Strata.Languages.Boogie.BoogieGen +import Strata.DDM.Util.DecimalRat --------------------------------------------------------------------- @@ -14,7 +15,7 @@ namespace Strata /- Translating concrete syntax into abstract syntax -/ -open Boogie Lambda Imperative +open Boogie Lambda Imperative Lean.Parser open Std (ToFormat Format format) --------------------------------------------------------------------- @@ -22,13 +23,14 @@ open Std (ToFormat Format format) /- Translation Monad -/ structure TransState where + inputCtx : InputContext errors : Array String def TransM := StateM TransState deriving Monad -def TransM.run (m : TransM α) : (α × Array String) := - let (v, s) := StateT.run m { errors := #[] } +def TransM.run (ictx : InputContext) (m : TransM α) : (α × Array String) := + let (v, s) := StateT.run m { inputCtx := ictx, errors := #[] } (v, s.errors) instance : ToString (Boogie.Program × Array String) where @@ -36,11 +38,29 @@ instance : ToString (Boogie.Program × Array String) where "Errors: " ++ (toString p.snd) def TransM.error [Inhabited α] (msg : String) : TransM α := do - fun s => ((), { errors := s.errors.push msg }) + fun s => ((), { s with errors := s.errors.push msg }) return panic msg --------------------------------------------------------------------- +/- Metadata -/ + +def SourceRange.toMetaData (ictx : InputContext) (sr : SourceRange) : Imperative.MetaData Boogie.Expression := + let file := ictx.fileName + 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 ⟩ ⟩ + #[fileRangeElt] + +def getOpMetaData (op : Operation) : TransM (Imperative.MetaData Boogie.Expression) := + return op.ann.toMetaData (← StateT.get).inputCtx + +def getArgMetaData (arg : Arg) : TransM (Imperative.MetaData Boogie.Expression) := + return arg.ann.toMetaData (← StateT.get).inputCtx + +--------------------------------------------------------------------- + def checkOp (op : Operation) (name : QualifiedIdent) (argc : Nat) : TransM (Option α) := do if op.name != name then @@ -133,7 +153,7 @@ structure GenNum where structure TransBindings where boundTypeVars : Array TyIdentifier := #[] - boundVars : Array (LExpr LMonoTy Visibility) := #[] + boundVars : Array (LExpr BoogieLParams.mono) := #[] freeVars : Array Boogie.Decl := #[] gen : GenNum := (GenNum.mk 0 0 0 0) @@ -160,13 +180,13 @@ instance : Inhabited (List Boogie.Statement × TransBindings) where default := ([], {}) instance : Inhabited Boogie.Decl where - default := .var "badguy" (.forAll [] (.tcons "bool" [])) (.const "false" (.some .bool)) + default := .var "badguy" (.forAll [] (.tcons "bool" [])) (.false ()) instance : Inhabited (Procedure.CheckAttr) where default := .Default instance : Inhabited (Boogie.Decl × TransBindings) where - default := (.var "badguy" (.forAll [] (.tcons "bool" [])) (.const "false" (.some .bool)), {}) + default := (.var "badguy" (.forAll [] (.tcons "bool" [])) (.false ()), {}) instance : Inhabited (Boogie.Decls × TransBindings) where default := ([], {}) @@ -219,14 +239,14 @@ partial def translateLMonoTy (bindings : TransBindings) (arg : Arg) : assert! i < bindings.freeVars.size let decl := bindings.freeVars[i]! let ty_core ← match decl with - | .type (.con tcons) => + | .type (.con tcons) _md => -- Type Declaration let ty := tcons.toType -- While the "unsafe" below looks scary, we should be alright as far as -- Boogie is concerned. See `Boogie.TypeConstructor`, where there is no -- facility for providing the type arguments. pure ty.toMonoTypeUnsafe - | .type (.syn syn) => + | .type (.syn syn) _md => let ty := syn.toLHSLMonoTy pure ty | _ => @@ -281,7 +301,8 @@ def translateTypeSynonym (bindings : TransBindings) (op : Operation) : s!"translateTypeSynonym expects a comma separated list: {repr bargs[0]!}") op.args[1]! let typedef ← translateLMonoTy bindings op.args[3]! - let decl := Boogie.Decl.type (.syn { name := name, typeArgs := targs, type := typedef }) + let md ← getOpMetaData op + let decl := Boogie.Decl.type (.syn { name := name, typeArgs := targs, type := typedef }) md return (decl, { bindings with freeVars := bindings.freeVars.push decl }) @@ -302,9 +323,10 @@ def translateTypeDecl (bindings : TransBindings) (op : Operation) : | _ => TransM.error s!"translateTypeDecl expects a comma separated list: {repr bargs[0]!}") op.args[1]! + let md ← getOpMetaData op -- Only the number of type arguments is important; the exact identifiers are -- irrelevant. - let decl := Boogie.Decl.type (.con { name := name, numargs := numargs }) + let decl := Boogie.Decl.type (.con { name := name, numargs := numargs }) md return (decl, { bindings with freeVars := bindings.freeVars.push decl }) --------------------------------------------------------------------- @@ -388,9 +410,9 @@ def translateOptionMonoDeclList (bindings : TransBindings) (arg : Arg) : partial def dealiasTypeExpr (p : Program) (te : TypeExpr) : TypeExpr := match te with | (.fvar _ idx #[]) => - match p.globalContext.vars[idx]! with - | (_, (.expr te)) => te - | (_, (.type [] (.some te))) => te + match p.globalContext.kindOf! idx with + | .expr te => te + | .type [] (.some te) => te | _ => te | _ => te @@ -570,6 +592,7 @@ def translateFn (ty? : Option LMonoTy) (q : QualifiedIdent) : TransM Boogie.Expr | _, q`Boogie.old => return polyOldOp | _, q`Boogie.str_len => return strLengthOp | _, q`Boogie.str_concat => return strConcatOp + | _, q`Boogie.str_substr => return strSubstrOp | _, q`Boogie.str_toregex => return strToRegexOp | _, q`Boogie.str_inregex => return strInRegexOp | _, q`Boogie.re_all => return reAllOp @@ -582,6 +605,7 @@ def translateFn (ty? : Option LMonoTy) (q : QualifiedIdent) : TransM Boogie.Expr | _, q`Boogie.re_union => return reUnionOp | _, q`Boogie.re_inter => return reInterOp | _, q`Boogie.re_comp => return reCompOp + | _, q`Boogie.re_none => return reNoneOp | _, _ => TransM.error s!"translateFn: Unknown/unimplemented function {repr q} at type {repr ty?}" mutual @@ -594,14 +618,14 @@ def translateQuantifier TransM Boogie.Expression.Expr := do let xsArray ← translateDeclList bindings xsa -- Note: the indices in the following are placeholders - let newBoundVars := List.toArray (xsArray.mapIdx (fun i _ => LExpr.bvar i)) + let newBoundVars := List.toArray (xsArray.mapIdx (fun i _ => LExpr.bvar () i)) let boundVars' := bindings.boundVars ++ newBoundVars let xbindings := { bindings with boundVars := boundVars' } let b ← translateExpr p xbindings bodya -- Handle triggers if present let triggers ← match triggersa with - | none => pure LExpr.noTrigger + | none => pure (LExpr.noTrigger ()) | some tsa => translateTriggers p xbindings tsa -- Create one quantifier constructor per variable @@ -612,8 +636,8 @@ def translateQuantifier let triggers := if first then triggers else - LExpr.noTrigger - (.quant qk (.some mty) triggers e, false) + LExpr.noTrigger () + (.quant () qk (.some mty) triggers e, false) | _ => panic! s!"Expected monomorphic type in quantifier, got: {ty}" return xsArray.foldr buildQuantifier (init := (b, true)) |>.1 @@ -626,7 +650,7 @@ def translateTriggerGroup (p: Program) (bindings : TransBindings) (arg : Arg) : match op.name, op.args with | q`Boogie.trigger, #[tsa] => do let ts ← translateCommaSep (fun t => translateExpr p bindings t) tsa - return ts.foldl (fun g t => .app (.app addTriggerOp t) g) emptyTriggerGroupOp + return ts.foldl (fun g t => .app () (.app () addTriggerOp t) g) emptyTriggerGroupOp | _, _ => panic! s!"Unexpected operator in trigger group" partial @@ -637,11 +661,11 @@ def translateTriggers (p: Program) (bindings : TransBindings) (arg : Arg) : match op.name, op.args with | q`Boogie.triggersAtom, #[group] => let g ← translateTriggerGroup p bindings group - return .app (.app addTriggerGroupOp g) emptyTriggersOp + return .app () (.app () addTriggerGroupOp g) emptyTriggersOp | q`Boogie.triggersPush, #[triggers, group] => do let ts ← translateTriggers p bindings triggers let g ← translateTriggerGroup p bindings group - return .app (.app addTriggerGroupOp g) ts + return .app () (.app () addTriggerGroupOp g) ts | _, _ => panic! s!"Unexpected operator in trigger" partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : @@ -652,58 +676,62 @@ partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : match op, args with -- Constants/Literals | .fn _ q`Boogie.btrue, [] => - return .const "true" Lambda.LMonoTy.bool + return .true () | .fn _ q`Boogie.bfalse, [] => - return .const "false" Lambda.LMonoTy.bool + return .false () | .fn _ q`Boogie.natToInt, [xa] => let n ← translateNat xa - return .const (toString n) Lambda.LMonoTy.int + return .intConst () n | .fn _ q`Boogie.bv1Lit, [xa] => let n ← translateBitVec 1 xa - return .const (toString n) Lambda.LMonoTy.bv1 + return .bitvecConst () 1 n | .fn _ q`Boogie.bv8Lit, [xa] => let n ← translateBitVec 8 xa - return .const (toString n) Lambda.LMonoTy.bv8 + return .bitvecConst () 8 n | .fn _ q`Boogie.bv16Lit, [xa] => let n ← translateBitVec 16 xa - return .const (toString n) Lambda.LMonoTy.bv16 + return .bitvecConst () 16 n | .fn _ q`Boogie.bv32Lit, [xa] => let n ← translateBitVec 32 xa - return .const (toString n) Lambda.LMonoTy.bv32 + return .bitvecConst () 32 n | .fn _ q`Boogie.bv64Lit, [xa] => let n ← translateBitVec 64 xa - return .const (toString n) Lambda.LMonoTy.bv64 + return .bitvecConst () 64 n | .fn _ q`Boogie.strLit, [xa] => let x ← translateStr xa - return .const x Lambda.LMonoTy.string + return .strConst () x | .fn _ q`Boogie.realLit, [xa] => let x ← translateReal xa - return .const (toString x) Lambda.LMonoTy.real + return .realConst () (Strata.Decimal.toRat x) -- Equality | .fn _ q`Boogie.equal, [_tpa, xa, ya] => let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya - return .eq x y + return .eq () x y | .fn _ q`Boogie.not_equal, [_tpa, xa, ya] => let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya - return (.app Boogie.boolNotOp (.eq x y)) + return (.app () Boogie.boolNotOp (.eq () x y)) | .fn _ q`Boogie.bvnot, [tpa, xa] => let tp ← translateLMonoTy bindings (dealiasTypeArg p tpa) let x ← translateExpr p bindings xa - let fn : LExpr LMonoTy Visibility ← + let fn : LExpr BoogieLParams.mono ← translateFn (.some tp) q`Boogie.bvnot - return (.app fn x) + return (.app () fn x) -- If-then-else expression | .fn _ q`Boogie.if, [_tpa, ca, ta, fa] => let c ← translateExpr p bindings ca let t ← translateExpr p bindings ta let f ← translateExpr p bindings fa - return .ite c t f + return .ite () c t f -- Re.AllChar | .fn _ q`Boogie.re_allchar, [] => let fn ← translateFn .none q`Boogie.re_allchar return fn + -- Re.None + | .fn _ q`Boogie.re_none, [] => + let fn ← translateFn .none q`Boogie.re_none + return fn -- Re.All | .fn _ q`Boogie.re_all, [] => let fn ← translateFn .none q`Boogie.re_all @@ -728,38 +756,43 @@ partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : | q`Boogie.re_comp => do let fn ← translateFn .none fni let x ← translateExpr p bindings xa - return .mkApp fn [x] + return .mkApp () fn [x] | _ => TransM.error s!"translateExpr unimplemented {repr op} {repr args}" | .fn _ q`Boogie.neg_expr, [tpa, xa] => let ty ← translateLMonoTy bindings (dealiasTypeArg p tpa) let fn ← translateFn ty q`Boogie.neg_expr let x ← translateExpr p bindings xa - return .mkApp fn [x] + return .mkApp () fn [x] -- Strings | .fn _ q`Boogie.str_concat, [xa, ya] => let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya - return .mkApp Boogie.strConcatOp [x, y] + return .mkApp () Boogie.strConcatOp [x, y] + | .fn _ q`Boogie.str_substr, [xa, ia, na] => + let x ← translateExpr p bindings xa + let i ← translateExpr p bindings ia + let n ← translateExpr p bindings na + return .mkApp () Boogie.strSubstrOp [x, i, n] | .fn _ q`Boogie.old, [_tp, xa] => let x ← translateExpr p bindings xa - return .mkApp Boogie.polyOldOp [x] + return .mkApp () Boogie.polyOldOp [x] | .fn _ q`Boogie.map_get, [_ktp, _vtp, ma, ia] => let kty ← translateLMonoTy bindings _ktp let vty ← translateLMonoTy bindings _vtp -- TODO: use Boogie.mapSelectOp, but specialized - let fn : LExpr LMonoTy Visibility := (LExpr.op "select" (.some (LMonoTy.mkArrow (mapTy kty vty) [kty, vty]))) + let fn : LExpr BoogieLParams.mono := (LExpr.op () "select" (.some (LMonoTy.mkArrow (mapTy kty vty) [kty, vty]))) let m ← translateExpr p bindings ma let i ← translateExpr p bindings ia - return .mkApp fn [m, i] + return .mkApp () fn [m, i] | .fn _ q`Boogie.map_set, [_ktp, _vtp, ma, ia, xa] => let kty ← translateLMonoTy bindings _ktp let vty ← translateLMonoTy bindings _vtp -- TODO: use Boogie.mapUpdateOp, but specialized - let fn : LExpr LMonoTy Visibility := (LExpr.op "update" (.some (LMonoTy.mkArrow (mapTy kty vty) [kty, vty, mapTy kty vty]))) + let fn : LExpr BoogieLParams.mono := (LExpr.op () "update" (.some (LMonoTy.mkArrow (mapTy kty vty) [kty, vty, mapTy kty vty]))) let m ← translateExpr p bindings ma let i ← translateExpr p bindings ia let x ← translateExpr p bindings xa - return .mkApp fn [m, i, x] + return .mkApp () fn [m, i, x] -- Quantifiers | .fn _ q`Boogie.forall, [xsa, ba] => translateQuantifier .all p bindings xsa .none ba @@ -774,13 +807,13 @@ partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : let fn ← translateFn .none fni let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya - return .mkApp fn [x, y] + return .mkApp () fn [x, y] | .fn _ q`Boogie.re_loop, [xa, ya, za] => let fn ← translateFn .none q`Boogie.re_loop let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya let z ← translateExpr p bindings za - return .mkApp fn [x, y, z] + return .mkApp () fn [x, y, z] -- Binary function applications (polymorphic) | .fn _ fni, [tpa, xa, ya] => match fni with @@ -812,7 +845,7 @@ partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : let fn ← translateFn (.some ty) fni let x ← translateExpr p bindings xa let y ← translateExpr p bindings ya - return .mkApp fn [x, y] + return .mkApp () fn [x, y] | _ => TransM.error s!"translateExpr unimplemented {repr op} {repr args}" -- NOTE: Bound and free variables are numbered differently. Bound variables -- ascending order (so closer to deBrujin levels). @@ -820,35 +853,35 @@ partial def translateExpr (p : Program) (bindings : TransBindings) (arg : Arg) : if i < bindings.boundVars.size then let expr := bindings.boundVars[bindings.boundVars.size - (i+1)]! match expr with - | .bvar _ => return .bvar i + | .bvar m _ => return .bvar m i | _ => return expr else TransM.error s!"translateExpr out-of-range bound variable: {i}" | .fvar _ i, [] => assert! i < bindings.freeVars.size let decl := bindings.freeVars[i]! - let ty? ← match p.globalContext.vars[i]!.2 with + let ty? ← match p.globalContext.kindOf! i with |.expr te => pure (some (← translateLMonoTy bindings (.type te))) | _ => pure none match decl with - | .var name _ty _expr => + | .var name _ty _expr _md => -- Global Variable - return (.fvar name ty?) - | .func func => + return (.fvar () name ty?) + | .func func _md => -- 0-ary Function - return (.op func.name ty?) + return (.op () func.name ty?) | _ => - TransM.error s!"translateExpr unimplemented fvar decl: {format decl}" + TransM.error s!"translateExpr unimplemented fvar decl (no args): {format decl}" | .fvar _ i, argsa => -- Call of a function declared/defined in Boogie. assert! i < bindings.freeVars.size let decl := bindings.freeVars[i]! match decl with - | .func func => + | .func func _md => let args ← translateExprs p bindings argsa.toArray - return .mkApp func.opExpr args.toList + return .mkApp () func.opExpr args.toList | _ => - TransM.error s!"translateExpr unimplemented fvar decl: {format decl}" + TransM.error s!"translateExpr unimplemented fvar decl: {format decl} \nargs:{repr argsa}" | op, args => TransM.error s!"translateExpr unimplemented op:\n\ Op: {repr op}\n\ @@ -888,7 +921,7 @@ def translateVarStatement (bindings : TransBindings) (decls : Array Arg) : let (stmts, bindings) ← initVarStmts tpids bindings let newVars ← tpids.mapM (fun (id, ty) => if h: ty.isMonoType then - return ((LExpr.fvar id (ty.toMonoType h)): LExpr LMonoTy Visibility) + return ((LExpr.fvar () id (ty.toMonoType h)): LExpr BoogieLParams.mono) else TransM.error s!"translateVarStatement requires {id} to have a monomorphic type, but it has type {ty}") let bbindings := bindings.boundVars ++ newVars @@ -903,7 +936,7 @@ def translateInitStatement (p : Program) (bindings : TransBindings) (args : Arra let lhs ← translateIdent BoogieIdent args[1]! let val ← translateExpr p bindings args[2]! let ty := (.forAll [] mty) - let newBinding: LExpr LMonoTy Visibility := LExpr.fvar lhs mty + let newBinding: LExpr BoogieLParams.mono := LExpr.fvar () lhs mty let bbindings := bindings.boundVars ++ [newBinding] return ([.init lhs ty val], { bindings with boundVars := bbindings }) @@ -921,48 +954,58 @@ partial def translateStmt (p : Program) (bindings : TransBindings) (arg : Arg) : | q`Boogie.assign, #[_tpa, lhsa, ea] => let lhs ← translateLhs lhsa let val ← translateExpr p bindings ea - return ([.set lhs val], bindings) + let md ← getOpMetaData op + return ([.set lhs val md], bindings) | q`Boogie.havoc_statement, #[ida] => let id ← translateIdent BoogieIdent ida - return ([.havoc id], bindings) + let md ← getOpMetaData op + return ([.havoc id md], bindings) | q`Boogie.assert, #[la, ca] => let c ← translateExpr p bindings ca let default_name := s!"assert_{bindings.gen.assert_def}" let bindings := incrNum .assert_def bindings let l ← translateOptionLabel default_name la - return ([.assert l c], bindings) + let md ← getOpMetaData op + return ([.assert l c md], bindings) | q`Boogie.assume, #[la, ca] => let c ← translateExpr p bindings ca let default_name := s!"assume_{bindings.gen.assume_def}" let bindings := incrNum .assume_def bindings let l ← translateOptionLabel default_name la - return ([.assume l c], bindings) + let md ← getOpMetaData op + return ([.assume l c md], bindings) | q`Boogie.if_statement, #[ca, ta, fa] => let c ← translateExpr p bindings ca let (tss, bindings) ← translateBlock p bindings ta let (fss, bindings) ← translateElse p bindings fa - return ([.ite c { ss := tss } { ss := fss } ], bindings) + let md ← getOpMetaData op + return ([.ite c tss fss md], bindings) | q`Boogie.while_statement, #[ca, ia, ba] => let c ← translateExpr p bindings ca let i ← translateInvariant p bindings ia let (bodyss, bindings) ← translateBlock p bindings ba - return ([.loop c .none i { ss := bodyss } ], bindings) + let md ← getOpMetaData op + return ([.loop c .none i bodyss md], bindings) | q`Boogie.call_statement, #[lsa, fa, esa] => - let ls ← translateCommaSep (translateIdent BoogieIdent) lsa - let f ← translateIdent String fa - let es ← translateCommaSep (fun a => translateExpr p bindings a) esa - return ([.call ls.toList f es.toList], bindings) + let ls ← translateCommaSep (translateIdent BoogieIdent) lsa + let f ← translateIdent String fa + let es ← translateCommaSep (fun a => translateExpr p bindings a) esa + let md ← getOpMetaData op + return ([.call ls.toList f es.toList md], bindings) | q`Boogie.call_unit_statement, #[fa, esa] => - let f ← translateIdent String fa - let es ← translateCommaSep (fun a => translateExpr p bindings a) esa - return ([.call [] f es.toList], bindings) + let f ← translateIdent String fa + let es ← translateCommaSep (fun a => translateExpr p bindings a) esa + let md ← getOpMetaData op + return ([.call [] f es.toList md], bindings) | q`Boogie.block_statement, #[la, ba] => let l ← translateIdent String la let (ss, bindings) ← translateBlock p bindings ba - return ([.block l { ss := ss }], bindings) + let md ← getOpMetaData op + return ([.block l ss md], bindings) | q`Boogie.goto_statement, #[la] => let l ← translateIdent String la - return ([.goto l], bindings) + let md ← getOpMetaData op + return ([.goto l md], bindings) | name, args => TransM.error s!"Unexpected statement {name.fullName} with {args.size} arguments." partial def translateBlock (p : Program) (bindings : TransBindings) (arg : Arg) : @@ -1033,7 +1076,8 @@ def translateRequires (p : Program) (name : BoogieIdent) (count : Nat) (bindings let l ← translateOptionLabel s!"{name.name}_requires_{count}" args[0]! let free? ← translateOptionFree args[1]! let e ← translateExpr p bindings args[2]! - return [(l, { expr := e, attr := free? })] + let md ← getArgMetaData arg + return [(l, { expr := e, attr := free?, md := md })] def translateEnsures (p : Program) (name : BoogieIdent) (count : Nat) (bindings : TransBindings) (arg : Arg) : TransM (ListMap BoogieLabel Procedure.Check) := do @@ -1041,7 +1085,8 @@ def translateEnsures (p : Program) (name : BoogieIdent) (count : Nat) (bindings let l ← translateOptionLabel s!"{name.name}_ensures_{count}" args[0]! let free? ← translateOptionFree args[1]! let e ← translateExpr p bindings args[2]! - return [(l, { expr := e, attr := free? })] + let md ← getArgMetaData arg + return [(l, { expr := e, attr := free?, md := md })] def translateSpecElem (p : Program) (name : BoogieIdent) (count : Nat) (bindings : TransBindings) (arg : Arg) : TransM (List BoogieIdent × ListMap BoogieLabel Procedure.Check × ListMap BoogieLabel Procedure.Check) := do @@ -1082,8 +1127,8 @@ def translateProcedure (p : Program) (bindings : TransBindings) (op : Operation) let typeArgs ← translateTypeArgs op.args[1]! let sig ← translateBindings bindings op.args[2]! let ret ← translateOptionMonoDeclList bindings op.args[3]! - let in_bindings := (sig.map (fun (v, ty) => (LExpr.fvar v ty))).toArray - let out_bindings := (ret.map (fun (v, ty) => (LExpr.fvar v ty))).toArray + let in_bindings := (sig.map (fun (v, ty) => (LExpr.fvar () v ty))).toArray + let out_bindings := (ret.map (fun (v, ty) => (LExpr.fvar () v ty))).toArray -- This bindings order -- original, then inputs, and then outputs, is -- critical here. Is this right though? let origBindings := bindings @@ -1097,6 +1142,7 @@ def translateProcedure (p : Program) (bindings : TransBindings) (op : Operation) | TransM.error s!"translateProcedure body expected here: {repr op.args[4]!}" let (body, bindings) ← if bodya.isSome then translateBlock p bindings bodya.get! else pure ([], bindings) let origBindings := { origBindings with gen := bindings.gen } + let md ← getOpMetaData op return (.proc { header := { name := pname, typeArgs := typeArgs.toList, inputs := sig, @@ -1105,7 +1151,8 @@ def translateProcedure (p : Program) (bindings : TransBindings) (op : Operation) preconditions := requires, postconditions := ensures }, body := body - }, + } + md, origBindings) --------------------------------------------------------------------- @@ -1116,11 +1163,13 @@ def translateConstant (bindings : TransBindings) (op : Operation) : let cname ← translateIdent BoogieIdent op.args[0]! let typeArgs ← translateTypeArgs op.args[1]! let ret ← translateLMonoTy bindings op.args[2]! + let md ← getOpMetaData op let decl := .func { name := cname, typeArgs := typeArgs.toList, inputs := [], output := ret, body := none } + md return (decl, { bindings with freeVars := bindings.freeVars.push decl }) --------------------------------------------------------------------- @@ -1132,7 +1181,8 @@ def translateAxiom (p : Program) (bindings : TransBindings) (op : Operation) : let bindings := incrNum .axiom_def bindings let l ← translateOptionLabel default_name op.args[0]! let e ← translateExpr p bindings op.args[1]! - return (.ax (Axiom.mk l e), bindings) + let md ← getOpMetaData op + return (.ax (Axiom.mk l e) md, bindings) def translateDistinct (p : Program) (bindings : TransBindings) (op : Operation) : TransM (Boogie.Decl × TransBindings) := do @@ -1143,7 +1193,8 @@ def translateDistinct (p : Program) (bindings : TransBindings) (op : Operation) let es ← translateCommaSep (translateExpr p bindings) op.args[1]! if !(es.all LExpr.isOp) then TransM.error s!"arguments to `distinct` must all be constant names: {es}" - return (.distinct l es.toList, bindings) + let md ← getOpMetaData op + return (.distinct l es.toList md, bindings) --------------------------------------------------------------------- @@ -1174,7 +1225,7 @@ def translateFunction (status : FnInterp) (p : Program) (bindings : TransBinding let typeArgs ← translateTypeArgs op.args[1]! let sig ← translateBindings bindings op.args[2]! let ret ← translateLMonoTy bindings op.args[3]! - let in_bindings := (sig.map (fun (v, ty) => (LExpr.fvar v ty))).toArray + let in_bindings := (sig.map (fun (v, ty) => (LExpr.fvar () v ty))).toArray -- This bindings order -- original, then inputs, is -- critical here. Is this right though? let orig_bbindings := bindings.boundVars @@ -1188,12 +1239,13 @@ def translateFunction (status : FnInterp) (p : Program) (bindings : TransBinding let inline? ← match status with | .Definition => translateOptionInline op.args[5]! | .Declaration => pure #[] + let md ← getOpMetaData op let decl := .func { name := fname, typeArgs := typeArgs.toList, inputs := sig, output := ret, body := body, - attr := inline? } + attr := inline? } md return (decl, { bindings with boundVars := orig_bbindings, @@ -1206,7 +1258,8 @@ def translateGlobalVar (bindings : TransBindings) (op : Operation) : let _ ← @checkOp (Boogie.Decl × TransBindings) op q`Boogie.command_var 1 let (id, targs, mty) ← translateBindMk bindings op.args[0]! let ty := LTy.forAll targs mty - let decl := (.var id ty (Names.initVarValue (id.name ++ "_" ++ (toString bindings.gen.var_def)))) + let md ← getOpMetaData op + let decl := (.var id ty (Names.initVarValue (id.name ++ "_" ++ (toString bindings.gen.var_def))) md) let bindings := incrNum .var_def bindings return (decl, { bindings with freeVars := bindings.freeVars.push decl}) diff --git a/Strata/Languages/Boogie/Env.lean b/Strata/Languages/Boogie/Env.lean index 13790cea0..642a960d1 100644 --- a/Strata/Languages/Boogie/Env.lean +++ b/Strata/Languages/Boogie/Env.lean @@ -13,6 +13,38 @@ namespace Boogie open Std (ToFormat Format format) open Imperative +instance : ToFormat ExpressionMetadata := + show ToFormat Unit from inferInstance + +-- ToFormat instance for Expression.Expr +instance : ToFormat Expression.Expr := by + show ToFormat (Lambda.LExpr BoogieLParams.mono) + infer_instance + +-- Custom ToFormat instance for our specific Scope type to get the desired formatting +private def formatScope (m : Map BoogieIdent (Option Lambda.LMonoTy × Expression.Expr)) : Std.Format := + match m with + | [] => "" + | [(k, (ty, v))] => go k ty v + | (k, (ty, v)) :: rest => + go k ty v ++ Format.line ++ formatScope rest + where go k ty v := + match ty with + | some ty => f!"({k} : {ty}) → {v}" + | none => f!"{k} → {v}" + +instance : ToFormat (Map BoogieIdent (Option Lambda.LMonoTy × Expression.Expr)) where + format := formatScope + +instance : Inhabited ExpressionMetadata := + show Inhabited Unit from inferInstance + +instance : Lambda.Traceable Lambda.LExpr.EvalProvenance ExpressionMetadata where + combine _ := () + +instance : Inhabited (Lambda.LExpr ⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩) := + show Inhabited (Lambda.LExpr ⟨⟨Unit, BoogieIdent⟩, LMonoTy⟩) from inferInstance + --------------------------------------------------------------------- def PathCondition.format (p : PathCondition Expression) : Format := @@ -28,13 +60,16 @@ def PathConditions.format (ps : PathConditions Expression) : Format := | p :: prest => f!"{PathCondition.format p}{Format.line}" ++ PathConditions.format prest -def PathCondition.getVars (p : PathCondition Expression) : List (Lambda.IdentT Visibility) := +def PathCondition.getVars (p : PathCondition Expression) + : List (Lambda.IdentT Lambda.LMonoTy Visibility) := p.map (fun (_, e) => Lambda.LExpr.freeVars e) |> .flatten |> .eraseDups -def PathConditions.getVars (ps : PathConditions Expression) : List (Lambda.IdentT Visibility) := +def PathConditions.getVars (ps : PathConditions Expression) + : List (Lambda.IdentT Lambda.LMonoTy Visibility) := ps.map (fun p => PathCondition.getVars p) |> .flatten |> .eraseDups -def ProofObligation.getVars (d : ProofObligation Expression) : List (Lambda.IdentT Visibility) := +def ProofObligation.getVars (d : ProofObligation Expression) + : List (Lambda.IdentT Lambda.LMonoTy Visibility) := let o_vars := Lambda.LExpr.freeVars d.obligation let pc_vars := PathConditions.getVars d.assumptions (o_vars ++ pc_vars).eraseDups @@ -62,7 +97,7 @@ instance : ToFormat (ProofObligations Expression) where -- (FIXME) Parameterize by EvalContext typeclass. def ProofObligation.create (label : String) (assumptions : PathConditions Expression) - (obligation : Procedure.Check) (md : MetaData Expression := #[]): + (obligation : Procedure.Check): Option (ProofObligation Expression) := open Lambda.LExpr.SyntaxMono in if obligation.attr == .Free then @@ -72,18 +107,18 @@ def ProofObligation.create -- dbg_trace f!"{Format.line}Obligation {label} proved via evaluation!{Format.line}" -- none else - some (ProofObligation.mk label assumptions obligation.expr md) + some (ProofObligation.mk label assumptions obligation.expr obligation.md) def ProofObligations.create (assumptions : PathConditions Expression) - (obligations : ListMap String Procedure.Check) (md : MetaData Expression := #[]) + (obligations : ListMap String Procedure.Check) : ProofObligations Expression := match obligations with | [] => #[] | o :: orest => - let orest' := ProofObligations.create assumptions orest md + let orest' := ProofObligations.create assumptions orest let o' := - match (ProofObligation.create o.fst assumptions o.snd md) with + match (ProofObligation.create o.fst assumptions o.snd) with | none => #[] | some o' => #[o'] o' ++ orest' @@ -98,33 +133,38 @@ structure Env where program : Program substMap : SubstMap exprEnv : Expression.EvalEnv + datatypes : @Lambda.TypeFactory Visibility distinct : List (List Expression.Expr) pathConditions : Imperative.PathConditions Expression warnings : List (Imperative.EvalWarning Expression) deferred : Imperative.ProofObligations Expression -def Env.init : Env := +def Env.init (empty_factory:=false): Env := + let σ := Lambda.LState.init + let σ := if empty_factory then σ else σ.setFactory Boogie.Factory { error := none, program := Program.init, substMap := [], - exprEnv := ∅, + exprEnv := σ, + datatypes := #[], distinct := [], pathConditions := [], warnings := [] deferred := ∅ } instance : EmptyCollection Env where - emptyCollection := Env.init + emptyCollection := Env.init (empty_factory := true) instance : Inhabited Env where default := Env.init instance : ToFormat Env where format s := - let { error, program := _, substMap, exprEnv, distinct := _, pathConditions, warnings, deferred } := s + let { error, program := _, substMap, exprEnv, datatypes, distinct := _, pathConditions, warnings, deferred } := s format f!"Error:{Format.line}{error}{Format.line}\ Subst Map:{Format.line}{substMap}{Format.line}\ Expression Env:{Format.line}{exprEnv}{Format.line}\ + Datatypes:{Format.line}{datatypes}{Format.line}\ Path Conditions:{Format.line}{PathConditions.format pathConditions}{Format.line}{Format.line}\ Warnings:{Format.line}{warnings}{Format.line}\ Deferred Proof Obligations:{Format.line}{deferred}{Format.line}" @@ -145,7 +185,7 @@ def oldVarSubst (subst : SubstMap) (E : Env) : SubstMap := def Env.exprEval (E : Env) (e : Expression.Expr) : Expression.Expr := e.eval E.exprEnv.config.fuel E.exprEnv -def Env.pushScope (E : Env) (scope : (Lambda.Scope Visibility)) : Env := +def Env.pushScope (E : Env) (scope : (Lambda.Scope BoogieLParams)) : Env := { E with exprEnv.state := E.exprEnv.state.push scope } def Env.pushEmptyScope (E : Env) : Env := @@ -154,37 +194,39 @@ def Env.pushEmptyScope (E : Env) : Env := def Env.popScope (E : Env) : Env := { E with exprEnv.state := E.exprEnv.state.pop } -def Env.factory (E : Env) : (@Lambda.Factory Visibility) := +def Env.factory (E : Env) : (@Lambda.Factory BoogieLParams) := E.exprEnv.config.factory -def Env.addFactory (E : Env) (f : (@Lambda.Factory Visibility)) : Except Format Env := do +def Env.addFactory (E : Env) (f : (@Lambda.Factory BoogieLParams)) : Except Format Env := do let exprEnv ← E.exprEnv.addFactory f .ok { E with exprEnv := exprEnv } -def Env.addFactoryFunc (E : Env) (func : (Lambda.LFunc Visibility)) : Except Format Env := do +def Env.addFactoryFunc (E : Env) (func : (Lambda.LFunc BoogieLParams)) : Except Format Env := do let exprEnv ← E.exprEnv.addFactoryFunc func .ok { E with exprEnv := exprEnv } -def Env.insertInContext (xt : (Lambda.IdentT Visibility)) (e : Expression.Expr) (E : Env) : Env := - { E with exprEnv.state := E.exprEnv.state.insert xt.ident (xt.monoty?, e) } +def Env.insertInContext (xt : (Lambda.IdentT Lambda.LMonoTy Visibility)) (e : Expression.Expr) (E : Env) : Env := + { E with exprEnv.state := E.exprEnv.state.insert xt.ident (xt.ty?, e) } /-- Insert each `(x, v)` in `xs` into the context. -/ -def Env.addToContext (xs : Map (Lambda.IdentT Visibility) Expression.Expr) (E : Env) : Env := +def Env.addToContext + (xs : Map (Lambda.IdentT Lambda.LMonoTy Visibility) Expression.Expr) (E : Env) + : Env := List.foldl (fun E (x, v) => E.insertInContext x v) E xs -- TODO: prove uniqueness, add different prefix -def Env.genSym (x : String) (c : (Lambda.EvalConfig Visibility)) : BoogieIdent × (Lambda.EvalConfig Visibility) := +def Env.genSym (x : String) (c : Lambda.EvalConfig BoogieLParams) : BoogieIdent × Lambda.EvalConfig BoogieLParams := let new_idx := c.gen let c := c.incGen let new_var := c.varPrefix ++ x ++ toString new_idx (.temp new_var, c) -def Env.genVar' (x : String) (σ : (Lambda.LState Visibility)) : - (BoogieIdent × (Lambda.LState Visibility)) := +def Env.genVar' (x : String) (σ : (Lambda.LState BoogieLParams)) : + (BoogieIdent × (Lambda.LState BoogieLParams)) := let (new_var, config) := Env.genSym x σ.config - let σ : Lambda.LState Visibility := { σ with config := config } + let σ : Lambda.LState BoogieLParams := { σ with config := config } -- let known_vars := Lambda.LState.knownVars σ -- if new_var ∈ known_vars then -- panic s!"[LState.genVar] Generated variable {Std.format new_var} is not fresh!\n\ @@ -198,7 +240,7 @@ def Env.genVar (x : Expression.Ident) (E : Env) : Expression.Ident × Env := let (var, σ) := Env.genVar' name E.exprEnv (var, { E with exprEnv := σ }) -def Env.genVars (xs : List String) (σ : (Lambda.LState Visibility)) : (List BoogieIdent × (Lambda.LState Visibility)) := +def Env.genVars (xs : List String) (σ : Lambda.LState BoogieLParams) : (List BoogieIdent × Lambda.LState BoogieLParams) := match xs with | [] => ([], σ) | x :: rest => @@ -210,21 +252,22 @@ def Env.genVars (xs : List String) (σ : (Lambda.LState Visibility)) : (List Boo Generate a fresh variable using the base name and pre-existing type, if any, from `xt`. -/ -def Env.genFVar (E : Env) (xt : (Lambda.IdentT Visibility)) : +def Env.genFVar (E : Env) (xt : (Lambda.IdentT Lambda.LMonoTy Visibility)) : Expression.Expr × Env := let (xid, E) := E.genVar xt.ident - let xe := match xt.monoty? with - | none => .fvar xid none - | some xty => .fvar xid xty + let xe := match xt.ty? with + | none => .fvar () xid none + | some xty => .fvar () xid xty (xe, E) /-- Generate fresh variables using the base names and any pre-existing types from `xs`. -/ -def Env.genFVars (E : Env) (xs : List (Lambda.IdentT Visibility)) : +def Env.genFVars (E : Env) (xs : List (Lambda.IdentT Lambda.LMonoTy Visibility)) : List Expression.Expr × Env := - let rec go (acc : List Expression.Expr) (E : Env) (xs : List (Lambda.IdentT Visibility)) : + let rec go (acc : List Expression.Expr) (E : Env) + (xs : List (Lambda.IdentT Lambda.LMonoTy Visibility)) : List Expression.Expr × Env := match xs with | [] => (acc.reverse, E) @@ -238,10 +281,10 @@ Insert `(xi, .fvar xi)`, for each `xi` in `xs`, in the _oldest_ scope in `ss`, only if `xi` is the identifier of a free variable, i.e., it is not in `ss`. -/ def Env.insertFreeVarsInOldestScope - (xs : List (Lambda.IdentT Visibility)) (E : Env) : Env := + (xs : List (Lambda.IdentT Lambda.LMonoTy Visibility)) (E : Env) : Env := let (xis, xtyei) := xs.foldl (fun (acc_ids, acc_pairs) x => - (x.fst :: acc_ids, (x.snd, .fvar x.fst x.snd) :: acc_pairs)) + (x.fst :: acc_ids, (x.snd, .fvar () x.fst x.snd) :: acc_pairs)) ([], []) let state' := Maps.addInOldest E.exprEnv.state xis xtyei { E with exprEnv := { E.exprEnv with state := state' }} @@ -250,10 +293,10 @@ def Env.insertFreeVarsInOldestScope open Imperative Lambda in def PathCondition.merge (cond : Expression.Expr) (pc1 pc2 : PathCondition Expression) : PathCondition Expression := let pc1' := pc1.map (fun (label, e) => (label, mkImplies cond e)) - let pc2' := pc2.map (fun (label, e) => (label, mkImplies (LExpr.ite cond LExpr.false LExpr.true) e)) + let pc2' := pc2.map (fun (label, e) => (label, mkImplies (LExpr.ite () cond (LExpr.false ()) (LExpr.true ())) e)) pc1' ++ pc2' - where mkImplies (ant con : LExpr LMonoTy Visibility) : (LExpr LMonoTy Visibility) := - LExpr.ite ant con LExpr.true + where mkImplies (ant con : Expression.Expr) : Expression.Expr := + LExpr.ite () ant con (LExpr.true ()) def Env.performMerge (cond : Expression.Expr) (E1 E2 : Env) (_h1 : E1.error.isNone) (_h2 : E2.error.isNone) : Env := @@ -276,6 +319,11 @@ def Env.merge (cond : Expression.Expr) (E1 E2 : Env) : Env := else Env.performMerge cond E1 E2 (by simp_all) (by simp_all) +def Env.addDatatypes (E: Env) (datatypes: List (Lambda.LDatatype Visibility)) : Except Format Env := do + let f ← Lambda.TypeFactory.genFactory (T:=BoogieLParams) (datatypes.toArray) + let env ← E.addFactory f + return { env with datatypes := datatypes.toArray } + end Boogie --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/Examples/Examples.lean b/Strata/Languages/Boogie/Examples/Examples.lean deleted file mode 100644 index 1ec3da2dd..000000000 --- a/Strata/Languages/Boogie/Examples/Examples.lean +++ /dev/null @@ -1,36 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Strata.Languages.Boogie.Examples.AdvancedMaps -import Strata.Languages.Boogie.Examples.AdvancedQuantifiers -import Strata.Languages.Boogie.Examples.AssertionDefaultNames -import Strata.Languages.Boogie.Examples.Axioms -import Strata.Languages.Boogie.Examples.BitVecParse -import Strata.Languages.Boogie.Examples.DDMAxiomsExtraction -import Strata.Languages.Boogie.Examples.DDMTransform -import Strata.Languages.Boogie.Examples.FreeRequireEnsure -import Strata.Languages.Boogie.Examples.Functions -import Strata.Languages.Boogie.Examples.Goto -import Strata.Languages.Boogie.Examples.GeneratedLabels -import Strata.Languages.Boogie.Examples.Havoc -import Strata.Languages.Boogie.Examples.Loops -import Strata.Languages.Boogie.Examples.Map -import Strata.Languages.Boogie.Examples.Min -import Strata.Languages.Boogie.Examples.OldExpressions -import Strata.Languages.Boogie.Examples.PrecedenceCheck -import Strata.Languages.Boogie.Examples.ProcedureCall -import Strata.Languages.Boogie.Examples.Quantifiers -import Strata.Languages.Boogie.Examples.QuantifiersWithTypeAliases -import Strata.Languages.Boogie.Examples.RealBitVector -import Strata.Languages.Boogie.Examples.RecursiveProcIte -import Strata.Languages.Boogie.Examples.Regex -import Strata.Languages.Boogie.Examples.RemoveIrrelevantAxioms -import Strata.Languages.Boogie.Examples.SimpleProc -import Strata.Languages.Boogie.Examples.String -import Strata.Languages.Boogie.Examples.TypeAlias -import Strata.Languages.Boogie.Examples.TypeDecl -import Strata.Languages.Boogie.Examples.TypeVarImplicitlyQuantified -import Strata.Languages.Boogie.Examples.UnreachableAssert diff --git a/Strata/Languages/Boogie/Expressions.lean b/Strata/Languages/Boogie/Expressions.lean index c9c574760..ba9cef6f1 100644 --- a/Strata/Languages/Boogie/Expressions.lean +++ b/Strata/Languages/Boogie/Expressions.lean @@ -15,18 +15,23 @@ namespace Boogie open Std (ToFormat Format format) --------------------------------------------------------------------- +def ExpressionMetadata := Unit + abbrev Expression : Imperative.PureExpr := { Ident := BoogieIdent, - Expr := Lambda.LExpr Lambda.LMonoTy Visibility, + EqIdent := inferInstanceAs (DecidableEq (Lambda.Identifier _)) + Expr := Lambda.LExpr ⟨⟨ExpressionMetadata, Visibility⟩, Lambda.LMonoTy⟩, Ty := Lambda.LTy, TyEnv := @Lambda.TEnv Visibility, - TyContext := @Lambda.LContext Visibility, - EvalEnv := Lambda.LState Visibility, - EqIdent := inferInstanceAs (DecidableEq (Lambda.Identifier _))} + TyContext := @Lambda.LContext ⟨ExpressionMetadata, Visibility⟩, + EvalEnv := Lambda.LState ⟨ExpressionMetadata, Visibility⟩ } instance : Imperative.HasVarsPure Expression Expression.Expr where getVars := Lambda.LExpr.LExpr.getVars +instance : Inhabited Expression.Expr where + default := .intConst () 0 + --------------------------------------------------------------------- end Boogie diff --git a/Strata/Languages/Boogie/Factory.lean b/Strata/Languages/Boogie/Factory.lean index f09687bf3..fa0493245 100644 --- a/Strata/Languages/Boogie/Factory.lean +++ b/Strata/Languages/Boogie/Factory.lean @@ -35,22 +35,71 @@ def KnownLTys : LTys := def KnownTypes : KnownTypes := makeKnownTypes (KnownLTys.map (fun ty => ty.toKnownType!)) +def TImplicit {Metadata: Type} (IDMeta: Type): LExprParamsT := ({Metadata := Metadata, IDMeta}: LExprParams).mono + /-- Convert an LExpr LMonoTy Unit to an LExpr LMonoTy Visibility TODO: Remove when Lambda elaborator offers parametric identifier type -/ -def ToBoogieIdent (ine: LExpr LMonoTy Unit): (LExpr LMonoTy Visibility) := +def ToBoogieIdent {M: Type} (ine: LExpr (@TImplicit M Unit)): LExpr (@TImplicit M Visibility) := match ine with - | .const c ty => .const c ty - | .op o oty => .op (BoogieIdent.unres o.name) oty - | .bvar deBruijnIndex => .bvar deBruijnIndex - | .fvar name oty => .fvar (BoogieIdent.unres name.name) oty - | .mdata info e => .mdata info (ToBoogieIdent e) - | .abs oty e => .abs oty (ToBoogieIdent e) - | .quant k oty tr e => .quant k oty (ToBoogieIdent tr) (ToBoogieIdent e) - | .app fn e => .app (ToBoogieIdent fn) (ToBoogieIdent e) - | .ite c t e => .ite (ToBoogieIdent c) (ToBoogieIdent t) (ToBoogieIdent e) - | .eq e1 e2 => .eq (ToBoogieIdent e1) (ToBoogieIdent e2) + | .const m c => .const m c + | .op m o oty => .op m (BoogieIdent.unres o.name) oty + | .bvar m deBruijnIndex => .bvar m deBruijnIndex + | .fvar m name oty => .fvar m (BoogieIdent.unres name.name) oty + | .abs m oty e => .abs m oty (ToBoogieIdent e) + | .quant m k oty tr e => .quant m k oty (ToBoogieIdent tr) (ToBoogieIdent e) + | .app m fn e => .app m (ToBoogieIdent fn) (ToBoogieIdent e) + | .ite m c t e => .ite m (ToBoogieIdent c) (ToBoogieIdent t) (ToBoogieIdent e) + | .eq m e1 e2 => .eq m (ToBoogieIdent e1) (ToBoogieIdent e2) + + +private def bvBinaryOp (fn:∀ {n}, BitVec n → BitVec n → BitVec n) + (check:∀ {n}, BitVec n → BitVec n → Bool) + (m:BoogieLParams.Metadata) + (ops:List (LExpr BoogieLParams.mono)) + : Option (LExpr BoogieLParams.mono) := + match ops with + | [.bitvecConst _ n1 b1, .bitvecConst _ n2 b2] => + if h : n1 = n2 then + if check (h ▸ b1) b2 then + .some (.bitvecConst m n2 (fn (h ▸ b1) b2)) + else .none + else .none + | _ => .none + +private def bvShiftOp (fn:∀ {n}, BitVec n → Nat → BitVec n) + (m:BoogieLParams.Metadata) + (ops:List (LExpr BoogieLParams.mono)) + : Option (LExpr BoogieLParams.mono) := + match ops with + | [.bitvecConst _ n1 b1, .bitvecConst _ n2 b2] => + let i2 := BitVec.toNat b2 + if n1 = n2 && i2 < n1 then + .some (.bitvecConst m n1 (fn b1 i2)) + else .none + | _ => .none + +private def bvUnaryOp (fn:∀ {n}, BitVec n → BitVec n) + (m:BoogieLParams.Metadata) + (ops:List (LExpr BoogieLParams.mono)) + : Option (LExpr BoogieLParams.mono) := + match ops with + | [.bitvecConst _ n b] => .some (.bitvecConst m n (fn b)) + | _ => .none + +private def bvBinaryPred (fn:∀ {n}, BitVec n → BitVec n → Bool) + (swap:Bool) + (m:BoogieLParams.Metadata) + (ops:List (LExpr BoogieLParams.mono)) + : Option (LExpr BoogieLParams.mono) := + match ops with + | [.bitvecConst _ n1 b1, .bitvecConst _ n2 b2] => + if h : n1 = n2 then + let res := if swap then fn b2 (h ▸ b1) else fn (h ▸ b1) b2 + .some (.boolConst m res) + else .none + | _ => .none private def BVOpNames := @@ -65,6 +114,31 @@ private def BVOpAritys := "binaryPredicate", "binaryPredicate", "binaryPredicate", "binaryPredicate", "binaryPredicate", "binaryPredicate", "binaryPredicate", "binaryPredicate" ] +private def BVOpEvals := + [("Neg", Option.some (bvUnaryOp BitVec.neg)), + ("Add", .some (bvBinaryOp BitVec.add (λ_ _ => true))), + ("Sub", .some (bvBinaryOp BitVec.sub (λ_ _ => true))), + ("Mul", .some (bvBinaryOp BitVec.mul (λ_ _ => true))), + ("UDiv", .some (bvBinaryOp BitVec.udiv (λ_ y => y ≠ 0))), + ("UMod", .some (bvBinaryOp BitVec.umod (λ_ y => y ≠ 0))), + ("SDiv", .some (bvBinaryOp BitVec.sdiv (λ_ y => y ≠ 0))), + ("SMod", .some (bvBinaryOp BitVec.srem (λ_ y => y ≠ 0))), + ("Not", .some (bvUnaryOp BitVec.not)), + ("And", .some (bvBinaryOp BitVec.and (λ_ _ => true))), + ("Or", .some (bvBinaryOp BitVec.or (λ_ _ => true))), + ("Xor", .some (bvBinaryOp BitVec.xor (λ_ _ => true))), + ("Shl", .some (bvShiftOp BitVec.shiftLeft)), + ("UShr", .some (bvShiftOp BitVec.ushiftRight)), + ("SShr", .some (bvShiftOp BitVec.sshiftRight)), + ("ULt", .some (bvBinaryPred BitVec.ult false)), + ("ULe", .some (bvBinaryPred BitVec.ule false)), + ("UGt", .some (bvBinaryPred BitVec.ult true)), + ("UGe", .some (bvBinaryPred BitVec.ule true)), + ("SLt", .some (bvBinaryPred BitVec.slt false)), + ("SLe", .some (bvBinaryPred BitVec.sle false)), + ("SGt", .some (bvBinaryPred BitVec.slt true)), + ("SGe", .some (bvBinaryPred BitVec.sle true))] + /-- info: [("Neg", "unaryOp"), ("Add", "binaryOp"), ("Sub", "binaryOp"), ("Mul", "binaryOp"), ("UDiv", "binaryOp"), ("UMod", "binaryOp"), ("SDiv", "binaryOp"), ("SMod", "binaryOp"), ("Not", "unaryOp"), ("And", "binaryOp"), @@ -75,6 +149,8 @@ info: [("Neg", "unaryOp"), ("Add", "binaryOp"), ("Sub", "binaryOp"), ("Mul", "bi #guard_msgs in #eval List.zip BVOpNames BVOpAritys +variable [Coe String BoogieLParams.Identifier] + open Lean Elab Command in elab "ExpandBVOpFuncDefs" "[" sizes:num,* "]" : command => do for size in sizes.getElems do @@ -84,130 +160,145 @@ elab "ExpandBVOpFuncDefs" "[" sizes:num,* "]" : command => do let funcArity := mkIdent (.str (.str .anonymous "Lambda") arity) let opName := Syntax.mkStrLit s!"Bv{s}.{op}" let bvTypeName := Name.mkSimple s!"bv{s}" - elabCommand (← `(def $funcName : LFunc Visibility := $funcArity $opName mty[$(mkIdent bvTypeName):ident] none)) + let opStr := Syntax.mkStrLit op + elabCommand (← `(def $funcName : LFunc BoogieLParams := + $funcArity $opName mty[$(mkIdent bvTypeName):ident] + ((BVOpEvals.find? (fun (k,_) => k == $opStr)).bind (fun (_,w)=>w)))) ExpandBVOpFuncDefs[1, 2, 8, 16, 32, 64] /- Real Arithmetic Operations -/ -def realAddFunc : LFunc Visibility := binaryOp "Real.Add" mty[real] none -def realSubFunc : LFunc Visibility := binaryOp "Real.Sub" mty[real] none -def realMulFunc : LFunc Visibility := binaryOp "Real.Mul" mty[real] none -def realDivFunc : LFunc Visibility := binaryOp "Real.Div" mty[real] none -def realNegFunc : LFunc Visibility := unaryOp "Real.Neg" mty[real] none +def realAddFunc : LFunc BoogieLParams := binaryOp "Real.Add" mty[real] none +def realSubFunc : LFunc BoogieLParams := binaryOp "Real.Sub" mty[real] none +def realMulFunc : LFunc BoogieLParams := binaryOp "Real.Mul" mty[real] none +def realDivFunc : LFunc BoogieLParams := binaryOp "Real.Div" mty[real] none +def realNegFunc : LFunc BoogieLParams := unaryOp "Real.Neg" mty[real] none /- Real Comparison Operations -/ -def realLtFunc : LFunc Visibility := binaryPredicate "Real.Lt" mty[real] none -def realLeFunc : LFunc Visibility := binaryPredicate "Real.Le" mty[real] none -def realGtFunc : LFunc Visibility := binaryPredicate "Real.Gt" mty[real] none -def realGeFunc : LFunc Visibility := binaryPredicate "Real.Ge" mty[real] none +def realLtFunc : LFunc BoogieLParams := binaryPredicate "Real.Lt" mty[real] none +def realLeFunc : LFunc BoogieLParams := binaryPredicate "Real.Le" mty[real] none +def realGtFunc : LFunc BoogieLParams := binaryPredicate "Real.Gt" mty[real] none +def realGeFunc : LFunc BoogieLParams := binaryPredicate "Real.Ge" mty[real] none /- String Operations -/ -def strLengthFunc : LFunc Visibility := +def strLengthFunc : LFunc BoogieLParams := { name := "Str.Length", typeArgs := [], inputs := [("x", mty[string])] output := mty[int], - concreteEval := some (unOpCeval String Int LExpr.denoteString - (fun s => (Int.ofNat (String.length s))) - mty[int])} + concreteEval := some (unOpCeval (T:=BoogieLParams) String Int (.intConst (T:=BoogieLParams.mono)) (@LExpr.denoteString BoogieLParams) + (fun s => (Int.ofNat (String.length s))))} -def strConcatFunc : LFunc Visibility := +def strConcatFunc : LFunc BoogieLParams := { name := "Str.Concat", typeArgs := [], inputs := [("x", mty[string]), ("y", mty[string])] output := mty[string], - concreteEval := some (binOpCeval String String LExpr.denoteString - String.append mty[string])} + concreteEval := some (binOpCeval String String (.strConst (T := BoogieLParams.mono)) + LExpr.denoteString String.append)} -def strToRegexFunc : LFunc Visibility := +def strSubstrFunc : LFunc BoogieLParams := + { name := "Str.Substr", + typeArgs := [], + -- longest substring of `x` of length at most `n` starting at position `i`. + inputs := [("x", mty[string]), ("i", mty[int]), ("n", mty[int])] + output := mty[string] } + +def strToRegexFunc : LFunc BoogieLParams := { name := "Str.ToRegEx", typeArgs := [], inputs := [("x", mty[string])] output := mty[regex] } -def strInRegexFunc : LFunc Visibility := +def strInRegexFunc : LFunc BoogieLParams := { name := "Str.InRegEx", typeArgs := [], inputs := [("x", mty[string]), ("y", mty[regex])] output := mty[bool] } -def reAllCharFunc : LFunc Visibility := +def reAllCharFunc : LFunc BoogieLParams := { name := "Re.AllChar", typeArgs := [], inputs := [] output := mty[regex] } -def reAllFunc : LFunc Visibility := +def reAllFunc : LFunc BoogieLParams := { name := "Re.All", typeArgs := [], inputs := [] output := mty[regex] } -def reRangeFunc : LFunc Visibility := +def reRangeFunc : LFunc BoogieLParams := { name := "Re.Range", typeArgs := [], inputs := [("x", mty[string]), ("y", mty[string])] output := mty[regex] } -def reConcatFunc : LFunc Visibility := +def reConcatFunc : LFunc BoogieLParams := { name := "Re.Concat", typeArgs := [], inputs := [("x", mty[regex]), ("y", mty[regex])] output := mty[regex] } -def reStarFunc : LFunc Visibility := +def reStarFunc : LFunc BoogieLParams := { name := "Re.Star", typeArgs := [], inputs := [("x", mty[regex])] output := mty[regex] } -def rePlusFunc : LFunc Visibility := +def rePlusFunc : LFunc BoogieLParams := { name := "Re.Plus", typeArgs := [], inputs := [("x", mty[regex])] output := mty[regex] } -def reLoopFunc : LFunc Visibility := +def reLoopFunc : LFunc BoogieLParams := { name := "Re.Loop", typeArgs := [], inputs := [("x", mty[regex]), ("n1", mty[int]), ("n2", mty[int])] output := mty[regex] } -def reUnionFunc : LFunc Visibility := +def reUnionFunc : LFunc BoogieLParams := { name := "Re.Union", typeArgs := [], inputs := [("x", mty[regex]), ("y", mty[regex])] output := mty[regex] } -def reInterFunc : LFunc Visibility := +def reInterFunc : LFunc BoogieLParams := { name := "Re.Inter", typeArgs := [], inputs := [("x", mty[regex]), ("y", mty[regex])] output := mty[regex] } -def reCompFunc : LFunc Visibility := +def reCompFunc : LFunc BoogieLParams := { name := "Re.Comp", typeArgs := [], inputs := [("x", mty[regex])] output := mty[regex] } +def reNoneFunc : LFunc BoogieLParams := + { name := "Re.None", + typeArgs := [], + inputs := [] + output := mty[regex] } + /- A polymorphic `old` function with type `∀a. a → a`. -/ -def polyOldFunc : LFunc Visibility := +def polyOldFunc : LFunc BoogieLParams := { name := "old", typeArgs := ["a"], inputs := [((BoogieIdent.locl "x"), mty[%a])] output := mty[%a]} /- A `Map` selection function with type `∀k, v. Map k v → k → v`. -/ -def mapSelectFunc : LFunc Visibility := +def mapSelectFunc : LFunc BoogieLParams := { name := "select", typeArgs := ["k", "v"], inputs := [("m", mapTy mty[%k] mty[%v]), ("i", mty[%k])], output := mty[%v] } /- A `Map` update function with type `∀k, v. Map k v → k → v → Map k v`. -/ -def mapUpdateFunc : LFunc Visibility := +def mapUpdateFunc : LFunc BoogieLParams := { name := "update", typeArgs := ["k", "v"], inputs := [("m", mapTy mty[%k] mty[%v]), ("i", mty[%k]), ("x", mty[%v])], @@ -241,29 +332,31 @@ def mapUpdateFunc : LFunc Visibility := ))))] ] } +instance : Coe String BoogieLParams.Identifier where + coe | s => ⟨s, .unres⟩ -def emptyTriggersFunc : LFunc Visibility := +def emptyTriggersFunc : LFunc BoogieLParams := { name := "Triggers.empty", typeArgs := [], inputs := [], output := mty[Triggers], concreteEval := none } -def addTriggerGroupFunc : LFunc Visibility := +def addTriggerGroupFunc : LFunc BoogieLParams := { name := "Triggers.addGroup", typeArgs := [], inputs := [("g", mty[TriggerGroup]), ("t", mty[Triggers])], output := mty[Triggers], concreteEval := none } -def emptyTriggerGroupFunc : LFunc Visibility := +def emptyTriggerGroupFunc : LFunc BoogieLParams := { name := "TriggerGroup.empty", typeArgs := [], inputs := [], output := mty[TriggerGroup], concreteEval := none } -def addTriggerFunc : LFunc Visibility := +def addTriggerFunc : LFunc BoogieLParams := { name := "TriggerGroup.addTrigger", typeArgs := ["a"], inputs := [("x", mty[%a]), ("t", mty[TriggerGroup])], @@ -279,14 +372,14 @@ macro "ExpandBVOpFuncNames" "[" sizes:num,* "]" : term => do allOps := allOps ++ ops.toArray `([$(allOps),*]) -def bvConcatFunc (size : Nat) : LFunc Visibility := +def bvConcatFunc (size : Nat) : LFunc BoogieLParams := { name := s!"Bv{size}.Concat", typeArgs := [], inputs := [("x", .bitvec size), ("y", .bitvec size)] output := .bitvec (size*2), concreteEval := none } -def bvExtractFunc (size hi lo: Nat) : LFunc Visibility := +def bvExtractFunc (size hi lo: Nat) : LFunc BoogieLParams := { name := s!"Bv{size}.Extract_{hi}_{lo}", typeArgs := [], inputs := [("x", .bitvec size)] @@ -307,7 +400,7 @@ def bv64Extract_31_0_Func := bvExtractFunc 64 31 0 def bv64Extract_15_0_Func := bvExtractFunc 64 15 0 def bv64Extract_7_0_Func := bvExtractFunc 64 7 0 -def Factory : @Factory Visibility := #[ +def Factory : @Factory BoogieLParams := #[ intAddFunc, intSubFunc, intMulFunc, @@ -315,10 +408,10 @@ def Factory : @Factory Visibility := #[ intModFunc, intNegFunc, - intLtFunc, - intLeFunc, - intGtFunc, - intGeFunc, + @intLtFunc BoogieLParams _, + @intLeFunc BoogieLParams _, + @intGtFunc BoogieLParams _, + @intGeFunc BoogieLParams _, realAddFunc, realSubFunc, @@ -330,14 +423,15 @@ def Factory : @Factory Visibility := #[ realGtFunc, realGeFunc, - boolAndFunc, - boolOrFunc, - boolImpliesFunc, - boolEquivFunc, - boolNotFunc, + @boolAndFunc BoogieLParams _, + @boolOrFunc BoogieLParams _, + @boolImpliesFunc BoogieLParams _, + @boolEquivFunc BoogieLParams _, + @boolNotFunc BoogieLParams _, strLengthFunc, strConcatFunc, + strSubstrFunc, strToRegexFunc, strInRegexFunc, reAllFunc, @@ -350,6 +444,7 @@ def Factory : @Factory Visibility := #[ reUnionFunc, reInterFunc, reCompFunc, + reNoneFunc, polyOldFunc, @@ -384,6 +479,9 @@ elab "DefBVOpFuncExprs" "[" sizes:num,* "]" : command => do let funcName := mkIdent (.str (.str .anonymous "Boogie") s!"bv{s}{op}Func") elabCommand (← `(def $opName : Expression.Expr := ($funcName).opExpr)) +instance : Inhabited BoogieLParams.Metadata where + default := () + DefBVOpFuncExprs [1, 8, 16, 32, 64] def bv8ConcatOp : Expression.Expr := bv8ConcatFunc.opExpr @@ -405,6 +503,9 @@ def addTriggerGroupOp : Expression.Expr := addTriggerGroupFunc.opExpr def emptyTriggerGroupOp : Expression.Expr := emptyTriggerGroupFunc.opExpr def addTriggerOp : Expression.Expr := addTriggerFunc.opExpr +instance : Inhabited (⟨ExpressionMetadata, BoogieIdent⟩: LExprParams).Metadata where + default := () + def intAddOp : Expression.Expr := intAddFunc.opExpr def intSubOp : Expression.Expr := intSubFunc.opExpr def intMulOp : Expression.Expr := intMulFunc.opExpr @@ -424,13 +525,14 @@ def realLtOp : Expression.Expr := realLtFunc.opExpr def realLeOp : Expression.Expr := realLeFunc.opExpr def realGtOp : Expression.Expr := realGtFunc.opExpr def realGeOp : Expression.Expr := realGeFunc.opExpr -def boolAndOp : Expression.Expr := boolAndFunc.opExpr -def boolOrOp : Expression.Expr := boolOrFunc.opExpr -def boolImpliesOp : Expression.Expr := boolImpliesFunc.opExpr -def boolEquivOp : Expression.Expr := boolEquivFunc.opExpr -def boolNotOp : Expression.Expr := boolNotFunc.opExpr +def boolAndOp : Expression.Expr := @boolAndFunc.opExpr BoogieLParams _ +def boolOrOp : Expression.Expr := @boolOrFunc.opExpr BoogieLParams _ +def boolImpliesOp : Expression.Expr := @boolImpliesFunc.opExpr BoogieLParams _ +def boolEquivOp : Expression.Expr := @boolEquivFunc.opExpr BoogieLParams _ +def boolNotOp : Expression.Expr := @boolNotFunc.opExpr BoogieLParams _ def strLengthOp : Expression.Expr := strLengthFunc.opExpr def strConcatOp : Expression.Expr := strConcatFunc.opExpr +def strSubstrOp : Expression.Expr := strSubstrFunc.opExpr def strToRegexOp : Expression.Expr := strToRegexFunc.opExpr def strInRegexOp : Expression.Expr := strInRegexFunc.opExpr def reAllOp : Expression.Expr := reAllFunc.opExpr @@ -443,16 +545,17 @@ def reLoopOp : Expression.Expr := reLoopFunc.opExpr def reUnionOp : Expression.Expr := reUnionFunc.opExpr def reInterOp : Expression.Expr := reInterFunc.opExpr def reCompOp : Expression.Expr := reCompFunc.opExpr +def reNoneOp : Expression.Expr := reNoneFunc.opExpr def polyOldOp : Expression.Expr := polyOldFunc.opExpr def mapSelectOp : Expression.Expr := mapSelectFunc.opExpr def mapUpdateOp : Expression.Expr := mapUpdateFunc.opExpr def mkTriggerGroup (ts : List Expression.Expr) : Expression.Expr := - ts.foldl (fun g t => .app (.app addTriggerOp t) g) emptyTriggerGroupOp + ts.foldl (fun g t => .app () (.app () addTriggerOp t) g) emptyTriggerGroupOp def mkTriggerExpr (ts : List (List Expression.Expr)) : Expression.Expr := let groups := ts.map mkTriggerGroup - groups.foldl (fun gs g => .app (.app addTriggerGroupOp g) gs) emptyTriggersOp + groups.foldl (fun gs g => .app () (.app () addTriggerGroupOp g) gs) emptyTriggersOp /-- Get all the built-in functions supported by Boogie. diff --git a/Strata/Languages/Boogie/Function.lean b/Strata/Languages/Boogie/Function.lean index 2dab43784..81e194839 100644 --- a/Strata/Languages/Boogie/Function.lean +++ b/Strata/Languages/Boogie/Function.lean @@ -17,17 +17,24 @@ open Lambda /-! # Boogie Functions -/ -abbrev Function := Lambda.LFunc Visibility +abbrev Function := Lambda.LFunc BoogieLParams + +-- Type class instances to enable type class resolution for BoogieLParams.Identifier +instance : DecidableEq BoogieLParams.IDMeta := + show DecidableEq Visibility from inferInstance + +instance : ToFormat BoogieLParams.IDMeta := + show ToFormat Visibility from inferInstance open LTy.Syntax LExpr.SyntaxMono in /-- info: ok: ∀[a, b]. (arrow int (arrow a (arrow b (arrow a a)))) -/ #guard_msgs in -#eval do let type ← LFunc.type (IDMeta:=Visibility) - ({ name := (BoogieIdent.unres "Foo"), +#eval do let type ← LFunc.type (T:=BoogieLParams) + ({ name := BoogieIdent.unres "Foo", typeArgs := ["a", "b"], - inputs := [((BoogieIdent.locl "w"), mty[int]), ((BoogieIdent.locl "x"), mty[%a]), ((BoogieIdent.locl "y"), mty[%b]), ((BoogieIdent.locl "z"), mty[%a])], + inputs := [(BoogieIdent.locl "w", mty[int]), (BoogieIdent.locl "x", mty[%a]), (BoogieIdent.locl "y", mty[%b]), (BoogieIdent.locl "z", mty[%a])], output := mty[%a], - body := some (.fvar (BoogieIdent.locl "x") none) } : Function) + body := some (LExpr.fvar () (BoogieIdent.locl "x") none) } : Function) return format type --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/FunctionType.lean b/Strata/Languages/Boogie/FunctionType.lean index 522054105..089b41ec2 100644 --- a/Strata/Languages/Boogie/FunctionType.lean +++ b/Strata/Languages/Boogie/FunctionType.lean @@ -18,31 +18,40 @@ namespace Function open Lambda Imperative open Std (ToFormat Format format) -def typeCheck (C: Boogie.Expression.TyContext) (T : Boogie.Expression.TyEnv) (func : Function) : - Except Format (Function × Boogie.Expression.TyEnv) := do +def typeCheck (C: Boogie.Expression.TyContext) (Env : Boogie.Expression.TyEnv) (func : Function) : + Except Format (Function × Boogie.Expression.TyEnv) := do -- (FIXME) Very similar to `Lambda.inferOp`, except that the body is annotated - -- using `LExprT.fromLExpr`. Can we share code here? + -- using `LExprT.resolve`. Can we share code here? -- -- `LFunc.type` below will also catch any ill-formed functions (e.g., -- where there are duplicates in the formals, etc.). let type ← func.type - let (_ty, T) ← LTy.instantiateWithCheck type C T + let (monoty, Env) ← LTy.instantiateWithCheck type C Env + let monotys := monoty.destructArrow + let input_mtys := monotys.dropLast + let output_mty := monotys.getLast (by exact LMonoTy.destructArrow_non_empty monoty) + -- Resolve type aliases and monomorphize inputs and output. + let func := { func with + typeArgs := monoty.freeVars.eraseDups, + inputs := func.inputs.keys.zip input_mtys, + output := output_mty} match func.body with - | none => .ok (func, T) + | none => .ok (func, Env) | some body => -- Temporarily add formals in the context. - let T := T.pushEmptyContext - let T := T.addToContext func.inputPolyTypes + let Env := Env.pushEmptyContext + let Env := Env.addToContext func.inputPolyTypes -- Type check and annotate the body, and ensure that it unifies with the -- return type. - let (bodya, T) ← LExprT.fromLExpr C T body + let (bodya, Env) ← LExpr.resolve C Env body let bodyty := bodya.toLMonoTy - let (retty, T) ← func.outputPolyType.instantiateWithCheck C T - let S ← Constraints.unify [(retty, bodyty)] T.stateSubstInfo - let T := T.updateSubst S - let T := T.popContext - let new_func := { func with body := bodya.toLExpr } - .ok (new_func, T) + let (retty, Env) ← func.outputPolyType.instantiateWithCheck C Env + let S ← Constraints.unify [(retty, bodyty)] Env.stateSubstInfo |>.mapError format + let Env := Env.updateSubst S + let Env := Env.popContext + -- Resolve type aliases and monomorphize the body. + let new_func := { func with body := some bodya.unresolved } + .ok (new_func, Env) end Function diff --git a/Strata/Languages/Boogie/Identifiers.lean b/Strata/Languages/Boogie/Identifiers.lean index ff8e6cbc9..081ef9509 100644 --- a/Strata/Languages/Boogie/Identifiers.lean +++ b/Strata/Languages/Boogie/Identifiers.lean @@ -7,6 +7,7 @@ import Strata.DL.Lambda.LExprTypeEnv +import Strata.DL.Lambda.Factory namespace Boogie open Std @@ -40,7 +41,7 @@ inductive Visibility where | glob | locl | temp -deriving DecidableEq, Repr +deriving DecidableEq, Repr, Inhabited instance : ToFormat Visibility where format @@ -49,10 +50,12 @@ instance : ToFormat Visibility where | .locl => "l:" | .temp => "t:" +abbrev BoogieIdent := Lambda.Identifier Visibility instance : ToString Visibility where toString v := toString $ ToFormat.format v -abbrev BoogieIdent := Lambda.Identifier Visibility +abbrev BoogieExprMetadata := Unit +abbrev BoogieLParams: Lambda.LExprParams := {Metadata := BoogieExprMetadata, IDMeta := Visibility} abbrev BoogieLabel := String def BoogieIdentDec : DecidableEq BoogieIdent := inferInstanceAs (DecidableEq (Lambda.Identifier Visibility)) @@ -94,6 +97,15 @@ def BoogieIdent.toPretty (x : BoogieIdent) : String := instance : ToFormat BoogieIdent where format i := BoogieIdent.toPretty i +-- Explicit instances for BoogieLParams field access +instance : ToFormat BoogieLParams.Identifier := + show ToFormat BoogieIdent from inferInstance + +instance : DecidableEq BoogieLParams.Identifier := + show DecidableEq BoogieIdent from inferInstance + + + /-- Full representation of Boogie Identifier with scope. This can be useful for both debugging and generating "unique" strings, for example, as labels of proof obligations in the VC generator. @@ -128,26 +140,34 @@ def elabBoogieIdent : Syntax → MetaM Expr return ← mkAppM ``BoogieIdent.unres #[mkStrLit s] | _ => throwUnsupportedSyntax -instance : MkIdent Visibility where +-- +instance : MkLExprParams ⟨BoogieExprMetadata, Visibility⟩ where elabIdent := elabBoogieIdent - toExpr := .const ``Visibility [] + toExpr := mkApp2 (mkConst ``Lambda.LExprParams.mk) (mkConst ``BoogieExprMetadata) (.const ``Visibility []) -elab "eb[" e:lexprmono "]" : term => elabLExprMono (IDMeta:=Visibility) e +elab "eb[" e:lexprmono "]" : term => elabLExprMono (T:=⟨BoogieExprMetadata, Visibility⟩) e -/-- info: Lambda.LExpr.op (BoogieIdent.unres "old") none : Lambda.LExpr Lambda.LMonoTy Visibility -/ +/-- +info: Lambda.LExpr.op () (BoogieIdent.unres "old") + none : Lambda.LExpr { Metadata := BoogieExprMetadata, IDMeta := Visibility }.mono +-/ #guard_msgs in #check eb[~old] /-- -info: (Lambda.LExpr.op (BoogieIdent.unres "old") none).app - (Lambda.LExpr.fvar (BoogieIdent.unres "a") none) : Lambda.LExpr Lambda.LMonoTy Visibility +info: Lambda.LExpr.app () (Lambda.LExpr.op () (BoogieIdent.unres "old") none) + (Lambda.LExpr.fvar () (BoogieIdent.unres "a") + none) : Lambda.LExpr { Metadata := BoogieExprMetadata, IDMeta := Visibility }.mono -/ #guard_msgs in #check eb[(~old a)] open Lambda.LTy.Syntax in -/-- info: Lambda.LExpr.fvar (BoogieIdent.unres "x") - (some (Lambda.LMonoTy.tcons "bool" [])) : Lambda.LExpr Lambda.LMonoTy Visibility -/ + +/-- +info: Lambda.LExpr.fvar () (BoogieIdent.unres "x") + (some (Lambda.LMonoTy.tcons "bool" [])) : Lambda.LExpr { Metadata := BoogieExprMetadata, IDMeta := Visibility }.mono +-/ #guard_msgs in #check eb[(x : bool)] diff --git a/Strata/Languages/Boogie/OldExpressions.lean b/Strata/Languages/Boogie/OldExpressions.lean index ea1386c6f..a1d6263a0 100644 --- a/Strata/Languages/Boogie/OldExpressions.lean +++ b/Strata/Languages/Boogie/OldExpressions.lean @@ -4,8 +4,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ - - import Strata.Languages.Boogie.BoogieGen import Strata.Languages.Boogie.Procedure @@ -21,7 +19,7 @@ From Section 4.3 in "This is Boogie 2": "Postconditions and procedure implementations are two-state contexts. This means that it is possible to refer to two different values of each variable. In a -postcondition, the two states are the pre- and post-states of the procedure’s +postcondition, the two states are the pre- and post-states of the procedure's invocations, and in a procedure implementation, the two states are the pre-state of the procedure and the current state. In both cases, the pre-state value of an expression is denoted by enclosing it as the argument to `old`. For example, in @@ -51,44 +49,49 @@ are only left with `old(var)` expressions: 2. Any `old(var)` in the postcondition of a procedure `Q` that is called in `P` can be replaced by the value of `var` immediately before `Q`'s call. -/ - @[match_pattern] def oldExpr + (mApp: ExpressionMetadata) + (mOp: ExpressionMetadata) {tyold : Option Lambda.LMonoTy} (e : Expression.Expr) : Expression.Expr - := .app (.op (BoogieIdent.unres "old") tyold) e + := + .app mApp (.op mOp (BoogieIdent.unres "old") tyold) e @[match_pattern] def oldVar + (mApp: ExpressionMetadata) + (mOp: ExpressionMetadata) + (mVar: ExpressionMetadata) {tyold : Option Lambda.LMonoTy} (v : Expression.Ident) {tyv : Option Lambda.LMonoTy} : Expression.Expr - := @oldExpr tyold (.fvar v tyv) + := @oldExpr mApp mOp tyold (.fvar mVar v tyv) inductive IsOldPred : Expression.Expr → Prop where - | oldPred : IsOldPred (.op "old" ty) + | oldPred : IsOldPred (.op m (BoogieIdent.unres "old") ty) def IsOldPred.decidablePred (e : Expression.Expr): Decidable (IsOldPred e) := match He : e with - | .op id ty => + | .op m id ty => if Hid : (id = "old") then by simp [Hid]; exact isTrue oldPred else by apply isFalse; intros Hold; cases Hold; contradiction - | .const _ _ | .bvar _ | .fvar _ _ | .mdata _ _ | .abs _ _ - | .quant _ _ _ _ | .app _ _ | .ite _ _ _ | .eq _ _ => + | .const _ _ | .bvar _ _ | .fvar _ _ _ | .abs _ _ _ + | .quant _ _ _ _ _ | .app _ _ _ | .ite _ _ _ _ | .eq _ _ _ => by apply isFalse; intros Hold; cases Hold inductive IsFvar : Expression.Expr → Prop where - | fvar : IsFvar (.fvar v ty) + | fvar : IsFvar (.fvar () v ty) def IsFvar.decidablePred (e : Expression.Expr): Decidable (IsFvar e) := match He : e with - | .fvar v ty => isTrue fvar - | .op _ _ | .const _ _ | .bvar _ | .mdata _ _ | .abs _ _ - | .quant _ _ _ _ | .app _ _ | .ite _ _ _ | .eq _ _ => + | .fvar _ v ty => isTrue fvar + | .op _ _ _ | .const _ _ | .bvar _ _ | .abs _ _ _ + | .quant _ _ _ _ _ | .app _ _ _ | .ite _ _ _ _ | .eq _ _ _ => by apply isFalse; intros H; cases H /-- Normalize an expression containing applications of the `old` function by @@ -100,32 +103,30 @@ E.g., `old(a + b + c) == old(a) + old(b) + old(c)` and `old(old(g)) == old(g)`. def normalizeOldExpr (e : Expression.Expr) (inOld : Bool := false) : Expression.Expr := match _He : e with - | .fvar v ty => + | .fvar _ v ty => if inOld then - @oldVar none v ty -- ignoring the operation type + @oldVar e.metadata e.metadata e.metadata none v ty -- ignoring the operation type else e - | .const _ _ | .bvar _ | .op _ _ => e - | .mdata m e' => .mdata m (normalizeOldExpr e' inOld) - | .abs ty e' => .abs ty (normalizeOldExpr e' inOld) - | .quant qk ty tr' e' => .quant qk ty (normalizeOldExpr tr' inOld) (normalizeOldExpr e' inOld) - | .app e1 e2 => + | .const _ _ | .bvar _ _ | .op _ _ _ => e + | .abs m ty e' => .abs m ty (normalizeOldExpr e' inOld) + | .quant m qk ty tr' e' => .quant m qk ty (normalizeOldExpr tr' inOld) (normalizeOldExpr e' inOld) + | .app m e1 e2 => match _He1 : e1 with - | .op o ty => + | .op m o ty => if _Hop : o = "old" then -- is an old var or old expr match _He2 : e2 with - | .fvar _ _ => e + | .fvar _ _ _ => e | e' => normalizeOldExpr e' true else - .app (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) - | _ => .app (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) - | .ite c t f => .ite (normalizeOldExpr c inOld) + .app m (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) + | _ => .app m (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) + | .ite m c t f => .ite m (normalizeOldExpr c inOld) (normalizeOldExpr t inOld) (normalizeOldExpr f inOld) - | .eq e1 e2 => .eq (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) + | .eq m e1 e2 => .eq m (normalizeOldExpr e1 inOld) (normalizeOldExpr e2 inOld) termination_by sizeOf e decreasing_by all_goals simp [sizeOf, Lambda.LExpr.sizeOf]; try simp_all; omega - simp [_He1, Lambda.LExpr.sizeOf]; omega def normalizeOldExprs (sm : List Expression.Expr) := sm.map normalizeOldExpr @@ -147,7 +148,7 @@ def normalizeOldExprs (sm : List Expression.Expr) := #eval normalizeOldExpr eb[(~old ((f a) g))] == eb[(((~old f) (~old a)) (~old g))] def normalizeOldCheck (c : Procedure.Check) : Procedure.Check := - { expr := normalizeOldExpr c.expr, attr := c.attr } + { c with expr := normalizeOldExpr c.expr } def normalizeOldChecks (c : ListMap String Procedure.Check) : ListMap String Procedure.Check := c.map (λ p ↦ (p.fst, normalizeOldCheck p.snd)) @@ -161,15 +162,14 @@ This function is agnostic of old expression normalization (see -/ def containsOldExpr (e : Expression.Expr) : Bool := match e with - | .op (BoogieIdent.unres "old") _ => true - | .op _ _ => false - | .const _ _ | .bvar _ | .fvar _ _ => false - | .mdata _ e' => containsOldExpr e' - | .abs _ e' => containsOldExpr e' - | .quant _ _ tr' e' => containsOldExpr tr' || containsOldExpr e' - | .app e1 e2 => containsOldExpr e1 || containsOldExpr e2 - | .ite c t f => containsOldExpr c || containsOldExpr t || containsOldExpr f - | .eq e1 e2 => containsOldExpr e1 || containsOldExpr e2 + | .op _ (BoogieIdent.unres "old") _ => true + | .op _ _ _ => false + | .const _ _ | .bvar _ _ | .fvar _ _ _ => false + | .abs _ _ e' => containsOldExpr e' + | .quant _ _ _ tr' e' => containsOldExpr tr' || containsOldExpr e' + | .app _ e1 e2 => containsOldExpr e1 || containsOldExpr e2 + | .ite _ c t f => containsOldExpr c || containsOldExpr t || containsOldExpr f + | .eq _ e1 e2 => containsOldExpr e1 || containsOldExpr e2 /-- info: true -/ #guard_msgs in @@ -186,16 +186,15 @@ Get a list of original global variable names that are referred to in an -/ def extractOldExprVars (expr : Expression.Expr) : List Expression.Ident := match expr with - | .const _ _ | .bvar _ | .fvar _ _ | .op _ _ => [] - | .mdata _ e => extractOldExprVars e - | .abs _ e => extractOldExprVars e - | .quant _ _ tr e => extractOldExprVars tr ++ extractOldExprVars e - | .app e1 e2 => match e1, e2 with - | .op (BoogieIdent.unres "old") _, .fvar v _ => [v] - | .op (BoogieIdent.unres "old") _, _ => panic! s!"Old expression {expr} not normalized" + | .const _ _ | .bvar _ _ | .fvar _ _ _ | .op _ _ _ => [] + | .abs _ _ e => extractOldExprVars e + | .quant _ _ _ tr e => extractOldExprVars tr ++ extractOldExprVars e + | .app _ e1 e2 => match e1, e2 with + | .op _ (BoogieIdent.unres "old") _, .fvar _ v _ => [v] + | .op _ (BoogieIdent.unres "old") _, _ => panic! s!"Old expression {expr} not normalized" | e1', e2' => extractOldExprVars e1' ++ extractOldExprVars e2' - | .ite c t e => extractOldExprVars c ++ extractOldExprVars t ++ extractOldExprVars e - | .eq e1 e2 => extractOldExprVars e1 ++ extractOldExprVars e2 + | .ite _ c t e => extractOldExprVars c ++ extractOldExprVars t ++ extractOldExprVars e + | .eq _ e1 e2 => extractOldExprVars e1 ++ extractOldExprVars e2 /-- info: [u:f, u:g] -/ #guard_msgs in @@ -207,23 +206,22 @@ Substitute `old(var)` in expression `e` with `s`. def substOld (var : Expression.Ident) (s e : Expression.Expr) : Expression.Expr := match e with - | .const _ _ | .fvar _ _ | .bvar _ | .op _ _ => e - | .mdata m e' => .mdata m (substOld var s e') - | .abs ty e' => .abs ty (substOld var s e') - | .quant qk ty tr' e' => .quant qk ty (substOld var s tr') (substOld var s e') - | .app e1 e2 => + | .const _ _ | .fvar _ _ _ | .bvar _ _ | .op _ _ _ => e + | .abs m ty e' => .abs m ty (substOld var s e') + | .quant m qk ty tr' e' => .quant m qk ty (substOld var s tr') (substOld var s e') + | .app m e1 e2 => match e1, e2 with - | .op (BoogieIdent.unres "old") _, .fvar x _ => + | .op _ (BoogieIdent.unres "old") _, .fvar _ x _ => -- NOTE: We rely on the typeChecker to normalize `e` ensure that `old` is -- only used with an `fvar`. if x == var -- substitute, if should be substituted then s else e - | _, _ => .app (substOld var s e1) (substOld var s e2) - | .ite c t f => .ite (substOld var s c) + | _, _ => .app m (substOld var s e1) (substOld var s e2) + | .ite m c t f => .ite m (substOld var s c) (substOld var s t) (substOld var s f) - | .eq e1 e2 => .eq (substOld var s e1) (substOld var s e2) + | .eq m e1 e2 => .eq m (substOld var s e1) (substOld var s e2) /-- For each `(var, val)` in `sm`, substitute `old(var)` in expression `e` with @@ -233,20 +231,19 @@ def substsOldExpr (sm : Map Expression.Ident Expression.Expr) (e : Expression.Ex : Expression.Expr := if sm.isEmpty then e else match e with - | .const _ _ | .fvar _ _ | .bvar _ | .op _ _ => e - | .mdata m e' => .mdata m (substsOldExpr sm e') - | .abs ty e' => .abs ty (substsOldExpr sm e') - | .quant qk ty tr' e' => .quant qk ty (substsOldExpr sm tr') (substsOldExpr sm e') - | .app e1 e2 => + | .const _ _ | .fvar _ _ _ | .bvar _ _ | .op _ _ _ => e + | .abs m ty e' => .abs m ty (substsOldExpr sm e') + | .quant m qk ty tr' e' => .quant m qk ty (substsOldExpr sm tr') (substsOldExpr sm e') + | .app m e1 e2 => match e1, e2 with - | .op (BoogieIdent.unres "old") _, .fvar x _ => + | .op _ (BoogieIdent.unres "old") _, .fvar _ x _ => match sm.find? x with | some s => s | none => e - | _, _ => .app (substsOldExpr sm e1) (substsOldExpr sm e2) - | .ite c t f => .ite (substsOldExpr sm c) + | _, _ => .app m (substsOldExpr sm e1) (substsOldExpr sm e2) + | .ite m c t f => .ite m (substsOldExpr sm c) (substsOldExpr sm t) (substsOldExpr sm f) - | .eq e1 e2 => .eq (substsOldExpr sm e1) (substsOldExpr sm e2) + | .eq m e1 e2 => .eq m (substsOldExpr sm e1) (substsOldExpr sm e2) /-- For each `(var, val)` in `sm`, substitute `old(var)` in each expression `es` @@ -275,14 +272,14 @@ protected def substsOldInProcChecks (sm : Map Expression.Ident Expression.Expr) (conds : Map String Procedure.Check) : Map String Procedure.Check := conds.map (fun (label, c) => - (label, { expr := substsOldExpr sm c.expr, attr := c.attr })) + (label, { c with expr := substsOldExpr sm c.expr })) protected def substsOldChecks (sm : Map Expression.Ident Expression.Expr) (conds : ListMap String Procedure.Check) : ListMap Expression.Ident Procedure.Check := conds.map (fun (label, c) => - (label, { expr := substsOldExpr sm c.expr, attr := c.attr })) + (label, { c with expr := substsOldExpr sm c.expr })) /-- Old predicate can only apply to var unapplied old predicates are ignored @@ -295,51 +292,47 @@ e.g. What if lhs is an abstraction that can reduce to old(·)? -/ inductive NormalizedOldExpr : Expression.Expr → Prop where -- | oldVar : NormalizedOldExpr (@oldVar tyOld v ty) - | mdata : NormalizedOldExpr e → - NormalizedOldExpr (.mdata _ e) | const : NormalizedOldExpr (.const _ _) - | op : NormalizedOldExpr (.op _ _) - | bvar : NormalizedOldExpr (.bvar _) - | fvar : NormalizedOldExpr (.fvar _ _) + | op : NormalizedOldExpr (.op _ _ _) + | bvar : NormalizedOldExpr (.bvar _ _) + | fvar : NormalizedOldExpr (.fvar _ _ _) | abs : NormalizedOldExpr e → - NormalizedOldExpr (.abs ty e) + NormalizedOldExpr (.abs m ty e) | quant : NormalizedOldExpr tr → NormalizedOldExpr e → - NormalizedOldExpr (.quant k ty tr e) + NormalizedOldExpr (.quant m k ty tr e) | app : NormalizedOldExpr fn → NormalizedOldExpr e → (IsOldPred fn → IsFvar e) → - NormalizedOldExpr (.app fn e) + NormalizedOldExpr (.app m fn e) | ite : NormalizedOldExpr c → NormalizedOldExpr t → NormalizedOldExpr e → - NormalizedOldExpr (.ite c t e) + NormalizedOldExpr (.ite m c t e) | eq : NormalizedOldExpr e1 → NormalizedOldExpr e2 → - NormalizedOldExpr (.eq e1 e2) + NormalizedOldExpr (.eq m e1 e2) inductive ValidExpression : Expression.Expr → Prop where - | mdata : ValidExpression e → - ValidExpression (.mdata _ e) | const : ValidExpression (.const _ _) - | op : ValidExpression (.op _ _) - | bvar : ValidExpression (.bvar _) - | fvar : ValidExpression (.fvar _ _) + | op : ValidExpression (.op _ _ _) + | bvar : ValidExpression (.bvar _ _) + | fvar : ValidExpression (.fvar _ _ _) | abs : ValidExpression e → - ValidExpression (.abs ty e) + ValidExpression (.abs m ty e) | quant : ValidExpression tr → ValidExpression e → - ValidExpression (.quant k ty tr e) + ValidExpression (.quant m k ty tr e) | app : ValidExpression fn → ValidExpression e → ¬ IsOldPred e → - ValidExpression (.app fn e) + ValidExpression (.app m fn e) | ite : ValidExpression c → ValidExpression t → ValidExpression e → - ValidExpression (.ite c t e) + ValidExpression (.ite m c t e) | eq : ValidExpression e1 → ValidExpression e2 → - ValidExpression (.eq e1 e2) + ValidExpression (.eq m e1 e2) -- This is not a ValidExpression -- #eval normalizeOldExpr eb[((~old ~old) (~old (a b)))] @@ -397,6 +390,7 @@ theorem IsOldPredNormalize : simp [normalizeOldExpr] at Hold cases Hold case neg Hneg' => + unfold BoogieIdent.unres at * unfold normalizeOldExpr at Hold split at Hold <;> simp_all split at Hold <;> simp_all @@ -461,10 +455,6 @@ theorem normalizedOldExprTrueSound : NormalizedOldExpr (normalizeOldExpr e true) := by intros Hval Hnorm induction e <;> try simp [normalizeOldExpr] at * <;> try constructor <;> try assumption -case mdata info e e_ih => - apply e_ih - . cases Hval <;> assumption - . cases Hnorm <;> assumption case fvar name ty => constructor case fvar name ty => @@ -503,7 +493,7 @@ case app fn e fn_ih e_ih => split at Hnorm <;> simp_all simp [normalizeOldExpr] at Hnorm next o ty o' ty' _he h heq => - generalize Hop : (Lambda.LExpr.op o' ty') = op at Hnorm + generalize Hop : (Lambda.LExpr.op () o' ty') = op at Hnorm generalize Hne : (normalizeOldExpr e) = ne at * cases Hnorm <;> simp_all . intros Hold @@ -559,10 +549,6 @@ theorem normalizeOldExprSound : NormalizedOldExpr (normalizeOldExpr e) := by intros Hvalid induction e <;> try simp [normalizeOldExpr] <;> try constructor <;> simp_all - case mdata info e e_ih => - constructor - apply e_ih - cases Hvalid <;> assumption case app fn e fn_ih e_ih => unfold normalizeOldExpr split <;> simp_all @@ -645,11 +631,6 @@ theorem substOldNormalizedMono : NormalizedOldExpr (substOld v s e) := by intros Hnold Hnorm Hnorm' induction e <;> simp [substOld] <;> try assumption -case mdata info e e_ih => - constructor - apply e_ih - cases Hnorm - assumption case abs ty e e_ih => constructor apply e_ih @@ -705,17 +686,16 @@ case eq e1 e2 e1_ih e2_ih => assumption inductive ContainsOldVar : Expression.Expr → Prop where - | old : ContainsOldVar (@oldVar tyOld v ty) - | mdata : ContainsOldVar e → ContainsOldVar (.mdata info e) - | abs : ContainsOldVar e → ContainsOldVar (.abs ty e) - | quant : ContainsOldVar e → ContainsOldVar (.quant k ty tr e) - | app_l : ContainsOldVar fn → ContainsOldVar (.app fn e) - | app_r : ContainsOldVar e → ContainsOldVar (.app fn e) - | ite_1 : ContainsOldVar c → ContainsOldVar (.ite c t e) - | ite_2 : ContainsOldVar t → ContainsOldVar (.ite c t e) - | ite_3 : ContainsOldVar e → ContainsOldVar (.ite c t e) - | eq_1 : ContainsOldVar e1 → ContainsOldVar (.eq e1 e2) - | eq_2 : ContainsOldVar e2 → ContainsOldVar (.eq e1 e2) + | old : ContainsOldVar (@oldVar mApp mOp mVar tyOld v ty) + | abs : ContainsOldVar e → ContainsOldVar (.abs m ty e) + | quant : ContainsOldVar e → ContainsOldVar (.quant m k ty tr e) + | app_l : ContainsOldVar fn → ContainsOldVar (.app m fn e) + | app_r : ContainsOldVar e → ContainsOldVar (.app m fn e) + | ite_1 : ContainsOldVar c → ContainsOldVar (.ite m c t e) + | ite_2 : ContainsOldVar t → ContainsOldVar (.ite m c t e) + | ite_3 : ContainsOldVar e → ContainsOldVar (.ite m c t e) + | eq_1 : ContainsOldVar e1 → ContainsOldVar (.eq m e1 e2) + | eq_2 : ContainsOldVar e2 → ContainsOldVar (.eq m e1 e2) end OldExpressions end Boogie diff --git a/Strata/Languages/Boogie/Procedure.lean b/Strata/Languages/Boogie/Procedure.lean index c7a882123..edfaf804a 100644 --- a/Strata/Languages/Boogie/Procedure.lean +++ b/Strata/Languages/Boogie/Procedure.lean @@ -16,6 +16,34 @@ namespace Boogie open Std (ToFormat Format format) open Lambda +-- Type class instances to enable deriving for structures containing Expression.Expr +instance : DecidableEq ExpressionMetadata := + show DecidableEq Unit from inferInstance + +instance : Repr ExpressionMetadata := + show Repr Unit from inferInstance + +instance : DecidableEq (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).base.Metadata := + show DecidableEq ExpressionMetadata from inferInstance + +instance : DecidableEq (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).base.IDMeta := + show DecidableEq BoogieIdent from inferInstance + +instance : DecidableEq (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).TypeType := + show DecidableEq LMonoTy from inferInstance + +instance : Repr (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).base.Metadata := + show Repr ExpressionMetadata from inferInstance + +instance : Repr (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).base.IDMeta := + show Repr BoogieIdent from inferInstance + +instance : Repr (⟨⟨ExpressionMetadata, BoogieIdent⟩, LMonoTy⟩ : LExprParamsT).TypeType := + show Repr LMonoTy from inferInstance + +instance : Repr Expression.Expr := + show Repr Expression.Expr from inferInstance + /-! # Boogie Procedures -/ structure Procedure.Header where @@ -51,8 +79,12 @@ instance : Std.ToFormat Procedure.CheckAttr where structure Procedure.Check where expr : Expression.Expr attr : CheckAttr := .Default + md : Imperative.MetaData Expression := #[] deriving Repr, DecidableEq +instance : Inhabited Procedure.Check where + default := { expr := Inhabited.default } + instance : ToFormat Procedure.Check where format c := f!"{c.expr}{c.attr}" @@ -63,7 +95,7 @@ structure Procedure.Spec where modifies : List Expression.Ident preconditions : ListMap BoogieLabel Procedure.Check postconditions : ListMap BoogieLabel Procedure.Check - deriving Repr, DecidableEq, Inhabited + deriving Inhabited, Repr instance : ToFormat Procedure.Spec where format p := diff --git a/Strata/Languages/Boogie/ProcedureEval.lean b/Strata/Languages/Boogie/ProcedureEval.lean index deec47a20..13b07ad69 100644 --- a/Strata/Languages/Boogie/ProcedureEval.lean +++ b/Strata/Languages/Boogie/ProcedureEval.lean @@ -32,7 +32,7 @@ def eval (E : Env) (p : Procedure) : List (Procedure × Env) := -- the context. These reflect the pre-state values of the globals. let modifies_tys := p.spec.modifies.map - (fun l => (E.exprEnv.state.findD l (none, .fvar l none)).fst) + (fun l => (E.exprEnv.state.findD l (none, .fvar () l none)).fst) let modifies_typed := p.spec.modifies.zip modifies_tys let (globals_fvars, E) := E.genFVars modifies_typed let global_init_subst := List.zip modifies_typed globals_fvars @@ -64,14 +64,14 @@ def eval (E : Env) (p : Procedure) : List (Procedure × Env) := -- that hides the expression from the evaluator, allowing us -- to retain the postcondition body instead of replacing it -- with "true". - (.assert label .true + (.assert label (.true ()) ((Imperative.MetaData.pushElem #[] (.label label) (.expr check.expr)).pushElem (.label label) (.msg "FreePostCondition"))) - | _ => (.assert label check.expr)) + | _ => (.assert label check.expr check.md)) p.spec.postconditions let precond_assumes := List.map (fun (label, check) => (.assume label check.expr)) p.spec.preconditions diff --git a/Strata/Languages/Boogie/ProcedureType.lean b/Strata/Languages/Boogie/ProcedureType.lean index 62297103c..63a66f5aa 100644 --- a/Strata/Languages/Boogie/ProcedureType.lean +++ b/Strata/Languages/Boogie/ProcedureType.lean @@ -16,72 +16,81 @@ import Strata.Languages.Boogie.OldExpressions namespace Boogie open Std (ToFormat Format format) +open Imperative (MetaData) namespace Procedure -def typeCheck (C: Boogie.Expression.TyContext) (T : Boogie.Expression.TyEnv) (p : Program) (proc : Procedure) : - Except Format (Procedure × Boogie.Expression.TyEnv) := +def typeCheck (C: Boogie.Expression.TyContext) (Env : Boogie.Expression.TyEnv) (p : Program) + (proc : Procedure) (md : MetaData Expression) : Except Format (Procedure × Boogie.Expression.TyEnv) := + let sourceLoc := MetaData.formatFileRangeD md (includeEnd? := false) + let sourceLoc := if sourceLoc.isEmpty then sourceLoc else f!"{sourceLoc} " + let errorWithSourceLoc := fun e => if sourceLoc.isEmpty then e else f!"{sourceLoc} {e}" if !proc.header.inputs.keys.Nodup then - .error f!"[{proc.header.name}] Duplicates found in the formals!" + .error f!"{sourceLoc}[{proc.header.name}] Duplicates found in the formals!" else if !proc.header.outputs.keys.Nodup then - .error f!"[{proc.header.name}] Duplicates found in the return variables!" + .error f!"{sourceLoc}[{proc.header.name}] Duplicates found in the return variables!" else if !proc.spec.modifies.Nodup then - .error f!"[{proc.header.name}] Duplicates found in the modifies clause!" + .error f!"{sourceLoc}[{proc.header.name}] Duplicates found in the modifies clause!" else if proc.spec.modifies.any (fun v => v ∈ proc.header.inputs.keys) then - .error f!"[{proc.header.name}] Variables in the modifies clause must \ + .error f!"{sourceLoc}[{proc.header.name}] Variables in the modifies clause must \ not appear in the formals.\n\ Modifies: {proc.spec.modifies}\n Formals: {proc.header.inputs.keys}" else if proc.spec.modifies.any (fun v => v ∈ proc.header.outputs.keys) then - .error f!"[{proc.header.name}] Variables in the modifies clause must \ + .error f!"{sourceLoc}[{proc.header.name}] Variables in the modifies clause must \ not appear in the return values.\n\ Modifies: {proc.spec.modifies}\n Returns: {proc.header.outputs.keys}" else if proc.header.inputs.keys.any (fun v => v ∈ proc.header.outputs.keys) then - .error f!"[{proc.header.name}] Variables in the formals must not appear \ + .error f!"{sourceLoc}[{proc.header.name}] Variables in the formals must not appear \ in the return values.\n\ Formals: {proc.header.inputs.keys}\n Returns: {proc.header.outputs.keys}" - else if proc.spec.modifies.any (fun v => (T.context.types.find? v).isNone) then - .error f!"[{proc.header.name}]: All the variables in the modifies \ + else if proc.spec.modifies.any (fun v => (Env.context.types.find? v).isNone) then + .error f!"{sourceLoc}[{proc.header.name}]: All the variables in the modifies \ clause must exist in the context! \ Modifies: {proc.spec.modifies}" else do - let modifiedVars := (Imperative.Stmts.modifiedVars proc.body).eraseDups - let definedVars := (Imperative.Stmts.definedVars proc.body).eraseDups + let modifiedVars := (Imperative.Block.modifiedVars proc.body).eraseDups + let definedVars := (Imperative.Block.definedVars proc.body).eraseDups let allowedVars := proc.header.outputs.keys ++ proc.spec.modifies ++ definedVars if modifiedVars.any (fun v => v ∉ allowedVars) then - .error f!"[{proc.header.name}]: This procedure modifies variables it is not allowed to!\n\ + .error f!"{sourceLoc}[{proc.header.name}]: This procedure modifies variables it is not allowed to!\n\ Variables actually modified: {modifiedVars}\n\ Modification allowed for these variables: {allowedVars}" else let preconditions := Procedure.Spec.getCheckExprs proc.spec.preconditions if preconditions.any (fun p => OldExpressions.containsOldExpr p) then - .error f!"[{proc.header.name}]: Preconditions cannot contain applications of the `old` function!" + .error f!"{sourceLoc}[{proc.header.name}]: Preconditions cannot contain applications of\ + the `old` function!" else -- 1. Temporarily add the formals and returns into the context. - let T := T.pushEmptyContext - let (mty_sig, T) ← Lambda.LMonoTySignature.instantiate C T proc.header.typeArgs + let Env := Env.pushEmptyContext + let (mty_sig, Env) ← Lambda.LMonoTySignature.instantiate C Env proc.header.typeArgs (proc.header.inputs ++ proc.header.outputs) + |>.mapError errorWithSourceLoc let lty_sig := Lambda.LMonoTySignature.toTrivialLTy mty_sig - let T := T.addToContext lty_sig + let Env := Env.addToContext lty_sig -- 2. Normalize the old expressions in the postconditions. The evaluator -- depends on this step! See also note in `OldExpressions.lean`. let postcondition_checks := OldExpressions.normalizeOldChecks proc.spec.postconditions -- 3. Ensure that the preconditions and postconditions are of type boolean. - let postconditions := postcondition_checks.map (fun (_, { expr := expr, attr := _ }) => expr) - let (preconditions_a, T) ← Lambda.LExprT.fromLExprs C T preconditions - let pre_tys := preconditions_a.map Lambda.LExprT.toLMonoTy - let preconditions := preconditions_a.map Lambda.LExprT.toLExpr - let (postconditions_a, T) ← Lambda.LExprT.fromLExprs C T postconditions - let post_tys := postconditions_a.map Lambda.LExprT.toLMonoTy - let postconditions := postconditions_a.map Lambda.LExprT.toLExpr + let postconditions := postcondition_checks.map (fun (_, c) => c.expr) + let (preconditions_a, Env) ← Lambda.LExpr.resolves C Env preconditions |>.mapError errorWithSourceLoc + let pre_tys := preconditions_a.map Lambda.LExpr.toLMonoTy + let preconditions := preconditions_a.map Lambda.LExpr.unresolved + let (postconditions_a, Env) ← Lambda.LExpr.resolves C Env postconditions |>.mapError errorWithSourceLoc + let post_tys := postconditions_a.map Lambda.LExpr.toLMonoTy + let postconditions := postconditions_a.map Lambda.LExpr.unresolved if (pre_tys ++ post_tys).any (fun ty => ty != .tcons "bool" []) then - .error f!"Expected pre- and post-conditions to be of type Bool!" + .error f!"{sourceLoc}[{proc.header.name}]: Expected pre- and post-conditions to be of type Bool!" else -- 4. Typecheck the body of the procedure. - let (annotated_body, T) ← Statement.typeCheck C T p (.some proc) proc.body - let T := T.popContext + -- Note that `Statement.typeCheck` already reports source locations in + -- error messages. + let (annotated_body, Env) ← Statement.typeCheck C Env p (.some proc) proc.body + -- Remove formals and returns from the context. + let Env := Env.popContext let preconditions := Procedure.Spec.updateCheckExprs preconditions proc.spec.preconditions let postconditions := Procedure.Spec.updateCheckExprs postconditions proc.spec.postconditions let new_hdr := { proc.header with typeArgs := [], @@ -89,7 +98,7 @@ def typeCheck (C: Boogie.Expression.TyContext) (T : Boogie.Expression.TyEnv) (p outputs := mty_sig.drop proc.header.inputs.length } let new_spec := { proc.spec with preconditions := preconditions, postconditions := postconditions } let new_proc := { proc with header := new_hdr, spec := new_spec, body := annotated_body } - .ok (new_proc, T) + .ok (new_proc, Env) --------------------------------------------------------------------- end Procedure diff --git a/Strata/Languages/Boogie/ProcedureWF.lean b/Strata/Languages/Boogie/ProcedureWF.lean index 1df073e9c..136592380 100644 --- a/Strata/Languages/Boogie/ProcedureWF.lean +++ b/Strata/Languages/Boogie/ProcedureWF.lean @@ -29,7 +29,7 @@ theorem snd_values_mem {ps : ListMap BoogieLabel Procedure.Check} : case inr mem => right ; exact (ih mem) case nil => cases Hin -theorem Procedure.typeCheckWF : Procedure.typeCheck C T p pp = Except.ok (pp', T') → WFProcedureProp p pp := by sorry +theorem Procedure.typeCheckWF : Procedure.typeCheck C T p pp md = Except.ok (pp', T') → WFProcedureProp p pp := by sorry /- diff --git a/Strata/Languages/Boogie/Program.lean b/Strata/Languages/Boogie/Program.lean index 8db108890..b62f23a25 100644 --- a/Strata/Languages/Boogie/Program.lean +++ b/Strata/Languages/Boogie/Program.lean @@ -18,10 +18,26 @@ namespace Boogie open Std (ToFormat Format format) open Imperative +-- Type class instances needed for deriving and formatting +instance : Inhabited TypeDecl where + default := .con { name := "DefaultType", numargs := 0 } + +-- ToFormat instance for Function (which is LFunc BoogieLParams) +-- Note: ToFormat BoogieLParams.Identifier is now defined in Identifiers.lean + inductive DeclKind : Type where | var | type | ax | distinct | proc | func deriving DecidableEq, Repr +instance : ToFormat DeclKind where + format k := match k with + | .var => "variable" + | .type => "type" + | .ax => "axiom" + | .distinct => "distinct" + | .proc => "procedure" + | .func => "function" + /-- A Boogie declaration. Note: constants are 0-ary functions. @@ -36,6 +52,15 @@ inductive Decl where | func (f : Function) (md : MetaData Boogie.Expression := .empty) deriving Inhabited +def Decl.metadata (d : Decl) : MetaData Expression := + match d with + | .var _ _ _ md => md + | .type _ md => md + | .ax _ md => md + | .distinct _ _ md => md + | .proc _ md => md + | .func _ md => md + def Decl.kind (d : Decl) : DeclKind := match d with | .var _ _ _ _ => .var @@ -98,16 +123,20 @@ def Decl.eraseTypes (d : Decl) : Decl := | .ax a md => .ax a.eraseTypes md | .proc p md => .proc p.eraseTypes md | .func f md => .func f.eraseTypes md - | _ => d + | .var _ _ _ _ | .type _ _ | .distinct _ _ _ => d +-- Metadata not included. instance : ToFormat Decl where format d := match d with - | .var name ty e md => f!"{md}var ({name} : {ty}) := {e}" - | .type t md => f!"{md}{t}" - | .ax a md => f!"{md}{a}" - | .distinct l es md => f!"{md}distinct [{l}] {es}" - | .proc p md => f!"{md}{p}" - | .func f md => f!"{md}{f}" + | .var name ty e _md => f!"var ({name} : {ty}) := {e}" + | .type t _md => f!"{t}" + | .ax a _md => f!"{a}" + | .distinct l es _md => f!"distinct [{l}] {es}" + | .proc p _md => f!"{p}" + | .func f _md => f!"{f}" + +def Decl.formatWithMetaData (decl : Decl) : Format := + f!"{decl.metadata}{decl}" abbrev Decls := List Decl @@ -127,6 +156,9 @@ instance : Inhabited Program where def Program.eraseTypes (p : Program) : Program := { p with decls := p.decls.map Decl.eraseTypes } +def Program.formatWithMetaData (p : Program) : Format := + Std.Format.joinSep (List.map Decl.formatWithMetaData p.decls) Format.line + --------------------------------------------------------------------- def Program.find? (P : Program) (k : DeclKind) (x : Expression.Ident) : Option Decl := diff --git a/Strata/Languages/Boogie/ProgramType.lean b/Strata/Languages/Boogie/ProgramType.lean index e850cc423..001acc0a6 100644 --- a/Strata/Languages/Boogie/ProgramType.lean +++ b/Strata/Languages/Boogie/ProgramType.lean @@ -21,67 +21,83 @@ open Lambda namespace Program -def typeCheck (C: Boogie.Expression.TyContext) (T : Boogie.Expression.TyEnv) (program : Program) : +def typeCheck (C: Boogie.Expression.TyContext) (Env : Boogie.Expression.TyEnv) (program : Program) : Except Format (Program × Boogie.Expression.TyEnv) := do -- Push a type substitution scope to store global type variables. - let T := T.updateSubst { subst := [[]], isWF := SubstWF_of_empty_empty } - let (decls, T) ← go C T program.decls [] - .ok ({ decls }, T) + let Env := Env.updateSubst { subst := [[]], isWF := SubstWF_of_empty_empty } + let (decls, Env) ← go C Env program.decls [] + .ok ({ decls }, Env) - where go C T remaining acc : Except Format (Decls × Boogie.Expression.TyEnv) := + where go C Env remaining acc : Except Format (Decls × Boogie.Expression.TyEnv) := match remaining with - | [] => .ok (acc.reverse, T) + | [] => .ok (acc.reverse, Env) | decl :: drest => do - let C := {C with idents := (← C.idents.addWithError decl.name f!"Error in Boogie declaration {decl}: {decl.name} already defined")} - let (decl', C, T) ← + let sourceLoc := Imperative.MetaData.formatFileRangeD decl.metadata (includeEnd? := true) + let errorWithSourceLoc := fun e => if sourceLoc.isEmpty then e else f!"{sourceLoc} {e}" + let C := {C with idents := (← C.idents.addWithError decl.name + f!"{sourceLoc} Error in {decl.kind} {decl.name}: \ + a declaration of this name already exists.")} + let (decl', C, Env) ← match decl with - | .var x ty val _ => - let (s', T) ← Statement.typeCheck C T program .none [.init x ty val .empty] + | .var x ty val md => + let (s', Env) ← Statement.typeCheck C Env program .none [.init x ty val md] match s' with - | [.init x' ty' val' _] => .ok (.var x' ty' val', C, T) - | _ => .error f!"Implementation error! \ + | [.init x' ty' val' _] => .ok (.var x' ty' val', C, Env) + | _ => .error f!"{sourceLoc}Implementation error! \ Statement typeChecker returned the following: \ {Format.line}\ {s'}{Format.line} Declaration: {decl}" - | .type td _ => + | .type td _ => try match td with | .con tc => let C ← C.addKnownTypeWithError { name := tc.name, metadata := tc.numargs } f!"This type declaration's name is reserved!\n\ {td}\n\ KnownTypes' names:\n\ {C.knownTypes.keywords}" - .ok (.type td, C, T) + .ok (.type td, C, Env) | .syn ts => - let T ← TEnv.addTypeAlias { typeArgs := ts.typeArgs, name := ts.name, type := ts.type } C T - .ok (.type td, C, T) - - | .ax a _ => - let (ae, T) ← LExprT.fromLExpr C T a.e + let Env ← TEnv.addTypeAlias { typeArgs := ts.typeArgs, name := ts.name, type := ts.type } C Env + .ok (.type td, C, Env) + | .data d => + let C ← C.addDatatype d + .ok (.type td, C, Env) + catch e => + .error (errorWithSourceLoc e) + + | .ax a _ => try + let (ae, Env) ← LExpr.resolve C Env a.e match ae.toLMonoTy with - | .bool => .ok (.ax { a with e := ae.toLExpr }, C, T) - | _ => .error f!"Axiom has non-boolean type: {a}" - - | .distinct l es md => - let es' ← es.mapM (LExprT.fromLExpr C T) - .ok (.distinct l (es'.map (λ e => e.fst.toLExpr)) md, C, T) - - | .proc proc _ => - let T := T.pushEmptySubstScope - let (proc', T) ← Procedure.typeCheck C T program proc - let T := T.popSubstScope - .ok (.proc proc', C, T) - - | .func func _ => - let T := T.pushEmptySubstScope - let (func', T) ← Function.typeCheck C T func + | .bool => .ok (.ax { a with e := ae.unresolved }, C, Env) + | _ => .error f!"Axiom {a.name} has non-boolean type." + catch e => + .error (errorWithSourceLoc e) + + | .distinct l es md => try + let es' ← es.mapM (LExpr.resolve C Env) + .ok (.distinct l (es'.map (λ e => e.fst.unresolved)) md, C, Env) + catch e => + .error (errorWithSourceLoc e) + + | .proc proc md => + -- Already reports source locations. + let Env := Env.pushEmptySubstScope + let (proc', Env) ← Procedure.typeCheck C Env program proc md + let Env := Env.popSubstScope + .ok (.proc proc', C, Env) + + | .func func _ => try + let Env := Env.pushEmptySubstScope + let (func', Env) ← Function.typeCheck C Env func let C := C.addFactoryFunction func' - let T := T.popSubstScope - .ok (.func func', C, T) + let Env := Env.popSubstScope + .ok (.func func', C, Env) + catch e => + .error (errorWithSourceLoc e) - go C T drest (decl' :: acc) + go C Env drest (decl' :: acc) --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/ProgramWF.lean b/Strata/Languages/Boogie/ProgramWF.lean index 65f1a4176..31ceca4ed 100644 --- a/Strata/Languages/Boogie/ProgramWF.lean +++ b/Strata/Languages/Boogie/ProgramWF.lean @@ -254,7 +254,7 @@ theorem Program.typeCheck.goWF : Program.typeCheck.go p C T ds [] = .ok (ds', T' | nil => simp [Program.typeCheck.go] at tcok cases tcok; constructor <;> try assumption | cons h t t_ih => - simp [Program.typeCheck.go, bind, Except.bind] at tcok + simp [Program.typeCheck.go, bind, Except.bind, tryCatch] at tcok split at tcok <;> try contradiction any_goals (split at tcok <;> try contradiction) any_goals (split at tcok <;> try contradiction) @@ -280,6 +280,12 @@ theorem addKnownTypeWithErrorIdents {C: Expression.TyContext}: C.addKnownTypeWit case error => intros _; contradiction case ok k'=> simp[Except.bind]; intros T'; subst T'; rfl +theorem addDatatypeIdents {C: Expression.TyContext}: C.addDatatype d = .ok C' → C.idents = C'.idents := by + unfold LContext.addDatatype; + simp only[bind, Except.bind, pure, Except.pure]; intros Hok + repeat (split at Hok <;> try contradiction) + cases Hok <;> rfl + /-- If a program typechecks successfully, then every identifier in the list of program decls is not in the original `LContext` @@ -288,7 +294,8 @@ theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc induction decls generalizing acc p d' T' T C with | nil => simp[Program.getNames.go] | cons r rs IH => - simp[Program.getNames.go, Program.typeCheck.go, bind, Except.bind] + simp[Program.getNames.go, Program.typeCheck.go, bind, Except.bind, + tryCatch, tryCatchThe, MonadExceptOf.tryCatch, Except.tryCatch] split <;> try (intros;contradiction) rename_i x v Hid split <;> intros tcok <;> split at tcok <;> try contradiction @@ -302,14 +309,26 @@ theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc unfold Program.getNames.go; rw[List.mem_map ]; exists a have a_notin := IH a.name a_in'; have Hcontains := Identifiers.addWithErrorContains Hid a.name) - . grind - . rename_i x v heq - have id_eq := addKnownTypeWithErrorIdents heq; simp at id_eq; grind - . grind - . grind - . grind - . grind - . simp[LContext.addFactoryFunction] at a_notin; grind + case _ => grind + case _ x v hmatch1 => + split at hmatch1 <;> try grind + rename_i hmatch2; split at hmatch2 <;> try grind + split at hmatch2 <;> try grind + rename_i heq + have id_eq := addKnownTypeWithErrorIdents heq + simp at id_eq; grind + split at hmatch2 <;> try grind + rename_i Heq + have :=addDatatypeIdents Heq; grind + case _ => grind + case _ => grind + case _ => grind + case _ x v hmatch1 => + rename_i x v hmatch1 + split at hmatch1 <;> try grind + rename_i hmatch2; split at hmatch2 <;> try grind + simp only [LContext.addFactoryFunction] at hmatch2; grind + done /-- If a program typechecks succesfully, all identifiers defined in the program are @@ -319,16 +338,17 @@ theorem Program.typeCheckFunctionNoDup : Program.typeCheck.go p C T decls acc = induction decls generalizing acc p C T with | nil => simp[Program.getNames.go] | cons r rs IH => - simp[Program.getNames.go, Program.typeCheck.go]; - cases Hid: (C.idents.addWithError r.name - (format "Error in Boogie declaration " ++ format r ++ format ": " ++ format r.name ++ - format " already defined")); simp[bind] + simp_all [Program.getNames.go, Program.typeCheck.go, + tryCatch, tryCatchThe, MonadExceptOf.tryCatch, Except.tryCatch]; + cases Hid: C.idents.addWithError r.name + (format (r.metadata.formatFileRangeD true) ++ format " Error in " ++ format r.kind ++ format " " ++ + format r.name ++ + format ": a declaration of this name already exists."); simp [bind] case error => intro C; cases C; done case ok id => intro C; simp[bind, Except.bind] at C; cases r <;> simp at C; repeat (split at C <;> try (intros _; contradiction) <;> try contradiction) <;> try contradiction any_goals (split at C <;> try contradiction) - any_goals (split at C <;> try contradiction) all_goals ( specialize (IH C); constructor <;> try assumption; intros x x_in; @@ -337,15 +357,25 @@ theorem Program.typeCheckFunctionNoDup : Program.typeCheck.go p C T decls acc = have x_notin := (Program.typeCheckFunctionDisjoint C x.name x_in') intro name_eq have x_contains := (Identifiers.addWithErrorContains Hid x.name)) - . grind - . rename_i y v heq; have : v.idents = id := by - have := addKnownTypeWithErrorIdents heq; symm; exact this - grind - . grind - . grind - . grind - . grind - . simp[LContext.addFactoryFunction] at x_notin; grind + case _ => grind + case _ x v hmatch1 => + rename_i x v hmatch1 + split at hmatch1 <;> try grind + rename_i hmatch2; split at hmatch2 <;> split at hmatch2 <;> try grind + rename_i heq + have id_eq := addKnownTypeWithErrorIdents heq + simp at id_eq; grind + rename_i Heq + have := addDatatypeIdents Heq; grind + case _ => grind + case _ => grind + case _ => grind + case _ => + rename_i x v hmatch1 + split at hmatch1 <;> try grind + rename_i hmatch2; split at hmatch2 <;> try grind + simp only [LContext.addFactoryFunction] at hmatch2; grind + done /-- The main lemma stating that a program 'p' that passes type checking is well formed diff --git a/Strata/Languages/Boogie/SMTEncoder.lean b/Strata/Languages/Boogie/SMTEncoder.lean index 261d23c9c..3eef7b872 100644 --- a/Strata/Languages/Boogie/SMTEncoder.lean +++ b/Strata/Languages/Boogie/SMTEncoder.lean @@ -9,6 +9,8 @@ import Strata.Languages.Boogie.Boogie import Strata.DL.SMT.SMT import Init.Data.String.Extra +import Strata.DDM.Util.DecimalRat +import Strata.DDM.Util.Graph.Tarjan --------------------------------------------------------------------- @@ -33,6 +35,8 @@ structure SMT.Context where ifs : Array SMT.IF := #[] axms : Array Term := #[] tySubst: Map String TermType := [] + datatypes : Array (LDatatype BoogieLParams.IDMeta) := #[] + datatypeFuns : Map String (Op.DatatypeFuncs × LConstr BoogieLParams.IDMeta) := Map.empty deriving Repr, DecidableEq, Inhabited def SMT.Context.default : SMT.Context := {} @@ -60,6 +64,119 @@ def SMT.Context.addSubst (ctx : SMT.Context) (newSubst: Map String TermType) : S def SMT.Context.removeSubst (ctx : SMT.Context) (newSubst: Map String TermType) : SMT.Context := { ctx with tySubst := newSubst.foldl (fun acc_m p => acc_m.erase p.fst) ctx.tySubst } +def SMT.Context.hasDatatype (ctx : SMT.Context) (name : String) : Bool := + (ctx.datatypes.map LDatatype.name).contains name + +def SMT.Context.addDatatype (ctx : SMT.Context) (d : LDatatype BoogieLParams.IDMeta) : SMT.Context := + if ctx.hasDatatype d.name then ctx + else + let (c, i, s) := d.genFunctionMaps + let m := Map.union ctx.datatypeFuns (c.fmap (fun (_, x) => (.constructor, x))) + let m := Map.union m (i.fmap (fun (_, x) => (.tester, x))) + let m := Map.union m (s.fmap (fun (_, x) => (.selector, x))) + { ctx with datatypes := ctx.datatypes.push d, datatypeFuns := m } + +/-- +Helper function to convert LMonoTy to SMT string representation. +For now, handles only monomorphic types and type variables without substitution. +-/ +private def lMonoTyToSMTString (ty : LMonoTy) : String := + match ty with + | .bitvec n => s!"(_ BitVec {n})" + | .tcons "bool" [] => "Bool" + | .tcons "int" [] => "Int" + | .tcons "real" [] => "Real" + | .tcons "string" [] => "String" + | .tcons "regex" [] => "RegLan" + | .tcons name args => + if args.isEmpty then name + else s!"({name} {String.intercalate " " (args.map lMonoTyToSMTString)})" + | .ftvar tv => tv + +/-- +Build a dependency graph for datatypes. +Returns a mapping from datatype names to their dependencies. +-/ +private def buildDatatypeDependencyGraph (datatypes : Array (LDatatype BoogieLParams.IDMeta)) : + Map String (Array String) := + let depMap := datatypes.foldl (fun acc d => + let deps := d.constrs.foldl (fun deps c => + c.args.foldl (fun deps (_, fieldTy) => + match fieldTy with + | .tcons typeName _ => + -- Only include dependencies on other datatypes in our set + if datatypes.any (fun dt => dt.name == typeName) then + deps.push typeName + else deps + | _ => deps + ) deps + ) #[] + acc.insert d.name deps + ) Map.empty + depMap + +/-- +Convert datatype dependency map to OutGraph for Tarjan's algorithm. +Returns the graph and a mapping from node indices to datatype names. +-/ +private def dependencyMapToGraph (depMap : Map String (Array String)) : + (n : Nat) × Strata.OutGraph n × Array String := + let names := depMap.keys.toArray + let n := names.size + let nameToIndex : Map String Nat := + names.mapIdx (fun i name => (name, i)) |>.foldl (fun acc (name, i) => acc.insert name i) Map.empty + + let edges := depMap.foldl (fun edges (fromName, deps) => + match nameToIndex.find? fromName with + | none => edges + | some fromIdx => + deps.foldl (fun edges depName => + match nameToIndex.find? depName with + | none => edges + | some toIdx => edges.push (fromIdx, toIdx) + ) edges + ) #[] + + let graph := Strata.OutGraph.ofEdges! n edges.toList + ⟨n, graph, names⟩ + +/-- +Emit datatype declarations to the solver in topologically sorted order. +For each datatype in ctx.datatypes, generates a declare-datatype command +with constructors and selectors following the TypeFactory naming convention. +Dependencies are emitted before the datatypes that depend on them, and +mutually recursive datatypes are not (yet) supported. +-/ +def SMT.Context.emitDatatypes (ctx : SMT.Context) : Strata.SMT.SolverM Unit := do + if ctx.datatypes.isEmpty then return + + -- Build dependency graph and SCCs + let depMap := buildDatatypeDependencyGraph ctx.datatypes + let ⟨_, graph, names⟩ := dependencyMapToGraph depMap + let sccs := Strata.OutGraph.tarjan graph + + -- Emit datatypes in topological order (reverse of SCC order) + for scc in sccs.reverse do + if scc.size > 1 then + let sccNames := scc.map (fun idx => names[idx]!) + throw (IO.userError s!"Mutually recursive datatypes not supported: {sccNames.toList}") + else + for nodeIdx in scc do + let datatypeName := names[nodeIdx]! + -- Find the datatype by name + match ctx.datatypes.find? (fun d => d.name == datatypeName) with + | none => throw (IO.userError s!"Datatype {datatypeName} not found in context") + | some d => + let constructors ← d.constrs.mapM fun c => do + let fieldPairs := c.args.map fun (name, fieldTy) => (name.name, lMonoTyToSMTString fieldTy) + let fieldStrs := fieldPairs.map fun (name, ty) => s!"({name} {ty})" + let fieldsStr := String.intercalate " " fieldStrs + if c.args.isEmpty then + pure s!"({c.name.name})" + else + pure s!"({c.name.name} {fieldsStr})" + Strata.SMT.Solver.declareDatatype d.name d.typeArgs constructors + abbrev BoundVars := List (String × TermType) --------------------------------------------------------------------- @@ -83,8 +200,32 @@ def extractTypeInstantiations (typeVars : List String) (patterns : List LMonoTy) Map.empty +/- +Add a type to the context. Sorts are easy, but datatypes are tricky: +we must also ensure we add the types of all arguments in the constructors +to the context, recursively. This is very tricky to prove terminating, so +we leave as `partial` for now. +-/ +partial def SMT.Context.addType (E: Env) (id: String) (args: List LMonoTy) (ctx: SMT.Context) : + SMT.Context := + match E.datatypes.getType id with + | some d => + if ctx.hasDatatype id then ctx else + let ctx := ctx.addDatatype d + d.constrs.foldl (fun (ctx : SMT.Context) c => + c.args.foldl (fun (ctx: SMT.Context) (_, t) => + match t with + | .bool | .int | .real | .string | .tcons "regex" [] => ctx + | .tcons id1 args1 => SMT.Context.addType E id1 args1 ctx + | _ => ctx + ) ctx + ) ctx + | none => + ctx.addSort { name := id, arity := args.length } + + mutual -def LMonoTy.toSMTType (ty : LMonoTy) (ctx : SMT.Context) : +def LMonoTy.toSMTType (E: Env) (ty : LMonoTy) (ctx : SMT.Context) : Except Format (TermType × SMT.Context) := do match ty with | .bitvec n => .ok (.bitvec n, ctx) @@ -94,21 +235,21 @@ def LMonoTy.toSMTType (ty : LMonoTy) (ctx : SMT.Context) : | .tcons "string" [] => .ok (.string, ctx) | .tcons "regex" [] => .ok (.regex, ctx) | .tcons id args => - let ctx := ctx.addSort { name := id, arity := args.length } - let (args', ctx) ← LMonoTys.toSMTType args ctx + let ctx := SMT.Context.addType E id args ctx + let (args', ctx) ← LMonoTys.toSMTType E args ctx .ok ((.constr id args'), ctx) | .ftvar tyv => match ctx.tySubst.find? tyv with | .some termTy => .ok (termTy, ctx) | _ => .error f!"Unimplemented encoding for type var {tyv}" -def LMonoTys.toSMTType (args : LMonoTys) (ctx : SMT.Context) : +def LMonoTys.toSMTType (E: Env) (args : LMonoTys) (ctx : SMT.Context) : Except Format ((List TermType) × SMT.Context) := do match args with | [] => .ok ([], ctx) | t :: trest => - let (t', ctx) ← LMonoTy.toSMTType t ctx - let (trest', ctx) ← LMonoTys.toSMTType trest ctx + let (t', ctx) ← LMonoTy.toSMTType E t ctx + let (trest', ctx) ← LMonoTys.toSMTType E trest ctx .ok ((t' :: trest'), ctx) end @@ -118,43 +259,18 @@ def convertQuantifierKind : Lambda.QuantifierKind -> Strata.SMT.QuantifierKind mutual -partial def toSMTTerm (E : Env) (bvs : BoundVars) (e : LExpr LMonoTy Visibility) (ctx : SMT.Context) +partial def toSMTTerm (E : Env) (bvs : BoundVars) (e : LExpr BoogieLParams.mono) (ctx : SMT.Context) : Except Format (Term × SMT.Context) := do match e with - | .const "true" _ => .ok ((Term.bool true), ctx) - | .const _ ty => - match ty with - | none => .error f!"Cannot encode unannotated constant {e}" - | some ty => - match ty with - | .bool => - match e.denoteBool with - | none => - .error f!"Unexpected boolean constant {e}" - | some b => .ok ((Term.bool b), ctx) - | .int => - match e.denoteInt with - | none => - .error f!"Unexpected integer constant {e}" - | some i => .ok ((Term.int i), ctx) - | .real => - match e.denoteReal with - | none => - .error f!"Unexpected real constant {e}" - | some r => .ok ((Term.real r), ctx) - | .bitvec n => - match e.denoteBitVec n with - | none => - .error f!"Unexpected bv constant {e}" - | some v => .ok ((Term.bitvec v), ctx) - | .string => - match e.denoteString with - | none => .error f!"Unexpected string constant {e}" - | some s => .ok ((Term.string s), ctx) - | _ => - .error f!"Unimplemented encoding for type {ty} in expression {e}" - - | .op fn fnty => + | .boolConst _ b => .ok (Term.bool b, ctx) + | .intConst _ i => .ok (Term.int i, ctx) + | .realConst _ r => + match Strata.Decimal.fromRat r with + | some d => .ok (Term.real d, ctx) + | none => .error f!"Non-decimal real value {e}" + | .bitvecConst _ n b => .ok (Term.bitvec b, ctx) + | .strConst _ s => .ok (Term.string s, ctx) + | .op _ fn fnty => match fnty with | none => .error f!"Cannot encode unannotated operation {fn}." | some fnty => @@ -162,55 +278,49 @@ partial def toSMTTerm (E : Env) (bvs : BoundVars) (e : LExpr LMonoTy Visibility) let (op, retty, ctx) ← toSMTOp E fn fnty ctx .ok (op [] retty, ctx) - | .bvar i => + | .bvar _ i => if h: i < bvs.length then do let var := bvs[i] .ok ((TermVar.mk var.fst var.snd), ctx) else .error f!"Bound variable index is out of bounds: {i}" - | .fvar f ty => + | .fvar _ f ty => match ty with | none => .error f!"Cannot encode unannotated free variable {e}" | some ty => - let (tty, ctx) ← LMonoTy.toSMTType ty ctx + let (tty, ctx) ← LMonoTy.toSMTType E ty ctx let uf := { id := (toString $ format f), args := [], out := tty } .ok (.app (.uf uf) [] tty, ctx.addUF uf) - | .mdata _info e => do - -- (FIXME) Add metadata as a comment in the SMT encoding. - toSMTTerm E bvs e ctx - - | .abs ty e => .error f!"Cannot encode lambda abstraction {e}" - - | .quant _ .none _ _ => .error f!"Cannot encode untyped quantifier {e}" + | .abs _ ty e => .error f!"Cannot encode lambda abstraction {e}" - | .quant qk (.some ty) tr e => + | .quant _ _ .none _ _ => .error f!"Cannot encode untyped quantifier {e}" + | .quant _ qk (.some ty) tr e => let x := s!"$__bv{bvs.length}" - let (ety, ctx) ← LMonoTy.toSMTType ty ctx + let (ety, ctx) ← LMonoTy.toSMTType E ty ctx let (trt, ctx) ← appToSMTTerm E ((x, ety) :: bvs) tr [] ctx let (et, ctx) ← toSMTTerm E ((x, ety) :: bvs) e ctx .ok (Factory.quant (convertQuantifierKind qk) x ety trt et, ctx) - - | .eq e1 e2 => + | .eq _ e1 e2 => let (e1t, ctx) ← toSMTTerm E bvs e1 ctx let (e2t, ctx) ← toSMTTerm E bvs e2 ctx .ok ((Factory.eq e1t e2t), ctx) - | .ite c t f => + | .ite _ c t f => let (ct, ctx) ← toSMTTerm E bvs c ctx let (tt, ctx) ← toSMTTerm E bvs t ctx let (ft, ctx) ← toSMTTerm E bvs f ctx .ok ((Factory.ite ct tt ft), ctx) - | .app _ _ => + | .app _ _ _ => appToSMTTerm E bvs e [] ctx -partial def appToSMTTerm (E : Env) (bvs : BoundVars) (e : (LExpr LMonoTy Visibility)) (acc : List Term) (ctx : SMT.Context) : +partial def appToSMTTerm (E : Env) (bvs : BoundVars) (e : LExpr BoogieLParams.mono) (acc : List Term) (ctx : SMT.Context) : Except Format (Term × SMT.Context) := do match e with -- Special case for indexed SMT operations. - | .app (.app (.app (.op "Re.Loop" _) x) n1) n2 => + | .app _ (.app _ (.app _ (.op _ "Re.Loop" _) x) n1) n2 => let (xt, ctx) ← toSMTTerm E bvs x ctx match Lambda.LExpr.denoteInt n1, Lambda.LExpr.denoteInt n2 with | .some n1i, .some n2i => @@ -222,13 +332,13 @@ partial def appToSMTTerm (E : Env) (bvs : BoundVars) (e : (LExpr LMonoTy Visibil | _, _ => .error f!"Natural numbers expected as indices for re.loop.\n\ Original expression: {e.eraseTypes}" - | .app (.app fn e1) e2 => do + | .app _ (.app m fn e1) e2 => do match e1, e2 with | _, _ => let (e2t, ctx) ← toSMTTerm E bvs e2 ctx - appToSMTTerm E bvs (.app fn e1) (e2t :: acc) ctx + appToSMTTerm E bvs (.app m fn e1) (e2t :: acc) ctx - | .app (.op fn fnty) e1 => do + | .app _ (.op _ fn fnty) e1 => do match fnty with | none => .error f!"Cannot encode unannotated operation {fn}. \n\ Appears in expression: {e}" @@ -236,27 +346,49 @@ partial def appToSMTTerm (E : Env) (bvs : BoundVars) (e : (LExpr LMonoTy Visibil let (op, retty, ctx) ← toSMTOp E fn fnty ctx let (e1t, ctx) ← toSMTTerm E bvs e1 ctx .ok (op (e1t :: acc) retty, ctx) - - | .app (.fvar fn (.some (.arrow intty outty))) e1 => do - let (smt_outty, ctx) ← LMonoTy.toSMTType outty ctx - let (smt_intty, ctx) ← LMonoTy.toSMTType intty ctx + | .app _ (.fvar _ fn (.some (.arrow intty outty))) e1 => do + let (smt_outty, ctx) ← LMonoTy.toSMTType E outty ctx + let (smt_intty, ctx) ← LMonoTy.toSMTType E intty ctx let argvars := [TermVar.mk (toString $ format intty) smt_intty] let (e1t, ctx) ← toSMTTerm E bvs e1 ctx let uf := UF.mk (id := (toString $ format fn)) (args := argvars) (out := smt_outty) .ok (((Term.app (.uf uf) [e1t] smt_outty)), ctx) - - | .app _ _ => + | .app _ _ _ => .error f!"Cannot encode expression {e}" | _ => toSMTTerm E bvs e ctx partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Context) : Except Format ((List Term → TermType → Term) × TermType × SMT.Context) := - open LTy.Syntax in - match E.factory.getFactoryLFunc fn with - | none => .error f!"Cannot find function {fn} in Boogie's Factory!" - | some func => - match func.name.name with + open LTy.Syntax in do + -- Encode the type to ensure any datatypes are registered in the context + let tys := LMonoTy.destructArrow fnty + let outty := tys.getLast (by exact @LMonoTy.destructArrow_non_empty fnty) + let intys := tys.take (tys.length - 1) + -- Need to encode arg types also (e.g. for testers) + let ctx := match LMonoTys.toSMTType E intys ctx with + | .ok (_, ctx') => ctx' + | .error _ => ctx + let (smt_outty, ctx) ← LMonoTy.toSMTType E outty ctx + + match ctx.datatypeFuns.find? fn.name with + | some (kind, c) => + let adtApp := fun (args : List Term) (retty : TermType) => + /- + Note: testers use constructor, translated in `Op.mkName` to is-foo + Selectors use full function name, directly translated to function app + -/ + let name := match kind with + | .selector => fn.name + | _ => c.name.name + Term.app (.datatype_op kind name) args retty + .ok (adtApp, smt_outty, ctx) + | none => + -- Not a constructor, tester, or destructor + match E.factory.getFactoryLFunc fn.name with + | none => .error f!"Cannot find function {fn} in Boogie's Factory!" + | some func => + match func.name.name with | "Bool.And" => .ok (.app Op.and, .bool, ctx) | "Bool.Or" => .ok (.app Op.or, .bool, ctx) | "Bool.Not" => .ok (.app Op.not, .bool, ctx) @@ -410,6 +542,7 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con | "Str.Length" => .ok (.app Op.str_length, .int, ctx) | "Str.Concat" => .ok (.app Op.str_concat, .string, ctx) + | "Str.Substr" => .ok (.app Op.str_substr, .string, ctx) | "Str.ToRegEx" => .ok (.app Op.str_to_re, .regex, ctx) | "Str.InRegEx" => .ok (.app Op.str_in_re, .bool, ctx) | "Re.All" => .ok (.app Op.re_all, .regex, ctx) @@ -421,6 +554,7 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con | "Re.Union" => .ok (.app Op.re_union, .regex, ctx) | "Re.Inter" => .ok (.app Op.re_inter, .regex, ctx) | "Re.Comp" => .ok (.app Op.re_comp, .regex, ctx) + | "Re.None" => .ok (.app Op.re_none, .regex, ctx) | "Triggers.empty" => .ok (.app Op.triggers, .trigger, ctx) | "TriggerGroup.empty" => .ok (.app Op.triggers, .trigger, ctx) @@ -431,11 +565,11 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con let formalStrs := formals.map (toString ∘ format) let tys := LMonoTy.destructArrow fnty let intys := tys.take (tys.length - 1) - let (smt_intys, ctx) ← LMonoTys.toSMTType intys ctx + let (smt_intys, ctx) ← LMonoTys.toSMTType E intys ctx let bvs := formalStrs.zip smt_intys let argvars := bvs.map (fun a => TermVar.mk (toString $ format a.fst) a.snd) let outty := tys.getLast (by exact @LMonoTy.destructArrow_non_empty fnty) - let (smt_outty, ctx) ← LMonoTy.toSMTType outty ctx + let (smt_outty, ctx) ← LMonoTy.toSMTType E outty ctx let uf := ({id := (toString $ format fn), args := argvars, out := smt_outty}) let (ctx, isNew) ← match func.body with @@ -443,7 +577,7 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con | some body => -- Substitute the formals in the function body with appropriate -- `.bvar`s. - let bvars := (List.range formals.length).map (fun i => LExpr.bvar i) + let bvars := (List.range formals.length).map (fun i => LExpr.bvar () i) let body := LExpr.substFvars body (formals.zip bvars) let (term, ctx) ← toSMTTerm E bvs body ctx .ok (ctx.addIF uf term, !ctx.ifs.contains ({ uf := uf, body := term })) @@ -457,11 +591,11 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con -- Extract type instantiations by matching patterns against concrete types let type_instantiations: Map String LMonoTy := extractTypeInstantiations func.typeArgs allPatterns (intys ++ [outty]) let smt_ty_inst ← type_instantiations.foldlM (fun acc_map (tyVar, monoTy) => do - let (smtTy, _) ← LMonoTy.toSMTType monoTy ctx + let (smtTy, _) ← LMonoTy.toSMTType E monoTy ctx .ok (acc_map.insert tyVar smtTy) ) Map.empty -- Add all axioms for this function to the context, with types binding for the type variables in the expr - let ctx ← func.axioms.foldlM (fun acc_ctx (ax: LExpr LMonoTy Visibility) => do + let ctx ← func.axioms.foldlM (fun acc_ctx (ax: LExpr BoogieLParams.mono) => do let current_axiom_ctx := acc_ctx.addSubst smt_ty_inst let (axiom_term, new_ctx) ← toSMTTerm E [] ax current_axiom_ctx .ok (new_ctx.addAxiom axiom_term) @@ -472,7 +606,7 @@ partial def toSMTOp (E : Env) (fn : BoogieIdent) (fnty : LMonoTy) (ctx : SMT.Con .ok (.app (Op.uf uf), smt_outty, ctx) end -def toSMTTerms (E : Env) (es : List (LExpr LMonoTy Visibility)) (ctx : SMT.Context) : +def toSMTTerms (E : Env) (es : List (LExpr BoogieLParams.mono)) (ctx : SMT.Context) : Except Format ((List Term) × SMT.Context) := do match es with | [] => .ok ([], ctx) @@ -497,7 +631,7 @@ def ProofObligation.toSMTTerms (E : Env) --------------------------------------------------------------------- /-- Convert an expression of type LExpr to a String representation in SMT-Lib syntax, for testing. -/ -def toSMTTermString (e : (LExpr LMonoTy Visibility)) (E : Env := Env.init) (ctx : SMT.Context := SMT.Context.default) +def toSMTTermString (e : LExpr BoogieLParams.mono) (E : Env := Env.init) (ctx : SMT.Context := SMT.Context.default) : IO String := do let smtctx := toSMTTerm E [] e ctx match smtctx with @@ -507,25 +641,25 @@ def toSMTTermString (e : (LExpr LMonoTy Visibility)) (E : Env := Env.init) (ctx /-- info: "(define-fun t0 () Bool (forall (($__bv0 Int)) (exists (($__bv1 Int)) (= $__bv0 $__bv1))))\n" -/ #guard_msgs in #eval toSMTTermString - (.quant .all (.some .int) LExpr.noTrigger - (.quant .exist (.some .int) LExpr.noTrigger - (.eq (.bvar 1) (.bvar 0)))) + (.quant () .all (.some .int) (LExpr.noTrigger ()) + (.quant () .exist (.some .int) (LExpr.noTrigger ()) + (.eq () (.bvar () 1) (.bvar () 0)))) /-- info: "; x\n(declare-const f0 Int)\n(define-fun t0 () Bool (exists (($__bv0 Int)) (= $__bv0 f0)))\n" -/ #guard_msgs in #eval toSMTTermString - (.quant .exist (.some .int) LExpr.noTrigger - (.eq (.bvar 0) (.fvar "x" (.some .int)))) + (.quant () .exist (.some .int) (LExpr.noTrigger ()) + (.eq () (.bvar () 0) (.fvar () "x" (.some .int)))) /-- info: "; f\n(declare-fun f0 (Int) Int)\n; x\n(declare-const f1 Int)\n(define-fun t0 () Bool (exists (($__bv0 Int)) (! (= $__bv0 f1) :pattern ((f0 $__bv0)))))\n" -/ #guard_msgs in #eval toSMTTermString - (.quant .exist (.some .int) (.app (.fvar "f" (.some (.arrow .int .int))) (.bvar 0)) - (.eq (.bvar 0) (.fvar "x" (.some .int)))) + (.quant () .exist (.some .int) (.app () (.fvar () "f" (.some (.arrow .int .int))) (.bvar () 0)) + (.eq () (.bvar () 0) (.fvar () "x" (.some .int)))) /-- @@ -533,23 +667,23 @@ info: "; f\n(declare-fun f0 (Int) Int)\n; x\n(declare-const f1 Int)\n(define-fun -/ #guard_msgs in #eval toSMTTermString - (.quant .exist (.some .int) (.app (.fvar "f" (.some (.arrow .int .int))) (.bvar 0)) - (.eq (.app (.fvar "f" (.some (.arrow .int .int))) (.bvar 0)) (.fvar "x" (.some .int)))) + (.quant () .exist (.some .int) (.app () (.fvar () "f" (.some (.arrow .int .int))) (.bvar () 0)) + (.eq () (.app () (.fvar () "f" (.some (.arrow .int .int))) (.bvar () 0)) (.fvar () "x" (.some .int)))) /-- info: "Cannot encode expression (f %0)" -/ #guard_msgs in #eval toSMTTermString - (.quant .exist (.some .int) (.app (.fvar "f" (.none)) (.bvar 0)) - (.eq (.app (.fvar "f" (.some (.arrow .int .int))) (.bvar 0)) (.fvar "x" (.some .int)))) + (.quant () .exist (.some .int) (.app () (.fvar () "f" (.none)) (.bvar () 0)) + (.eq () (.app () (.fvar () "f" (.some (.arrow .int .int))) (.bvar () 0)) (.fvar () "x" (.some .int)))) /-- info: "; f\n(declare-const f0 (arrow Int Int))\n; f\n(declare-fun f1 (Int) Int)\n; x\n(declare-const f2 Int)\n(define-fun t0 () Bool (exists (($__bv0 Int)) (! (= (f1 $__bv0) f2) :pattern (f0))))\n" -/ #guard_msgs in #eval toSMTTermString - (.quant .exist (.some .int) - (mkTriggerExpr [[.fvar "f" (.some (.arrow .int .int))]]) - (.eq (.app (.fvar "f" (.some (.arrow .int .int))) (.bvar 0)) (.fvar "x" (.some .int)))) + (.quant () .exist (.some .int) + (mkTriggerExpr [[.fvar () "f" (.some (.arrow .int .int))]]) + (.eq () (.app () (.fvar () "f" (.some (.arrow .int .int))) (.bvar () 0)) (.fvar () "x" (.some .int)))) (ctx := SMT.Context.default) (E := {Env.init with exprEnv := { Env.init.exprEnv with @@ -563,15 +697,15 @@ info: "; f\n(declare-fun f0 (Int Int) Int)\n; x\n(declare-const f1 Int)\n(define -/ #guard_msgs in #eval toSMTTermString - (.quant .all (.some .int) (.bvar 0) (.quant .all (.some .int) (.app (.app (.op "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar 0)) (.bvar 1)) - (.eq (.app (.app (.op "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar 0)) (.bvar 1)) (.fvar "x" (.some .int))))) - (ctx := SMT.Context.mk #[] #[UF.mk "f" ((TermVar.mk "m" TermType.int) ::(TermVar.mk "n" TermType.int) :: []) TermType.int] #[] #[] []) + (.quant () .all (.some .int) (.bvar () 0) (.quant () .all (.some .int) (.app () (.app () (.op () "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar () 0)) (.bvar () 1)) + (.eq () (.app () (.app () (.op () "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar () 0)) (.bvar () 1)) (.fvar () "x" (.some .int))))) + (ctx := SMT.Context.mk #[] #[UF.mk "f" ((TermVar.mk "m" TermType.int) ::(TermVar.mk "n" TermType.int) :: []) TermType.int] #[] #[] [] #[] []) (E := {Env.init with exprEnv := { Env.init.exprEnv with config := { Env.init.exprEnv.config with factory := Env.init.exprEnv.config.factory.push $ - LFunc.mk "f" [] [("m", LMonoTy.int), ("n", LMonoTy.int)] LMonoTy.int .none #[] .none [] + LFunc.mk "f" [] False [("m", LMonoTy.int), ("n", LMonoTy.int)] LMonoTy.int .none #[] .none [] } }}) @@ -581,15 +715,15 @@ info: "; f\n(declare-fun f0 (Int Int) Int)\n; x\n(declare-const f1 Int)\n(define -/ #guard_msgs in -- No valid trigger #eval toSMTTermString - (.quant .all (.some .int) (.bvar 0) (.quant .all (.some .int) (.bvar 0) - (.eq (.app (.app (.op "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar 0)) (.bvar 1)) (.fvar "x" (.some .int))))) - (ctx := SMT.Context.mk #[] #[UF.mk "f" ((TermVar.mk "m" TermType.int) ::(TermVar.mk "n" TermType.int) :: []) TermType.int] #[] #[] []) + (.quant () .all (.some .int) (.bvar () 0) (.quant () .all (.some .int) (.bvar () 0) + (.eq () (.app () (.app () (.op () "f" (.some (.arrow .int (.arrow .int .int)))) (.bvar () 0)) (.bvar () 1)) (.fvar () "x" (.some .int))))) + (ctx := SMT.Context.mk #[] #[UF.mk "f" ((TermVar.mk "m" TermType.int) ::(TermVar.mk "n" TermType.int) :: []) TermType.int] #[] #[] [] #[] []) (E := {Env.init with exprEnv := { Env.init.exprEnv with config := { Env.init.exprEnv.config with factory := Env.init.exprEnv.config.factory.push $ - LFunc.mk "f" [] [("m", LMonoTy.int), ("n", LMonoTy.int)] LMonoTy.int .none #[] .none [] + LFunc.mk "f" [] False [("m", LMonoTy.int), ("n", LMonoTy.int)] LMonoTy.int .none #[] .none [] } }}) diff --git a/Strata/Languages/Boogie/Statement.lean b/Strata/Languages/Boogie/Statement.lean index fc1f04907..ce91d1c58 100644 --- a/Strata/Languages/Boogie/Statement.lean +++ b/Strata/Languages/Boogie/Statement.lean @@ -39,8 +39,8 @@ We parameterize Boogie's Commands with Lambda dialect's expressions. abbrev Command := CmdExt Expression instance : HasPassiveCmds Expression Command where - assert l e := .cmd (.assert l e) - assume l e := .cmd (.assume l e) + assert l e md := .cmd (.assert l e md) + assume l e md := .cmd (.assume l e md) instance : HasHavoc Expression Command where havoc x := .cmd (.havoc x) @@ -58,8 +58,8 @@ instance [ToFormat (Cmd P)] [ToFormat (MetaData P)] ToFormat (CmdExt P) where format c := match c with | .cmd c => format c - | .call lhs pname args md => - f!"{md}call " ++ (if lhs.isEmpty then f!"" else f!"{lhs} := ") ++ + | .call lhs pname args _md => + f!"call " ++ (if lhs.isEmpty then f!"" else f!"{lhs} := ") ++ f!"{pname}({args})" --------------------------------------------------------------------- @@ -91,6 +91,10 @@ abbrev Statement.call (lhs : List Expression.Ident) (pname : String) (args : Lis --------------------------------------------------------------------- +abbrev Block := Imperative.Block Boogie.Expression Boogie.Command + +--------------------------------------------------------------------- + def Command.eraseTypes (c : Command) : Command := match c with | .cmd c => @@ -107,15 +111,15 @@ mutual def Statement.eraseTypes (s : Statement) : Statement := match s with | .cmd c => .cmd (Command.eraseTypes c) - | .block label ⟨ bss ⟩ md => + | .block label bss md => let ss' := Statements.eraseTypes bss - .block label { ss := ss' } md - | .ite cond ⟨ tss ⟩ ⟨ ess ⟩ md => - let thenb' := { ss := Statements.eraseTypes tss } - let elseb' := { ss := Statements.eraseTypes ess } + .block label ss' md + | .ite cond tss ess md => + let thenb' := Statements.eraseTypes tss + let elseb' := Statements.eraseTypes ess .ite cond thenb' elseb' md - | .loop guard measure invariant ⟨ bss ⟩ md => - let body' := { ss := Statements.eraseTypes bss } + | .loop guard measure invariant bss md => + let body' := Statements.eraseTypes bss .loop guard measure invariant body' md | .goto l md => .goto l md termination_by (Stmt.sizeOf s) @@ -164,10 +168,10 @@ instance : HasVarsImp Expression Statement where touchedVars := Stmt.touchedVars instance : HasVarsImp Expression (List Statement) where - definedVars := Stmts.definedVars - modifiedVars := Stmts.modifiedVars + definedVars := Block.definedVars + modifiedVars := Block.modifiedVars -- order matters for Havoc, so needs to override the default - touchedVars := Stmts.touchedVars + touchedVars := Block.touchedVars --------------------------------------------------------------------- @@ -190,10 +194,10 @@ def Statement.modifiedVarsTrans : List Expression.Ident := match s with | .cmd cmd => Command.modifiedVarsTrans π cmd | .goto _ _ => [] - | .block _ ⟨ bss ⟩ _ => Statements.modifiedVarsTrans π bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => + | .block _ bss _ => Statements.modifiedVarsTrans π bss + | .ite _ tbss ebss _ => Statements.modifiedVarsTrans π tbss ++ Statements.modifiedVarsTrans π ebss - | .loop _ _ _ ⟨ bss ⟩ _ => + | .loop _ _ _ bss _ => Statements.modifiedVarsTrans π bss termination_by (Stmt.sizeOf s) @@ -204,7 +208,7 @@ def Statements.modifiedVarsTrans : List Expression.Ident := match ss with | [] => [] | s :: ss => Statement.modifiedVarsTrans π s ++ Statements.modifiedVarsTrans π ss - termination_by (Stmts.sizeOf ss) + termination_by (Block.sizeOf ss) end def Command.getVarsTrans @@ -228,10 +232,10 @@ def Statement.getVarsTrans : List Expression.Ident := match s with | .cmd cmd => Command.getVarsTrans π cmd | .goto _ _ => [] - | .block _ ⟨ bss ⟩ _ => Statements.getVarsTrans π bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => + | .block _ bss _ => Statements.getVarsTrans π bss + | .ite _ tbss ebss _ => Statements.getVarsTrans π tbss ++ Statements.getVarsTrans π ebss - | .loop _ _ _ ⟨ bss ⟩ _ => + | .loop _ _ _ bss _ => Statements.getVarsTrans π bss termination_by (Stmt.sizeOf s) @@ -242,7 +246,7 @@ def Statements.getVarsTrans : List Expression.Ident := match ss with | [] => [] | s :: ss => Statement.getVarsTrans π s ++ Statements.getVarsTrans π ss - termination_by (Stmts.sizeOf ss) + termination_by (Block.sizeOf ss) end -- don't need to transitively lookup for procedures @@ -261,7 +265,7 @@ def Statement.definedVarsTrans -- since call statement does not define any new variables def Statements.definedVarsTrans (_ : String → Option ProcType) (s : Statements) := - Stmts.definedVars s + Block.definedVars s mutual /-- get all variables touched by the statement `s`. -/ @@ -273,9 +277,9 @@ def Statement.touchedVarsTrans match s with | .cmd cmd => Command.definedVarsTrans π cmd ++ Command.modifiedVarsTrans π cmd | .goto _ _ => [] - | .block _ ⟨ bss ⟩ _ => Statements.touchedVarsTrans π bss - | .ite _ ⟨ tbss ⟩ ⟨ ebss ⟩ _ => Statements.touchedVarsTrans π tbss ++ Statements.touchedVarsTrans π ebss - | .loop _ _ _ ⟨ bss ⟩ _ => Statements.touchedVarsTrans π bss + | .block _ bss _ => Statements.touchedVarsTrans π bss + | .ite _ tbss ebss _ => Statements.touchedVarsTrans π tbss ++ Statements.touchedVarsTrans π ebss + | .loop _ _ _ bss _ => Statements.touchedVarsTrans π bss termination_by (Stmt.sizeOf s) def Statements.touchedVarsTrans @@ -286,7 +290,7 @@ def Statements.touchedVarsTrans match ss with | [] => [] | s :: srest => Statement.touchedVarsTrans π s ++ Statements.touchedVarsTrans π srest - termination_by (Stmts.sizeOf ss) + termination_by (Block.sizeOf ss) end def Statement.allVarsTrans @@ -300,4 +304,68 @@ def Statements.allVarsTrans | [] => [] | s :: ss => Statement.allVarsTrans π s ++ Statements.allVarsTrans π ss +--------------------------------------------------------------------- + +mutual +partial def Block.substFvar (b : Block) (fr:Expression.Ident) + (to:Expression.Expr) : Block := + List.map (fun s => Statement.substFvar s fr to) b + +partial def Statement.substFvar (s : Boogie.Statement) + (fr:Expression.Ident) + (to:Expression.Expr) : Statement := + match s with + | .init lhs ty rhs metadata => + .init lhs ty (Lambda.LExpr.substFvar rhs fr to) metadata + | .set lhs rhs metadata => + .set lhs (Lambda.LExpr.substFvar rhs fr to) metadata + | .havoc _ _ => s + | .assert lbl b metadata => + .assert lbl (Lambda.LExpr.substFvar b fr to) metadata + | .assume lbl b metadata => + .assume lbl (Lambda.LExpr.substFvar b fr to) metadata + | .call lhs pname args metadata => + .call lhs pname (List.map (Lambda.LExpr.substFvar · fr to) args) metadata + + | .block lbl b metadata => + .block lbl (Block.substFvar b fr to) metadata + | .ite cond thenb elseb metadata => + .ite (Lambda.LExpr.substFvar cond fr to) (Block.substFvar thenb fr to) + (Block.substFvar elseb fr to) metadata + | .loop guard measure invariant body metadata => + .loop (Lambda.LExpr.substFvar guard fr to) + (Option.map (Lambda.LExpr.substFvar · fr to) measure) + (Option.map (Lambda.LExpr.substFvar · fr to) invariant) + (Block.substFvar body fr to) + metadata + | .goto _ _ => s +end + +--------------------------------------------------------------------- + +mutual +partial def Block.renameLhs (b : Block) + (fr: Lambda.Identifier Visibility) (to: Lambda.Identifier Visibility) : Block := + List.map (fun s => Statement.renameLhs s fr to) b + +partial def Statement.renameLhs (s : Boogie.Statement) + (fr: Lambda.Identifier Visibility) (to: Lambda.Identifier Visibility) + : Statement := + match s with + | .init lhs ty rhs metadata => + .init (if lhs.name == fr then to else lhs) ty rhs metadata + | .set lhs rhs metadata => + .set (if lhs.name == fr then to else lhs) rhs metadata + | .call lhs pname args metadata => + .call (lhs.map (fun l => + if l.name == fr then to else l)) pname args metadata + | .block lbl b metadata => + .block lbl (Block.renameLhs b fr to) metadata + | .havoc _ _ | .assert _ _ _ | .assume _ _ _ | .ite _ _ _ _ + | .loop _ _ _ _ _ | .goto _ _ => s +end + +--------------------------------------------------------------------- + + end Boogie diff --git a/Strata/Languages/Boogie/StatementEval.lean b/Strata/Languages/Boogie/StatementEval.lean index 608387b4e..8ef9bfde5 100644 --- a/Strata/Languages/Boogie/StatementEval.lean +++ b/Strata/Languages/Boogie/StatementEval.lean @@ -39,7 +39,7 @@ a `.call` statement. def callConditions (proc : Procedure) (condType : CondType) (conditions : ListMap String Procedure.Check) - (subst : Map (Lambda.IdentT Visibility) Expression.Expr) : + (subst : Map (Lambda.IdentT Lambda.LMonoTy Visibility) Expression.Expr) : ListMap String Procedure.Check := let names := List.map (fun k => s!"(Origin_{proc.header.name.name}_{condType}){k}") @@ -48,8 +48,7 @@ def callConditions (proc : Procedure) (fun p => List.foldl (fun c (x, v) => - { expr := LExpr.substFvar c.expr x.fst v , - attr := c.attr}) + { c with expr := LExpr.substFvar c.expr x.fst v }) p subst) conditions.values List.zip names exprs @@ -75,17 +74,19 @@ def Command.evalCall (E : Env) (old_var_subst : SubstMap) | some proc => -- Create a mapping from the formals to the evaluated actuals. let args' := List.map (fun a => E.exprEval (OldExpressions.substsOldExpr old_var_subst a)) args - let formal_tys := proc.header.inputs.keys.map (fun k => ((k, none) : (Lambda.IdentT Visibility))) + let formal_tys := proc.header.inputs.keys.map + (fun k => ((k, none) : (Lambda.IdentT Lambda.LMonoTy Visibility))) let formal_arg_subst := List.zip formal_tys args' -- Generate fresh variables for the LHS, and then create a mapping -- from the procedure's return variables to these LHS fresh -- variables. let lhs_tys := lhs.map - (fun l => (E.exprEnv.state.findD l (none, .fvar l none)).fst) + (fun l => (E.exprEnv.state.findD l (none, .fvar () l none)).fst) let lhs_typed := lhs.zip lhs_tys let (lhs_fvars, E) := E.genFVars lhs_typed - let return_tys := proc.header.outputs.keys.map (fun k => ((k, none) : (Lambda.IdentT Visibility))) + let return_tys := proc.header.outputs.keys.map + (fun k => ((k, none) : (Lambda.IdentT Lambda.LMonoTy Visibility))) let return_lhs_subst := List.zip return_tys lhs_fvars -- The LHS fresh variables reflect the values of these variables -- in the post-call state. @@ -103,7 +104,7 @@ def Command.evalCall (E : Env) (old_var_subst : SubstMap) -- reflect the post-call value of these globals. let modifies_tys := proc.spec.modifies.map - (fun l => (E.exprEnv.state.findD l (none, .fvar l none)).fst) + (fun l => (E.exprEnv.state.findD l (none, .fvar () l none)).fst) let modifies_typed := proc.spec.modifies.zip modifies_tys let (globals_fvars, E) := E.genFVars modifies_typed let globals_post_subst := List.zip modifies_typed globals_fvars @@ -115,7 +116,7 @@ def Command.evalCall (E : Env) (old_var_subst : SubstMap) let preconditions := callConditions proc .Requires proc.spec.preconditions subst let preconditions := preconditions.map - (fun (l, e) => (toString l, Procedure.Check.mk (E.exprEval e.expr) e.attr)) + (fun (l, e) => (toString l, Procedure.Check.mk (E.exprEval e.expr) e.attr e.md)) -- A free precondition is not checked at call sites, which is -- accounted for by `ProofObligations.create` below. let deferred_pre := ProofObligations.create E.pathConditions preconditions @@ -193,7 +194,7 @@ def processGoto : Statements → Option String → (Statements × Option String) def evalAux (E : Env) (old_var_subst : SubstMap) (ss : Statements) (optLabel : Option String) : List EnvWithNext := open LTy.Syntax in - go (Imperative.Stmts.sizeOf ss) (EnvWithNext.mk E .none []) ss optLabel + go (Imperative.Block.sizeOf ss) (EnvWithNext.mk E .none []) ss optLabel where go steps Ewn ss optLabel := match steps, Ewn.env.error with | _, some _ => [{Ewn with nextLabel := .none}] @@ -213,7 +214,7 @@ def evalAux (E : Env) (old_var_subst : SubstMap) (ss : Statements) (optLabel : O env := E, nextLabel := .none }] - | .block label { ss } md => + | .block label ss md => let orig_stk := Ewn.stk let Ewn := { Ewn with env := Ewn.env.pushEmptyScope, stk := orig_stk.push [] } @@ -223,29 +224,29 @@ def evalAux (E : Env) (old_var_subst : SubstMap) (ss : Statements) (optLabel : O { ewn with env := ewn.env.popScope, stk := let ss' := ewn.stk.top - let s' := Imperative.Stmt.block label { ss := ss' } md + let s' := Imperative.Stmt.block label ss' md orig_stk.appendToTop [s'] }) Ewns - | .ite cond { ss := then_ss } { ss := else_ss } md => + | .ite cond then_ss else_ss md => let orig_stk := Ewn.stk let Ewn := { Ewn with stk := orig_stk.push [] } let cond' := Ewn.env.exprEval cond match cond' with - | .const "true" _ => + | .true _ => let Ewns := go' Ewn then_ss .none -- Not allowed to jump into a block let Ewns := Ewns.map (fun (ewn : EnvWithNext) => let ss' := ewn.stk.top - let s' := Imperative.Stmt.ite cond' { ss := ss' } { ss := [] } md + let s' := Imperative.Stmt.ite cond' ss' [] md { ewn with stk := orig_stk.appendToTop [s']}) Ewns - | .const "false" _ => + | .false _ => let Ewns := go' Ewn else_ss .none -- Not allowed to jump into a block let Ewns := Ewns.map (fun (ewn : EnvWithNext) => let ss' := ewn.stk.top - let s' := Imperative.Stmt.ite cond' { ss := [] } { ss := ss' } md + let s' := Imperative.Stmt.ite cond' [] ss' md { ewn with stk := orig_stk.appendToTop [s']}) Ewns | _ => @@ -254,7 +255,7 @@ def evalAux (E : Env) (old_var_subst : SubstMap) (ss : Statements) (optLabel : O let label_false := toString (f!"") let path_conds_true := Ewn.env.pathConditions.push [(label_true, cond')] let path_conds_false := Ewn.env.pathConditions.push - [(label_false, (.ite cond' LExpr.false LExpr.true))] + [(label_false, (.ite () cond' (LExpr.false ()) (LExpr.true ())))] let Ewns_t := go' {Ewn with env := {Ewn.env with pathConditions := path_conds_true}} then_ss .none -- We empty the deferred proof obligations in the `else` path to -- avoid duplicate verification checks -- the deferred obligations @@ -265,19 +266,19 @@ def evalAux (E : Env) (old_var_subst : SubstMap) (ss : Statements) (optLabel : O -- with no next label, we can merge both states into one. | [{ stk := stk_t, env := E_t, nextLabel := .none}], [{ stk := stk_f, env := E_f, nextLabel := .none}] => - let s' := Imperative.Stmt.ite cond' { ss := stk_t.top } { ss := stk_f.top } md + let s' := Imperative.Stmt.ite cond' stk_t.top stk_f.top md [EnvWithNext.mk (Env.merge cond' E_t E_f).popScope .none (orig_stk.appendToTop [s'])] | _, _ => let Ewns_t := Ewns_t.map (fun (ewn : EnvWithNext) => - let s' := Imperative.Stmt.ite LExpr.true { ss := ewn.stk.top } { ss := [] } md + let s' := Imperative.Stmt.ite (LExpr.true ()) ewn.stk.top [] md { ewn with env := ewn.env.popScope, stk := orig_stk.appendToTop [s']}) let Ewns_f := Ewns_f.map (fun (ewn : EnvWithNext) => - let s' := Imperative.Stmt.ite LExpr.false { ss := [] } { ss := ewn.stk.top } md + let s' := Imperative.Stmt.ite (LExpr.false ()) [] ewn.stk.top md { ewn with env := ewn.env.popScope, stk := orig_stk.appendToTop [s']}) Ewns_t ++ Ewns_f diff --git a/Strata/Languages/Boogie/StatementSemantics.lean b/Strata/Languages/Boogie/StatementSemantics.lean index f16ff47fb..9b0f9ed51 100644 --- a/Strata/Languages/Boogie/StatementSemantics.lean +++ b/Strata/Languages/Boogie/StatementSemantics.lean @@ -14,25 +14,25 @@ namespace Boogie /-- expressions that can't be reduced when evaluating -/ inductive Value : Boogie.Expression.Expr → Prop where - | const : Value (.const _ _) - | bvar : Value (.bvar _) - | op : Value (.op _ _) - | abs : Value (.abs _ _) + | const : Value (.const () _) + | bvar : Value (.bvar () _) + | op : Value (.op () _ _) + | abs : Value (.abs () _ _) open Imperative instance : HasVal Boogie.Expression where value := Value instance : HasFvar Boogie.Expression where - mkFvar := (.fvar · none) + mkFvar := (.fvar () · none) getFvar - | .fvar v _ => some v + | .fvar _ v _ => some v | _ => none @[match_pattern] -def Boogie.true : Boogie.Expression.Expr := .const "true" (some .bool) +def Boogie.true : Boogie.Expression.Expr := .boolConst () Bool.true @[match_pattern] -def Boogie.false : Boogie.Expression.Expr := .const "false" (some .bool) +def Boogie.false : Boogie.Expression.Expr := .boolConst () Bool.false instance : HasBool Boogie.Expression where tt := Boogie.true @@ -42,37 +42,41 @@ instance : HasNot Boogie.Expression where not | Boogie.true => Boogie.false | Boogie.false => Boogie.true - | e => Lambda.LExpr.app Lambda.boolNotFunc.opExpr e + | e => Lambda.LExpr.app () (Lambda.LFunc.opExpr (T:=BoogieLParams) Lambda.boolNotFunc) e abbrev BoogieEval := SemanticEval Expression abbrev BoogieStore := SemanticStore Expression -def WellFormedBoogieEvalCong (δ : BoogieEval) : Prop := - (∀ e₁ e₁' σ₀ σ σ₀' σ', - δ σ₀ σ e₁ = δ σ₀' σ' e₁' → - (∀ ty, δ σ₀ σ (.abs ty e₁) = δ σ₀' σ' (.abs ty e₁')) ∧ - (∀ info, δ σ₀ σ (.mdata info e₁) = δ σ₀' σ' (.mdata info e₁')) ∧ - -- binary congruence - (∀ e₂ e₂', - δ σ₀ σ e₂ = δ σ₀' σ' e₂' → - δ σ₀ σ (.app e₁ e₂) = δ σ₀' σ' (.app e₁' e₂') ∧ - δ σ₀ σ (.eq e₁ e₂) = δ σ₀' σ' (.eq e₁' e₂') ∧ - (∀ k ty, δ σ₀ σ (.quant k ty e₁ e₂) = δ σ₀' σ' (.quant k ty e₁' e₂')) ∧ - -- ternary congruence - (∀ e₃ e₃', - δ σ₀ σ e₃ = δ σ₀' σ' e₃' → - δ σ₀ σ (.ite e₃ e₁ e₂) = δ σ₀' σ' (.ite e₃' e₁' e₂') - )) - ) - -inductive EvalExpressions {P} [HasVarsPure P P.Expr] : SemanticEval P → SemanticStore P → SemanticStore P → List P.Expr → List P.Expr → Prop where +structure WellFormedBoogieEvalCong (δ : BoogieEval): Prop where + abscongr: (∀ σ σ' e₁ e₁' , + δ σ e₁ = δ σ' e₁' → + (∀ ty m, δ σ (.abs ty m e₁) = δ σ' (.abs ty m e₁'))) + appcongr: (∀ σ σ' m e₁ e₁' e₂ e₂', + δ σ e₁ = δ σ' e₁' → + δ σ e₂ = δ σ' e₂' → + (δ σ (.app m e₁ e₂) = δ σ' (.app m e₁' e₂'))) + eqcongr: (∀ σ σ' m e₁ e₁' e₂ e₂', + δ σ e₁ = δ σ' e₁' → + δ σ e₂ = δ σ' e₂' → + (δ σ (.eq m e₁ e₂) = δ σ' (.eq m e₁' e₂'))) + quantcongr: (∀ σ σ' m k ty e₁ e₁' e₂ e₂', + δ σ e₁ = δ σ' e₁' → + δ σ e₂ = δ σ' e₂' → + (δ σ (.quant m k ty e₁ e₂) = δ σ' (.quant m k ty e₁' e₂'))) + itecongr: (∀ σ σ' m e₁ e₁' e₂ e₂' e₃ e₃', + δ σ e₁ = δ σ' e₁' → + δ σ e₂ = δ σ' e₂' → + δ σ e₃ = δ σ' e₃' → + (δ σ (.ite m e₃ e₁ e₂) = δ σ' (.ite m e₃' e₁' e₂'))) + +inductive EvalExpressions {P} [HasVarsPure P P.Expr] : SemanticEval P → SemanticStore P → List P.Expr → List P.Expr → Prop where | eval_none : - EvalExpressions δ σ₀ σ [] [] + EvalExpressions δ σ [] [] | eval_some : isDefined σ (HasVarsPure.getVars e) → - δ σ₀ σ e = .some v → - EvalExpressions δ σ₀ σ es vs → - EvalExpressions δ σ₀ σ (e :: es) (v :: vs) + δ σ e = .some v → + EvalExpressions δ σ es vs → + EvalExpressions δ σ (e :: es) (v :: vs) inductive ReadValues : SemanticStore P → List P.Ident → List P.Expr → Prop where | read_none : @@ -164,29 +168,32 @@ def WellFormedBoogieEvalTwoState (δ : BoogieEval) (σ₀ σ : BoogieStore) : Pr (∃ vs vs' σ₁, HavocVars σ₀ vs σ₁ ∧ InitVars σ₁ vs' σ) ∧ (∀ vs vs' σ₀ σ₁ σ, (HavocVars σ₀ vs σ₁ ∧ InitVars σ₁ vs' σ) → - -- if the variable is modified, then old variable should lookup in the old store ∀ v, - (v ∈ vs → ∀ oty ty, δ σ₀ σ (@oldVar oty v ty) = σ₀ v) ∧ + (v ∈ vs → + ∀ oty mApp mOp mVar ty, + δ σ (@oldVar (tyold := oty) mApp mOp mVar v ty) = σ₀ v) ∧ -- if the variable is not modified, then old variable is identity - (¬ v ∈ vs → ∀ oty ty, δ σ₀ σ (@oldVar oty v ty) = σ v)) ∧ + (¬ v ∈ vs → + ∀ oty mApp mOp mVar ty, + δ σ (@oldVar (tyold := oty) mApp mOp mVar v ty) = σ v)) ∧ -- evaluating on an old complex expression is the same as evlauating on its normal form -- TODO: can possibly break this into more sub-components, proving it using congruence and normalization property -- Might not be needed if we assume all expressions are normalized - (∀ e σ₀ σ, δ σ₀ σ e = δ σ₀ σ (normalizeOldExpr e)) + (∀ e σ, δ σ e = δ σ (normalizeOldExpr e)) inductive EvalCommand : (String → Option Procedure) → BoogieEval → - BoogieStore → BoogieStore → Command → BoogieStore → Prop where - | cmd_sem {π δ σ₀ σ c σ'} : - Imperative.EvalCmd (P:=Expression) δ σ₀ σ c σ' → + BoogieStore → Command → BoogieStore → Prop where + | cmd_sem {π δ σ c σ'} : + Imperative.EvalCmd (P:=Expression) δ σ c σ' → ---- - EvalCommand π δ σ₀ σ (CmdExt.cmd c) σ' + EvalCommand π δ σ (CmdExt.cmd c) σ' /- NOTE: If π is NOT the first implicit variable below, Lean complains as follows; wish this error message actually mentioned which local variable was the problematic one. - invalid nested inductive datatype 'Imperative.EvalStmts', nested inductive + invalid nested inductive datatype 'Imperative.EvalBlock', nested inductive datatypes parameters cannot contain local variables. Here's a Zulip thread that can shed some light on this error message: @@ -194,7 +201,7 @@ inductive EvalCommand : (String → Option Procedure) → BoogieEval → -/ | call_sem {π δ σ₀ σ args vals oVals σA σAO σR n p modvals lhs σ'} : π n = .some p → - EvalExpressions (P:=Expression) δ σ₀ σ args vals → + EvalExpressions (P:=Expression) δ σ args vals → ReadValues σ lhs oVals → WellFormedSemanticEvalVal δ → WellFormedSemanticEvalVar δ → @@ -214,36 +221,36 @@ inductive EvalCommand : (String → Option Procedure) → BoogieEval → -- Preconditions, if any, must be satisfied for execution to continue. (∀ pre, (Procedure.Spec.getCheckExprs p.spec.preconditions).contains pre → isDefinedOver (HasVarsPure.getVars) σAO pre ∧ - δ σAO σAO pre = .some HasBool.tt) → - @Imperative.EvalStmts Expression Command (EvalCommand π) _ _ _ _ _ _ δ σAO σAO p.body σR → + δ σAO pre = .some HasBool.tt) → + @Imperative.EvalBlock Expression Command (EvalCommand π) _ _ _ _ _ _ δ σAO p.body σR → -- Postconditions, if any, must be satisfied for execution to continue. (∀ post, (Procedure.Spec.getCheckExprs p.spec.postconditions).contains post → isDefinedOver (HasVarsPure.getVars) σAO post ∧ - δ σAO σR post = .some HasBool.tt) → + δ σR post = .some HasBool.tt) → ReadValues σR (ListMap.keys (p.header.outputs) ++ p.spec.modifies) modvals → UpdateStates σ (lhs ++ p.spec.modifies) modvals σ' → ---- - EvalCommand π δ σ₀ σ (CmdExt.call lhs n args) σ' + EvalCommand π δ σ (CmdExt.call lhs n args) σ' abbrev EvalStatement (π : String → Option Procedure) : BoogieEval → - BoogieStore → BoogieStore → Statement → BoogieStore → Prop := + BoogieStore → Statement → BoogieStore → Prop := Imperative.EvalStmt Expression Command (EvalCommand π) abbrev EvalStatements (π : String → Option Procedure) : BoogieEval → - BoogieStore → BoogieStore → List Statement → BoogieStore → Prop := - Imperative.EvalStmts Expression Command (EvalCommand π) + BoogieStore → List Statement → BoogieStore → Prop := + Imperative.EvalBlock Expression Command (EvalCommand π) inductive EvalCommandContract : (String → Option Procedure) → BoogieEval → - BoogieStore → BoogieStore → Command → BoogieStore → Prop where - | cmd_sem {π δ σ₀ σ c σ'} : - Imperative.EvalCmd (P:=Expression) δ σ₀ σ c σ' → + BoogieStore → Command → BoogieStore → Prop where + | cmd_sem {π δ σ c σ'} : + Imperative.EvalCmd (P:=Expression) δ σ c σ' → ---- - EvalCommandContract π δ σ₀ σ (CmdExt.cmd c) σ' + EvalCommandContract π δ σ (CmdExt.cmd c) σ' - | call_sem {π δ σ₀ σ args oVals vals σA σAO σO σR n p modvals lhs σ'} : + | call_sem {π δ σ args oVals vals σA σAO σO σR n p modvals lhs σ'} : π n = .some p → - EvalExpressions (P:=Boogie.Expression) δ σ₀ σ args vals → + EvalExpressions (P:=Boogie.Expression) δ σ args vals → ReadValues σ lhs oVals → WellFormedSemanticEvalVal δ → WellFormedSemanticEvalVar δ → @@ -263,22 +270,22 @@ inductive EvalCommandContract : (String → Option Procedure) → BoogieEval -- Preconditions, if any, must be satisfied for execution to continue. (∀ pre, (Procedure.Spec.getCheckExprs p.spec.preconditions).contains pre → isDefinedOver (HasVarsPure.getVars) σAO pre ∧ - δ σAO σAO pre = .some HasBool.tt) → + δ σAO pre = .some HasBool.tt) → HavocVars σAO (ListMap.keys p.header.outputs) σO → HavocVars σO p.spec.modifies σR → -- Postconditions, if any, must be satisfied for execution to continue. (∀ post, (Procedure.Spec.getCheckExprs p.spec.postconditions).contains post → isDefinedOver (HasVarsPure.getVars) σAO post ∧ - δ σO σR post = .some HasBool.tt) → + δ σR post = .some HasBool.tt) → ReadValues σR (ListMap.keys (p.header.outputs) ++ p.spec.modifies) modvals → UpdateStates σ (lhs ++ p.spec.modifies) modvals σ' → ---- - EvalCommandContract π δ σ₀ σ (.call lhs n args) σ' + EvalCommandContract π δ σ (.call lhs n args) σ' abbrev EvalStatementContract (π : String → Option Procedure) : BoogieEval → - BoogieStore → BoogieStore → Statement → BoogieStore → Prop := + BoogieStore → Statement → BoogieStore → Prop := Imperative.EvalStmt Expression Command (EvalCommandContract π) abbrev EvalStatementsContract (π : String → Option Procedure) : BoogieEval → - BoogieStore → BoogieStore → List Statement → BoogieStore → Prop := - Imperative.EvalStmts Expression Command (EvalCommandContract π) + BoogieStore → List Statement → BoogieStore → Prop := + Imperative.EvalBlock Expression Command (EvalCommandContract π) diff --git a/Strata/Languages/Boogie/StatementSemanticsProps.lean b/Strata/Languages/Boogie/StatementSemanticsProps.lean index c82dd8785..4ddfdcf54 100644 --- a/Strata/Languages/Boogie/StatementSemanticsProps.lean +++ b/Strata/Languages/Boogie/StatementSemanticsProps.lean @@ -39,18 +39,18 @@ theorem TouchVarsEmpty : @TouchVars P σ [] σ' → σ = σ' := by intros H; cases H <;> simp -theorem EvalStmtsEmpty {P : PureExpr} {Cmd : Type} {EvalCmd : EvalCmdParam P Cmd} - { σ σ' σ₀: SemanticStore P } { δ : SemanticEval P } +theorem EvalBlockEmpty {P : PureExpr} {Cmd : Type} {EvalCmd : EvalCmdParam P Cmd} + { σ σ': SemanticStore P } { δ : SemanticEval P } [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] [HasFvar P] [HasVal P] [HasBool P] [HasNot P] : - EvalStmts P Cmd EvalCmd δ σ₀ σ ([]: (List (Stmt P Cmd))) σ' → σ = σ' := by + EvalBlock P Cmd EvalCmd δ σ ([]: (List (Stmt P Cmd))) σ' → σ = σ' := by intros H; cases H <;> simp theorem EvalStatementsEmpty : - EvalStatements π δ σ₀ σ [] σ' → σ = σ' := by + EvalStatements π δ σ [] σ' → σ = σ' := by intros H; cases H <;> simp theorem EvalStatementsContractEmpty : - EvalStatementsContract π δ σ₀ σ [] σ' → σ = σ' := by + EvalStatementsContract π δ σ [] σ' → σ = σ' := by intros H; cases H <;> simp theorem UpdateStateNotDefMonotone @@ -610,7 +610,7 @@ theorem ReadValuesLength : induction Hrd <;> simp_all theorem EvalExpressionsLength : - EvalExpressions (P:=Boogie.Expression) δ σ σ₀ ks vs → + EvalExpressions (P:=Boogie.Expression) δ σ ks vs → ks.length = vs.length := by intros Hrd induction Hrd <;> simp_all @@ -1311,15 +1311,15 @@ theorem ReadValuesSubstStores : . exact ih Ht Ht' theorem EvalStatementsContractApp' : - EvalStatementsContract π δ σ₀ σ (ss₁ ++ ss₂) σ'' → + EvalStatementsContract π δ σ (ss₁ ++ ss₂) σ'' → ∃ σ', - EvalStatementsContract π δ σ₀ σ ss₁ σ' ∧ - EvalStatementsContract π δ σ₀ σ' ss₂ σ'' := by + EvalStatementsContract π δ σ ss₁ σ' ∧ + EvalStatementsContract π δ σ' ss₂ σ'' := by intros Heval induction ss₁ generalizing σ <;> simp_all case nil => exists σ <;> simp_all - exact EvalStmts.stmts_none_sem + exact EvalBlock.stmts_none_sem case cons h t ih => cases Heval with | stmts_some_sem Hh Ht => @@ -1329,12 +1329,12 @@ theorem EvalStatementsContractApp' : | intro σ'' Heval => exists σ'' simp_all - exact EvalStmts.stmts_some_sem Hh Heval.1 + exact EvalBlock.stmts_some_sem Hh Heval.1 theorem EvalStatementsContractApp : - EvalStatementsContract π δ σ₀ σ ss₁ σ' → - EvalStatementsContract π δ σ₀ σ' ss₂ σ'' → - EvalStatementsContract π δ σ₀ σ (ss₁ ++ ss₂) σ'' := by + EvalStatementsContract π δ σ ss₁ σ' → + EvalStatementsContract π δ σ' ss₂ σ'' → + EvalStatementsContract π δ σ (ss₁ ++ ss₂) σ'' := by intros Heval1 Heval2 induction ss₁ generalizing σ <;> simp_all case nil => @@ -1349,16 +1349,16 @@ theorem EvalStatementsContractApp : exact Heval' theorem EvalStatementsApp : - EvalStatements π δ σ₀ σ ss₁ σ' → - EvalStatements π δ σ₀ σ' ss₂ σ'' → - EvalStatements π δ σ₀ σ (ss₁ ++ ss₂) σ'' := by + EvalStatements π δ σ ss₁ σ' → + EvalStatements π δ σ' ss₂ σ'' → + EvalStatements π δ σ (ss₁ ++ ss₂) σ'' := by apply Nat.strongRecOn (motive := λ m ↦ ∀ ss₁ ss₂ σ σ' σ'', sizeOf (ss₁ ++ ss₂) = m → - EvalStatements π δ σ₀ σ ss₁ σ' → - EvalStatements π δ σ₀ σ' ss₂ σ'' → - EvalStatements π δ σ₀ σ (ss₁ ++ ss₂) σ'') + EvalStatements π δ σ ss₁ σ' → + EvalStatements π δ σ' ss₂ σ'' → + EvalStatements π δ σ (ss₁ ++ ss₂) σ'') (sizeOf (ss₁ ++ ss₂)) intros n ih ss₁ ss₂ σ σ' σ'' Hsize Heval1 Heval2 . cases Heval1 with @@ -1367,7 +1367,7 @@ theorem EvalStatementsApp : next s σ₁ ss => constructor <;> try assumption simp [sizeOf] at * - have Hsz : Stmts.sizeOf (ss ++ ss₂) = n - 1 - s.sizeOf := by omega + have Hsz : Block.sizeOf (ss ++ ss₂) = n - 1 - s.sizeOf := by omega apply ih _ (by omega) ss ss₂ σ₁ σ' σ'' Hsz assumption assumption @@ -1707,7 +1707,7 @@ theorem HavocVarsDefined : theorem EvalCmdDefMonotone : isDefined σ v → - EvalCmd Boogie.Expression δ σ₀ σ c σ' → + EvalCmd Boogie.Expression δ σ c σ' → isDefined σ' v := by intros Hdef Heval cases Heval <;> try exact Hdef @@ -1717,7 +1717,7 @@ theorem EvalCmdDefMonotone : theorem EvalCmdTouch [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] : - EvalCmd P δ σ₀ σ c σ' → + EvalCmd P δ σ c σ' → TouchVars σ (HasVarsImp.touchedVars c) σ' := by intro Heval induction Heval <;> simp [HasVarsImp.touchedVars, Cmd.definedVars, Cmd.modifiedVars] @@ -1766,8 +1766,8 @@ theorem UpdateStatesTouchVars : UpdateStates σ vars modvals σ' → TouchVars apply Hup2 theorem EvalCmdRefinesContract : -EvalCmd Expression δ σ₀ σ c σ' → -EvalCommandContract π δ σ₀ σ (CmdExt.cmd c) σ' := by +EvalCmd Expression δ σ c σ' → +EvalCommandContract π δ σ (CmdExt.cmd c) σ' := by intros H; constructor; assumption theorem InvStoresUpdatedStateDisjRightMono : @@ -2017,19 +2017,19 @@ NOTE: variables (that is, lhs ++ modifies) -/ theorem EvalCallBodyRefinesContract : - ∀ {π δ σ₀ σ lhs n args σ' p}, + ∀ {π δ σ lhs n args σ' p}, π n = .some p → p.spec.modifies = Imperative.HasVarsTrans.modifiedVarsTrans π p.body → - EvalCommand π δ σ₀ σ (CmdExt.call lhs n args) σ' → - EvalCommandContract π δ σ₀ σ (CmdExt.call lhs n args) σ' := by - intros π δ σ₀ σ lhs n args σ' p pFound modValid H + EvalCommand π δ σ (CmdExt.call lhs n args) σ' → + EvalCommandContract π δ σ (CmdExt.call lhs n args) σ' := by + intros π δ σ lhs n args σ' p pFound modValid H cases H with | call_sem lkup Heval Hwfval Hwfvars Hwfb Hwf Hwf2 Hup Hhav Hpre Heval2 Hpost Hrd Hup2 => sorry theorem EvalCommandRefinesContract : -EvalCommand π δ σ₀ σ c σ' → -EvalCommandContract π δ σ₀ σ c σ' := by +EvalCommand π δ σ c σ' → +EvalCommandContract π δ σ c σ' := by intros H cases H with | cmd_sem H => exact EvalCommandContract.cmd_sem H @@ -2041,13 +2041,13 @@ EvalCommandContract π δ σ₀ σ c σ' := by /-- NOTE: should follow the same approach as `DetToNondetCorrect` to prove this mutually recursive theorem due to meta variable bug -/ -theorem EvalStmtsRefinesContract : - EvalStmts Expression Command (EvalCommand π) δ σ₀ σ ss σ' → - EvalStmts Expression Command (EvalCommandContract π) δ σ₀ σ ss σ' := by +theorem EvalBlockRefinesContract : + EvalBlock Expression Command (EvalCommand π) δ σ ss σ' → + EvalBlock Expression Command (EvalCommandContract π) δ σ ss σ' := by intros Heval cases ss case nil => - simp [EvalStmtsEmpty Heval] + simp [EvalBlockEmpty Heval] constructor case cons h t => cases Heval with @@ -2056,15 +2056,15 @@ theorem EvalStmtsRefinesContract : . sorry -- apply EvalStmtRefinesContract -- apply Heval - . apply EvalStmtsRefinesContract + . apply EvalBlockRefinesContract apply Hevals - termination_by (Stmts.sizeOf ss) + termination_by (Block.sizeOf ss) decreasing_by all_goals simp_all <;> omega theorem EvalStmtRefinesContract : - EvalStmt Expression Command (EvalCommand π) δ σ₀ σ s σ' → - EvalStmt Expression Command (EvalCommandContract π) δ σ₀ σ s σ' := by + EvalStmt Expression Command (EvalCommand π) δ σ s σ' → + EvalStmt Expression Command (EvalCommandContract π) δ σ s σ' := by intros H cases H with | cmd_sem Hdef Heval => @@ -2072,19 +2072,13 @@ theorem EvalStmtRefinesContract : exact EvalCommandRefinesContract Hdef | block_sem Heval => constructor - constructor - cases Heval - apply EvalStmtsRefinesContract <;> assumption + apply EvalBlockRefinesContract <;> assumption | ite_true_sem Hdef Hwf Heval => - cases Heval apply EvalStmt.ite_true_sem <;> try assumption - constructor - apply EvalStmtsRefinesContract <;> assumption + apply EvalBlockRefinesContract <;> assumption | ite_false_sem Hdef Hwf Heval => - cases Heval apply EvalStmt.ite_false_sem <;> try assumption - constructor - apply EvalStmtsRefinesContract <;> assumption + apply EvalBlockRefinesContract <;> assumption /-- Currently we cannot prove this theorem, since the WellFormedSemanticEval definition does not assert @@ -2095,21 +2089,17 @@ theorem EvalStmtRefinesContract : theorem EvalExpressionIsDefined : WellFormedBoogieEvalCong δ → WellFormedSemanticEvalVar δ → - (δ σ₀ σ e).isSome → + (δ σ e).isSome → isDefined σ (HasVarsPure.getVars e) := by intros Hwfc Hwfvr Hsome intros v Hin - simp [WellFormedBoogieEvalCong] at Hwfc simp [WellFormedSemanticEvalVar] at Hwfvr induction e generalizing v <;> simp [HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * - case fvar v' ty' => - specialize Hwfvr (Lambda.LExpr.fvar v' ty') v' σ₀ σ + case fvar m v' ty' => + specialize Hwfvr (Lambda.LExpr.fvar m v' ty') v' σ simp [HasFvar.getFvar] at Hwfvr simp_all - case mdata info e ih => - -- Need extra congruence properties -- if f(a) is defined, then a must be defined - sorry case abs => sorry case quant => sorry case app => sorry diff --git a/Strata/Languages/Boogie/StatementType.lean b/Strata/Languages/Boogie/StatementType.lean index 9b4723940..cf4f8a6a0 100644 --- a/Strata/Languages/Boogie/StatementType.lean +++ b/Strata/Languages/Boogie/StatementType.lean @@ -25,17 +25,20 @@ Type checker for Boogie commands. Note that this function needs the entire program to type-check `call` commands by looking up the corresponding procedure's information. -/ -def typeCheckCmd (C: LContext Visibility) (T : (TEnv Visibility)) (P : Program) (c : Command) : +def typeCheckCmd (C: LContext BoogieLParams) (Env : TEnv Visibility) (P : Program) (c : Command) : Except Format (Command × (TEnv Visibility)) := do match c with | .cmd c => - let (c, T) ← Imperative.Cmd.typeCheck C T c - .ok (.cmd c, T) - | .call lhs pname args md => + -- Any errors in `Imperative.Cmd.typeCheck` already include source + -- locations. + let (c, Env) ← Imperative.Cmd.typeCheck C Env c + .ok (.cmd c, Env) + | .call lhs pname args md => try + -- `try`: to augment any errors with source location info. match Program.Procedure.find? P pname with | none => .error f!"[{c}]: Procedure {pname} not found!" | some proc => - if lhs.any (fun l => (T.context.types.find? l).isNone) then + if lhs.any (fun (l: BoogieIdent) => (Env.context.types.find? l).isNone) then .error f!"[{c}]: All the return variables {lhs} must exist in the context!" else if lhs.length != proc.header.outputs.length then .error f!"[{c}]: Arity mismatch in this call's return values!\ @@ -46,88 +49,96 @@ def typeCheckCmd (C: LContext Visibility) (T : (TEnv Visibility)) (P : Program) else do -- Get the types of lhs variables and unify with the procedures' -- return types. - let lhsinsts ← Lambda.Identifier.instantiateAndSubsts lhs C T + let lhsinsts ← Lambda.Identifier.instantiateAndSubsts lhs C Env match lhsinsts with | none => .error f!"Implementation error. \ Types of {lhs} should have been known." - | some (lhs_tys, T) => - let _ ← T.freeVarChecks args - let (ret_sig, T) ← LMonoTySignature.instantiate C T proc.header.typeArgs proc.header.outputs - let ret_mtys := LMonoTys.subst T.stateSubstInfo.subst ret_sig.values + | some (lhs_tys, Env) => + let _ ← Env.freeVarChecks args + let (ret_sig, Env) ← LMonoTySignature.instantiate C Env proc.header.typeArgs proc.header.outputs + let ret_mtys := LMonoTys.subst Env.stateSubstInfo.subst ret_sig.values let ret_lhs_constraints := lhs_tys.zip ret_mtys -- Infer the types of the actuals and unify with the types of the -- procedure's formals. - let (argsa, T) ← Lambda.LExprT.fromLExprs C T args - let args_tys := argsa.map LExprT.toLMonoTy - let args' := argsa.map $ LExprT.toLExpr - let (inp_sig, T) ← LMonoTySignature.instantiate C T proc.header.typeArgs proc.header.inputs - let inp_mtys := LMonoTys.subst T.stateSubstInfo.subst inp_sig.values + let (argsa, Env) ← Lambda.LExpr.resolves C Env args + let args_tys := argsa.map LExpr.toLMonoTy + let args' := argsa.map $ LExpr.unresolved + let (inp_sig, Env) ← LMonoTySignature.instantiate C Env proc.header.typeArgs proc.header.inputs + let inp_mtys := LMonoTys.subst Env.stateSubstInfo.subst inp_sig.values let lhs_inp_constraints := (args_tys.zip inp_mtys) - let S ← Constraints.unify (lhs_inp_constraints ++ ret_lhs_constraints) T.stateSubstInfo - let T := T.updateSubst S + let S ← Constraints.unify (lhs_inp_constraints ++ ret_lhs_constraints) Env.stateSubstInfo |> .mapError format + let Env := Env.updateSubst S let s' := .call lhs pname args' md - .ok (s', T) + .ok (s', Env) + catch e => + -- Add source location to error messages. + .error f!"{@MetaData.formatFileRangeD Expression _ md false} {e}" -def typeCheckAux (C: LContext Visibility) (T : (TEnv Visibility)) (P : Program) (op : Option Procedure) (ss : List Statement) : - Except Format (List Statement × (TEnv Visibility)) := - go T ss [] +def typeCheckAux (C: LContext BoogieLParams) (Env : TEnv Visibility) (P : Program) (op : Option Procedure) (ss : List Statement) : + Except Format (List Statement × TEnv Visibility) := + go Env ss [] where - go (T : TEnv Visibility) (ss : List Statement) (acc : List Statement) : - Except Format (List Statement × (TEnv Visibility)) := + go (Env : TEnv Visibility) (ss : List Statement) (acc : List Statement) : + Except Format (List Statement × TEnv Visibility) := + let pfx := fun md => @MetaData.formatFileRangeD Expression _ md false + let errorWithSourceLoc := fun e md => if (pfx md).isEmpty then e else f!"{pfx md} {e}" match ss with - | [] => .ok (acc.reverse, T) + | [] => .ok (acc.reverse, Env) | s :: srest => do - let (s', T) ← + let (s', Env) ← match s with | .cmd cmd => do - let (c', T) ← typeCheckCmd C T P cmd - .ok (.cmd c', T) - - | .block label ⟨ bss ⟩ md => do - let T := T.pushEmptyContext - let (ss', T) ← go T bss [] - let s' := .block label ⟨ss'⟩ md - .ok (s', T.popContext) - - | .ite cond ⟨ tss ⟩ ⟨ ess ⟩ md => do - let _ ← T.freeVarCheck cond f!"[{s}]" - let (conda, T) ← LExprT.fromLExpr C T cond + let (c', Env) ← typeCheckCmd C Env P cmd + .ok (.cmd c', Env) + + | .block label bss md => do + let Env := Env.pushEmptyContext + let (ss', Env) ← go Env bss [] + let s' := .block label ss' md + .ok (s', Env.popContext) + + | .ite cond tss ess md => do try + let _ ← Env.freeVarCheck cond f!"[{s}]" + let (conda, Env) ← LExpr.resolve C Env cond let condty := conda.toLMonoTy match condty with | .tcons "bool" [] => - let (tb, T) ← go T [(.block "$$_then" ⟨ tss ⟩ #[])] [] - let (eb, T) ← go T [(.block "$$_else" ⟨ ess ⟩ #[])] [] - let s' := .ite conda.toLExpr ⟨tb⟩ ⟨eb⟩ md - .ok (s', T) + let (tb, Env) ← go Env [(.block "$$_then" tss #[])] [] + let (eb, Env) ← go Env [(.block "$$_else" ess #[])] [] + let s' := .ite conda.unresolved tb eb md + .ok (s', Env) | _ => .error f!"[{s}]: If's condition {cond} is not of type `bool`!" + catch e => + -- Add source location to error messages. + .error (errorWithSourceLoc e md) - | .loop guard measure invariant ⟨ bss ⟩ md => do - let _ ← T.freeVarCheck guard f!"[{s}]" - let (conda, T) ← LExprT.fromLExpr C T guard + | .loop guard measure invariant bss md => do try + let _ ← Env.freeVarCheck guard f!"[{s}]" + let (conda, Env) ← LExpr.resolve C Env guard let condty := conda.toLMonoTy - let (mt, T) ← match measure with + let (mt, Env) ← (match measure with | .some m => do - let _ ← T.freeVarCheck m f!"[{s}]" - let (ma, T) ← LExprT.fromLExpr C T m - .ok (some ma, T) - | _ => .ok (none, T) - let (it, T) ← match invariant with + let _ ← Env.freeVarCheck m f!"[{s}]" + let (ma, Env) ← LExpr.resolve C Env m + .ok (some ma, Env) + | _ => .ok (none, Env)) + let (it, Env) ← (match invariant with | .some i => do - let _ ← T.freeVarCheck i f!"[{s}]" - let (ia, T) ← LExprT.fromLExpr C T i - .ok (some ia, T) - | _ => .ok (none, T) - let mty := mt.map LExprT.toLMonoTy - let ity := it.map LExprT.toLMonoTy + let _ ← Env.freeVarCheck i f!"[{s}]" + let (ia, Env) ← LExpr.resolve C Env i + .ok (some ia, Env) + | _ => .ok (none, Env)) + let mty := mt.map LExpr.toLMonoTy + let ity := it.map LExpr.toLMonoTy match (condty, mty, ity) with | (.tcons "bool" [], none, none) | (.tcons "bool" [], some (.tcons "int" []), none) | (.tcons "bool" [], none, some (.tcons "bool" [])) | (.tcons "bool" [], some (.tcons "int" []), some (.tcons "bool" [])) => - let (tb, T) ← go T [(.block "$$_loop_body" ⟨ bss ⟩ #[])] [] - let s' := .loop conda.toLExpr (mt.map LExprT.toLExpr) (it.map LExprT.toLExpr) ⟨tb⟩ md - .ok (s', T) + let (tb, Env) ← go Env [(.block "$$_loop_body" bss #[])] [] + let s' := .loop conda.unresolved (mt.map LExpr.unresolved) (it.map LExpr.unresolved) tb md + .ok (s', Env) | _ => match condty with | .tcons "bool" [] => @@ -138,18 +149,24 @@ where | _ => .error f!"[{s}]: Loop's invariant {invariant} is not of type `bool`!" | _ => .error f!"[{s}]: Loop's measure {measure} is not of type `int`!" | _ => .error f!"[{s}]: Loop's guard {guard} is not of type `bool`!" + catch e => + -- Add source location to error messages. + .error (errorWithSourceLoc e md) - | .goto label _ => + | .goto label md => do try match op with | .some p => - if Stmts.hasLabelInside label p.body then - .ok (s, T) + if Block.hasLabelInside label p.body then + .ok (s, Env) else .error f!"Label {label} does not exist in the body of {p.header.name}" | .none => .error f!"{s} occurs outside a procedure." + catch e => + -- Add source location to error messages. + .error (errorWithSourceLoc e md) - go T srest (s' :: acc) - termination_by Stmts.sizeOf ss + go Env srest (s' :: acc) + termination_by Block.sizeOf ss decreasing_by all_goals simp_wf <;> omega @@ -182,12 +199,12 @@ Apply type substitution `S` to a statement. def Statement.subst (S : Subst) (s : Statement) : Statement := match s with | .cmd cmd => .cmd (Command.subst S cmd) - | .block label ⟨ bss ⟩ md => - .block label ⟨go S bss []⟩ md - | .ite cond ⟨ tss ⟩ ⟨ ess ⟩ md => - .ite (cond.applySubst S) ⟨go S tss []⟩ ⟨go S ess []⟩ md - | .loop guard m i ⟨ bss ⟩ md => - .loop (guard.applySubst S) (substOptionExpr S m) (substOptionExpr S i) ⟨go S bss []⟩ md + | .block label bss md => + .block label (go S bss []) md + | .ite cond tss ess md => + .ite (cond.applySubst S) (go S tss []) (go S ess []) md + | .loop guard m i bss md => + .loop (guard.applySubst S) (substOptionExpr S m) (substOptionExpr S i) (go S bss []) md | .goto _ _ => s where go S ss acc : List Statement := @@ -202,13 +219,13 @@ Note that this function needs the entire program to type-check statements to check whether `goto` targets exist (or .none for statements that don't occur inside a procedure). -/ -def typeCheck (C: Expression.TyContext) (T : Expression.TyEnv) (P : Program) (op : Option Procedure) (ss : List Statement) : +def typeCheck (C: Expression.TyContext) (Env : Expression.TyEnv) (P : Program) (op : Option Procedure) (ss : List Statement) : Except Format (List Statement × Expression.TyEnv) := do - let (ss', T) ← typeCheckAux C T P op ss - let context := TContext.subst T.context T.stateSubstInfo.subst - let T := T.updateContext context - let ss' := Statement.subst.go T.stateSubstInfo.subst ss' [] - .ok (ss', T) + let (ss', Env) ← typeCheckAux C Env P op ss + let context := TContext.subst Env.context Env.stateSubstInfo.subst + let Env := Env.updateContext context + let ss' := Statement.subst.go Env.stateSubstInfo.subst ss' [] + .ok (ss', Env) --------------------------------------------------------------------- end Statement diff --git a/Strata/Languages/Boogie/StatementWF.lean b/Strata/Languages/Boogie/StatementWF.lean index 549803d32..e9e90c5ef 100644 --- a/Strata/Languages/Boogie/StatementWF.lean +++ b/Strata/Languages/Boogie/StatementWF.lean @@ -30,6 +30,9 @@ theorem typeCheckCmdWF: Statement.typeCheckCmd C T p c = Except.ok v sorry sorry sorry + sorry + sorry + sorry theorem Statement.typeCheckAux_elim_acc: Statement.typeCheckAux.go C p proc T ss (acc1 ++ acc2) = Except.ok (pp, T') ↔ (List.IsPrefix acc2.reverse pp ∧ Statement.typeCheckAux.go C p proc T ss acc1 = Except.ok (pp.drop acc2.length, T')) @@ -108,7 +111,6 @@ theorem Statement.typeCheckAux_go_WF : | goto l => simp [Except.bind] at tcok split at tcok <;> try contradiction - split at tcok <;> try contradiction have tcok := Statement.typeCheckAux_elim_singleton tcok rw[List.append_cons]; apply ih tcok <;> try assumption diff --git a/Strata/Languages/Boogie/TypeDecl.lean b/Strata/Languages/Boogie/TypeDecl.lean index 2aa7b8c8c..6b1d075c4 100644 --- a/Strata/Languages/Boogie/TypeDecl.lean +++ b/Strata/Languages/Boogie/TypeDecl.lean @@ -86,6 +86,7 @@ def TypeSynonym.toRHSLTy (t : TypeSynonym) : LTy := inductive TypeDecl where | con : TypeConstructor → TypeDecl | syn : TypeSynonym → TypeDecl + | data : LDatatype Visibility → TypeDecl deriving Repr instance : ToFormat TypeDecl where @@ -93,10 +94,12 @@ instance : ToFormat TypeDecl where match d with | .con tc => f!"{tc}" | .syn ts => f!"{ts}" + | .data td => f!"{td}" def TypeDecl.name (d : TypeDecl) : Expression.Ident := match d with | .con tc => tc.name | .syn ts => ts.name + | .data td => td.name --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/Verifier.lean b/Strata/Languages/Boogie/Verifier.lean index b370cffd9..aeb9f88f7 100644 --- a/Strata/Languages/Boogie/Verifier.lean +++ b/Strata/Languages/Boogie/Verifier.lean @@ -8,6 +8,7 @@ import Strata.Languages.Boogie.DDMTransform.Translate import Strata.Languages.Boogie.Options import Strata.Languages.Boogie.CallGraph import Strata.Languages.Boogie.SMTEncoder +import Strata.DL.Imperative.MetaData import Strata.DL.Imperative.SMTUtils import Strata.DL.SMT.CexParser @@ -24,6 +25,7 @@ def encodeBoogie (ctx : Boogie.SMT.Context) (prelude : SolverM Unit) (ts : List Solver.setLogic "ALL" prelude let _ ← ctx.sorts.mapM (fun s => Solver.declareSort s.name s.arity) + ctx.emitDatatypes let (_ufs, estate) ← ctx.ufs.mapM (fun uf => encodeUF uf) |>.run EncoderState.init let (_ifs, estate) ← ctx.ifs.mapM (fun fn => encodeFunction fn.uf fn.body) |>.run estate let (_axms, estate) ← ctx.axms.mapM (fun ax => encodeTerm False ax) |>.run estate @@ -45,7 +47,7 @@ open Lambda Strata.SMT -- (TODO) Use DL.Imperative.SMTUtils. -abbrev CounterEx := Map (IdentT Visibility) String +abbrev CounterEx := Map (IdentT LMonoTy Visibility) String def CounterEx.format (cex : CounterEx) : Format := match cex with @@ -60,20 +62,22 @@ instance : ToFormat CounterEx where /-- Find the Id for the SMT encoding of `x`. -/ -def getSMTId (x : (IdentT Visibility)) (ctx : SMT.Context) (E : EncoderState) : Except Format String := do - match x with - | (var, none) => .error f!"Expected variable {var} to be annotated with a type!" - | (var, some ty) => do - let (ty', _) ← LMonoTy.toSMTType ty ctx - let key : Strata.SMT.UF := { id := var.name, args := [], out := ty' } - .ok (E.ufs[key]!) +def getSMTId (x : (IdentT LMonoTy Visibility)) (ctx : SMT.Context) (E : EncoderState) + : Except Format String := do + match x with + | (var, none) => .error f!"Expected variable {var} to be annotated with a type!" + | (var, some ty) => do + -- NOTE: OK to use Env.init here because ctx should already contain datatypes + let (ty', _) ← LMonoTy.toSMTType Env.init ty ctx + let key : Strata.SMT.UF := { id := var.name, args := [], out := ty' } + .ok (E.ufs[key]!) def getModel (m : String) : Except Format (List Strata.SMT.CExParser.KeyValue) := do let cex ← Strata.SMT.CExParser.parseCEx m return cex.pairs def processModel - (vars : List (IdentT Visibility)) (cexs : List Strata.SMT.CExParser.KeyValue) + (vars : List (IdentT LMonoTy Visibility)) (cexs : List Strata.SMT.CExParser.KeyValue) (ctx : SMT.Context) (E : EncoderState) : Except Format CounterEx := do match vars with @@ -97,16 +101,19 @@ inductive Result where | err (msg : String) deriving DecidableEq, Repr +def Result.formatWithVerbose (r : Result) (verbose : Bool) : Format := + match r with + | .sat cex => if verbose then f!"failed\nCEx: {cex}" else "failed" + | .unsat => f!"verified" + | .unknown => f!"unknown" + | .err msg => f!"err {msg}" + instance : ToFormat Result where - format r := match r with - | .sat cex => f!"failed\nCEx: {cex}" - | .unsat => f!"verified" - | .unknown => f!"unknown" - | .err msg => f!"err {msg}" + format r := r.formatWithVerbose true def VC_folder_name: String := "vcs" -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 @@ -114,30 +121,53 @@ def runSolver (solver : String) (args : Array String) : IO String := do -- 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 (vars : List (IdentT Visibility)) (ans : String) (ctx : SMT.Context) (E : EncoderState) : +def solverResult (vars : List (IdentT LMonoTy Visibility)) (output: IO.Process.Output) + (ctx : SMT.Context) (E : EncoderState) : Except Format Result := do - let pos := (ans.find (fun c => c == '\n')).byteIdx - let verdict := (ans.take pos).trim - let rest := ans.drop pos + let stdout := output.stdout + let pos := (stdout.find (fun c => c == '\n')).byteIdx + let verdict := (stdout.take pos).trim + let rest := stdout.drop pos match verdict with | "sat" => let rawModel ← getModel rest - let model ← processModel vars rawModel ctx E - .ok (.sat model) + -- We suppress any counterexample processing errors. + -- Likely, these would be because of the suboptimal implementation + -- of the counterexample parser, which shouldn't hold back useful + -- feedback (i.e., problem was `sat`) from the user. + match (processModel vars rawModel ctx E) with + | .ok model => .ok (.sat model) + | .error _model_err => (.ok (.sat [])) | "unsat" => .ok .unsat | "unknown" => .ok .unknown - | other => .error other + | _ => .error (stdout ++ output.stderr) + +open Imperative + +def formatPositionMetaData [BEq P.Ident] [ToFormat P.Expr] (md : MetaData P): Option Format := do + let fileRangeElem ← md.findElem MetaData.fileRange + match fileRangeElem.value with + | .fileRange m => + let baseName := match m.file with + | .file path => (path.splitToList (· == '/')).getLast! + return f!"{baseName}({m.start.line}, {m.start.column})" + | _ => none structure VCResult where obligation : Imperative.ProofObligation Expression result : Result := .unknown estate : EncoderState := EncoderState.init + verbose : Bool := true + +def VCResult.formatWithVerbose (r : VCResult) (verbose : Bool) : Format := + f!"Obligation: {r.obligation.label}\n\ + Result: {r.result.formatWithVerbose verbose}" instance : ToFormat VCResult where format r := f!"Obligation: {r.obligation.label}\n\ - Result: {r.result}" + Result: {r.result.formatWithVerbose r.verbose}" -- EState : {repr r.estate.terms} abbrev VCResults := Array VCResult @@ -172,13 +202,13 @@ def getSolverFlags (options : Options) (solver : String) : Array String := let setTimeout := match solver with | "cvc5" => #[s!"--tlimit={options.solverTimeout*1000}"] - | "z3" => #[s!"-t:{options.solverTimeout*1000}"] + | "z3" => #[s!"-T:{options.solverTimeout}"] | _ => #[] produceModels ++ setTimeout def dischargeObligation (options : Options) - (vars : List (IdentT Visibility)) (smtsolver filename : String) + (vars : List (IdentT LMonoTy Visibility)) (smtsolver filename : String) (terms : List Term) (ctx : SMT.Context) : IO (Except Format (Result × EncoderState)) := do if !(← System.FilePath.isDir VC_folder_name) then @@ -191,8 +221,8 @@ def dischargeObligation let _ ← solver.checkSat ids -- Will return unknown for Solver.fileWriter if options.verbose then IO.println s!"Wrote problem to {filename}." let flags := getSolverFlags options smtsolver - let solver_out ← runSolver smtsolver (#[filename] ++ flags) - match solverResult vars solver_out ctx estate with + let output ← runSolver smtsolver (#[filename] ++ flags) + match solverResult vars output ctx estate with | .error e => return .error e | .ok result => return .ok (result, estate) @@ -210,7 +240,7 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option -- We don't need the SMT solver if PE (partial evaluation) is enough to -- reduce the consequent to true. if obligation.obligation.isTrue then - results := results.push { obligation, result := .unsat } + results := results.push { obligation, result := .unsat, verbose := options.verbose } continue -- If PE determines that the consequent is false and the path conditions -- are empty, then we can immediate report a verification failure. Note @@ -222,7 +252,7 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option dbg_trace f!"\n\nObligation {obligation.label}: failed!\ \n\nResult obtained during partial evaluation.\ {if options.verbose then prog else ""}" - results := results.push { obligation, result := .sat .empty } + results := results.push { obligation, result := .sat .empty, verbose := options.verbose } if options.stopOnFirstError then break let obligation := if options.removeIrrelevantAxioms then @@ -232,6 +262,7 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option let cg := Program.toFunctionCG p let fns := obligation.obligation.getOps.map BoogieIdent.toPretty let relevant_fns := (fns ++ (CallGraph.getAllCalleesClosure cg fns)).dedup + let irrelevant_axs := Program.getIrrelevantAxioms p relevant_fns let new_assumptions := Imperative.PathConditions.removeByNames obligation.assumptions irrelevant_axs { obligation with assumptions := new_assumptions } @@ -245,7 +276,7 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option {err}\n\n\ Evaluated program: {p}\n\n" let _ ← dbg_trace msg - results := results.push { obligation, result := .err msg } + results := results.push { obligation, result := .err msg, verbose := options.verbose } if options.stopOnFirstError then break | .ok (terms, ctx) => -- let ufids := (ctx.ufs.map (fun f => f.id)) @@ -267,15 +298,15 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option terms ctx) match ans with | .ok (result, estate) => - results := results.push { obligation, result, estate } + results := results.push { obligation, result, estate, verbose := options.verbose } if result ≠ .unsat then let prog := f!"\n\nEvaluated program:\n{p}" dbg_trace f!"\n\nObligation {obligation.label}: could not be proved!\ - \n\nResult: {result}\ + \n\nResult: {result.formatWithVerbose options.verbose}\ {if options.verbose then prog else ""}" if options.stopOnFirstError then break | .error e => - results := results.push { obligation, result := .err (toString e) } + results := results.push { obligation, result := .err (toString e), verbose := options.verbose } let prog := f!"\n\nEvaluated program:\n{p}" dbg_trace f!"\n\nObligation {obligation.label}: solver error!\ \n\nError: {e}\ @@ -283,10 +314,13 @@ def verifySingleEnv (smtsolver : String) (pE : Program × Env) (options : Option if options.stopOnFirstError then break return results -def verify (smtsolver : String) (program : Program) (options : Options := Options.default) : EIO Format VCResults := do - match Boogie.typeCheckAndPartialEval options program with +def verify (smtsolver : String) (program : Program) + (options : Options := Options.default) + (moreFns : @Lambda.Factory BoogieLParams := Lambda.Factory.default) : + EIO Format VCResults := do + match Boogie.typeCheckAndPartialEval options program moreFns with | .error err => - .error f!"[Strata.Boogie] Type checking error: {format err}" + .error f!"[Strata.Boogie] Type checking error.\n{format err}" | .ok pEs => let VCss ← if options.checkOnly then pure [] @@ -300,28 +334,66 @@ end Boogie namespace Strata -def Boogie.getProgram (p : Strata.Program) : Boogie.Program × Array String := - TransM.run (translateProgram p) +open Lean.Parser -def typeCheck (env : Program) (options : Options := Options.default) : +def typeCheck (ictx : InputContext) (env : Program) (options : Options := Options.default) + (moreFns : @Lambda.Factory Boogie.BoogieLParams := Lambda.Factory.default) : Except Std.Format Boogie.Program := do - let (program, errors) := Boogie.getProgram env + let (program, errors) := TransM.run ictx (translateProgram env) if errors.isEmpty then -- dbg_trace f!"AST: {program}" - Boogie.typeCheck options program + Boogie.typeCheck options program moreFns else .error s!"DDM Transform Error: {repr errors}" -def verify (smtsolver : String) (env : Program) - (options : Options := Options.default) : IO Boogie.VCResults := do - let (program, errors) := Boogie.getProgram env +def Boogie.getProgram + (p : Strata.Program) + (ictx : InputContext := Inhabited.default) : Boogie.Program × Array String := + TransM.run ictx (translateProgram p) + +def verify + (smtsolver : String) (env : Program) + (ictx : InputContext := Inhabited.default) + (options : Options := Options.default) + (moreFns : @Lambda.Factory Boogie.BoogieLParams := Lambda.Factory.default) + : IO Boogie.VCResults := do + let (program, errors) := Boogie.getProgram env ictx if errors.isEmpty then -- dbg_trace f!"AST: {program}" EIO.toIO (fun f => IO.Error.userError (toString f)) - (Boogie.verify smtsolver program options) + (Boogie.verify smtsolver program options moreFns) else panic! s!"DDM Transform Error: {repr errors}" +/-- A diagnostic produced by analyzing a file -/ +structure Diagnostic where + start : Lean.Position + ending : Lean.Position + message : String + deriving Repr, BEq + +def toDiagnostic (vcr : Boogie.VCResult) : Option Diagnostic := do + -- Only create a diagnostic if the result is not .unsat (i.e., verification failed) + match vcr.result with + | .unsat => none -- Verification succeeded, no diagnostic + | result => + -- Extract file range from metadata + let fileRangeElem ← vcr.obligation.metadata.findElem Imperative.MetaData.fileRange + match fileRangeElem.value with + | .fileRange range => + let message := match result with + | .sat _ => "assertion does not hold" + | .unknown => "assertion could not be proved" + | .err msg => s!"verification error: {msg}" + | _ => "verification failed" + some { + -- Subtract headerOffset to account for program header we added + start := { line := range.start.line, column := range.start.column } + ending := { line := range.ending.line, column := range.ending.column } + message := message + } + | _ => none + end Strata --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/WF.lean b/Strata/Languages/Boogie/WF.lean index caf2008b2..e79c6f445 100644 --- a/Strata/Languages/Boogie/WF.lean +++ b/Strata/Languages/Boogie/WF.lean @@ -62,21 +62,21 @@ def WFCmdExtProp (p : Program) (c : CmdExt Expression) : Prop := match c with | .cmd c => WFcmdProp p c | .call (lhs : List Expression.Ident) (procName : String) (args : List Expression.Expr) _ => WFcallProp p lhs procName args -structure WFblockProp (Cmd : Type) (p : Program) (label : String) (b : Block Expression Cmd) : Prop where +structure WFblockProp (Cmd : Type) (p : Program) (label : String) (b : Block) : Prop where -structure WFifProp (Cmd : Type) (p : Program) (cond : Expression.Expr) (thenb : Block Expression Cmd) (elseb : Block Expression Cmd) : Prop where +structure WFifProp (Cmd : Type) (p : Program) (cond : Expression.Expr) (thenb : Block) (elseb : Block) : Prop where -structure WFloopProp (Cmd : Type) (p : Program) (guard : Expression.Expr) (measure : Option Expression.Expr) (invariant : Option Expression.Expr) (b : Block Expression Cmd) : Prop where +structure WFloopProp (Cmd : Type) (p : Program) (guard : Expression.Expr) (measure : Option Expression.Expr) (invariant : Option Expression.Expr) (b : Block) : Prop where structure WFgotoProp (p : Program) (label : String) : Prop where @[simp] def WFStatementProp (p : Program) (stmt : Statement) : Prop := match stmt with | .cmd cmd => WFCmdExtProp p cmd - | .block (label : String) (b : Block Expression (CmdExt Expression)) _ => WFblockProp (CmdExt Expression) p label b - | .ite (cond : Expression.Expr) (thenb : Block Expression (CmdExt Expression)) (elseb : Block Expression (CmdExt Expression)) _ => + | .block (label : String) (b : Block) _ => WFblockProp (CmdExt Expression) p label b + | .ite (cond : Expression.Expr) (thenb : Block) (elseb : Block) _ => WFifProp (CmdExt Expression) p cond thenb elseb - | .loop (guard : Expression.Expr) (measure : Option Expression.Expr) (invariant : Option Expression.Expr) (body : Block Expression (CmdExt Expression)) _ => + | .loop (guard : Expression.Expr) (measure : Option Expression.Expr) (invariant : Option Expression.Expr) (body : Block) _ => WFloopProp (CmdExt Expression) p guard measure invariant body | .goto (label : String) _ => WFgotoProp p label diff --git a/Strata/Languages/C_Simp/C_Simp.lean b/Strata/Languages/C_Simp/C_Simp.lean index 84b4cd44a..e292aa697 100644 --- a/Strata/Languages/C_Simp/C_Simp.lean +++ b/Strata/Languages/C_Simp/C_Simp.lean @@ -21,14 +21,16 @@ import Strata.DL.Lambda.Identifiers namespace Strata namespace C_Simp +abbrev CSimpLParams: Lambda.LExprParams := {Metadata := Unit, IDMeta := Unit} + -- Our expression language is `DL/Lambda` abbrev Expression : Imperative.PureExpr := { Ident := Lambda.Identifier Unit, - Expr := Lambda.LExpr Lambda.LMonoTy Unit, + Expr := Lambda.LExpr CSimpLParams.mono, Ty := Lambda.LTy, - TyEnv := Lambda.TEnv String, - TyContext := Lambda.LContext String, - EvalEnv := Lambda.LState String, + TyEnv := Lambda.TEnv Unit, + TyContext := Lambda.LContext ⟨Unit, Unit⟩, + EvalEnv := Lambda.LState ⟨Unit, String⟩, EqIdent := Lambda.instDecidableEqIdentifier } @@ -65,7 +67,7 @@ instance [ToFormat Expression.Ident] [ToFormat Expression.Expr] [ToFormat Expres instance [ToFormat Expression.Ident] [ToFormat Expression.Expr] [ToFormat Expression.Ty] [ToFormat Command]: ToFormat (List Statement) where - format ss := Imperative.formatStmts Expression ss + format ss := Imperative.formatBlock Expression ss instance : Std.ToFormat Function where format f := diff --git a/Strata/Languages/C_Simp/DDMTransform/Translate.lean b/Strata/Languages/C_Simp/DDMTransform/Translate.lean index 3caf0e490..8cae3842f 100644 --- a/Strata/Languages/C_Simp/DDMTransform/Translate.lean +++ b/Strata/Languages/C_Simp/DDMTransform/Translate.lean @@ -22,8 +22,8 @@ open Std (ToFormat Format format) --------------------------------------------------------------------- -def initVarValue (id : String) : LExpr LMonoTy Unit := - .fvar ("init_" ++ id) none +def initVarValue (id : String) : LExpr CSimpLParams.mono := + .fvar () ("init_" ++ id) none --------------------------------------------------------------------- @@ -103,7 +103,7 @@ def translateNat (arg : Arg) : TransM Nat := do structure TransBindings where boundTypeVars : Array String := #[] - boundVars : Array (LExpr LMonoTy Unit) := #[] + boundVars : Array (LExpr CSimpLParams.mono) := #[] freeVars : Array String := #["return"] -- There's a global variable "return" for return values instance : ToFormat TransBindings where @@ -117,7 +117,7 @@ instance : Inhabited (List Statement × TransBindings) where default := ([], {}) instance : Inhabited (C_Simp.Function × TransBindings) where - default := ({name := "badfun", pre := (.const "true" none), post := (.const "true" none), body := [], ret_ty := (.tcons "bad" []), inputs := {} }, {}) + default := ({name := "badfun", pre := .true (), post := .true (), body := [], ret_ty := (.tcons "bad" []), inputs := {} }, {}) instance : Inhabited (List C_Simp.Function × TransBindings) where default := ([], {}) @@ -160,78 +160,78 @@ end --------------------------------------------------------------------- -def translateFn (q : QualifiedIdent) : TransM (LExpr LMonoTy Unit) := +def translateFn (q : QualifiedIdent) : TransM (LExpr CSimpLParams.mono) := match q with - | q`C_Simp.and => return (.op "Bool.And" none) - | q`C_Simp.or => return (.op "Bool.Or" none) - | q`C_Simp.not => return (.op "Bool.Not" none) - | q`C_Simp.le => return (.op "Int.Le" none) - | q`C_Simp.lt => return (.op "Int.Lt" none) - | q`C_Simp.ge => return (.op "Int.Ge" none) - | q`C_Simp.gt => return (.op "Int.Gt" none) - | q`C_Simp.add => return (.op "Int.Add" none) - | q`C_Simp.sub => return (.op "Int.Sub" none) - | q`C_Simp.mul => return (.op "Int.Mul" none) - | q`C_Simp.div => return (.op "Int.Div" none) - | q`C_Simp.mod => return (.op "Int.Mod" none) - | q`C_Simp.len => return (.op "Array.Len" none) - | q`C_Simp.get => return (.op "Array.Get" none) + | q`C_Simp.and => return (.op () "Bool.And" none) + | q`C_Simp.or => return (.op () "Bool.Or" none) + | q`C_Simp.not => return (.op () "Bool.Not" none) + | q`C_Simp.le => return (.op () "Int.Le" none) + | q`C_Simp.lt => return (.op () "Int.Lt" none) + | q`C_Simp.ge => return (.op () "Int.Ge" none) + | q`C_Simp.gt => return (.op () "Int.Gt" none) + | q`C_Simp.add => return (.op () "Int.Add" none) + | q`C_Simp.sub => return (.op () "Int.Sub" none) + | q`C_Simp.mul => return (.op () "Int.Mul" none) + | q`C_Simp.div => return (.op () "Int.Div" none) + | q`C_Simp.mod => return (.op () "Int.Mod" none) + | q`C_Simp.len => return (.op () "Array.Len" none) + | q`C_Simp.get => return (.op () "Array.Get" none) | _ => TransM.error s!"translateFn: Unknown/unimplemented function {repr q}" mutual partial def translateExpr (bindings : TransBindings) (arg : Arg) : - TransM (LExpr LMonoTy Unit) := do + TransM (LExpr CSimpLParams.mono) := do let .expr expr := arg | TransM.error s!"translateExpr expected expr {repr arg}" let (op, args) := expr.flatten match op, args with -- Constants/Literals | .fn _ q`C_Simp.btrue, [] => - return .const "true" none + return .true () | .fn _ q`C_Simp.bfalse, [] => - return .const "false" none + return .false () | .fn _ q`C_Simp.to_int, [xa] => let n ← translateNat xa - return .const (toString n) none + return .intConst () n -- Equality | .fn _ q`C_Simp.eq, [_tpa, xa, ya] => let x ← translateExpr bindings xa let y ← translateExpr bindings ya - return .eq x y + return .eq () x y -- Unary function applications | .fn _ q`C_Simp.not, [xa] => - let fn := (LExpr.op "Bool.Not" none) + let fn := LExpr.op () ⟨"Bool.Not", ()⟩ none let x ← translateExpr bindings xa - return .mkApp fn [x] + return .mkApp () fn [x] -- Unary array operations | .fn _ q`C_Simp.len, [xa] => let fn ← translateFn q`C_Simp.len let x ← translateExpr bindings xa - return .mkApp fn [x] + return .mkApp () fn [x] -- Binary function applications | .fn _ fni, [xa, ya] => let fn ← translateFn fni let x ← translateExpr bindings xa let y ← translateExpr bindings ya - return .mkApp fn [x, y] + return .mkApp () fn [x, y] -- NOTE: Bound and free variables are numbered differently. Bound variables -- ascending order (so closer to deBrujin levels). | .bvar _ i, [] => assert! i < bindings.boundVars.size let expr := bindings.boundVars[bindings.boundVars.size - (i+1)]! match expr with - | .bvar _ => return .bvar i + | .bvar _ _ => return .bvar () i | _ => return expr | .fvar _ i, [] => assert! i < bindings.freeVars.size let name := bindings.freeVars[i]! - return (.fvar name none) + return (.fvar () name none) | .fvar _ i, argsa => -- Call of a function declared/defined in C_Simp. assert! i < bindings.freeVars.size let name := bindings.freeVars[i]! let args ← translateExprs bindings argsa.toArray - return .mkApp (.op name none) args.toList + return .mkApp () (.op () name none) args.toList | op, args => TransM.error s!"translateExpr unimplemented op:\n\ Op: {repr op}\n\ @@ -239,11 +239,11 @@ partial def translateExpr (bindings : TransBindings) (arg : Arg) : Bindings: {format bindings}}" partial def translateExprs (bindings : TransBindings) (args : Array Arg) : - TransM (Array (LExpr LMonoTy Unit)) := + TransM (Array (LExpr CSimpLParams.mono)) := args.mapM (fun a => translateExpr bindings a) end -def translateMeasure (bindings : TransBindings) (arg : Arg) : TransM (Option (LExpr LMonoTy Unit)) := do +def translateMeasure (bindings : TransBindings) (arg : Arg) : TransM (Option (LExpr CSimpLParams.mono)) := do translateOption (fun maybe_arg => do match maybe_arg with | none => return none @@ -253,7 +253,7 @@ def translateMeasure (bindings : TransBindings) (arg : Arg) : TransM (Option (LE return some (← translateExpr bindings e[0]!)) arg -def translateInvariant (bindings : TransBindings) (arg : Arg) : TransM (Option (LExpr LMonoTy Unit)) := do +def translateInvariant (bindings : TransBindings) (arg : Arg) : TransM (Option (LExpr CSimpLParams.mono)) := do translateOption (fun maybe_arg => do match maybe_arg with | none => return none @@ -361,7 +361,7 @@ partial def translateStmt (bindings : TransBindings) (arg : Arg) : let id ← translateIdent ida let tp ← translateLMonoTy bindings tpa let ty := (.forAll [] tp) - let newFVar: LExpr LMonoTy Unit := LExpr.fvar id none + let newFVar: LExpr CSimpLParams.mono := LExpr.fvar () id none let bbindings := bindings.boundVars ++ [newFVar] let newBindings := { bindings with boundVars := bbindings, @@ -372,7 +372,7 @@ partial def translateStmt (bindings : TransBindings) (arg : Arg) : let tp ← translateLMonoTy bindings tpa let val ← translateExpr bindings ea let ty := (.forAll [] tp) - let newFVar: LExpr LMonoTy Unit := LExpr.fvar id none + let newFVar: LExpr CSimpLParams.mono := LExpr.fvar () id none let bbindings := bindings.boundVars ++ [newFVar] let newBindings := { bindings with boundVars := bbindings, @@ -384,12 +384,10 @@ partial def translateStmt (bindings : TransBindings) (arg : Arg) : return ([(.cmd (.set id val))], bindings) | q`C_Simp.if_command, #[ca, ta, fa] => let c ← translateExpr bindings ca - let t := { ss := ← translateBlock bindings ta } - let f := { ss := ← translateElse bindings fa } - return ([(.ite c t f)], bindings) + return ([(.ite c (← translateBlock bindings ta) (← translateElse bindings fa))], bindings) | q`C_Simp.while_command, #[ga, measurea, invarianta, ba] => -- TODO: Handle measure and invariant - return ([.loop (← translateExpr bindings ga) (← translateMeasure bindings measurea) (← translateInvariant bindings invarianta) { ss := ← translateBlock bindings ba }], bindings) + return ([.loop (← translateExpr bindings ga) (← translateMeasure bindings measurea) (← translateInvariant bindings invarianta) (← translateBlock bindings ba)], bindings) | q`C_Simp.return, #[_tpa, ea] => -- Return statements are assignments to the global `return` variable -- TODO: I don't think this works if we have functions with different return types @@ -446,7 +444,7 @@ def translateProcedure (bindings : TransBindings) (op : Operation) : let pname ← translateIdent op.args[3]! -- Add parameters to bindings for pre/post/body translation - let paramBindings := (sig.keys.map (fun v => (LExpr.fvar v none))).toArray + let paramBindings := (sig.keys.map (fun v => (LExpr.fvar () v none))).toArray let extendedBindings := { bindings with boundVars := bindings.boundVars ++ paramBindings, freeVars := bindings.freeVars ++ sig.keys.toArray.map Identifier.name } diff --git a/Strata/Languages/C_Simp/Examples/Examples.lean b/Strata/Languages/C_Simp/Examples/Examples.lean deleted file mode 100644 index 681c49f3c..000000000 --- a/Strata/Languages/C_Simp/Examples/Examples.lean +++ /dev/null @@ -1,13 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Strata.Languages.C_Simp.Examples.Coprime -import Strata.Languages.C_Simp.Examples.LinearSearch -import Strata.Languages.C_Simp.Examples.LoopSimple -import Strata.Languages.C_Simp.Examples.LoopTrivial -import Strata.Languages.C_Simp.Examples.Min -import Strata.Languages.C_Simp.Examples.SimpleTest -import Strata.Languages.C_Simp.Examples.Trivial diff --git a/Strata/Languages/C_Simp/Verify.lean b/Strata/Languages/C_Simp/Verify.lean index 7a164360f..40e73afd2 100644 --- a/Strata/Languages/C_Simp/Verify.lean +++ b/Strata/Languages/C_Simp/Verify.lean @@ -16,20 +16,19 @@ namespace Strata -- 2. Running SymExec of Lambda and Imp -def translate_expr (e : C_Simp.Expression.Expr) : Lambda.LExpr Lambda.LMonoTy Boogie.Visibility := +def translate_expr (e : C_Simp.Expression.Expr) : Lambda.LExpr Boogie.BoogieLParams.mono := match e with - | .const c ty => .const c ty - | .op o ty => .op ⟨o.name, .unres⟩ ty - | .bvar n => .bvar n - | .fvar n ty => .fvar ⟨n.name, .unres⟩ ty - | .mdata i e => .mdata i (translate_expr e) - | .abs ty e => .abs ty (translate_expr e) - | .quant k ty tr e => .quant k ty (translate_expr tr) (translate_expr e) - | .app fn e => .app (translate_expr fn) (translate_expr e) - | .ite c t e => .ite (translate_expr c) (translate_expr t) (translate_expr e) - | .eq e1 e2 => .eq (translate_expr e1) (translate_expr e2) - -def translate_opt_expr (e : Option C_Simp.Expression.Expr) : Option (Lambda.LExpr Lambda.LMonoTy Boogie.Visibility) := + | .const m c => .const m c + | .op m o ty => .op m ⟨o.name, .unres⟩ ty + | .bvar m n => .bvar m n + | .fvar m n ty => .fvar m ⟨n.name, .unres⟩ ty + | .abs m ty e => .abs m ty (translate_expr e) + | .quant m k ty tr e => .quant m k ty (translate_expr tr) (translate_expr e) + | .app m fn e => .app m (translate_expr fn) (translate_expr e) + | .ite m c t e => .ite m (translate_expr c) (translate_expr t) (translate_expr e) + | .eq m e1 e2 => .eq m (translate_expr e1) (translate_expr e2) + +def translate_opt_expr (e : Option C_Simp.Expression.Expr) : Option (Lambda.LExpr Boogie.BoogieLParams.mono) := match e with | some e => translate_expr e | none => none @@ -45,9 +44,9 @@ def translate_cmd (c: C_Simp.Command) : Boogie.Command := partial def translate_stmt (s: Imperative.Stmt C_Simp.Expression C_Simp.Command) : Boogie.Statement := match s with | .cmd c => .cmd (translate_cmd c) - | .block l b _md => .block l {ss := b.ss.map translate_stmt} {} - | .ite cond thenb elseb _md => .ite (translate_expr cond) {ss := thenb.ss.map translate_stmt} {ss := elseb.ss.map translate_stmt} {} - | .loop guard measure invariant body _md => .loop (translate_expr guard) (translate_opt_expr measure) (translate_opt_expr invariant) {ss := body.ss.map translate_stmt} {} + | .block l b _md => .block l (b.map translate_stmt) {} + | .ite cond thenb elseb _md => .ite (translate_expr cond) (thenb.map translate_stmt) (elseb.map translate_stmt) {} + | .loop guard measure invariant body _md => .loop (translate_expr guard) (translate_opt_expr measure) (translate_opt_expr invariant) (body.map translate_stmt) {} | .goto label _md => .goto label {} @@ -76,34 +75,34 @@ def loop_elimination_statement(s : C_Simp.Statement) : Boogie.Statement := match measure, invariant with | .some measure, some invariant => -- let bodyss : := body.ss - let assigned_vars := (Imperative.Stmts.modifiedVars body.ss).map (λ s => ⟨s.name, .unres⟩) - let havocd : Boogie.Statement := .block "loop havoc" {ss:= assigned_vars.map (λ n => Boogie.Statement.havoc n {})} {} + let assigned_vars := (Imperative.Block.modifiedVars body).map (λ s => ⟨s.name, .unres⟩) + let havocd : Boogie.Statement := .block "loop havoc" (assigned_vars.map (λ n => Boogie.Statement.havoc n {})) {} - let measure_pos := (.app (.app (.op "Int.Ge" none) (translate_expr measure)) (.const "0" none)) + let measure_pos := (.app () (.app () (.op () "Int.Ge" none) (translate_expr measure)) (.intConst () 0)) let entry_invariant : Boogie.Statement := .assert "entry_invariant" (translate_expr invariant) {} let assert_measure_positive : Boogie.Statement := .assert "assert_measure_pos" measure_pos {} - let first_iter_facts : Boogie.Statement := .block "first_iter_asserts" {ss := [entry_invariant, assert_measure_positive]} {} + let first_iter_facts : Boogie.Statement := .block "first_iter_asserts" [entry_invariant, assert_measure_positive] {} - let arbitrary_iter_assumes := .block "arbitrary_iter_assumes" {ss := [(Boogie.Statement.assume "assume_guard" (translate_expr guard) {}), (Boogie.Statement.assume "assume_invariant" (translate_expr invariant) {}), (Boogie.Statement.assume "assume_measure_pos" measure_pos {})]} {} + let arbitrary_iter_assumes := .block "arbitrary_iter_assumes" [(Boogie.Statement.assume "assume_guard" (translate_expr guard) {}), (Boogie.Statement.assume "assume_invariant" (translate_expr invariant) {}), (Boogie.Statement.assume "assume_measure_pos" measure_pos {})] {} let measure_old_value_assign : Boogie.Statement := .init "special-name-for-old-measure-value" (.forAll [] (.tcons "int" [])) (translate_expr measure) {} - let measure_decreases : Boogie.Statement := .assert "measure_decreases" (.app (.app (.op "Int.Lt" none) (translate_expr measure)) (.fvar "special-name-for-old-measure-value" none)) {} - let measure_imp_not_guard : Boogie.Statement := .assert "measure_imp_not_guard" (.ite (.app (.app (.op "Int.Le" none) (translate_expr measure)) (.const "0" none)) (.app (.op "Bool.Not" none) (translate_expr guard)) (.const "true" none)) {} + let measure_decreases : Boogie.Statement := .assert "measure_decreases" (.app () (.app () (.op () "Int.Lt" none) (translate_expr measure)) (.fvar () "special-name-for-old-measure-value" none)) {} + let measure_imp_not_guard : Boogie.Statement := .assert "measure_imp_not_guard" (.ite () (.app () (.app () (.op () "Int.Le" none) (translate_expr measure)) (.intConst () 0)) (.app () (.op () "Bool.Not" none) (translate_expr guard)) (.true ())) {} let maintain_invariant : Boogie.Statement := .assert "arbitrary_iter_maintain_invariant" (translate_expr invariant) {} - let body_statements : List Boogie.Statement := body.ss.map translate_stmt - let arbitrary_iter_facts : Boogie.Statement := .block "arbitrary iter facts" {ss := [havocd, arbitrary_iter_assumes, measure_old_value_assign] ++ body_statements ++ [measure_decreases, measure_imp_not_guard, maintain_invariant]} {} + let body_statements : List Boogie.Statement := body.map translate_stmt + let arbitrary_iter_facts : Boogie.Statement := .block "arbitrary iter facts" ([havocd, arbitrary_iter_assumes, measure_old_value_assign] ++ body_statements ++ [measure_decreases, measure_imp_not_guard, maintain_invariant]) {} - let not_guard : Boogie.Statement := .assume "not_guard" (.app (.op "Bool.Not" none) (translate_expr guard)) {} + let not_guard : Boogie.Statement := .assume "not_guard" (.app () (.op () "Bool.Not" none) (translate_expr guard)) {} let invariant : Boogie.Statement := .assume "invariant" (translate_expr invariant) {} - .ite (translate_expr guard) {ss := [first_iter_facts, arbitrary_iter_facts, havocd, not_guard, invariant]} {ss := []} {} + .ite (translate_expr guard) [first_iter_facts, arbitrary_iter_facts, havocd, not_guard, invariant] [] {} | _, _ => panic! "Loop elimination require measure and invariant" | _ => translate_stmt s -- C_Simp functions are Boogie procedures def loop_elimination_function(f : C_Simp.Function) : Boogie.Procedure := - let boogie_preconditions := [("pre", {expr := translate_expr f.pre})] - let boogie_postconditions := [("post", {expr := translate_expr f.post})] + let boogie_preconditions := [("pre", {expr := translate_expr f.pre })] + let boogie_postconditions := [("post", {expr := translate_expr f.post })] {header := {name := f.name.name, typeArgs := [], inputs := f.inputs.map (λ p => (p.fst.name, p.snd)), outputs := [("return", f.ret_ty)]}, diff --git a/Strata/Languages/Dyn/DDMTransform/Parse.lean b/Strata/Languages/Dyn/DDMTransform/Parse.lean index b1c9acfc9..c8b4c71db 100644 --- a/Strata/Languages/Dyn/DDMTransform/Parse.lean +++ b/Strata/Languages/Dyn/DDMTransform/Parse.lean @@ -36,10 +36,10 @@ fn strLit (s : Str) : string => s; // Typecasts // Casts to Any -fn str_to_Any (s: string) : Any => "str_to_Any("s")"; -fn int_to_Any (i: int) : Any => "int_to_Any("i")"; -fn bool_to_Any (b: bool) : Any => "bool_to_Any("b")"; -fn fun_to_Any (f: Any -> Any -> Any) : Any => "fun_to_Any("f")"; +fn str_to_Any (s: string) : Any => "str_to_Any(" s ")"; +fn int_to_Any (i: int) : Any => "int_to_Any(" i ")"; +fn bool_to_Any (b: bool) : Any => "bool_to_Any(" b ")"; +fn fun_to_Any (f: Any -> Any -> Any) : Any => "fun_to_Any(" f ")"; // Casts from Any fn Any_to_string (a: Any) : string => "Any_to_str("a")"; diff --git a/Strata/Languages/Dyn/Examples/Examples.lean b/Strata/Languages/Dyn/Examples/Examples.lean deleted file mode 100644 index 03a72efb9..000000000 --- a/Strata/Languages/Dyn/Examples/Examples.lean +++ /dev/null @@ -1,15 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import Strata.Languages.Dyn.Examples.Trivial -import Strata.Languages.Dyn.Examples.BasicTypes -import Strata.Languages.Dyn.Examples.ListOperations -import Strata.Languages.Dyn.Examples.ControlFlow -import Strata.Languages.Dyn.Examples.Arithmetic -import Strata.Languages.Dyn.Examples.StringOps -import Strata.Languages.Dyn.Examples.TypeIntrospection -import Strata.Languages.Dyn.Examples.HeapOps -import Strata.Languages.Dyn.Examples.FunctionCalls diff --git a/Strata/Languages/Laurel/Examples/ConstrainedTypes.lr.st b/Strata/Languages/Laurel/Examples/ConstrainedTypes.lr.st deleted file mode 100644 index 278ed6ba0..000000000 --- a/Strata/Languages/Laurel/Examples/ConstrainedTypes.lr.st +++ /dev/null @@ -1,15 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ --- Constrained primitive type -constrained nat = x: int where x >= 0 - --- Something analogous to an algebriac datatype -composite OptionBase {} -composite Some extends OptionBase { - value: int -} -composite None extends OptionBase -constrained Option = x: OptionBase where x is Some || x is None \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/Jumps.lr.st b/Strata/Languages/Laurel/Examples/Jumps.lr.st deleted file mode 100644 index 4182afd60..000000000 --- a/Strata/Languages/Laurel/Examples/Jumps.lr.st +++ /dev/null @@ -1,26 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -val forLoopLikeWithBreakAndContinue = procedure(steps: int, continueSteps: int, exitSteps: int): int { - var counter = 0 - breakLabel { - while(steps > 0) - invariant counter >= 0 - { - continueLabel { - if (steps == exitSteps) { - counter = -10; - exit breakLabel; - } - if (steps == continueSteps) { - exit continueLabel; - } - counter = counter + 1 - } - steps = steps - 1; - } - } - counter; -} \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/PureAllocation.lr.st b/Strata/Languages/Laurel/Examples/PureAllocation.lr.st deleted file mode 100644 index 9d493312c..000000000 --- a/Strata/Languages/Laurel/Examples/PureAllocation.lr.st +++ /dev/null @@ -1,26 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ --- WIP. needs further design -composite Immutable { - val x: int - - val construct = function(): pure Immutable - constructor - requires constructing = {this} - ensures constructing == {} - { - this.x = 3; - construct this; - this - } -} - -val pureCompositeAllocator = function(): boolean { - var i: pure Empty = Immutable.construct(); -- can be called in a pure construct, because it is a function - var j: pure Empty = Immutable.construct(); - i =&= j --- ^^^ reference equality operator '=&=' can not be used on pure types -} \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/ReadsAndModifiesClauses.lr.st b/Strata/Languages/Laurel/Examples/ReadsAndModifiesClauses.lr.st deleted file mode 100644 index 338153d62..000000000 --- a/Strata/Languages/Laurel/Examples/ReadsAndModifiesClauses.lr.st +++ /dev/null @@ -1,59 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -composite Container { - var value: int -} - -val permissionLessReader = function(c: Container): int - { c.value } --- ^^^^^^^ error: enclosing function 'permissionLessReader' does not have permission to read 'c.value' - -val varReader = function(c: Container): int - reads c - { c.value } - -composite ImmutableContainer { - val value: int -} - -val valReader = function(c: ImmutableContainer): int - { c.value } -- no reads clause needed because value is immutable - -val opaqueFunction = function(c: Container): int - reads c - ensures true - { 3 } - -val foo = procedure(c: Container, d: Container) -{ - var x = opaqueFunction(c); - modifyContainer(d); - var y = opaqueFunction(c); - assert x == y; -- functions return the same result when the arguments and read objects are the same - modifyContainer(c); - c.value = c.value + 1; - var z = opaqueFunction(c); - assert x == z; --- ^^ error: could not prove assert -} - -val modifyContainer(c: Container) - modifies c -{ - c.value = c.value + 1; -} - -val modifyContainerWithoutPermission(c: Container) -{ - c.value = c.value + 1; --- ^ error: enclosing function 'modifyContainerWithoutPermission' does not have permission to modify 'c.value' -} - --- Pure types -val impureTypeUser = function(i: pure Container, j: pure Container): boolean { - i =&= j --- ^^^ reference equality operator '=&=' can not be used on pure types -} \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/StmtExpr.lr.st b/Strata/Languages/Laurel/Examples/StmtExpr.lr.st deleted file mode 100644 index d34dd24aa..000000000 --- a/Strata/Languages/Laurel/Examples/StmtExpr.lr.st +++ /dev/null @@ -1,37 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -function nesting(a: int): int - requires a > 0 && a < 100 - returns -{ - var b = a + 2; - if (b > 2) { - var c = b + 3; - if (c > 3) { - return c + 4; - } - var d = c + 5; - return d + 6; - } - var e = b + 1; - e -} - -composite Counter { - var value: int -} - -int nestedImpureCalls(counter: Counter) { - if (add(counter, 1) == 1) { - var x = add(counter, add(counter, 2)); - return x; - } - return add(counter, 3); -} - -method add(counter: Counter, amount: int): int { - counter.value = counter.value + amount -} \ No newline at end of file diff --git a/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean new file mode 100644 index 000000000..dddb18df2 --- /dev/null +++ b/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean @@ -0,0 +1,288 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.AST +import Strata.Languages.Laurel.Grammar.LaurelGrammar +import Strata.Languages.Laurel.Laurel +import Strata.DL.Imperative.MetaData +import Strata.Languages.Boogie.Expressions + +namespace Laurel + +open Std (ToFormat Format format) +open Strata (QualifiedIdent Arg SourceRange) +open Lean.Parser (InputContext) +open Imperative (MetaData Uri FileRange) + +structure TransState where + inputCtx : InputContext + +abbrev TransM := StateT TransState (Except String) + +def TransM.run (ictx : InputContext) (m : TransM α) : Except String α := + match StateT.run m { inputCtx := ictx } with + | .ok (v, _) => .ok v + | .error e => .error e + +def TransM.error (msg : String) : TransM α := + throw msg + +def SourceRange.toMetaData (ictx : InputContext) (sr : SourceRange) : Imperative.MetaData Boogie.Expression := + let file := ictx.fileName + let startPos := ictx.fileMap.toPosition sr.start + let endPos := ictx.fileMap.toPosition sr.stop + let uri : Uri := .file file + let fileRangeElt := ⟨ Imperative.MetaDataElem.Field.label "fileRange", .fileRange ⟨ uri, startPos, endPos ⟩ ⟩ + #[fileRangeElt] + +def getArgMetaData (arg : Arg) : TransM (Imperative.MetaData Boogie.Expression) := + return SourceRange.toMetaData (← get).inputCtx arg.ann + +def checkOp (op : Strata.Operation) (name : QualifiedIdent) (argc : Nat) : + TransM Unit := do + if op.name != name then + TransM.error s!"Op name mismatch! \n\ + Name: {repr name}\n\ + Op: {repr op}" + if op.args.size != argc then + TransM.error s!"Op arg count mismatch! \n\ + Expected: {argc}\n\ + Got: {op.args.size}\n\ + Op: {repr op}" + return () + +def translateIdent (arg : Arg) : TransM Identifier := do + let .ident _ id := arg + | TransM.error s!"translateIdent expects ident" + return id + +def translateBool (arg : Arg) : TransM Bool := do + match arg with + | .expr (.fn _ name) => + match name with + | q`Laurel.boolTrue => return true + | q`Laurel.boolFalse => return false + | _ => TransM.error s!"translateBool expects boolTrue or boolFalse, got {repr name}" + | .op op => + match op.name with + | q`Laurel.boolTrue => return true + | q`Laurel.boolFalse => return false + | _ => TransM.error s!"translateBool expects boolTrue or boolFalse, got {repr op.name}" + | x => TransM.error s!"translateBool expects expression or operation, got {repr x}" + +instance : Inhabited HighType where + default := .TVoid + +instance : Inhabited Parameter where + default := { name := "", type := .TVoid } + +def translateHighType (arg : Arg) : TransM HighType := do + match arg with + | .op op => + match op.name with + | q`Laurel.intType => return .TInt + | q`Laurel.boolType => return .TBool + | _ => TransM.error s!"translateHighType expects intType or boolType, got {repr op.name}" + | _ => TransM.error s!"translateHighType expects operation" + +def translateNat (arg : Arg) : TransM Nat := do + let .num _ n := arg + | TransM.error s!"translateNat expects num literal" + return n + +def translateParameter (arg : Arg) : TransM Parameter := do + let .op op := arg + | TransM.error s!"translateParameter expects operation" + match op.name, op.args with + | q`Laurel.parameter, #[arg0, arg1] => + let name ← translateIdent arg0 + let paramType ← translateHighType arg1 + return { name := name, type := paramType } + | q`Laurel.parameter, args => + TransM.error s!"parameter needs two arguments, not {args.size}" + | _, _ => + TransM.error s!"translateParameter expects parameter operation, got {repr op.name}" + +def translateParameters (arg : Arg) : TransM (List Parameter) := do + match arg with + | .commaSepList _ args => + args.toList.mapM translateParameter + | _ => pure [] + +instance : Inhabited Procedure where + default := { + name := "" + inputs := [] + outputs := [] + precondition := .LiteralBool true + decreases := none + determinism := Determinism.deterministic none + modifies := none + body := .Transparent (.LiteralBool true) + } + +def getBinaryOp? (name : QualifiedIdent) : Option Operation := + match name with + | q`Laurel.add => some Operation.Add + | q`Laurel.eq => some Operation.Eq + | q`Laurel.neq => some Operation.Neq + | q`Laurel.gt => some Operation.Gt + | q`Laurel.lt => some Operation.Lt + | q`Laurel.le => some Operation.Leq + | q`Laurel.ge => some Operation.Geq + | _ => none + +mutual + +partial def translateStmtExpr (arg : Arg) : TransM StmtExpr := do + match arg with + | .op op => match op.name, op.args with + | q`Laurel.assert, #[arg0] => + let cond ← translateStmtExpr arg0 + let md ← getArgMetaData (.op op) + return .Assert cond md + | q`Laurel.assume, #[arg0] => + let cond ← translateStmtExpr arg0 + let md ← getArgMetaData (.op op) + return .Assume cond md + | q`Laurel.block, #[arg0] => + let stmts ← translateSeqCommand arg0 + return .Block stmts none + | q`Laurel.boolTrue, _ => return .LiteralBool true + | q`Laurel.boolFalse, _ => return .LiteralBool false + | q`Laurel.int, #[arg0] => + let n ← translateNat arg0 + return .LiteralInt n + | q`Laurel.varDecl, #[arg0, typeArg, assignArg] => + let name ← translateIdent arg0 + let varType ← match typeArg with + | .option _ (some (.op typeOp)) => match typeOp.name, typeOp.args with + | q`Laurel.optionalType, #[typeArg0] => translateHighType typeArg0 + | _, _ => pure .TInt + | _ => pure .TInt + let value ← match assignArg with + | .option _ (some (.op assignOp)) => match assignOp.args with + | #[assignArg0] => translateStmtExpr assignArg0 >>= (pure ∘ some) + | _ => TransM.error s!"assignArg {repr assignArg} didn't match expected pattern for variable {name}" + | .option _ none => pure none + | _ => TransM.error s!"assignArg {repr assignArg} didn't match expected pattern for variable {name}" + return .LocalVariable name varType value + | q`Laurel.identifier, #[arg0] => + let name ← translateIdent arg0 + return .Identifier name + | q`Laurel.parenthesis, #[arg0] => translateStmtExpr arg0 + | q`Laurel.assign, #[arg0, arg1] => + let target ← translateStmtExpr arg0 + let value ← translateStmtExpr arg1 + return .Assign target value + | q`Laurel.call, #[arg0, argsSeq] => + let callee ← translateStmtExpr arg0 + let calleeName := match callee with + | .Identifier name => name + | _ => "" + let argsList ← match argsSeq with + | .commaSepList _ args => args.toList.mapM translateStmtExpr + | _ => pure [] + return .StaticCall calleeName argsList + | q`Laurel.return, #[arg0] => + let value ← translateStmtExpr arg0 + return .Return (some value) + | q`Laurel.ifThenElse, #[arg0, arg1, elseArg] => + let cond ← translateStmtExpr arg0 + let thenBranch ← translateStmtExpr arg1 + let elseBranch ← match elseArg with + | .option _ (some (.op elseOp)) => match elseOp.name, elseOp.args with + | q`Laurel.optionalElse, #[elseArg0] => translateStmtExpr elseArg0 >>= (pure ∘ some) + | _, _ => pure none + | _ => pure none + return .IfThenElse cond thenBranch elseBranch + | _, #[arg0, arg1] => match getBinaryOp? op.name with + | some primOp => + let lhs ← translateStmtExpr arg0 + let rhs ← translateStmtExpr arg1 + return .PrimitiveOp primOp [lhs, rhs] + | none => TransM.error s!"Unknown operation: {op.name}" + | _, _ => TransM.error s!"Unknown operation: {op.name}" + | _ => TransM.error s!"translateStmtExpr expects operation" + +partial def translateSeqCommand (arg : Arg) : TransM (List StmtExpr) := do + let .seq _ args := arg + | TransM.error s!"translateSeqCommand expects seq" + let mut stmts : List StmtExpr := [] + for arg in args do + let stmt ← translateStmtExpr arg + stmts := stmts ++ [stmt] + return stmts + +partial def translateCommand (arg : Arg) : TransM StmtExpr := do + translateStmtExpr arg + +end + +def parseProcedure (arg : Arg) : TransM Procedure := do + let .op op := arg + | TransM.error s!"parseProcedure expects operation" + + match op.name, op.args with + | q`Laurel.procedure, #[arg0, arg1, returnParamsArg, arg3] => + let name ← translateIdent arg0 + let parameters ← translateParameters arg1 + -- returnParamsArg is ReturnParameters category, need to unwrap returnParameters operation + let returnParameters ← match returnParamsArg with + | .option _ (some (.op returnOp)) => match returnOp.name, returnOp.args with + | q`Laurel.returnParameters, #[returnArg0] => translateParameters returnArg0 + | _, _ => TransM.error s!"Expected returnParameters operation, got {repr returnOp.name}" + | .option _ none => pure [] + | _ => TransM.error s!"Expected returnParameters operation, got {repr returnParamsArg}" + let body ← translateCommand arg3 + return { + name := name + inputs := parameters + outputs := returnParameters + precondition := .LiteralBool true + decreases := none + determinism := Determinism.deterministic none + modifies := none + body := .Transparent body + } + | q`Laurel.procedure, args => + TransM.error s!"parseProcedure expects 4 arguments, got {args.size}" + | _, _ => + TransM.error s!"parseProcedure expects procedure, got {repr op.name}" + +/-- +Translate concrete Laurel syntax into abstract Laurel syntax +-/ +def parseProgram (prog : Strata.Program) : TransM Laurel.Program := do + -- Unwrap the program operation if present + -- The parsed program may have a single `program` operation wrapping the procedures + let commands : Array Strata.Operation := + -- support the program optionally being wrapped in a top level command + if prog.commands.size == 1 && prog.commands[0]!.name == q`Laurel.program then + -- Extract procedures from the program operation's first argument (Seq Procedure) + match prog.commands[0]!.args[0]! with + | .seq _ procs => procs.filterMap fun arg => + match arg with + | .op op => some op + | _ => none + | _ => prog.commands + else + prog.commands + + let mut procedures : List Procedure := [] + for op in commands do + if op.name == q`Laurel.procedure then + let proc ← parseProcedure (.op op) + procedures := procedures ++ [proc] + else + TransM.error s!"Unknown top-level declaration: {op.name}" + return { + staticProcedures := procedures + staticFields := [] + types := [] + } + +end Laurel diff --git a/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean new file mode 100644 index 000000000..54e60016b --- /dev/null +++ b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean @@ -0,0 +1,78 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +-- Minimal Laurel dialect for AssertFalse example +import Strata + +#dialect +dialect Laurel; + +// Types +category LaurelType; +op intType : LaurelType => "int"; +op boolType : LaurelType => "bool"; + +category StmtExpr; + +op boolTrue() : StmtExpr => "true"; +op boolFalse() : StmtExpr => "false"; +op int(n : Num) : StmtExpr => n; + +// Variable declarations +category OptionalType; +op optionalType(varType: LaurelType): OptionalType => ":" varType; + +category OptionalAssignment; +op optionalAssignment(value: StmtExpr): OptionalAssignment => ":=" value:0; + +op varDecl (name: Ident, varType: Option OptionalType, assignment: Option OptionalAssignment): StmtExpr + => @[prec(0)] "var " name varType assignment ";"; + +// Identifiers/Variables +op identifier (name: Ident): StmtExpr => name; +op parenthesis (inner: StmtExpr): StmtExpr => "(" inner ")"; + +// Assignment +op assign (target: StmtExpr, value: StmtExpr): StmtExpr => target ":=" value ";"; + +// Binary operators +op add (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(60)] lhs "+" rhs; +op eq (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs "==" rhs; +op neq (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs "!=" rhs; +op gt (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs ">" rhs; +op lt (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs "<" rhs; +op le (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs "<=" rhs; +op ge (lhs: StmtExpr, rhs: StmtExpr): StmtExpr => @[prec(40)] lhs ">=" rhs; + +op call(callee: StmtExpr, args: CommaSepBy StmtExpr): StmtExpr => callee "(" args ")"; + +// If-else +category OptionalElse; +op optionalElse(stmts : StmtExpr) : OptionalElse => "else" stmts; + +op ifThenElse (cond: StmtExpr, thenBranch: StmtExpr, elseBranch: Option OptionalElse): StmtExpr => + @[prec(20)] "if (" cond ") " thenBranch:0 elseBranch:0; + +op assert (cond : StmtExpr) : StmtExpr => "assert " cond ";"; +op assume (cond : StmtExpr) : StmtExpr => "assume " cond ";"; +op return (value : StmtExpr) : StmtExpr => "return " value ";"; +op block (stmts : Seq StmtExpr) : StmtExpr => @[prec(1000)] "{" stmts "}"; + +category Parameter; +op parameter (name: Ident, paramType: LaurelType): Parameter => name ":" paramType; + +category ReturnParameters; +op returnParameters(parameters: CommaSepBy Parameter): ReturnParameters => "returns" "(" parameters ")"; + +category Procedure; +op procedure (name : Ident, parameters: CommaSepBy Parameter, + returnParameters: Option ReturnParameters, + body : StmtExpr) : Procedure => + "procedure " name "(" parameters ")" returnParameters body:0; + +op program (staticProcedures: Seq Procedure): Command => staticProcedures; + +#end diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index 8aaefe9ca..b113a13ba 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -4,6 +4,10 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +import Strata.DL.Imperative.MetaData +import Strata.Languages.Boogie.Expressions +import Strata.Languages.Boogie.Procedure + /- The Laurel language is supposed to serve as an intermediate verification language for at least Java, Python, JavaScript. @@ -19,17 +23,16 @@ Features currently not present: Design choices: - Pure contracts: contracts may only contain pure code. Pure code does not modify the heap, neither by modifying existing objects are creating new ones. -- Callables: instead of functions and methods we have a single more general concept called a 'callable'. -- Purity: Callables can be marked as pure or impure. Pure callables have a reads clause while impure ones have a modifies clause. - A reads clause is currently not useful for impure callables, since reads clauses are used to determine when the output changes, but impure callables can be non-determinismic so the output can always change. -- Opacity: callables can have a body that's transparant or opaque. Only an opaque body may declare a postcondition. A transparant callable must be pure. +- Procedures: instead of functions and methods we have a single more general concept called a 'procedure'. +- Determinism: procedures can be marked as deterministic or not. For deterministic procedures with a non-empty reads clause, we can assume the result is unchanged if the read references are the same. +- Opacity: procedures can have a body that's transparant or opaque. Only an opaque body may declare a postcondition. - StmtExpr: Statements and expressions are part of the same type. This reduces duplication since the same concepts are needed in both, such as conditions and variable declarations. - Loops: The only loop is a while, but this can be used to compile do-while and for loops to as well. - Jumps: Instead of break and continue statements, there is a labelled block that can be exited from using an exit statement inside of it. This can be used to model break statements and continue statements for both while and for loops. - User defined types consist of two categories: composite types and constrained types. -- Composite types have fields and callables, and may extend other composite types. +- Composite types have fields and procedures, and may extend other composite types. - Fields state whether they are mutable, which impacts what permissions are needed to access them - Fields state their type, which is needed to know the resulting type when reading a field. - Constrained types are defined by a base type and a constraint over that type. @@ -40,19 +43,38 @@ Design choices: - Construction of composite types is WIP. It needs a design first. -/ +namespace Laurel abbrev Identifier := String /- Potentially this could be an Int to save resources. -/ +inductive Operation: Type where + /- Works on Bool -/ + /- Equality on composite types uses reference equality for impure types, and structural equality for pure ones -/ + | Eq | Neq + | And | Or | Not + /- Works on Int/Float64 -/ + | Neg | Add | Sub | Mul | Div | Mod + | Lt | Leq | Gt | Geq + deriving Repr + +-- Explicit instance needed for deriving Repr in the mutual block +instance : Repr (Imperative.MetaData Boogie.Expression) := inferInstance + mutual -structure Callable: Type where +structure Procedure: Type where name : Identifier inputs : List Parameter - output : HighType + outputs : List Parameter precondition : StmtExpr - decreases : StmtExpr - purity : Purity + decreases : Option StmtExpr -- optionally prove termination + determinism: Determinism + modifies : Option StmtExpr body : Body +inductive Determinism where + | deterministic (reads: Option StmtExpr) + | nondeterministic + structure Parameter where name : Identifier type : HighType @@ -71,14 +93,6 @@ inductive HighType : Type where | Intersection (types : List HighType) deriving Repr -inductive Purity: Type where -/- -Since a reads clause is used to determine when the result of a call changes, -a reads clause is only useful for deterministic callables. --/ - | Pure (reads : StmtExpr) - | Impure (modifies : StmtExpr) - /- No support for something like function-by-method yet -/ inductive Body where | Transparent (body : StmtExpr) @@ -88,17 +102,6 @@ inductive Body where A type containing any members with abstract bodies can not be instantiated. -/ | Abstract (postcondition : StmtExpr) -/- We will support these operations for dynamic types as well -/ -/- The 'truthy' concept from JavaScript should be implemented using a library function -/ -inductive Operation: Type where - /- Works on Bool -/ - /- Equality on composite types uses reference equality for impure types, and structural equality for pure ones -/ - | Eq | Neq - | And | Or | Not - /- Works on Int/Float64 -/ - | Neg | Add | Sub | Mul | Div | Mod - | Lt | Leq | Gt | Geq - /- A StmtExpr contains both constructs that we typically find in statements and those in expressions. By using a single datatype we prevent duplication of constructs that can be used in both contexts, @@ -150,8 +153,8 @@ inductive StmtExpr : Type where | Fresh(value : StmtExpr) /- Related to proofs -/ - | Assert (condition: StmtExpr) - | Assume (condition: StmtExpr) + | Assert (condition: StmtExpr) (md : Imperative.MetaData Boogie.Expression) + | Assume (condition: StmtExpr) (md : Imperative.MetaData Boogie.Expression) /- ProveBy allows writing proof trees. Its semantics are the same as that of the given `value`, but the `proof` is used to help prove any assertions in `value`. @@ -170,8 +173,8 @@ ProveBy( | ContractOf (type: ContractType) (function: StmtExpr) /- Abstract can be used as the root expr in a contract for reads/modifies/precondition/postcondition. For example: `reads(abstract)` -It can only be used for instance callables and it makes the containing type abstract, meaning it can not be instantiated. -An extending type can become concrete by redefining any callables that had abstracts contracts and providing non-abstract contracts. +It can only be used for instance procedures and it makes the containing type abstract, meaning it can not be instantiated. +An extending type can become concrete by redefining all procedures that had abstract contracts and providing non-abstract contracts. -/ | Abstract | All -- All refers to all objects in the heap. Can be used in a reads or modifies clause @@ -182,6 +185,9 @@ inductive ContractType where | Reads | Modifies | Precondition | PostCondition end +instance : Inhabited StmtExpr where + default := .Hole + partial def highEq (a: HighType) (b: HighType) : Bool := match a, b with | HighType.TVoid, HighType.TVoid => true | HighType.TBool, HighType.TBool => true @@ -210,11 +216,11 @@ structure CompositeType where name : Identifier /- The type hierarchy affects the results of IsType and AsType, - and can add checks to the postcondition of callables that extend another one + and can add checks to the postcondition of procedures that extend another one -/ extending : List Identifier fields : List Field - instanceCallables : List Callable + instanceProcedures : List Procedure structure ConstrainedType where name : Identifier @@ -237,9 +243,9 @@ Example 2: -/ inductive TypeDefinition where | Composite (ty : CompositeType) - | Constrainted {ConstrainedType} (ty : ConstrainedType) + | Constrained (ty : ConstrainedType) structure Program where - staticCallables : List Callable + staticProcedures : List Procedure staticFields : List Field types : List TypeDefinition diff --git a/Strata/Languages/Laurel/LaurelFormat.lean b/Strata/Languages/Laurel/LaurelFormat.lean new file mode 100644 index 000000000..1c34062a3 --- /dev/null +++ b/Strata/Languages/Laurel/LaurelFormat.lean @@ -0,0 +1,194 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Laurel.Laurel + +namespace Laurel + +open Std (Format) + +mutual +def formatOperation : Operation → Format + | .Eq => "==" + | .Neq => "!=" + | .And => "&&" + | .Or => "||" + | .Not => "!" + | .Neg => "-" + | .Add => "+" + | .Sub => "-" + | .Mul => "*" + | .Div => "/" + | .Mod => "%" + | .Lt => "<" + | .Leq => "<=" + | .Gt => ">" + | .Geq => ">=" + +def formatHighType : HighType → Format + | .TVoid => "void" + | .TBool => "bool" + | .TInt => "int" + | .TFloat64 => "float64" + | .UserDefined name => Format.text name + | .Applied base args => + Format.text "(" ++ formatHighType base ++ " " ++ + Format.joinSep (args.map formatHighType) " " ++ ")" + | .Pure base => "pure(" ++ formatHighType base ++ ")" + | .Intersection types => + Format.joinSep (types.map formatHighType) " & " + +def formatStmtExpr (s:StmtExpr) : Format := + match h: s with + | .IfThenElse cond thenBr elseBr => + "if " ++ formatStmtExpr cond ++ " then " ++ formatStmtExpr thenBr ++ + match elseBr with + | none => "" + | some e => " else " ++ formatStmtExpr e + | .Block stmts _ => + "{ " ++ Format.joinSep (stmts.map formatStmtExpr) "; " ++ " }" + | .LocalVariable name ty init => + "var " ++ Format.text name ++ ": " ++ formatHighType ty ++ + match init with + | none => "" + | some e => " := " ++ formatStmtExpr e + | .While cond _ _ body => + "while " ++ formatStmtExpr cond ++ " " ++ formatStmtExpr body + | .Exit target => "exit " ++ Format.text target + | .Return value => + "return" ++ + match value with + | none => "" + | some v => " " ++ formatStmtExpr v + | .LiteralInt n => Format.text (toString n) + | .LiteralBool b => if b then "true" else "false" + | .Identifier name => Format.text name + | .Assign target value => + formatStmtExpr target ++ " := " ++ formatStmtExpr value + | .FieldSelect target field => + formatStmtExpr target ++ "." ++ Format.text field + | .PureFieldUpdate target field value => + formatStmtExpr target ++ " with { " ++ Format.text field ++ " := " ++ formatStmtExpr value ++ " }" + | .StaticCall name args => + Format.text name ++ "(" ++ Format.joinSep (args.map formatStmtExpr) ", " ++ ")" + | .PrimitiveOp op args => + match args with + | [a] => formatOperation op ++ formatStmtExpr a + | [a, b] => formatStmtExpr a ++ " " ++ formatOperation op ++ " " ++ formatStmtExpr b + | _ => formatOperation op ++ "(" ++ Format.joinSep (args.map formatStmtExpr) ", " ++ ")" + | .This => "this" + | .ReferenceEquals lhs rhs => + formatStmtExpr lhs ++ " === " ++ formatStmtExpr rhs + | .AsType target ty => + formatStmtExpr target ++ " as " ++ formatHighType ty + | .IsType target ty => + formatStmtExpr target ++ " is " ++ formatHighType ty + | .InstanceCall target name args => + formatStmtExpr target ++ "." ++ Format.text name ++ "(" ++ + Format.joinSep (args.map formatStmtExpr) ", " ++ ")" + | .Forall name ty body => + "forall " ++ Format.text name ++ ": " ++ formatHighType ty ++ " => " ++ formatStmtExpr body + | .Exists name ty body => + "exists " ++ Format.text name ++ ": " ++ formatHighType ty ++ " => " ++ formatStmtExpr body + | .Assigned name => "assigned(" ++ formatStmtExpr name ++ ")" + | .Old value => "old(" ++ formatStmtExpr value ++ ")" + | .Fresh value => "fresh(" ++ formatStmtExpr value ++ ")" + | .Assert cond _ => "assert " ++ formatStmtExpr cond + | .Assume cond _ => "assume " ++ formatStmtExpr cond + | .ProveBy value proof => + "proveBy(" ++ formatStmtExpr value ++ ", " ++ formatStmtExpr proof ++ ")" + | .ContractOf _ fn => "contractOf(" ++ formatStmtExpr fn ++ ")" + | .Abstract => "abstract" + | .All => "all" + | .Hole => "" + decreasing_by + all_goals (simp_wf; try omega) + any_goals (rename_i x_in; have := List.sizeOf_lt_of_mem x_in; omega) + subst_vars; cases h; rename_i x_in; have := List.sizeOf_lt_of_mem x_in; omega + +def formatParameter (p : Parameter) : Format := + Format.text p.name ++ ": " ++ formatHighType p.type + +def formatDeterminism : Determinism → Format + | .deterministic none => "deterministic" + | .deterministic (some reads) => "deterministic reads " ++ formatStmtExpr reads + | .nondeterministic => "nondeterministic" + +def formatBody : Body → Format + | .Transparent body => formatStmtExpr body + | .Opaque post impl => + "opaque ensures " ++ formatStmtExpr post ++ + match impl with + | none => "" + | some e => " := " ++ formatStmtExpr e + | .Abstract post => "abstract ensures " ++ formatStmtExpr post + +def formatProcedure (proc : Procedure) : Format := + "procedure " ++ Format.text proc.name ++ + "(" ++ Format.joinSep (proc.inputs.map formatParameter) ", " ++ ") returns " ++ Format.line ++ + "(" ++ Format.joinSep (proc.outputs.map formatParameter) ", " ++ ")" ++ Format.line ++ formatBody proc.body + +def formatField (f : Field) : Format := + (if f.isMutable then "var " else "val ") ++ + Format.text f.name ++ ": " ++ formatHighType f.type + +def formatCompositeType (ct : CompositeType) : Format := + "composite " ++ Format.text ct.name ++ + (if ct.extending.isEmpty then Format.nil else " extends " ++ + Format.joinSep (ct.extending.map Format.text) ", ") ++ + " { " ++ Format.joinSep (ct.fields.map formatField) "; " ++ " }" + +def formatConstrainedType (ct : ConstrainedType) : Format := + "constrained " ++ Format.text ct.name ++ + " = " ++ Format.text ct.valueName ++ ": " ++ formatHighType ct.base ++ + " | " ++ formatStmtExpr ct.constraint + +def formatTypeDefinition : TypeDefinition → Format + | .Composite ty => formatCompositeType ty + | .Constrained ty => formatConstrainedType ty + +def formatProgram (prog : Program) : Format := + Format.joinSep (prog.staticProcedures.map formatProcedure) "\n\n" + +end + +instance : Std.ToFormat Operation where + format := formatOperation + +instance : Std.ToFormat HighType where + format := formatHighType + +instance : Std.ToFormat StmtExpr where + format := formatStmtExpr + +instance : Std.ToFormat Parameter where + format := formatParameter + +instance : Std.ToFormat Determinism where + format := formatDeterminism + +instance : Std.ToFormat Body where + format := formatBody + +instance : Std.ToFormat Procedure where + format := formatProcedure + +instance : Std.ToFormat Field where + format := formatField + +instance : Std.ToFormat CompositeType where + format := formatCompositeType + +instance : Std.ToFormat ConstrainedType where + format := formatConstrainedType + +instance : Std.ToFormat TypeDefinition where + format := formatTypeDefinition + +instance : Std.ToFormat Program where + format := formatProgram + +end Laurel diff --git a/Strata/Languages/Laurel/LaurelToBoogieTranslator.lean b/Strata/Languages/Laurel/LaurelToBoogieTranslator.lean new file mode 100644 index 000000000..445806ffa --- /dev/null +++ b/Strata/Languages/Laurel/LaurelToBoogieTranslator.lean @@ -0,0 +1,223 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Program +import Strata.Languages.Boogie.Verifier +import Strata.Languages.Boogie.Statement +import Strata.Languages.Boogie.Procedure +import Strata.Languages.Boogie.Options +import Strata.Languages.Laurel.Laurel +import Strata.Languages.Laurel.LiftExpressionAssignments +import Strata.DL.Imperative.Stmt +import Strata.DL.Lambda.LExpr +import Strata.Languages.Laurel.LaurelFormat + +namespace Laurel + +open Boogie (VCResult VCResults) +open Strata + +open Boogie (intAddOp intSubOp intMulOp intDivOp intModOp intNegOp intLtOp intLeOp intGtOp intGeOp boolAndOp boolOrOp boolNotOp) +open Lambda (LMonoTy LTy LExpr) + +/- +Translate Laurel HighType to Boogie Type +-/ +def translateType (ty : HighType) : LMonoTy := + match ty with + | .TInt => LMonoTy.int + | .TBool => LMonoTy.bool + | .TVoid => LMonoTy.bool -- Using bool as placeholder for void + | _ => panic s!"unsupported type {repr ty}" + +/-- +Translate Laurel StmtExpr to Boogie Expression +-/ +def translateExpr (expr : StmtExpr) : Boogie.Expression.Expr := + match h: expr with + | .LiteralBool b => .const () (.boolConst b) + | .LiteralInt i => .const () (.intConst i) + | .Identifier name => + let ident := Boogie.BoogieIdent.locl name + .fvar () ident (some LMonoTy.int) -- Default to int type + | .PrimitiveOp op [e] => + match op with + | .Not => .app () boolNotOp (translateExpr e) + | .Neg => .app () intNegOp (translateExpr e) + | _ => panic! s!"translateExpr: Invalid unary op: {repr op}" + | .PrimitiveOp op [e1, e2] => + let binOp (bop : Boogie.Expression.Expr): Boogie.Expression.Expr := + LExpr.mkApp () bop [translateExpr e1, translateExpr e2] + match op with + | .Eq => .eq () (translateExpr e1) (translateExpr e2) + | .Neq => .app () boolNotOp (.eq () (translateExpr e1) (translateExpr e2)) + | .And => binOp boolAndOp + | .Or => binOp boolOrOp + | .Add => binOp intAddOp + | .Sub => binOp intSubOp + | .Mul => binOp intMulOp + | .Div => binOp intDivOp + | .Mod => binOp intModOp + | .Lt => binOp intLtOp + | .Leq => binOp intLeOp + | .Gt => binOp intGtOp + | .Geq => binOp intGeOp + | _ => panic! s!"translateExpr: Invalid binary op: {repr op}" + | .PrimitiveOp op args => + panic! s!"translateExpr: PrimitiveOp {repr op} with {args.length} args" + | .IfThenElse cond thenBranch elseBranch => + let bcond := translateExpr cond + let bthen := translateExpr thenBranch + let belse := match elseBranch with + | some e => translateExpr e + | none => .const () (.intConst 0) + .ite () bcond bthen belse + | .Assign _ value => translateExpr value -- For expressions, just translate the value + | .StaticCall name args => + -- Create function call as an op application + let ident := Boogie.BoogieIdent.glob name + let fnOp := .op () ident (some LMonoTy.int) -- Assume int return type + args.foldl (fun acc arg => .app () acc (translateExpr arg)) fnOp + | _ => panic! Std.Format.pretty (Std.ToFormat.format expr) + decreasing_by + all_goals (simp_wf; try omega) + rename_i x_in; have := List.sizeOf_lt_of_mem x_in; omega + +/-- +Translate Laurel StmtExpr to Boogie Statements +Takes the list of output parameter names to handle return statements correctly +-/ +def translateStmt (outputParams : List Parameter) (stmt : StmtExpr) : List Boogie.Statement := + match stmt with + | @StmtExpr.Assert cond md => + let boogieExpr := translateExpr cond + [Boogie.Statement.assert "assert" boogieExpr md] + | @StmtExpr.Assume cond md => + let boogieExpr := translateExpr cond + [Boogie.Statement.assume "assume" boogieExpr md] + | .Block stmts _ => + stmts.flatMap (translateStmt outputParams) + | .LocalVariable name ty initializer => + let boogieMonoType := translateType ty + let boogieType := LTy.forAll [] boogieMonoType + let ident := Boogie.BoogieIdent.locl name + match initializer with + | some initExpr => + let boogieExpr := translateExpr initExpr + [Boogie.Statement.init ident boogieType boogieExpr] + | none => + -- Initialize with default value + let defaultExpr := match ty with + | .TInt => .const () (.intConst 0) + | .TBool => .const () (.boolConst false) + | _ => .const () (.intConst 0) + [Boogie.Statement.init ident boogieType defaultExpr] + | .Assign target value => + match target with + | .Identifier name => + let ident := Boogie.BoogieIdent.locl name + let boogieExpr := translateExpr value + [Boogie.Statement.set ident boogieExpr] + | _ => [] -- Can only assign to simple identifiers + | .IfThenElse cond thenBranch elseBranch => + let bcond := translateExpr cond + let bthen := translateStmt outputParams thenBranch + let belse := match elseBranch with + | some e => translateStmt outputParams e + | none => [] + -- Use Boogie's if-then-else construct + [Imperative.Stmt.ite bcond bthen belse .empty] + | .StaticCall name args => + let boogieArgs := args.map translateExpr + [Boogie.Statement.call [] name boogieArgs] + | .Return valueOpt => + -- In Boogie, returns are done by assigning to output parameters + match valueOpt, outputParams.head? with + | some value, some outParam => + -- Assign to the first output parameter, then assume false for no fallthrough + let ident := Boogie.BoogieIdent.locl outParam.name + let boogieExpr := translateExpr value + let assignStmt := Boogie.Statement.set ident boogieExpr + let noFallThrough := Boogie.Statement.assume "return" (.const () (.boolConst false)) .empty + [assignStmt, noFallThrough] + | none, _ => + -- Return with no value - just indicate no fallthrough + let noFallThrough := Boogie.Statement.assume "return" (.const () (.boolConst false)) .empty + [noFallThrough] + | some _, none => + -- Error: trying to return a value but no output parameters + panic! "Return statement with value but procedure has no output parameters" + | _ => panic! Std.Format.pretty (Std.ToFormat.format stmt) + +/-- +Translate Laurel Parameter to Boogie Signature entry +-/ +def translateParameterToBoogie (param : Parameter) : (Boogie.BoogieIdent × LMonoTy) := + let ident := Boogie.BoogieIdent.locl param.name + let ty := translateType param.type + (ident, ty) + +/-- +Translate Laurel Procedure to Boogie Procedure +-/ +def translateProcedure (proc : Procedure) : Boogie.Procedure := + -- Translate input parameters + let inputPairs := proc.inputs.map translateParameterToBoogie + let inputs := inputPairs + + let header : Boogie.Procedure.Header := { + name := proc.name + typeArgs := [] + inputs := inputs + outputs := proc.outputs.map translateParameterToBoogie + } + let spec : Boogie.Procedure.Spec := { + modifies := [] + preconditions := [] + postconditions := [] + } + let body : List Boogie.Statement := + match proc.body with + | .Transparent bodyExpr => translateStmt proc.outputs bodyExpr + | _ => [] -- TODO: handle Opaque and Abstract bodies + { + header := header + spec := spec + body := body + } + +/-- +Translate Laurel Program to Boogie Program +-/ +def translate (program : Program) : Boogie.Program := + -- First, sequence all assignments (move them out of expression positions) + let sequencedProgram := liftExpressionAssignments program + dbg_trace "=== Sequenced program Program ===" + dbg_trace (toString (Std.Format.pretty (Std.ToFormat.format sequencedProgram))) + dbg_trace "=================================" + -- Then translate to Boogie + let procedures := sequencedProgram.staticProcedures.map translateProcedure + let decls := procedures.map (fun p => Boogie.Decl.proc p .empty) + { decls := decls } + +/-- +Verify a Laurel program using an SMT solver +-/ +def verifyToVcResults (smtsolver : String) (program : Program) + (options : Options := Options.default) : IO VCResults := do + let boogieProgram := translate program + -- Debug: Print the generated Boogie program + dbg_trace "=== Generated Boogie Program ===" + dbg_trace (toString (Std.Format.pretty (Std.ToFormat.format boogieProgram))) + dbg_trace "=================================" + EIO.toIO (fun f => IO.Error.userError (toString f)) + (Boogie.verify smtsolver boogieProgram options) + +def verifyToDiagnostics (smtsolver : String) (program : Program): IO (Array Diagnostic) := do + let results <- verifyToVcResults smtsolver program + return results.filterMap toDiagnostic + +end Laurel diff --git a/Strata/Languages/Laurel/LiftExpressionAssignments.lean b/Strata/Languages/Laurel/LiftExpressionAssignments.lean new file mode 100644 index 000000000..0221e4d40 --- /dev/null +++ b/Strata/Languages/Laurel/LiftExpressionAssignments.lean @@ -0,0 +1,183 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Laurel.Laurel + +namespace Laurel + +/- +Transform assignments that appear in expression contexts into preceding statements. + +For example: + if ((x := x + 1) == (y := x)) { ... } + +Becomes: + var x1 := x + 1; + x := x1; + var y1 := x; + y := y1; + if (x1 == y1) { ... } +-/ + +structure SequenceState where + prependedStmts : List StmtExpr := [] + tempCounter : Nat := 0 + +abbrev SequenceM := StateM SequenceState + +def SequenceM.addPrependedStmt (stmt : StmtExpr) : SequenceM Unit := + modify fun s => { s with prependedStmts := stmt :: s.prependedStmts } + +def SequenceM.takePrependedStmts : SequenceM (List StmtExpr) := do + let stmts := (← get).prependedStmts + modify fun s => { s with prependedStmts := [] } + return stmts.reverse + +def SequenceM.freshTemp : SequenceM Identifier := do + let counter := (← get).tempCounter + modify fun s => { s with tempCounter := s.tempCounter + 1 } + return s!"__t{counter}" + +mutual +/- +Process an expression, extracting any assignments to preceding statements. +Returns the transformed expression with assignments replaced by variable references. +-/ +def transformExpr (expr : StmtExpr) : SequenceM StmtExpr := do + match expr with + | .Assign target value => + -- This is an assignment in expression context + -- We need to: 1) execute the assignment, 2) capture the value in a temporary + -- This prevents subsequent assignments to the same variable from changing the value + let seqValue ← transformExpr value + let assignStmt := StmtExpr.Assign target seqValue + SequenceM.addPrependedStmt assignStmt + -- Create a temporary variable to capture the assigned value + -- Use TInt as the type (could be refined with type inference) + let tempName ← SequenceM.freshTemp + let tempDecl := StmtExpr.LocalVariable tempName .TInt (some target) + SequenceM.addPrependedStmt tempDecl + -- Return the temporary variable as the expression value + return .Identifier tempName + + | .PrimitiveOp op args => + let seqArgs ← args.mapM transformExpr + return .PrimitiveOp op seqArgs + + | .IfThenElse cond thenBranch elseBranch => + let seqCond ← transformExpr cond + let seqThen ← transformExpr thenBranch + let seqElse ← match elseBranch with + | some e => transformExpr e >>= (pure ∘ some) + | none => pure none + return .IfThenElse seqCond seqThen seqElse + + | .StaticCall name args => + let seqArgs ← args.mapM transformExpr + return .StaticCall name seqArgs + + | .Block stmts metadata => + -- Block in expression position: move all but last statement to prepended + let rec next (remStmts: List StmtExpr) := match remStmts with + | [last] => transformExpr last + | head :: tail => do + let seqStmt ← transformStmt head + for s in seqStmt do + SequenceM.addPrependedStmt s + next tail + | [] => return .Block [] metadata + + next stmts + + -- Base cases: no assignments to extract + | .LiteralBool _ => return expr + | .LiteralInt _ => return expr + | .Identifier _ => return expr + | .LocalVariable _ _ _ => return expr + | _ => return expr -- Other cases + +/- +Process a statement, handling any assignments in its sub-expressions. +Returns a list of statements (the original one may be split into multiple). +-/ +def transformStmt (stmt : StmtExpr) : SequenceM (List StmtExpr) := do + match stmt with + | @StmtExpr.Assert cond md => + -- Process the condition, extracting any assignments + let seqCond ← transformExpr cond + SequenceM.addPrependedStmt <| StmtExpr.Assert seqCond md + SequenceM.takePrependedStmts + + | @StmtExpr.Assume cond md => + let seqCond ← transformExpr cond + SequenceM.addPrependedStmt <| StmtExpr.Assume seqCond md + SequenceM.takePrependedStmts + + | .Block stmts metadata => + let seqStmts ← stmts.mapM transformStmt + return [.Block (seqStmts.flatten) metadata] + + | .LocalVariable name ty initializer => + match initializer with + | some initExpr => do + let seqInit ← transformExpr initExpr + SequenceM.addPrependedStmt <| .LocalVariable name ty (some seqInit) + SequenceM.takePrependedStmts + | none => + return [stmt] + + | .Assign target value => + -- Top-level assignment (statement context) + let seqTarget ← transformExpr target + let seqValue ← transformExpr value + SequenceM.addPrependedStmt <| .Assign seqTarget seqValue + SequenceM.takePrependedStmts + + | .IfThenElse cond thenBranch elseBranch => + let seqThen ← transformStmt thenBranch + let thenBlock := .Block seqThen none + + let seqElse ← match elseBranch with + | some e => + let se ← transformStmt e + pure (some (.Block se none)) + | none => pure none + + let seqCond ← transformExpr cond + SequenceM.addPrependedStmt <| .IfThenElse seqCond thenBlock seqElse + SequenceM.takePrependedStmts + + | .StaticCall name args => + let seqArgs ← args.mapM transformExpr + SequenceM.addPrependedStmt <| .StaticCall name seqArgs + SequenceM.takePrependedStmts + + | _ => + return [stmt] + +end + +def transformProcedureBody (body : StmtExpr) : StmtExpr := + let (seqStmts, _) := transformStmt body |>.run {} + match seqStmts with + | [single] => single + | multiple => .Block multiple.reverse none + +def transformProcedure (proc : Procedure) : Procedure := + match proc.body with + | .Transparent bodyExpr => + let seqBody := transformProcedureBody bodyExpr + { proc with body := .Transparent seqBody } + | _ => proc -- Opaque and Abstract bodies unchanged + +/-- +Transform a program to lift all assignments that occur in an expression context. +-/ +def liftExpressionAssignments (program : Program) : Program := + let seqProcedures := program.staticProcedures.map transformProcedure + { program with staticProcedures := seqProcedures } + +end Laurel diff --git a/Strata/Languages/Python/BoogiePrelude.lean b/Strata/Languages/Python/BoogiePrelude.lean new file mode 100644 index 000000000..80715e2b8 --- /dev/null +++ b/Strata/Languages/Python/BoogiePrelude.lean @@ -0,0 +1,559 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Elab +import Strata.DDM.AST +import Strata.Languages.Boogie.DDMTransform.Parse +import Strata.Languages.Boogie.Verifier + +namespace Strata + +def boogiePrelude := +#strata +program Boogie; + +type None; +const None_none : None; + +type Object; +function Object_len(x : Object) : int; +axiom [Object_len_ge_zero]: (forall x : Object :: Object_len(x) >= 0); + +function inheritsFrom(child : string, parent : string) : (bool); +axiom [inheritsFrom_refl]: (forall s: string :: {inheritsFrom(s, s)} inheritsFrom(s, s)); + +// ///////////////////////////////////////////////////////////////////////////////////// + +// Exceptions +// TODO: Formalize the exception hierarchy here: +// https://docs.python.org/3/library/exceptions.html#exception-hierarchy +// We use the name "Error" to stand for Python's Exceptions + +// our own special indicator, Unimplemented which is an artifact of +// Strata that indicates that our models is partial. +type Error; + +// Constructors +function Error_TypeError (msg : string) : Error; +function Error_AttributeError (msg : string) : Error; +function Error_RePatternError (msg : string) : Error; +function Error_Unimplemented (msg : string) : Error; + +// Testers +function Error_isTypeError (e : Error) : bool; +function Error_isAttributeError (e : Error) : bool; +function Error_isRePatternError (e : Error) : bool; +function Error_isUnimplemented (e : Error) : bool; + +// Destructors +function Error_getTypeError (e : Error) : string; +function Error_getAttributeError (e : Error) : string; +function Error_getRePatternError (e : Error) : string; +function Error_getUnimplemented (e : Error) : string; + +// Axioms +// Testers of Constructors +axiom [Error_isTypeError_TypeError]: + (forall msg : string :: {(Error_TypeError(msg))} + Error_isTypeError(Error_TypeError(msg))); +axiom [Error_isAttributeError_AttributeError]: + (forall msg : string :: {(Error_AttributeError(msg))} + Error_isAttributeError(Error_AttributeError(msg))); +axiom [Error_isRePatternError_RePatternError]: + (forall msg : string :: + Error_isRePatternError(Error_RePatternError(msg))); +axiom [Error_isUnimplemented_Unimplemented]: + (forall msg : string :: + Error_isUnimplemented(Error_Unimplemented(msg))); +// Destructors of Constructors +axiom [Error_getTypeError_TypeError]: + (forall msg : string :: + Error_getTypeError(Error_TypeError(msg)) == msg); +axiom [Error_getAttributeError_AttributeError]: + (forall msg : string :: + Error_getAttributeError(Error_AttributeError(msg)) == msg); +axiom [Error_getUnimplemented_Unimplemented]: + (forall msg : string :: + Error_getUnimplemented(Error_Unimplemented(msg)) == msg); + +// ///////////////////////////////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////////////// +// Regular Expressions + +type Except (err : Type, ok : Type); + +// FIXME: +// Once DDM support polymorphic functions (and not just type declarations), +// we will be able to define the following generic functions and axioms. For now, +// we manually define appropriate instantiations. +// Also: when ADT support is lifted up to Boogie, all these +// constructors, testers, destructors, and axioms will be auto-generated. +// How will the DDM keep track of them? + +// // Constructors +// function Except_mkOK(err : Type, ok : Type, val : ok) : Except err ok; +// function Except_mkErr(err : Type, ok : Type, val : err) : Except err ok; +// // Testers +// function Except_isOK(err : Type, ok : Type, x : Except err ok) : bool; +// function Except_isErr(err : Type, ok : Type, x : Except err ok) : bool; +// // Destructors +// function Except_getOK(err : Type, ok : Type, x : Except err ok) : ok; +// function Except_getErr(err : Type, ok : Type, x : Except err ok) : err; +// // Axioms +// // Testers of Constructors +// axiom [Except_isOK_mkOK]: (forall x : ok :: Except_isOK(Except_mkOK x)); +// axiom [Except_isErr_mkErr]: (forall x : err :: Except_isErr(Except_mkErr x)); +// // Destructors of Constructors +// axiom [Except_getOK_mkOK]: (forall x : ok :: Except_getOK(Except_mkOK x) == x); +// axiom [Except_getErr_mkErr]: (forall x : err :: Except_isErr(Except_mkErr x)); + +type ExceptErrorRegex := Except Error regex; + +// Constructors +function ExceptErrorRegex_mkOK(x : regex) : ExceptErrorRegex; +function ExceptErrorRegex_mkErr(x : Error) : ExceptErrorRegex; +// Testers +function ExceptErrorRegex_isOK(x : ExceptErrorRegex) : bool; +function ExceptErrorRegex_isErr(x : ExceptErrorRegex) : bool; +// Destructors +function ExceptErrorRegex_getOK(x : ExceptErrorRegex) : regex; +function ExceptErrorRegex_getErr(x : ExceptErrorRegex) : Error; +// Axioms +// Testers of Constructors +axiom [ExceptErrorRegex_isOK_mkOK]: + (forall x : regex :: {(ExceptErrorRegex_mkOK(x))} + ExceptErrorRegex_isOK(ExceptErrorRegex_mkOK(x))); +axiom [ExceptErrorRegex_isError_mkErr]: + (forall e : Error :: {(ExceptErrorRegex_mkErr(e))} + ExceptErrorRegex_isErr(ExceptErrorRegex_mkErr(e))); +// Destructors of Constructors +axiom [ExceptErrorRegex_getOK_mkOK]: + (forall x : regex :: {(ExceptErrorRegex_mkOK(x))} + ExceptErrorRegex_getOK(ExceptErrorRegex_mkOK(x)) == x); +axiom [ExceptErrorRegex_getError_mkError]: + (forall e : Error :: {(ExceptErrorRegex_mkErr(e))} + ExceptErrorRegex_getErr(ExceptErrorRegex_mkErr(e)) == e); + +// NOTE: `re.match` returns a `Re.Match` object, but for now, we are interested +// only in match/nomatch, which is why we return `bool` here. +function PyReMatchRegex(pattern : regex, str : string, flags : int) : bool; +// We only support Re.Match when flags == 0. +axiom [PyReMatchRegex_def_noFlg]: + (forall pattern : regex, str : string :: {PyReMatchRegex(pattern, str, 0)} + PyReMatchRegex(pattern, str, 0) == str.in.re(str, pattern)); + +// Unsupported/uninterpreted: eventually, this would first call PyReCompile and if there's +// no exception, call PyReMatchRegex. +function PyReMatchStr(pattern : string, str : string, flags : int) : Except Error bool; + +// ///////////////////////////////////////////////////////////////////////////////////// + +// List of strings +type ListStr; +function ListStr_nil() : (ListStr); +function ListStr_cons(x0 : string, x1 : ListStr) : (ListStr); + +// ///////////////////////////////////////////////////////////////////////////////////// + +// Temporary Types + +type ExceptOrNone; +type ExceptCode := string; +type ExceptNone; +const Except_none : ExceptNone; +type ExceptOrNoneTag; +const EN_STR_TAG : ExceptOrNoneTag; +const EN_NONE_TAG : ExceptOrNoneTag; +function ExceptOrNone_tag(v : ExceptOrNone) : ExceptOrNoneTag; +function ExceptOrNone_code_val(v : ExceptOrNone) : ExceptCode; +function ExceptOrNone_none_val(v : ExceptOrNone) : ExceptNone; +function ExceptOrNone_mk_code(s : ExceptCode) : ExceptOrNone; +function ExceptOrNone_mk_none(v : ExceptNone) : ExceptOrNone; +axiom [ExceptOrNone_mk_code_axiom]: (forall s : ExceptCode :: {(ExceptOrNone_mk_code(s))} + ExceptOrNone_tag(ExceptOrNone_mk_code(s)) == EN_STR_TAG && + ExceptOrNone_code_val(ExceptOrNone_mk_code(s)) == s); +axiom [ExceptOrNone_mk_none_axiom]: (forall n : ExceptNone :: {(ExceptOrNone_mk_none(n))} + ExceptOrNone_tag(ExceptOrNone_mk_none(n)) == EN_NONE_TAG && + ExceptOrNone_none_val(ExceptOrNone_mk_none(n)) == n); +axiom [ExceptOrNone_tag_axiom]: (forall v : ExceptOrNone :: {ExceptOrNone_tag(v)} + ExceptOrNone_tag(v) == EN_STR_TAG || + ExceptOrNone_tag(v) == EN_NONE_TAG); +axiom [unique_ExceptOrNoneTag]: EN_STR_TAG != EN_NONE_TAG; + +// IntOrNone +type IntOrNone; +type IntOrNoneTag; +const IN_INT_TAG : IntOrNoneTag; +const IN_NONE_TAG : IntOrNoneTag; +function IntOrNone_tag(v : IntOrNone) : IntOrNoneTag; +function IntOrNone_int_val(v : IntOrNone) : int; +function IntOrNone_none_val(v : IntOrNone) : None; +function IntOrNone_mk_int(i : int) : IntOrNone; +function IntOrNone_mk_none(v : None) : IntOrNone; +axiom [IntOrNone_mk_int_axiom]: (forall i : int :: {(IntOrNone_mk_int(i))} + IntOrNone_tag(IntOrNone_mk_int(i)) == IN_INT_TAG && + IntOrNone_int_val(IntOrNone_mk_int(i)) == i); +axiom [IntOrNone_mk_none_axiom]: (forall n : None :: {(IntOrNone_mk_none(n))} + IntOrNone_tag(IntOrNone_mk_none(n)) == IN_NONE_TAG && + IntOrNone_none_val(IntOrNone_mk_none(n)) == n); +axiom [IntOrNone_tag_axiom]: (forall v : IntOrNone :: {IntOrNone_tag(v)} + IntOrNone_tag(v) == IN_INT_TAG || + IntOrNone_tag(v) == IN_NONE_TAG); +axiom [unique_IntOrNoneTag]: IN_INT_TAG != IN_NONE_TAG; + +// StrOrNone +type StrOrNone; +type StrOrNoneTag; +const SN_STR_TAG : StrOrNoneTag; +const SN_NONE_TAG : StrOrNoneTag; +function StrOrNone_tag(v : StrOrNone) : StrOrNoneTag; +function StrOrNone_str_val(v : StrOrNone) : string; +function StrOrNone_none_val(v : StrOrNone) : None; +function StrOrNone_mk_str(s : string) : StrOrNone; +function StrOrNone_mk_none(v : None) : StrOrNone; + +axiom [StrOrNone_tag_of_mk_str_axiom]: (forall s : string :: {StrOrNone_tag(StrOrNone_mk_str(s)), (StrOrNone_mk_str(s))} + StrOrNone_tag(StrOrNone_mk_str(s)) == SN_STR_TAG); +axiom [StrOrNone_val_of_mk_str_axiom]: (forall s : string :: {StrOrNone_str_val(StrOrNone_mk_str(s)), (StrOrNone_mk_str(s))} + StrOrNone_str_val(StrOrNone_mk_str(s)) == s); +axiom [StrOrNone_mk_none_axiom]: (forall n : None :: {(StrOrNone_mk_none(n))} + StrOrNone_tag(StrOrNone_mk_none(n)) == SN_NONE_TAG && + StrOrNone_none_val(StrOrNone_mk_none(n)) == n); +axiom [StrOrNone_tag_axiom]: (forall v : StrOrNone :: {StrOrNone_tag(v)} + StrOrNone_tag(v) == SN_STR_TAG || + StrOrNone_tag(v) == SN_NONE_TAG); +axiom [unique_StrOrNoneTag]: SN_STR_TAG != SN_NONE_TAG; + +function strOrNone_toObject(v : StrOrNone) : Object; +// Injectivity axiom: different StrOrNone map to different objects. +axiom (forall s1:StrOrNone, s2: StrOrNone :: {strOrNone_toObject(s1), strOrNone_toObject(s2)} + s1 != s2 ==> + strOrNone_toObject(s1) != strOrNone_toObject(s2)); +axiom (forall s : StrOrNone :: {StrOrNone_tag(s)} + StrOrNone_tag(s) == SN_STR_TAG ==> + Object_len(strOrNone_toObject(s)) == str.len(StrOrNone_str_val(s))); + +// AnyOrNone +type AnyOrNone; +type AnyOrNoneTag; +const AN_ANY_TAG : AnyOrNoneTag; +const AN_NONE_TAG : AnyOrNoneTag; +function AnyOrNone_tag(v : AnyOrNone) : AnyOrNoneTag; +function AnyOrNone_str_val(v : AnyOrNone) : string; +function AnyOrNone_none_val(v : AnyOrNone) : None; +function AnyOrNone_mk_str(s : string) : AnyOrNone; +function AnyOrNone_mk_none(v : None) : AnyOrNone; +axiom (forall s : string :: {(AnyOrNone_mk_str(s))} + AnyOrNone_tag(AnyOrNone_mk_str(s)) == AN_ANY_TAG && + AnyOrNone_str_val(AnyOrNone_mk_str(s)) == s); +axiom (forall n : None :: {(AnyOrNone_mk_none(n))} + AnyOrNone_tag(AnyOrNone_mk_none(n)) == AN_NONE_TAG && + AnyOrNone_none_val(AnyOrNone_mk_none(n)) == n); +axiom (forall v : AnyOrNone :: {AnyOrNone_tag(v)} + AnyOrNone_tag(v) == AN_ANY_TAG || + AnyOrNone_tag(v) == AN_NONE_TAG); +axiom [unique_AnyOrNoneTag]: AN_ANY_TAG != AN_NONE_TAG; + +// BoolOrNone +type BoolOrNone; +type BoolOrNoneTag; +const BN_BOOL_TAG : BoolOrNoneTag; +const BN_NONE_TAG : BoolOrNoneTag; +function BoolOrNone_tag(v : BoolOrNone) : BoolOrNoneTag; +function BoolOrNone_str_val(v : BoolOrNone) : string; +function BoolOrNone_none_val(v : BoolOrNone) : None; +function BoolOrNone_mk_str(s : string) : BoolOrNone; +function BoolOrNone_mk_none(v : None) : BoolOrNone; +axiom (forall s : string :: {BoolOrNone_mk_str(s)} + BoolOrNone_tag(BoolOrNone_mk_str(s)) == BN_BOOL_TAG && + BoolOrNone_str_val(BoolOrNone_mk_str(s)) == s); +axiom (forall n : None :: {BoolOrNone_mk_none(n)} + BoolOrNone_tag(BoolOrNone_mk_none(n)) == BN_NONE_TAG && + BoolOrNone_none_val(BoolOrNone_mk_none(n)) == n); +axiom (forall v : BoolOrNone :: {BoolOrNone_tag(v)} + BoolOrNone_tag(v) == BN_BOOL_TAG || + BoolOrNone_tag(v) == BN_NONE_TAG); +axiom [unique_BoolOrNoneTag]: BN_BOOL_TAG != BN_NONE_TAG; + +// BoolOrStrOrNone +type BoolOrStrOrNone; +type BoolOrStrOrNoneTag; +const BSN_BOOL_TAG : BoolOrStrOrNoneTag; +const BSN_STR_TAG : BoolOrStrOrNoneTag; +const BSN_NONE_TAG : BoolOrStrOrNoneTag; +function BoolOrStrOrNone_tag(v : BoolOrStrOrNone) : BoolOrStrOrNoneTag; +function BoolOrStrOrNone_bool_val(v : BoolOrStrOrNone) : bool; +function BoolOrStrOrNone_str_val(v : BoolOrStrOrNone) : string; +function BoolOrStrOrNone_none_val(v : BoolOrStrOrNone) : None; +function BoolOrStrOrNone_mk_bool(b : bool) : BoolOrStrOrNone; +function BoolOrStrOrNone_mk_str(s : string) : BoolOrStrOrNone; +function BoolOrStrOrNone_mk_none(v : None) : BoolOrStrOrNone; +axiom (forall b : bool :: {BoolOrStrOrNone_mk_bool(b)} + BoolOrStrOrNone_tag(BoolOrStrOrNone_mk_bool(b)) == BSN_BOOL_TAG && + BoolOrStrOrNone_bool_val(BoolOrStrOrNone_mk_bool(b)) == b); +axiom (forall s : string :: {BoolOrStrOrNone_mk_str(s)} + BoolOrStrOrNone_tag(BoolOrStrOrNone_mk_str(s)) == BSN_STR_TAG && + BoolOrStrOrNone_str_val(BoolOrStrOrNone_mk_str(s)) == s); +axiom (forall n : None :: {BoolOrStrOrNone_mk_none(n)} + BoolOrStrOrNone_tag(BoolOrStrOrNone_mk_none(n)) == BSN_NONE_TAG && + BoolOrStrOrNone_none_val(BoolOrStrOrNone_mk_none(n)) == n); +axiom (forall v : BoolOrStrOrNone :: {BoolOrStrOrNone_tag(v)} + BoolOrStrOrNone_tag(v) == BSN_BOOL_TAG || + BoolOrStrOrNone_tag(v) == BSN_STR_TAG || + BoolOrStrOrNone_tag(v) == BSN_NONE_TAG); + +// DictStrStrOrNone +type DictStrStrOrNone; +type DictStrStrOrNoneTag; +const DSSN_BOOL_TAG : DictStrStrOrNoneTag; +const DSSN_NONE_TAG : DictStrStrOrNoneTag; +function DictStrStrOrNone_tag(v : DictStrStrOrNone) : DictStrStrOrNoneTag; +function DictStrStrOrNone_str_val(v : DictStrStrOrNone) : string; +function DictStrStrOrNone_none_val(v : DictStrStrOrNone) : None; +function DictStrStrOrNone_mk_str(s : string) : DictStrStrOrNone; +function DictStrStrOrNone_mk_none(v : None) : DictStrStrOrNone; +axiom (forall s : string :: {DictStrStrOrNone_mk_str(s)} + DictStrStrOrNone_tag(DictStrStrOrNone_mk_str(s)) == DSSN_BOOL_TAG && + DictStrStrOrNone_str_val(DictStrStrOrNone_mk_str(s)) == s); +axiom (forall n : None :: {DictStrStrOrNone_mk_none(n)} + DictStrStrOrNone_tag(DictStrStrOrNone_mk_none(n)) == DSSN_NONE_TAG && + DictStrStrOrNone_none_val(DictStrStrOrNone_mk_none(n)) == n); +axiom (forall v : DictStrStrOrNone :: {DictStrStrOrNone_tag(v)} + DictStrStrOrNone_tag(v) == DSSN_BOOL_TAG || + DictStrStrOrNone_tag(v) == DSSN_NONE_TAG); +axiom [unique_DictStrStrOrNoneTag]: DSSN_BOOL_TAG != DSSN_NONE_TAG; + +type BytesOrStrOrNone; +function BytesOrStrOrNone_mk_none(v : None) : (BytesOrStrOrNone); +function BytesOrStrOrNone_mk_str(s : string) : (BytesOrStrOrNone); + +type DictStrAny; +function DictStrAny_mk(s : string) : (DictStrAny); + +type ListDictStrAny; +function ListDictStrAny_nil() : (ListDictStrAny); + +type Client; +type ClientTag; +const C_S3_TAG : ClientTag; +const C_CW_TAG : ClientTag; +function Client_tag(v : Client) : (ClientTag); + +// Unique const axioms +axiom [unique_BoolOrStrOrNoneTag]: BSN_BOOL_TAG != BSN_STR_TAG && BSN_BOOL_TAG != BSN_NONE_TAG && BSN_STR_TAG != BSN_NONE_TAG; + + +// ///////////////////////////////////////////////////////////////////////////////////// +// Datetime + +////// 1. Timedelta. + +// According to http://docs.python.org/3/library/datetime.html, +// "" +// Only days, seconds and microseconds are stored internally. Arguments are +// converted to those units: +// - A millisecond is converted to 1000 microseconds. +// - A minute is converted to 60 seconds. +// - An hour is converted to 3600 seconds. +// - A week is converted to 7 days. +// and days, seconds and microseconds are then normalized so that the +// representation is unique, with +// - 0 <= microseconds < 1000000 +// - 0 <= seconds < 3600*24 (the number of seconds in one day) +// - -999999999 <= days <= 999999999 +// "" + +// In Boogie representation, an int type that corresponds to the full +// milliseconds is simply used. See Timedelta_mk. + + +procedure timedelta(days: IntOrNone, hours: IntOrNone) returns (delta : int, maybe_except: ExceptOrNone) +spec{ +} +{ + havoc delta; + var days_i : int := 0; + if (IntOrNone_tag(days) == IN_INT_TAG) { + days_i := IntOrNone_int_val(days); + } + var hours_i : int := 0; + if (IntOrNone_tag(hours) == IN_INT_TAG) { + days_i := IntOrNone_int_val(hours); + } + assume [assume_timedelta_sign_matches]: (delta == (((days_i * 24) + hours_i) * 3600) * 1000000); +}; + +function Timedelta_mk(days : int, seconds : int, microseconds : int): int { + ((days * 3600 * 24) + seconds) * 1000000 + microseconds +} + +function Timedelta_get_days(timedelta : int) : int; +function Timedelta_get_seconds(timedelta : int) : int; +function Timedelta_get_microseconds(timedelta : int) : int; + +axiom [Timedelta_deconstructors]: + (forall days0 : int, seconds0 : int, msecs0 : int, + days : int, seconds : int, msecs : int + :: {(Timedelta_mk(days0, seconds0, msecs0))} + Timedelta_mk(days0, seconds0, msecs0) == + Timedelta_mk(days, seconds, msecs) && + 0 <= msecs && msecs < 1000000 && + 0 <= seconds && seconds < 3600 * 24 && + -999999999 <= days && days <= 999999999 + ==> Timedelta_get_days(Timedelta_mk(days0, seconds0, msecs0)) == days && + Timedelta_get_seconds(Timedelta_mk(days0, seconds0, msecs0)) == seconds && + Timedelta_get_microseconds(Timedelta_mk(days0, seconds0, msecs0)) == msecs); + + +////// Datetime. +// Datetime is abstractly defined as a pair of (base time, relative timedelta). +// datetime.now() returns (, 0). +// Adding or subtracting datetime.timedelta updates +type Datetime; +type Datetime_base; + +function Datetime_get_base(d : Datetime) : Datetime_base; +function Datetime_get_timedelta(d : Datetime) : int; + +// now() returns an abstract, fresh current datetime. +// This abstract now() does not guarantee monotonic increase of time, and this +// means subtracting an 'old' timestamp from a 'new' timestamp may return +// a negative difference. + +procedure datetime_now() returns (d:Datetime, maybe_except: ExceptOrNone) +spec { + ensures (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0)); +} +{ + havoc d; + assume [assume_datetime_now]: (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0)); +}; + +procedure datetime_utcnow() returns (d:Datetime, maybe_except: ExceptOrNone) +spec { + ensures (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0)); +} +{ + havoc d; + assume [assume_datetime_now]: (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0)); +}; + +// Addition/subtraction of Datetime and Timedelta. +function Datetime_add(d:Datetime, timedelta:int):Datetime; +function Datetime_sub(d:Datetime, timedelta:int):Datetime { + Datetime_add(d, -timedelta) +} + +axiom [Datetime_add_ax]: + (forall d:Datetime, timedelta:int :: {} + Datetime_get_base(Datetime_add(d,timedelta)) == Datetime_get_base(d) && + Datetime_get_timedelta(Datetime_add(d,timedelta)) == + Datetime_get_timedelta(d) + timedelta); + +// Comparison of Datetimes is abstractly defined so that the result is +// meaningful only if the two datetimes have same base. +function Datetime_lt(d1:Datetime, d2:Datetime):bool; + +axiom [Datetime_lt_ax]: + (forall d1:Datetime, d2:Datetime :: {} + Datetime_get_base(d1) == Datetime_get_base(d2) + ==> Datetime_lt(d1, d2) == + (Datetime_get_timedelta(d1) < Datetime_get_timedelta(d2))); + + +type Date; +procedure datetime_date(dt: Datetime) returns (d : Datetime, maybe_except: ExceptOrNone) +spec{} +{havoc d;}; + +function datetime_to_str(dt : Datetime) : string; + +function datetime_to_int() : int; + +procedure datetime_strptime(time: string, format: string) returns (d : Datetime, maybe_except: ExceptOrNone) +spec{ + requires [req_format_str]: (format == "%Y-%m-%d"); + ensures [ensures_str_strp_reverse]: (forall dt : Datetime :: {d == dt} ((time == datetime_to_str(dt)) <==> (d == dt))); +} +{ + havoc d; + assume [assume_str_strp_reverse]: (forall dt : Datetime :: {d == dt} ((time == datetime_to_str(dt)) <==> (d == dt))); +}; + +///////////////////////////////////////////////////////////////////////////////////// + + +// ///////////////////////////////////////////////////////////////////////////////////// + +// Uninterpreted procedures +procedure importFrom(module : string, names : ListStr, level : int) returns (); +procedure import(names : ListStr) returns (); +procedure print(msg : string, opt : StrOrNone) returns (); + +procedure json_dumps(msg : DictStrAny, opt_indent : IntOrNone) returns (s: string, maybe_except: ExceptOrNone) +spec{} +{havoc s;} +; + +procedure json_loads(msg : string) returns (d: DictStrAny, maybe_except: ExceptOrNone) +spec{} +{havoc d;} +; + +procedure input(msg : string) returns (result: string, maybe_except: ExceptOrNone) +spec{} +{havoc result;} +; + +procedure random_choice(l : ListStr) returns (result: string, maybe_except: ExceptOrNone) +spec{} +{havoc result;} +; + +function str_in_list_str(s : string, l: ListStr) : bool; + +function str_in_dict_str_any(s : string, l: DictStrAny) : bool; + +function list_str_get(l : ListStr, i: int) : string; + +function str_len(s : string) : int; + +function dict_str_any_get(d : DictStrAny, k: string) : DictStrAny; + +function dict_str_any_get_list_str(d : DictStrAny, k: string) : ListStr; + +function dict_str_any_get_str(d : DictStrAny, k: string) : string; + +function dict_str_any_length(d : DictStrAny) : int; + +procedure str_to_float(s : string) returns (result: string, maybe_except: ExceptOrNone) +; + +function Float_gt(lhs : string, rhs: string) : bool; + +// ///////////////////////////////////////////////////////////////////////////////////// + + + +procedure test_helper_procedure(req_name : string, opt_name : StrOrNone) returns (maybe_except: ExceptOrNone) +spec { + requires [req_name_is_foo]: req_name == "foo"; + requires [req_opt_name_none_or_str]: (if (StrOrNone_tag(opt_name) != SN_NONE_TAG) then (StrOrNone_tag(opt_name) == SN_STR_TAG) else true); + requires [req_opt_name_none_or_bar]: (if (StrOrNone_tag(opt_name) == SN_STR_TAG) then (StrOrNone_str_val(opt_name) == "bar") else true); + ensures [ensures_maybe_except_none]: (ExceptOrNone_tag(maybe_except) == EN_NONE_TAG); +} +{ + assert [assert_name_is_foo]: req_name == "foo"; + assert [assert_opt_name_none_or_str]: (if (StrOrNone_tag(opt_name) != SN_NONE_TAG) then (StrOrNone_tag(opt_name) == SN_STR_TAG) else true); + assert [assert_opt_name_none_or_bar]: (if (StrOrNone_tag(opt_name) == SN_STR_TAG) then (StrOrNone_str_val(opt_name) == "bar") else true); + assume [assume_maybe_except_none]: (ExceptOrNone_tag(maybe_except) == EN_NONE_TAG); +}; + +#end + +def Boogie.prelude : Boogie.Program := + Boogie.getProgram Strata.boogiePrelude |>.fst + +end Strata diff --git a/Strata/Languages/Python/FunctionSignatures.lean b/Strata/Languages/Python/FunctionSignatures.lean new file mode 100644 index 000000000..2f0c7809b --- /dev/null +++ b/Strata/Languages/Python/FunctionSignatures.lean @@ -0,0 +1,161 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Boogie + +namespace Strata +namespace Python + +/-- A type identifier in the Python Boogie prelude. -/ +abbrev TypeId := String + +/-- An argument declaration for a Python method -/ +structure ArgDecl where + name : String + type : TypeId +deriving Inhabited + +/-- A function signature with argument information. -/ +structure FuncDecl where + /-- Array of arguments. -/ + args : Array ArgDecl + /-- + Number of position-only arguments. + + Position only arguments occur before other arguments. + -/ + posOnlyCount : Nat := 0 + /-- + First index for keyword only arguments. + + Keyword only arguments appear after other arguments in args. + -/ + keywordOnly : Nat := args.size + /-- + Position only arguments are before start of keyword only. + -/ + posOnlyBound : posOnlyCount <= keywordOnly := by omega + /-- + Keyword only arguments (if any) come at end + -/ + keywordBound : keywordOnly <= args.size := by omega + /-- Map from argument names to their index in args. -/ + argIndexMap : Std.HashMap String (Fin args.size) + +instance : Inhabited FuncDecl where + default := { args := #[], argIndexMap := {} } + +/-- The name of a Python method as encoded in the Boogie dialect-/ +abbrev FuncName := String + +/-- A collection of function signatures. -/ +class Signatures where + functions : Std.HashMap FuncName FuncDecl := {} +deriving Inhabited + +namespace Signatures + +def getFuncSigOrder (db : Signatures) (fname: FuncName) : List String := + match db.functions[fname]? with + | some decl => decl.args |>.map (·.name) |>.toList + | none => panic! s!"Missing function signature : {fname}" + +-- We should extract the function signatures from the prelude: +def getFuncSigType (db : Signatures) (fname: FuncName) (arg: String) : String := + match db.functions[fname]? with + | none => panic! s!"Missing function signature : {fname}" + | some decl => + match decl.argIndexMap[arg]? with + | none => panic! s!"Unrecognized arg : {arg}" + | some idx => decl.args[idx].type + +end Signatures + +/-- +Monad for extending a signatures collection. +-/ +def SignatureM := StateM Signatures +deriving Monad, MonadState Signatures + +namespace SignatureM + +def run (m : SignatureM Unit) (init : Signatures := {}) : Signatures := m init |>.snd + +def decl (name : FuncName) (args : List ArgDecl) + (posOnlyCount : Nat := 0) + (keywordOnly := args.length) : SignatureM Unit := do + assert! name ∉ (←get).functions + assert! posOnlyCount <= keywordOnly + let args := args.toArray + assert! keywordOnly <= args.size + + let argIndexMap : Std.HashMap String (Fin args.size) := + Fin.foldl args.size (init := {}) fun m i => + let a := args[i] + assert! a.name ∉ m + m.insert a.name i + + let .isTrue posOnlyBound := inferInstanceAs (Decidable (posOnlyCount <= keywordOnly)) + | return panic! "Invalid number of position-only parameters." + let .isTrue keywordBound := inferInstanceAs (Decidable (keywordOnly <= args.size)) + | return panic! "Invalid start for keyword only parameters." + + let decl : FuncDecl := { + args, + posOnlyCount, + keywordOnly, + posOnlyBound, + keywordBound, + argIndexMap, + } + modify fun m => { m with functions := m.functions.insert name decl } + +private def identToStr (t : Lean.TSyntax `ident) : Lean.StrLit := + match t.raw.isIdOrAtom? with + | none => panic! "Unexpected string" + | some s => Lean.Syntax.mkStrLit s + +scoped macro v:ident ":<" t:ident : term => `(ArgDecl.mk $(identToStr v) $(identToStr t)) + +end SignatureM + +section +open SignatureM + +def addCoreDecls : SignatureM Unit := do + decl "test_helper_procedure" [req_name :< string, opt_name :< StrOrNone] + decl "print" [msg :< string, opt :< StrOrNone] + decl "json_dumps" [msg :< DictStrAny, opt_indent :< IntOrNone] + decl "json_loads" [msg :< string] + decl "input" [msg :< string] + decl "random_choice" [l :< ListStr] + decl "datetime_now" [] + decl "datetime_utcnow" [] + decl "datetime_date" [dt :< Datetime] + decl "timedelta" [ days :< IntOrNone, hours :< IntOrNone] + decl "datetime_strptime" [time :< string, format :< string] + decl "str_to_float" [s :< string] + +def coreSignatures : Signatures := addCoreDecls |>.run + +end + +def TypeStrToBoogieExpr (ty: String) : Boogie.Expression.Expr := + if !ty.endsWith "OrNone" then + panic! s!"Should only be called for possibly None types. Called for: {ty}" + else + match ty with + | "StrOrNone" => .app () (.op () "StrOrNone_mk_none" none) (.op () "None_none" none) + | "BoolOrNone" => .app () (.op () "BoolOrNone_mk_none" none) (.op () "None_none" none) + | "BoolOrStrOrNone" => .app () (.op () "BoolOrStrOrNone_mk_none" none) (.op () "None_none" none) + | "AnyOrNone" => .app () (.op () "AnyOrNone_mk_none" none) (.op () "None_none" none) + | "IntOrNone" => .app () (.op () "IntOrNone_mk_none" none) (.op () "None_none" none) + | "BytesOrStrOrNone" => .app () (.op () "BytesOrStrOrNone_mk_none" none) (.op () "None_none" none) + | "DictStrStrOrNone" => .app () (.op () "DictStrStrOrNone_mk_none" none) (.op () "None_none" none) + | _ => panic! s!"unsupported type: {ty}" + +end Python +end Strata diff --git a/Strata/Languages/Python/PyFactory.lean b/Strata/Languages/Python/PyFactory.lean new file mode 100644 index 000000000..927e8dfd4 --- /dev/null +++ b/Strata/Languages/Python/PyFactory.lean @@ -0,0 +1,102 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Verifier +import Strata.Languages.Python.Regex.ReToBoogie + +namespace Strata +namespace Python + +------------------------------------------------------------------------------- + +/- +Candidate translation pass for Python `re` code: + +## Python Code: + +``` +... +PATTERN = r"^[a-z0-9][a-z0-9.-]{1,3}[a-z0-9]$" +REGEX = re.compile(PATTERN) # default flags == 0 +... +if not re.match(REGEX, name) then # default flags == 0 + return False +... +``` + +## Corresponding Strata.Boogie: + +``` +procedure _main () { + +var PATTERN : string = "^[a-z0-9][a-z0-9.-]{1,3}[a-z0-9]$"; + +var REGEX : regex; +var $__REGEX : Except Error regex := PyReCompile(PATTERN, 0) + +if ExceptErrorRegex_isOK($__REGEX) then { + REGEX := ExceptErrorRegex_getOK($__REGEX); +} else if (Error_isUnimplemented(ExceptErrorRegex_getError($__REGEX)) then { + // Unsupported by Strata. + havoc REGEX; +} else { + // + // TODO: Implement a version of `assert` that takes an expression to be + // evaluated when the assertion fails. In this case, we'd display the + // (computed) error message in `ExceptErrorRegex_getError($__REGEX)`. + // + // E.g., `assert false (printOnFailure := ExceptErrorRegex_getError($__REGEX));` + // + assert false; +} +... + +if not PyReMatch(REGEX, name, 0) then + return false +} +``` + +-/ + +open Boogie +open Lambda LTy.Syntax LExpr.SyntaxMono + +def reCompileFunc : LFunc Boogie.BoogieLParams := + { name := "PyReCompile", + typeArgs := [], + inputs := [("string", mty[string]), + ("flags", mty[int])] + output := mty[ExceptErrorRegex], + concreteEval := some + (fun _ args => match args with + | [LExpr.strConst () s, LExpr.intConst () 0] => + -- This function has a concrete evaluation implementation only when + -- flags == 0. + -- (FIXME): We use `.match` mode below because we support only + -- `re.match` for now. However, `re.compile` isn't mode-specific in + -- general. + let (expr, maybe_err) := pythonRegexToBoogie s .match + match maybe_err with + | none => + -- Note: Do not use `eb` (in Boogie.Syntax) here (e.g., see below) + -- eb[(~ExceptErrorRegex_mkOK expr)] + -- that captures `expr` as an `.fvar`. + .some (LExpr.mkApp () (.op () "ExceptErrorRegex_mkOK" none) [expr]) + | some (ParseError.unimplemented msg _pattern _pos) => + .some (LExpr.mkApp () (.op () "ExceptErrorRegex_mkErr" none) + [LExpr.mkApp () (.op () "Error_Unimplemented" none) [.strConst () (toString msg)]]) + | some (ParseError.patternError msg _pattern _pos) => + .some (LExpr.mkApp () (.op () "ExceptErrorRegex_mkErr" none) + [LExpr.mkApp () (.op () "Error_RePatternErr" none) [.strConst () (toString msg)]]) + | _ => .none) + } + +def ReFactory : @Factory Boogie.BoogieLParams := + #[ + reCompileFunc + ] + +------------------------------------------------------------------------------- diff --git a/Strata/Languages/Python/Python.lean b/Strata/Languages/Python/Python.lean new file mode 100644 index 000000000..dc56fc802 --- /dev/null +++ b/Strata/Languages/Python/Python.lean @@ -0,0 +1,10 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Python.PythonToBoogie +import Strata.Languages.Python.PythonDialect +import StrataTest.Internal.InternalBoogiePrelude +import StrataTest.Internal.InternalFunctionSignatures diff --git a/Strata/Languages/Python/PythonDialect.lean b/Strata/Languages/Python/PythonDialect.lean new file mode 100644 index 000000000..8ae84a57d --- /dev/null +++ b/Strata/Languages/Python/PythonDialect.lean @@ -0,0 +1,21 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean + + +namespace Strata + + + +namespace Python +#load_dialect "../../../Tools/Python/test_results/dialects/Python.dialect.st.ion" + +#strata_gen Python +end Python + + +end Strata diff --git a/Strata/Languages/Python/PythonToBoogie.lean b/Strata/Languages/Python/PythonToBoogie.lean new file mode 100644 index 000000000..e1bc2cf2b --- /dev/null +++ b/Strata/Languages/Python/PythonToBoogie.lean @@ -0,0 +1,791 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Elab +import Strata.DDM.AST + +import Strata.Languages.Boogie.DDMTransform.Parse + +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Python.PythonDialect +import Strata.Languages.Python.FunctionSignatures +import Strata.Languages.Python.Regex.ReToBoogie +import Strata.Languages.Python.PyFactory +import Strata.Languages.Python.FunctionSignatures + +namespace Strata +open Lambda.LTy.Syntax +-- Some hard-coded things we'll need to fix later: + +def clientType : Boogie.Expression.Ty := .forAll [] (.tcons "Client" []) +def dummyClient : Boogie.Expression.Expr := .fvar () "DUMMY_CLIENT" none + +def dictStrAnyType : Boogie.Expression.Ty := .forAll [] (.tcons "DictStrAny" []) +def dummyDictStrAny : Boogie.Expression.Expr := .fvar () "DUMMY_DICT_STR_ANY" none + +def strType : Boogie.Expression.Ty := .forAll [] (.tcons "string" []) +def dummyStr : Boogie.Expression.Expr := .fvar () "DUMMY_STR" none + +def listStrType : Boogie.Expression.Ty := .forAll [] (.tcons "ListStr" []) +def dummyListStr : Boogie.Expression.Expr := .fvar () "DUMMY_LIST_STR" none + +def datetimeType : Boogie.Expression.Ty := .forAll [] (.tcons "Datetime" []) +def dummyDatetime : Boogie.Expression.Expr := .fvar () "DUMMY_DATETIME" none + +def dateType : Boogie.Expression.Ty := .forAll [] (.tcons "Date" []) +def dummyDate : Boogie.Expression.Expr := .fvar () "DUMMY_DATE" none + +def timedeltaType : Boogie.Expression.Ty := .forAll [] (.tcons "int" []) +def dummyTimedelta : Boogie.Expression.Expr := .fvar () "DUMMY_Timedelta" none + +------------------------------------------------------------------------------- + +-- Translating a Python expression can require Boogie statements, e.g., a function call +-- We translate these by first defining temporary variables to store the results of the stmts +-- and then using those variables in the expression. +structure PyExprTranslated where + stmts : List Boogie.Statement + expr: Boogie.Expression.Expr + post_stmts : List Boogie.Statement := [] +deriving Inhabited + +structure PythonFunctionDecl where + name : String + args : List (String × String) -- Elements are (arg_name, arg_ty) where `arg_ty` is the string representation of the type in Python + ret : String +deriving Repr, BEq, Inhabited + +structure PythonClassDecl where + name : String +deriving Repr, BEq, Inhabited + +structure TranslationContext where + signatures : Python.Signatures + expectedType : Option (Lambda.LMonoTy) := none + variableTypes : List (String × Lambda.LMonoTy) := [] + func_infos : List PythonFunctionDecl := [] + class_infos : List PythonClassDecl := [] +deriving Inhabited + +------------------------------------------------------------------------------- + + +def toPyCommands (a : Array Operation) : Array (Python.Command SourceRange) := + a.map (λ op => match Python.Command.ofAst op with + | .error e => panic! s!"Failed to translate to Python.Command: {e}" + | .ok cmd => cmd) + +def unwrapModule (c : Python.Command SourceRange) : Array (Python.stmt SourceRange) := + match c with + | Python.Command.Module _ body _ => body.val + | _ => panic! "Expected module" + +def strToBoogieExpr (s: String) : Boogie.Expression.Expr := + .strConst () s + +def intToBoogieExpr (i: Int) : Boogie.Expression.Expr := + .intConst () i + +def PyIntToInt (i : Python.int SourceRange) : Int := + match i with + | .IntPos _ n => n.val + | .IntNeg _ n => -n.val + +def PyConstToBoogie (c: Python.constant SourceRange) : Boogie.Expression.Expr := + match c with + | .ConString _ s => .strConst () s.val + | .ConPos _ i => .intConst () i.val + | .ConNeg _ i => .intConst () (-i.val) + | .ConBytes _ _b => .const () (.strConst "") -- TODO: fix + | .ConFloat _ f => .strConst () (f.val) + | _ => panic! s!"Unhandled Constant: {repr c}" + +def PyAliasToBoogieExpr (a : Python.alias SourceRange) : Boogie.Expression.Expr := + match a with + | .mk_alias _ n as_n => + assert! as_n.val.isNone + .strConst () n.val + +def handleAdd (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + let lty : Lambda.LMonoTy := mty[string] + let rty : Lambda.LMonoTy := mty[string] + match lty, rty with + | (.tcons "string" []), (.tcons "string" []) => .app () (.app () (.op () "Str.Concat" mty[string → (string → string)]) lhs) rhs + | _, _ => panic! s!"Unimplemented add op for {lhs} + {rhs}" + +def handleSub (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + let lty : Lambda.LMonoTy := (.tcons "Datetime" []) + let rty : Lambda.LMonoTy := (.tcons "int" []) + match lty, rty with + | (.tcons "Datetime" []), (.tcons "int" []) => .app () (.app () (.op () "Datetime_sub" none) lhs) rhs + | _, _ => panic! s!"Unimplemented add op for {lhs} + {rhs}" + +def handleMult (translation_ctx: TranslationContext) (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + match lhs, rhs with + | .strConst () s, .intConst () i => .strConst () (String.join (List.replicate i.toNat s)) + | .intConst () l, .intConst () r => .intConst () (l * r) + | .fvar () l _, .fvar () r _ => + let l := translation_ctx.variableTypes.find? (λ p => p.fst == l.name) + let r := translation_ctx.variableTypes.find? (λ p => p.fst == r.name) + match l, r with + | .some lty, .some rty => + match lty.snd, rty.snd with + | .tcons "int" [], .tcons "int" [] => .app () (.app () (.op () "Int.Mul" mty[int → (int → int)]) lhs) rhs + | _, _ => panic! s!"Unsupported types for fvar *. Types: {lty} and {rty}" + | _, _ => panic! s!"Missing needed type information for *. Exprs: {lhs} and {rhs}" + | _ , _ => panic! s!"Unsupported args for * . Got: {lhs} and {rhs}" + +def handleFloorDiv (_translation_ctx: TranslationContext) (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + .app () (.app () (.op () "Int.Div" mty[int → (int → int)]) lhs) rhs + +def handleNot (arg: Boogie.Expression.Expr) : Boogie.Expression.Expr := + let ty : Lambda.LMonoTy := (.tcons "ListStr" []) + match ty with + | (.tcons "ListStr" []) => .eq () arg (.op () "ListStr_nil" none) + | _ => panic! s!"Unimplemented not op for {arg}" + +def handleLtE (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + let eq := (.eq () lhs rhs) + let lt := (.app () (.app () (.op () "Datetime_lt" none) lhs) rhs) + (.app () (.app () (.op () "Bool.Or" none) eq) lt) + +def handleGt (lhs rhs: Boogie.Expression.Expr) : Boogie.Expression.Expr := + (.app () (.app () (.op () "Float_gt" none) lhs) rhs) + +structure SubstitutionRecord where + pyExpr : Python.expr SourceRange + boogieExpr : Boogie.Expression.Expr + +instance : Repr (List SubstitutionRecord) where + reprPrec xs _ := + let py_exprs := xs.map (λ r => r.pyExpr) + s!"{repr py_exprs}" + +def PyExprIdent (e1 e2: Python.expr SourceRange) : Bool := + match e1, e2 with + | .Call sr1 _ _ _, .Call sr2 _ _ _ => sr1 == sr2 + | _ , _ => false + +-- TODO: handle rest of names +def PyListStrToBoogie (names : Array (Python.alias SourceRange)) : Boogie.Expression.Expr := + .app () (.app () (.op () "ListStr_cons" mty[string → (ListStr → ListStr)]) (PyAliasToBoogieExpr names[0]!)) + (.op () "ListStr_nil" mty[ListStr]) + +def handleList (_elmts: Array (Python.expr SourceRange)) (expected_type : Lambda.LMonoTy): PyExprTranslated := + match expected_type with + | (.tcons "ListStr" _) => {stmts := [], expr := (.op () "ListStr_nil" expected_type)} + | (.tcons "ListDictStrAny" _) => {stmts := [], expr := (.op () "ListDictStrAny_nil" expected_type)} + | _ => panic! s!"Unexpected type : {expected_type}" + +def PyOptExprToString (e : Python.opt_expr SourceRange) : String := + match e with + | .some_expr _ (.Constant _ (.ConString _ s) _) => s.val + | _ => panic! "Expected some constant string: {e}" + +partial def PyExprToString (e : Python.expr SourceRange) : String := + match e with + | .Name _ n _ => n.val + | .Attribute _ v attr _ => s!"{PyExprToString v}_{attr.val}" + | .Subscript _ v slice _ => + let v_name := PyExprToString v + match v_name with + | "Dict" => + match slice with + | .Tuple _ elts _ => + assert! elts.val.size == 2 + s!"Dict[{PyExprToString elts.val[0]!} {PyExprToString elts.val[1]!}]" + | _ => panic! s!"Unsupported slice: {repr slice}" + | "List" => + match slice with + | .Name _ id _ => s!"List[{id.val}]" + | _ => panic! s!"Unsupported slice: {repr slice}" + | _ => panic! s!"Unsupported subscript to string: {repr e}" + | _ => panic! s!"Unhandled Expr: {repr e}" + +def PyExprToMonoTy (e : Python.expr SourceRange) : Lambda.LMonoTy := + match e with + | .Name _ n _ => + match n.val with + | "bool" => .tcons "bool" [] + | "int" => .tcons "int" [] + | "str" => .tcons "string" [] + | "float" => .tcons "string" [] + | "Dict[str Any]" => .tcons "DictStrAny" [] + | "List[str]" => .tcons "ListStr" [] + | "datetime" => .tcons "Datetime" [] + | "date" => .tcons "Date" [] + | "timedelta" => .tcons "Timedelta" [] + | "Client" => .tcons "Client" [] + | "LatencyAnalyzer" => .tcons "LatencyAnalyzer" [] + | _ => panic! s!"Unhandled name: {repr e}" + | .Subscript _ val _slice _ => + match val with + | .Name _ n _ => + match n.val with + | "Dict" => .tcons "DictStrAny" [] + | "List" => .tcons "ListStr" [] + | _ => panic! s!"Unsupported name: {repr n}" + | _ => panic! s!"Expected name: {repr e}" + | _ => panic! s!"Unhandled Expr: {repr e}" + + +-- This information should come from our prelude. For now, we use the fact that +-- these functions are exactly the ones +-- represented as `Call(Attribute(Name(...)))` in the AST (instead of `Call(Name(...))`). +def callCanThrow (func_infos : List PythonFunctionDecl) (stmt: Python.stmt SourceRange) : Bool := + match stmt with + | .Expr _ (.Call _ (.Attribute _ _ _ _) _ _) | .Assign _ _ (.Call _ (.Attribute _ _ _ _) _ _) _ => true + | .Expr _ (.Call _ f _ _) | .Assign _ _ (.Call _ f _ _) _ => match f with + | .Name _ f _ => func_infos.any (λ fi => fi.name == f.val) + | _ => false + | _ => false + +def noneOrExpr (translation_ctx : TranslationContext) (fname n : String) (e: Boogie.Expression.Expr) : Boogie.Expression.Expr := + let type_str := translation_ctx.signatures.getFuncSigType fname n + if type_str.endsWith "OrNone" then + -- Optional param. Need to wrap e.g., string into StrOrNone + match type_str with + | "IntOrNone" => .app () (.op () "IntOrNone_mk_int" none) e + | "StrOrNone" => .app () (.op () "StrOrNone_mk_str" none) e + | "BytesOrStrOrNone" => .app () (.op () "BytesOrStrOrNone_mk_str" none) e + | _ => panic! "Unsupported type_str: "++ type_str + else + e + +def handleCallThrow (jmp_target : String) : Boogie.Statement := + let cond := .eq () (.app () (.op () "ExceptOrNone_tag" none) (.fvar () "maybe_except" none)) (.op () "EN_STR_TAG" none) + .ite cond [.goto jmp_target] [] + +def deduplicateTypeAnnotations (l : List (String × Option String)) : List (String × String) := Id.run do + let mut m : Map String String := [] + for p in l do + let name := p.fst + let oty := p.snd + match oty with + | .some ty => + match m.find? name with + | .some other_ty => + if ty != other_ty then + panic! s!"Type annotation mismatch: {other_ty} vs {ty}" + | .none => m := (name, ty) :: m + | .none => () + let names := l.map (λ p => p.fst) + let unique_names := names.dedup + unique_names.map (λ n => + match m.find? n with + | .some ty => (n, ty) + | .none => panic s!"Missing type annotations for {n}") + +partial def collectVarDecls (translation_ctx : TranslationContext) (stmts: Array (Python.stmt SourceRange)) : List Boogie.Statement := + let rec go (s : Python.stmt SourceRange) : List (String × Option String) := + match s with + | .Assign _ lhs _ _ => + let names := lhs.val.toList.map PyExprToString + names.map (λ n => (n, none)) + | .AnnAssign _ lhs ty _ _ => + [(PyExprToString lhs, PyExprToString ty)] + | .If _ _ body _ => body.val.toList.flatMap go + | .For _ _ _ body _ _ => body.val.toList.flatMap go + | _ => [] + let dup := stmts.toList.flatMap go + let dedup := deduplicateTypeAnnotations dup + let toBoogie (p: String × String) : List Boogie.Statement := + let name := p.fst + let ty_name := p.snd + match ty_name with + | "bool" => [(.init name t[bool] (.boolConst () false)), (.havoc name)] + | "str" => [(.init name t[string] (.strConst () "")), (.havoc name)] + | "int" => [(.init name t[int] (.intConst () 0)), (.havoc name)] + | "float" => [(.init name t[string] (.strConst () "0.0")), (.havoc name)] -- Floats as strs for now + | "bytes" => [(.init name t[string] (.strConst () "")), (.havoc name)] + | "Client" => [(.init name clientType dummyClient), (.havoc name)] + | "Dict[str Any]" => [(.init name dictStrAnyType dummyDictStrAny), (.havoc name)] + | "List[str]" => [(.init name listStrType dummyListStr), (.havoc name)] + | "datetime" => [(.init name datetimeType dummyDatetime), (.havoc name)] + | "date" => [(.init name dateType dummyDate), (.havoc name)] + | "timedelta" => [(.init name timedeltaType dummyTimedelta), (.havoc name)] + | _ => + let user_defined_class := translation_ctx.class_infos.find? (λ i => i.name == ty_name) + match user_defined_class with + | .some i => + let user_defined_class_ty := .forAll [] (.tcons i.name []) + let user_defined_class_dummy := .fvar () ("DUMMY_" ++ i.name) none + [(.init name user_defined_class_ty user_defined_class_dummy), (.havoc name)] + | .none => panic! s!"Unsupported type annotation: `{ty_name}`" + let foo := dedup.map toBoogie + foo.flatten + +def isCall (e: Python.expr SourceRange) : Bool := + match e with + | .Call _ _ _ _ => true + | _ => false + +def remapFname (translation_ctx: TranslationContext) (fname: String) : String := + match translation_ctx.class_infos.find? (λ i => i.name == fname) with + | .some i => + i.name ++ "___init__" + | _ => + match fname with + | "float" => "str_to_float" + | _ => fname + +mutual + +partial def PyExprToBoogieWithSubst (translation_ctx : TranslationContext) (substitution_records : Option (List SubstitutionRecord)) (e : Python.expr SourceRange) : PyExprTranslated := + PyExprToBoogie translation_ctx e substitution_records + +partial def PyKWordsToBoogie (substitution_records : Option (List SubstitutionRecord)) (kw : Python.keyword SourceRange) : (String × PyExprTranslated) := + match kw with + | .mk_keyword _ name expr => + match name.val with + | some n => (n.val, PyExprToBoogieWithSubst default substitution_records expr) + | none => panic! "Keyword arg should have a name" + +-- TODO: we should be checking that args are right +partial def argsAndKWordsToCanonicalList (translation_ctx : TranslationContext) + (fname: String) + (args : Array (Python.expr SourceRange)) + (kwords: Array (Python.keyword SourceRange)) + (substitution_records : Option (List SubstitutionRecord) := none) : List Boogie.Expression.Expr × List Boogie.Statement := + if translation_ctx.func_infos.any (λ e => e.name == fname) || translation_ctx.class_infos.any (λ e => e.name++"___init__" == fname) then + if translation_ctx.func_infos.any (λ e => e.name == fname) then + (args.toList.map (λ a => (PyExprToBoogieWithSubst default substitution_records a).expr), []) + else + (args.toList.map (λ a => (PyExprToBoogieWithSubst default substitution_records a).expr), []) + else + let required_order := translation_ctx.signatures.getFuncSigOrder fname + assert! args.size <= required_order.length + let remaining := required_order.drop args.size + let kws_and_exprs := kwords.toList.map (PyKWordsToBoogie substitution_records) + let ordered_remaining_args := remaining.map (λ n => match kws_and_exprs.find? (λ p => p.fst == n) with + | .some p => + noneOrExpr translation_ctx fname n p.snd.expr + | .none => Strata.Python.TypeStrToBoogieExpr (translation_ctx.signatures.getFuncSigType fname n)) + let args := args.map (PyExprToBoogieWithSubst default substitution_records) + let args := (List.range required_order.length).filterMap (λ n => + if n < args.size then + let arg_name := required_order[n]! -- Guaranteed by range. Using finRange causes breaking coercions to Nat. + some (noneOrExpr translation_ctx fname arg_name args[n]!.expr) + else + none) + (args ++ ordered_remaining_args, kws_and_exprs.flatMap (λ p => p.snd.stmts)) + +partial def handleDict (keys: Array (Python.opt_expr SourceRange)) (values: Array (Python.expr SourceRange)) : PyExprTranslated := + let dict := .app () (.op () "DictStrAny_mk" none) (.strConst () "DefaultDict") -- TODO: need to generate unique dict arg + assert! keys.size == values.size + let zipped := Array.zip keys values + + let res := zipped.toList.flatMap (λ (k, v) => + let n := PyOptExprToString k + let in_dict := (.assume s!"assume_{n}_in_dict" (.app () (.app () (.op () "str_in_dict_str_any" none) (.strConst () n)) dict)) + match v with + | .Call _ f args _ => + match f with + | .Name _ {ann := _ , val := "str"} _ => + assert! args.val.size == 1 + let dt := (.app () (.op () "datetime_to_str" none) ((PyExprToBoogie default args.val[0]!).expr)) + let dict_of_v_is_k := (.assume s!"assume_{n}_key" (.eq () (.app () (.app () (.op () "dict_str_any_get_str" none) dict) (.strConst () n)) dt)) + [in_dict, dict_of_v_is_k] + | _ => panic! "Unsupported function when constructing map" + | _ => + let dict_of_v_is_k := (.assume s!"assume_{n}_key" (.eq () (.app () (.app () (.op () "dict_str_any_get_str" none) dict) (.strConst () n)) (.strConst () "DummyVal"))) + [in_dict, dict_of_v_is_k]) + + {stmts := res , expr := dict, post_stmts := []} + +partial def PyExprToBoogie (translation_ctx : TranslationContext) (e : Python.expr SourceRange) (substitution_records : Option (List SubstitutionRecord) := none) : PyExprTranslated := + if h : substitution_records.isSome && (substitution_records.get!.find? (λ r => PyExprIdent r.pyExpr e)).isSome then + have hr : (List.find? (fun r => PyExprIdent r.pyExpr e) substitution_records.get!).isSome = true := by rw [Bool.and_eq_true] at h; exact h.2 + let record := (substitution_records.get!.find? (λ r => PyExprIdent r.pyExpr e)).get hr + {stmts := [], expr := record.boogieExpr} + else + match e with + | .Call _ f args kwords => + panic! s!"Call should be handled at stmt level: \n(func: {repr f}) \n(Records: {repr substitution_records}) \n(AST: {repr e.toAst})" + | .Constant _ c _ => {stmts := [], expr := PyConstToBoogie c} + | .Name _ n _ => + match n.val with + | "AssertionError" | "Exception" => {stmts := [], expr := .strConst () n.val} + | s => + match translation_ctx.variableTypes.find? (λ p => p.fst == s) with + | .some p => + if translation_ctx.expectedType == some (.tcons "bool" []) && p.snd == (.tcons "DictStrAny" []) then + let a := .fvar () n.val none + let e := .app () (.op () "Bool.Not" none) (.eq () (.app () (.op () "dict_str_any_length" none) a) (.intConst () 0)) + {stmts := [], expr := e} + else + {stmts := [], expr := .fvar () n.val none} + | .none => {stmts := [], expr := .fvar () n.val none} + | .JoinedStr _ ss => PyExprToBoogie translation_ctx ss.val[0]! -- TODO: need to actually join strings + | .BinOp _ lhs op rhs => + let lhs := (PyExprToBoogie translation_ctx lhs) + let rhs := (PyExprToBoogie translation_ctx rhs) + match op with + | .Add _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := handleAdd lhs.expr rhs.expr} + | .Sub _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := handleSub lhs.expr rhs.expr} + | .Mult _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := handleMult translation_ctx lhs.expr rhs.expr} + | _ => panic! s!"Unhandled BinOp: {repr e}" + | .Compare _ lhs op rhs => + let lhs := PyExprToBoogie translation_ctx lhs + assert! rhs.val.size == 1 + let rhs := PyExprToBoogie translation_ctx rhs.val[0]! + match op.val with + | #[v] => match v with + | Strata.Python.cmpop.Eq _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := (.eq () lhs.expr rhs.expr)} + | Strata.Python.cmpop.In _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := .app () (.app () (.op () "str_in_dict_str_any" none) lhs.expr) rhs.expr} + | Strata.Python.cmpop.LtE _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := handleLtE lhs.expr rhs.expr} + | Strata.Python.cmpop.Gt _ => + {stmts := lhs.stmts ++ rhs.stmts, expr := handleGt lhs.expr rhs.expr} + | _ => panic! s!"Unhandled comparison op: {repr op.val}" + | _ => panic! s!"Unhandled comparison op: {repr op.val}" + | .Dict _ keys values => + let res := handleDict keys.val values.val + res + | .ListComp _ keys values => panic! "ListComp must be handled at stmt level" + | .UnaryOp _ op arg => match op with + | .Not _ => {stmts := [], expr := handleNot (PyExprToBoogie translation_ctx arg).expr} + | _ => panic! "Unsupported UnaryOp: {repr e}" + | .Subscript _ v slice _ => + let l := PyExprToBoogie translation_ctx v + let k := PyExprToBoogie translation_ctx slice + -- TODO: we need to plumb the type of `v` here + match s!"{repr l.expr}" with + | "LExpr.fvar () { name := \"keys\", metadata := Boogie.Visibility.unres } none" => + -- let access_check : Boogie.Statement := .assert "subscript_bounds_check" (.app () (.app () (.op () "str_in_dict_str_any" none) k.expr) l.expr) + {stmts := l.stmts ++ k.stmts, expr := .app () (.app () (.op () "list_str_get" none) l.expr) k.expr} + | "LExpr.fvar () { name := \"blended_cost\", metadata := Boogie.Visibility.unres } none" => + -- let access_check : Boogie.Statement := .assert "subscript_bounds_check" (.app () (.app () (.op () "str_in_dict_str_any" none) k.expr) l.expr) + {stmts := l.stmts ++ k.stmts, expr := .app () (.app () (.op () "dict_str_any_get_str" none) l.expr) k.expr} + | _ => + match translation_ctx.expectedType with + | .some (.tcons "ListStr" []) => + let access_check : Boogie.Statement := .assert "subscript_bounds_check" (.app () (.app () (.op () "str_in_dict_str_any" none) k.expr) l.expr) + {stmts := l.stmts ++ k.stmts ++ [access_check], expr := .app () (.app () (.op () "dict_str_any_get_list_str" none) l.expr) k.expr} + | _ => + let access_check : Boogie.Statement := .assert "subscript_bounds_check" (.app () (.app () (.op () "str_in_dict_str_any" none) k.expr) l.expr) + {stmts := l.stmts ++ k.stmts ++ [access_check], expr := .app () (.app () (.op () "dict_str_any_get" none) l.expr) k.expr} + | .List _ elmts _ => + match elmts.val[0]! with + | .Constant _ expr _ => match expr with + | .ConString _ s => handleList elmts.val (.tcons "ListStr" []) + | _ => panic! s!"Expr: {repr expr}" + | .Dict _ _ _ => handleList elmts.val (.tcons "ListDictStrAny" []) + | _ => panic! s!"Unexpected element: {repr elmts.val[0]!}" + | _ => panic! s!"Unhandled Expr: {repr e}" + +partial def initTmpParam (p: Python.expr SourceRange × String) : List Boogie.Statement := + match p.fst with + | .Call _ f args _ => + match f with + | .Name _ n _ => + match n.val with + | "json_dumps" => [(.init p.snd t[string] (.strConst () "")), .call [p.snd, "maybe_except"] "json_dumps" [(.app () (.op () "DictStrAny_mk" none) (.strConst () "DefaultDict")), (Strata.Python.TypeStrToBoogieExpr "IntOrNone")]] + | "str" => + assert! args.val.size == 1 + [(.init p.snd t[string] (.strConst () "")), .set p.snd (.app () (.op () "datetime_to_str" none) ((PyExprToBoogie default args.val[0]!).expr))] + | "int" => [(.init p.snd t[int] (.intConst () 0)), .set p.snd (.op () "datetime_to_int" none)] + | _ => panic! s!"Unsupported name {n.val}" + | _ => panic! s!"Unsupported tmp param init call: {repr f}" + | _ => panic! "Expected Call" + +partial def exceptHandlersToBoogie (jmp_targets: List String) (translation_ctx: TranslationContext) (h : Python.excepthandler SourceRange) : List Boogie.Statement := + assert! jmp_targets.length >= 2 + match h with + | .ExceptHandler _ ex_ty _ body => + let set_ex_ty_matches := match ex_ty.val with + | .some ex_ty => + let inherits_from : Boogie.BoogieIdent := "inheritsFrom" + let get_ex_tag : Boogie.BoogieIdent := "ExceptOrNone_code_val" + let exception_ty : Boogie.Expression.Expr := .app () (.op () get_ex_tag none) (.fvar () "maybe_except" none) + let rhs_curried : Boogie.Expression.Expr := .app () (.op () inherits_from none) exception_ty + let res := PyExprToBoogie translation_ctx ex_ty + let rhs : Boogie.Expression.Expr := .app () rhs_curried (res.expr) + let call := .set "exception_ty_matches" rhs + res.stmts ++ [call] + | .none => + [.set "exception_ty_matches" (.boolConst () false)] + let cond := .fvar () "exception_ty_matches" none + let body_if_matches := body.val.toList.flatMap (λ s => (PyStmtToBoogie jmp_targets translation_ctx s).fst) ++ [.goto jmp_targets[1]!] + set_ex_ty_matches ++ [.ite cond body_if_matches []] + +partial def handleFunctionCall (lhs: List Boogie.Expression.Ident) + (fname: String) + (args: Ann (Array (Python.expr SourceRange)) SourceRange) + (kwords: Ann (Array (Python.keyword SourceRange)) SourceRange) + (_jmp_targets: List String) + (translation_ctx: TranslationContext) + (_s : Python.stmt SourceRange) : List Boogie.Statement := + + let fname := remapFname translation_ctx fname + + -- Boogie doesn't allow nested function calls, so we need to introduce temporary variables for each nested call + let nested_args_calls := args.val.filterMap (λ a => if isCall a then some a else none) + let args_calls_to_tmps := nested_args_calls.map (λ a => (a, s!"call_arg_tmp_{a.toAst.ann.start}")) + let nested_kwords_calls := kwords.val.filterMap (λ a => + let arg := match a with + | .mk_keyword _ _ f => f + if isCall arg then some arg else none) + let kwords_calls_to_tmps := nested_kwords_calls.map (λ a => (a, s!"call_kword_tmp_{a.toAst.ann.start}")) + + let substitution_records : List SubstitutionRecord := args_calls_to_tmps.toList.map (λ p => {pyExpr := p.fst, boogieExpr := .fvar () p.snd none}) ++ + kwords_calls_to_tmps.toList.map (λ p => {pyExpr := p.fst, boogieExpr := .fvar () p.snd none}) + let res := argsAndKWordsToCanonicalList translation_ctx fname args.val kwords.val substitution_records + args_calls_to_tmps.toList.flatMap initTmpParam ++ + kwords_calls_to_tmps.toList.flatMap initTmpParam ++ + res.snd ++ [.call lhs fname res.fst] + +partial def handleComprehension (lhs: Python.expr SourceRange) (gen: Array (Python.comprehension SourceRange)) : List Boogie.Statement := + assert! gen.size == 1 + match gen[0]! with + | .mk_comprehension _ _ itr _ _ => + let res := PyExprToBoogie default itr + let guard := .app () (.op () "Bool.Not" none) (.eq () (.app () (.op () "dict_str_any_length" none) res.expr) (.intConst () 0)) + let then_ss: List Boogie.Statement := [.havoc (PyExprToString lhs)] + let else_ss: List Boogie.Statement := [.set (PyExprToString lhs) (.op () "ListStr_nil" none)] + res.stmts ++ [.ite guard then_ss else_ss] + +partial def PyStmtToBoogie (jmp_targets: List String) (translation_ctx : TranslationContext) (s : Python.stmt SourceRange) : List Boogie.Statement × TranslationContext := + assert! jmp_targets.length > 0 + let non_throw : List Boogie.Statement × Option (String × Lambda.LMonoTy) := match s with + | .Import _ names => + ([.call [] "import" [PyListStrToBoogie names.val]], none) + | .ImportFrom _ s names i => + let n := match s.val with + | some s => [strToBoogieExpr s.val] + | none => [] + let i := match i.val with + | some i => [intToBoogieExpr (PyIntToInt i)] + | none => [] + ([.call [] "importFrom" (n ++ [PyListStrToBoogie names.val] ++ i)], none) + | .Expr _ (.Call _ func args kwords) => + let fname := PyExprToString func + if callCanThrow translation_ctx.func_infos s then + (handleFunctionCall ["maybe_except"] fname args kwords jmp_targets translation_ctx s, none) + else + (handleFunctionCall [] fname args kwords jmp_targets translation_ctx s, none) + | .Expr _ (.Constant _ (.ConString _ _) _) => + -- TODO: Check that it's a doc string + ([], none) -- Doc string + | .Expr _ _ => + panic! s!"Can't handle Expr statements that aren't calls: {repr s}" + | .Assign _ lhs (.Call _ func args kwords) _ => + assert! lhs.val.size == 1 + let fname := PyExprToString func + (handleFunctionCall [PyExprToString lhs.val[0]!, "maybe_except"] fname args kwords jmp_targets translation_ctx s, none) + | .Assign _ lhs rhs _ => + assert! lhs.val.size == 1 + let res := PyExprToBoogie translation_ctx rhs + (res.stmts ++ [.set (PyExprToString lhs.val[0]!) res.expr], none) + | .AnnAssign _ lhs ty { ann := _ , val := (.some (.Call _ func args kwords))} _ => + let fname := PyExprToString func + (handleFunctionCall [PyExprToString lhs, "maybe_except"] fname args kwords jmp_targets translation_ctx s, some (PyExprToString lhs, PyExprToMonoTy ty)) + | .AnnAssign _ lhs ty { ann := _ , val := (.some (.ListComp _ _ gen))} _ => + (handleComprehension lhs gen.val, some (PyExprToString lhs, PyExprToMonoTy ty)) + | .AnnAssign _ lhs ty {ann := _, val := (.some e)} _ => + let res := (PyExprToBoogie {translation_ctx with expectedType := PyExprToMonoTy ty} e) + (res.stmts ++ [.set (PyExprToString lhs) res.expr], some (PyExprToString lhs, PyExprToMonoTy ty)) + | .Try _ body handlers _orelse _finalbody => + let new_target := s!"excepthandlers_{jmp_targets[0]!}" + let entry_except_handlers := [.block new_target []] + let new_jmp_stack := new_target :: jmp_targets + let except_handlers := handlers.val.toList.flatMap (exceptHandlersToBoogie new_jmp_stack translation_ctx) + let var_decls := collectVarDecls translation_ctx body.val + ([.block "try_block" (var_decls ++ body.val.toList.flatMap (λ s => (PyStmtToBoogie new_jmp_stack translation_ctx s).fst) ++ entry_except_handlers ++ except_handlers)], none) + | .FunctionDef _ _ _ _ _ _ _ _ => panic! "Can't translate FunctionDef to Boogie statement" + | .If _ test then_b else_b => + let guard_ctx := {translation_ctx with expectedType := some (.tcons "bool" [])} + ([.ite (PyExprToBoogie guard_ctx test).expr (ArrPyStmtToBoogie translation_ctx then_b.val).fst (ArrPyStmtToBoogie translation_ctx else_b.val).fst], none) + | .Return _ v => + match v.val with + | .some v => ([.set "ret" (PyExprToBoogie translation_ctx v).expr, .goto jmp_targets[0]!], none) -- TODO: need to thread return value name here. For now, assume "ret" + | .none => ([.goto jmp_targets[0]!], none) + | .For _ tgt itr body _ _ => + -- Do one unrolling: + let guard := .app () (.op () "Bool.Not" none) (.eq () (.app () (.op () "dict_str_any_length" none) (PyExprToBoogie default itr).expr) (.intConst () 0)) + match tgt with + | .Name _ n _ => + let assign_tgt := [(.init n.val dictStrAnyType dummyDictStrAny)] + ([.ite guard (assign_tgt ++ (ArrPyStmtToBoogie translation_ctx body.val).fst) []], none) + | _ => panic! s!"tgt must be single name: {repr tgt}" + -- TODO: missing havoc + | .While _ test body _ => + -- Do one unrolling: + let guard := .app () (.op () "Bool.Not" none) (.eq () (.app () (.op () "dict_str_any_length" none) (PyExprToBoogie default test).expr) (.intConst () 0)) + ([.ite guard (ArrPyStmtToBoogie translation_ctx body.val).fst []], none) + -- TODO: missing havoc + | .Assert _ a _ => + let res := PyExprToBoogie translation_ctx a + ([(.assert "py_assertion" res.expr)], none) + | .AugAssign _ lhs op rhs => + match op with + | .Add _ => + match lhs with + | .Name _ n _ => + let rhs := PyExprToBoogie translation_ctx rhs + let new_lhs := (.strConst () "DUMMY_FLOAT") + (rhs.stmts ++ [.set n.val new_lhs], none) + | _ => panic! s!"Expected lhs to be name: {repr lhs}" + | .FloorDiv _ => + match lhs with + | .Name _ n _ => + let lhs := PyExprToBoogie translation_ctx lhs + let rhs := PyExprToBoogie translation_ctx rhs + let new_lhs := .app () (.app () (.op () "Int.Div" mty[int → (int → int)]) lhs.expr) rhs.expr + (rhs.stmts ++ [.set n.val new_lhs], none) + | _ => panic! s!"Expected lhs to be name: {repr lhs}" + | _ => panic! s!"Unsupported AugAssign op: {repr op}" + | _ => + panic! s!"Unsupported {repr s}" + let new_translation_ctx := match non_throw.snd with + | .some s => {translation_ctx with variableTypes := s :: translation_ctx.variableTypes} + | .none => translation_ctx + if callCanThrow translation_ctx.func_infos s then + (non_throw.fst ++ [handleCallThrow jmp_targets[0]!], new_translation_ctx) + else + (non_throw.fst, new_translation_ctx) + +partial def ArrPyStmtToBoogie (translation_ctx: TranslationContext) (a : Array (Python.stmt SourceRange)) : (List Boogie.Statement × TranslationContext) := + a.foldl (fun (stmts, ctx) stmt => + let (newStmts, newCtx) := PyStmtToBoogie ["end"] ctx stmt + (stmts ++ newStmts, newCtx) + ) ([], translation_ctx) + +end --mutual + + +def translateFunctions (a : Array (Python.stmt SourceRange)) (translation_ctx: TranslationContext) : List Boogie.Decl := + a.toList.filterMap (λ s => match s with + | .FunctionDef _ name _args body _ _ret _ _ => + + let varDecls : List Boogie.Statement := [] + let proc : Boogie.Procedure := { + header := { + name := name.val, + typeArgs := [], + inputs := [], + outputs := [("maybe_except", (.tcons "ExceptOrNone" []))]}, + spec := default, + body := varDecls ++ (ArrPyStmtToBoogie translation_ctx body.val).fst ++ [.block "end" []] + } + some (.proc proc) + | _ => none) + +def pyTyStrToLMonoTy (ty_str: String) : Lambda.LMonoTy := + match ty_str with + | "str" => mty[string] + | "int" => mty[int] + | "datetime" => (.tcons "Datetime" []) + | _ => panic! s!"Unsupported type: {ty_str}" + +def pythonFuncToBoogie (name : String) (args: List (String × String)) (body: Array (Python.stmt SourceRange)) (ret : Option (Python.expr SourceRange)) (spec : Boogie.Procedure.Spec) (translation_ctx : TranslationContext) : Boogie.Procedure := + let inputs : List (Lambda.Identifier Boogie.Visibility × Lambda.LMonoTy) := args.map (λ p => (p.fst, pyTyStrToLMonoTy p.snd)) + let varDecls := collectVarDecls translation_ctx body ++ [(.init "exception_ty_matches" t[bool] (.boolConst () false)), (.havoc "exception_ty_matches")] + let stmts := (ArrPyStmtToBoogie translation_ctx body).fst + let body := varDecls ++ stmts ++ [.block "end" []] + let constructor := name.endsWith "___init__" + let outputs : Lambda.LMonoTySignature := if not constructor then + match ret with + | .some _v => [("ret", (.tcons "DictStrAny" [])), ("maybe_except", (.tcons "ExceptOrNone" []))] + | .none => [("maybe_except", (.tcons "ExceptOrNone" []))] + else + let class_ty_name := name.dropRight ("___init__".length) + [("ret", (.tcons s!"{class_ty_name}" [])), ("maybe_except", (.tcons "ExceptOrNone" []))] + { + header := {name, + typeArgs := [], + inputs, + outputs}, + spec, + body + } + +def unpackPyArguments (args: Python.arguments SourceRange) : List (String × String) := +-- Python AST: +-- arguments = (arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs, +-- expr* kw_defaults, arg? kwarg, expr* defaults) + match args with -- TODO: Error if any other types of args + | .mk_arguments _ _ args _ _ _ _ _ => + let combined := args.val + combined.toList.filterMap (λ a => + match a with + | .mk_arg _ name oty _ => + if name.val == "self" then + none + else + match oty.val with + | .some ty => some (name.val, PyExprToString ty) + | _ => panic! s!"Missing type annotation on arg: {repr a} ({repr args})") + +def PyFuncDefToBoogie (s: Python.stmt SourceRange) (translation_ctx: TranslationContext) : List Boogie.Decl × PythonFunctionDecl := + match s with + | .FunctionDef _ name args body _ ret _ _ => + let args := unpackPyArguments args + ([.proc (pythonFuncToBoogie name.val args body.val ret.val default translation_ctx)], {name := name.val, args, ret := s!"{repr ret}"}) + | _ => panic! s!"Expected function def: {repr s}" + +def PyClassDefToBoogie (s: Python.stmt SourceRange) (translation_ctx: TranslationContext) : List Boogie.Decl × PythonClassDecl := + match s with + | .ClassDef _ c_name _ _ body _ _ => + let member_fn_defs := body.val.toList.filterMap (λ s => match s with + | .FunctionDef _ name args body _ ret _ _ => some (name, args, body, ret) + | _ => none) + (member_fn_defs.map (λ f => + let name := f.fst.val + let args := unpackPyArguments f.snd.fst + let body := f.snd.snd.fst.val + let ret := f.snd.snd.snd.val + .proc (pythonFuncToBoogie (c_name.val++"_"++name) args body ret default translation_ctx)), {name := c_name.val}) + | _ => panic! s!"Expected function def: {repr s}" + +def pythonToBoogie (signatures : Python.Signatures) (pgm: Strata.Program): Boogie.Program := + let pyCmds := toPyCommands pgm.commands + assert! pyCmds.size == 1 + let insideMod := unwrapModule pyCmds[0]! + let func_defs := insideMod.filter (λ s => match s with + | .FunctionDef _ _ _ _ _ _ _ _ => true + | _ => false) + + let class_defs := insideMod.filter (λ s => match s with + | .ClassDef _ _ _ _ _ _ _ => true + | _ => false) + + let non_func_blocks := insideMod.filter (λ s => match s with + | .FunctionDef _ _ _ _ _ _ _ _ => false + | .ClassDef _ _ _ _ _ _ _ => false + | _ => true) + + let globals := [(.var "__name__" (.forAll [] mty[string]) (.strConst () "__main__"))] + + let rec helper {α : Type} (f : Python.stmt SourceRange → TranslationContext → List Boogie.Decl × α) + (update : TranslationContext → α → TranslationContext) + (acc : TranslationContext) : + List (Python.stmt SourceRange) → List Boogie.Decl × TranslationContext + | [] => ([], acc) + | x :: xs => + let (y, info) := f x acc + let new_acc := update acc info + let (ys, acc'') := helper f update new_acc xs + (y ++ ys, acc'') + let func_info : TranslationContext := { signatures } + + let func_defs_and_infos := helper PyFuncDefToBoogie (fun acc info => {acc with func_infos := info :: acc.func_infos}) func_info func_defs.toList + let func_defs := func_defs_and_infos.fst + let func_infos := func_defs_and_infos.snd + + let class_defs_and_infos := helper PyClassDefToBoogie (fun acc info => {acc with class_infos := info :: acc.class_infos}) func_infos class_defs.toList + let class_defs := class_defs_and_infos.fst + let class_infos := class_defs_and_infos.snd + let class_ty_decls := [(.type (.con {name := "LatencyAnalyzer", numargs := 0})) ] + + {decls := globals ++ class_ty_decls ++ func_defs ++ class_defs ++ [.proc (pythonFuncToBoogie "__main__" [] non_func_blocks none default class_infos)]} + +end Strata diff --git a/Strata/Languages/Python/Regex/ReParser.lean b/Strata/Languages/Python/Regex/ReParser.lean new file mode 100644 index 000000000..5832c8288 --- /dev/null +++ b/Strata/Languages/Python/Regex/ReParser.lean @@ -0,0 +1,733 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +namespace Strata +namespace Python + +/- +Parser and translator for some basic regular expression patterns supported by +Python's `re` library +Ref.: https://docs.python.org/3/library/re.html + +Also see +https://github.com/python/cpython/blob/759a048d4bea522fda2fe929be0fba1650c62b0e/Lib/re/_parser.py +for a reference implementation. +-/ + +------------------------------------------------------------------------------- + +inductive ParseError where + /-- + `patternError` is raised when Python's `re.patternError` exception is + raised. + [Reference: Python's re exceptions](https://docs.python.org/3/library/re.html#exceptions): + + "Exception raised when a string passed to one of the functions here is not a + valid regular expression (for example, it might contain unmatched + parentheses) or when some other error occurs during compilation or matching. + It is never an error if a string contains no match for a pattern." + -/ + | patternError (message : String) (pattern : String) (pos : String.Pos.Raw) + /-- + `unimplemented` is raised whenever we don't support some regex operations + (e.g., lookahead assertions). + -/ + | unimplemented (message : String) (pattern : String) (pos : String.Pos.Raw) + deriving Repr + +def ParseError.toString : ParseError → String + | .patternError msg pat pos => s!"Pattern error at position {pos.byteIdx}: {msg} in pattern '{pat}'" + | .unimplemented msg pat pos => s!"Unimplemented at position {pos.byteIdx}: {msg} in pattern '{pat}'" + +instance : ToString ParseError where + toString := ParseError.toString + +------------------------------------------------------------------------------- + +/-- +Regular Expression Nodes +-/ +inductive RegexAST where + /-- Single literal character: `a` -/ + | char : Char → RegexAST + /-- Character range: `[a-z]` -/ + | range : Char → Char → RegexAST + /-- Alternation: `a|b` -/ + | union : RegexAST → RegexAST → RegexAST + /-- Concatenation: `ab` -/ + | concat : RegexAST → RegexAST → RegexAST + /-- Any character: `.` -/ + | anychar : RegexAST + /-- Zero or more: `a*` -/ + | star : RegexAST → RegexAST + /-- One or more: `a+` -/ + | plus : RegexAST → RegexAST + /-- Zero or one: `a?` -/ + | optional : RegexAST → RegexAST + /-- Bounded repetition: `a{n,m}` -/ + | loop : RegexAST → Nat → Nat → RegexAST + /-- Start of string: `^` -/ + | anchor_start : RegexAST + /-- End of string: `$` -/ + | anchor_end : RegexAST + /-- Grouping: `(abc)` -/ + | group : RegexAST → RegexAST + /-- Empty string: `()` or `""` -/ + | empty : RegexAST + /-- Complement: `[^a-z]` -/ + | complement : RegexAST → RegexAST + deriving Inhabited, Repr + +------------------------------------------------------------------------------- + +/-- Parse character class like [a-z], [0-9], etc. into union of ranges and + chars. Note that this parses `|` as a character. -/ +def parseCharClass (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do + if pos.get? s != some '[' then throw (.patternError "Expected '[' at start of character class" s pos) + let mut i := pos.next s + + -- Check for complement (negation) with leading ^ + let isComplement := !i.atEnd s && i.get? s == some '^' + if isComplement then + i := i.next s + + let mut result : Option RegexAST := none + + -- Process each element in the character class. + while !i.atEnd s && i.get? s != some ']' do + let some c1 := i.get? s | throw (.patternError "Invalid character in class" s i) + let i1 := i.next s + -- Check for range pattern: c1-c2. + if !i1.atEnd s && i1.get? s == some '-' then + let i2 := i1.next s + if !i2.atEnd s && i2.get? s != some ']' then + let some c2 := i2.get? s | throw (.patternError "Invalid character in range" s i2) + if c1 > c2 then + throw (.patternError s!"Invalid character range [{c1}-{c2}]: \ + start character '{c1}' is greater than end character '{c2}'" s i) + let r := RegexAST.range c1 c2 + -- Union with previous elements. + result := some (match result with | none => r | some prev => RegexAST.union prev r) + i := i2.next s + continue + -- Single character. + let r := RegexAST.char c1 + result := some (match result with | none => r | some prev => RegexAST.union prev r) + i := i.next s + + let some ast := result | throw (.patternError "Unterminated character set" s pos) + let finalAst := if isComplement then RegexAST.complement ast else ast + pure (finalAst, i.next s) + +------------------------------------------------------------------------------- + +/-- Parse numeric repeats like `{10}` or `{1,10}` into min and max bounds. -/ +def parseBounds (s : String) (pos : String.Pos.Raw) : Except ParseError (Nat × Nat × String.Pos.Raw) := do + if pos.get? s != some '{' then throw (.patternError "Expected '{' at start of bounds" s pos) + let mut i := pos.next s + let mut numStr := "" + + -- Parse first number. + while !i.atEnd s && (i.get? s).any Char.isDigit do + numStr := numStr.push ((i.get? s).get!) + i := i.next s + + let some n := numStr.toNat? | throw (.patternError "Invalid minimum bound" s pos) + + -- Check for comma (range) or closing brace (exact count). + match i.get? s with + | some '}' => pure (n, n, i.next s) -- {n} means exactly n times. + | some ',' => + i := i.next s + -- Parse maximum bound + numStr := "" + while !i.atEnd s && (i.get? s).any Char.isDigit do + numStr := numStr.push ((i.get? s).get!) + i := i.next s + let some max := numStr.toNat? | throw (.patternError "Invalid maximum bound" s i) + if i.get? s != some '}' then throw (.patternError "Expected '}' at end of bounds" s i) + -- Validate bounds order + if max < n then + throw (.patternError s!"Invalid repeat bounds \{{n},{max}}: \ + maximum {max} is less than minimum {n}" s pos) + pure (n, max, i.next s) + | _ => throw (.patternError "Invalid bounds syntax" s i) + +------------------------------------------------------------------------------- + +-- N.B. This disables a feature introduced in Lean PR #10823 that +-- causes a timeout in the mutual block below. +-- +-- Once we upgrade past 4.26.0, we should be able to remove this option. +set_option backwards.match.sparseCases false + +mutual +/-- +Parse atom: single element (char, class, anchor, group) with optional +quantifier. Stops at the first `|`. +-/ +partial def parseAtom (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do + if pos.atEnd s then throw (.patternError "Unexpected end of regex" s pos) + + let some c := pos.get? s | throw (.patternError "Invalid position" s pos) + + -- Detect invalid quantifier at start + if c == '*' || c == '+' || c == '{' || c == '?' then + throw (.patternError s!"Quantifier '{c}' at position {pos} has nothing to quantify" s pos) + + -- Detect unbalanced closing parenthesis + if c == ')' then + throw (.patternError "Unbalanced parenthesis" s pos) + + -- Parse base element (anchor, char class, group, anychar, escape, or single char). + let (base, nextPos) ← match c with + | '^' => pure (RegexAST.anchor_start, pos.next s) + | '$' => pure (RegexAST.anchor_end, pos.next s) + | '[' => parseCharClass s pos + | '(' => parseExplicitGroup s pos + | '.' => pure (RegexAST.anychar, pos.next s) + | '\\' => + -- Handle escape sequence. + -- Note: Python uses a single backslash as an escape character, but Lean + -- strings need to escape that. After DDMification, we will see two + -- backslashes in Strata for every Python backslash. + let nextPos := pos.next s + if nextPos.atEnd s then throw (.patternError "Incomplete escape sequence at end of regex" s pos) + let some escapedChar := nextPos.get? s | throw (.patternError "Invalid escape position" s nextPos) + -- Check for special sequences (unsupported right now). + match escapedChar with + | 'A' | 'b' | 'B' | 'd' | 'D' | 's' | 'S' | 'w' | 'W' | 'z' | 'Z' => + throw (.unimplemented s!"Special sequence \\{escapedChar} is not supported" s pos) + | 'a' | 'f' | 'n' | 'N' | 'r' | 't' | 'u' | 'U' | 'v' | 'x' => + throw (.unimplemented s!"Escape sequence \\{escapedChar} is not supported" s pos) + | c => + if c.isDigit then + throw (.unimplemented s!"Backreference \\{c} is not supported" s pos) + else + pure (RegexAST.char escapedChar, nextPos.next s) + | _ => pure (RegexAST.char c, pos.next s) + + -- Check for numeric repeat suffix on base element (but not on anchors) + match base with + | .anchor_start | .anchor_end => pure (base, nextPos) + | _ => + if !nextPos.atEnd s then + match nextPos.get? s with + | some '{' => + let (min, max, finalPos) ← parseBounds s nextPos + pure (RegexAST.loop base min max, finalPos) + | some '*' => + let afterStar := nextPos.next s + if !afterStar.atEnd s then + match afterStar.get? s with + | some '?' => throw (.unimplemented "Non-greedy quantifier *? is not supported" s nextPos) + | some '+' => throw (.unimplemented "Possessive quantifier *+ is not supported" s nextPos) + | _ => pure (RegexAST.star base, afterStar) + else pure (RegexAST.star base, afterStar) + | some '+' => + let afterPlus := nextPos.next s + if !afterPlus.atEnd s then + match afterPlus.get? s with + | some '?' => throw (.unimplemented "Non-greedy quantifier +? is not supported" s nextPos) + | some '+' => throw (.unimplemented "Possessive quantifier ++ is not supported" s nextPos) + | _ => pure (RegexAST.plus base, afterPlus) + else pure (RegexAST.plus base, afterPlus) + | some '?' => + let afterQuestion := nextPos.next s + if !afterQuestion.atEnd s then + match afterQuestion.get? s with + | some '?' => throw (.unimplemented "Non-greedy quantifier ?? is not supported" s nextPos) + | some '+' => throw (.unimplemented "Possessive quantifier ?+ is not supported" s nextPos) + | _ => pure (RegexAST.optional base, afterQuestion) + else pure (RegexAST.optional base, afterQuestion) + | _ => pure (base, nextPos) + else + pure (base, nextPos) + +/-- Parse explicit group with parentheses. -/ +partial def parseExplicitGroup (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do + if pos.get? s != some '(' then throw (.patternError "Expected '(' at start of group" s pos) + let mut i := pos.next s + + -- Check for extension notation (?... + if !i.atEnd s && i.get? s == some '?' then + let i1 := i.next s + if !i1.atEnd s then + match i1.get? s with + | some '=' => throw (.unimplemented "Positive lookahead (?=...) is not supported" s pos) + | some '!' => throw (.unimplemented "Negative lookahead (?!...) is not supported" s pos) + | _ => throw (.unimplemented "Extension notation (?...) is not supported" s pos) + + let (inner, finalPos) ← parseGroup s i (some ')') + pure (.group inner, finalPos) + +/-- Parse group: handles alternation and concatenation at current scope. -/ +partial def parseGroup (s : String) (pos : String.Pos.Raw) (endChar : Option Char) : + Except ParseError (RegexAST × String.Pos.Raw) := do + let mut alternatives : List (List RegexAST) := [[]] + let mut i := pos + + -- Parse until end of string or `endChar`. + while !i.atEnd s && (endChar.isNone || i.get? s != endChar) do + if i.get? s == some '|' then + -- Push a new scope to `alternatives`. + alternatives := [] :: alternatives + i := i.next s + else + let (ast, nextPos) ← parseAtom s i + alternatives := match alternatives with + | [] => [[ast]] + | head :: tail => (ast :: head) :: tail + i := nextPos + + -- Check for expected end character. + if let some ec := endChar then + if i.get? s != some ec then + throw (.patternError s!"Expected '{ec}'" s i) + i := i.next s + + -- Build result: concatenate each alternative, then union them. + let concatAlts := alternatives.reverse.filterMap fun alt => + match alt.reverse with + | [] => -- Empty regex. + some (.empty) + | [single] => some single + | head :: tail => some (tail.foldl RegexAST.concat head) + + match concatAlts with + | [] => pure (.empty, i) + | [single] => pure (single, i) + | head :: tail => pure (tail.foldl RegexAST.union head, i) + +end + +set_option backwards.match.sparseCases true + +/-- Parse entire regex string (implicit top-level group). -/ +def parseTop (s : String) : Except ParseError RegexAST := + parseGroup s 0 none |>.map (fun (r, _) => r) + +------------------------------------------------------------------------------- + +section Test.parseCharClass + +/-- info: Except.ok (Strata.Python.RegexAST.range 'A' 'z', { byteIdx := 5 }) -/ +#guard_msgs in +#eval parseCharClass "[A-z]" ⟨0⟩ +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Invalid character range [a-Z]: start character 'a' is greater than end character 'Z'" + "[a-Z]" + { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseCharClass "[a-Z]" ⟨0⟩ + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Invalid character range [a-0]: start character 'a' is greater than end character '0'" + "[a-0]" + { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseCharClass "[a-0]" ⟨0⟩ + +/-- +info: Except.ok (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.range 'a' 'z') (Strata.Python.RegexAST.range '0' '9')) + (Strata.Python.RegexAST.range 'A' 'Z'), + { byteIdx := 11 }) +-/ +#guard_msgs in +#eval parseCharClass "[a-z0-9A-Z]" ⟨0⟩ +/-- +info: Except.ok (Strata.Python.RegexAST.union (Strata.Python.RegexAST.char '0') (Strata.Python.RegexAST.range 'a' 'z'), + { byteIdx := 6 }) +-/ +#guard_msgs in +#eval parseCharClass "[0a-z]" ⟨0⟩ +/-- info: Except.ok (Strata.Python.RegexAST.char 'a', { byteIdx := 3 }) -/ +#guard_msgs in +#eval parseCharClass "[a]" ⟨0⟩ +/-- +info: Except.error (Strata.Python.ParseError.patternError "Expected '[' at start of character class" "a" { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseCharClass "a" ⟨0⟩ + +end Test.parseCharClass + +section Test.parseBounds + +/-- info: Except.ok (23, 23, { byteIdx := 4 }) -/ +#guard_msgs in +#eval parseBounds "{23}" ⟨0⟩ +/-- info: Except.ok (100, 100, { byteIdx := 9 }) -/ +#guard_msgs in +#eval parseBounds "{100,100}" ⟨0⟩ +/-- +info: Except.error (Strata.Python.ParseError.patternError "Expected '{' at start of bounds" "abc" { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseBounds "abc" ⟨0⟩ +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Invalid repeat bounds {100,2}: maximum 2 is less than minimum 100" + "{100,2}" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseBounds "{100,2}" ⟨0⟩ + +end Test.parseBounds + +section Test.parseTop + +/-- +info: Except.ok (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.char '1') (Strata.Python.RegexAST.range '0' '1')) + (Strata.Python.RegexAST.char '5')) +-/ +#guard_msgs in +/- +Cross-checked with: +>>> re._parser.parse('[10-15]') +[(IN, [(LITERAL, 49), (RANGE, (48, 49)), (LITERAL, 53)])] +-/ +#eval parseTop "[10-15]" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.char 'a') + (Strata.Python.RegexAST.optional (Strata.Python.RegexAST.char 'b'))) +-/ +#guard_msgs in +#eval parseTop "ab?" + +/-- info: Except.ok (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar)) -/ +#guard_msgs in +#eval parseTop ".*" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar)) + (Strata.Python.RegexAST.char '.')) + (Strata.Python.RegexAST.char '.')) + (Strata.Python.RegexAST.anychar)) + (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar))) + (Strata.Python.RegexAST.char 'x')) +-/ +#guard_msgs in +#eval parseTop ".*\\.\\...*x" + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Quantifier '{' at position 2 has nothing to quantify" + ".*{1,10}" + { byteIdx := 2 }) +-/ +#guard_msgs in +#eval parseTop ".*{1,10}" + +/-- info: Except.ok (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar)) -/ +#guard_msgs in +#eval parseTop ".*" + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Quantifier '*' at position 0 has nothing to quantify" + "*abc" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "*abc" + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Quantifier '+' at position 0 has nothing to quantify" + "+abc" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "+abc" + +/-- info: Except.ok (Strata.Python.RegexAST.loop (Strata.Python.RegexAST.range 'a' 'z') 1 10) -/ +#guard_msgs in +#eval parseTop "[a-z]{1,10}" + +/-- info: Except.ok (Strata.Python.RegexAST.loop (Strata.Python.RegexAST.range 'a' 'z') 10 10) -/ +#guard_msgs in +#eval parseTop "[a-z]{10}" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.anchor_start) + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.range 'a' 'z') (Strata.Python.RegexAST.range '0' '9'))) + (Strata.Python.RegexAST.loop + (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.range 'a' 'z') (Strata.Python.RegexAST.range '0' '9')) + (Strata.Python.RegexAST.char '.')) + (Strata.Python.RegexAST.char '-')) + 1 + 10)) + (Strata.Python.RegexAST.anchor_end)) +-/ +#guard_msgs in +#eval parseTop "^[a-z0-9][a-z0-9.-]{1,10}$" + +-- Test escape sequences (need \\ in Lean strings to get single \) +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar)) + (Strata.Python.RegexAST.char '.')) + (Strata.Python.RegexAST.char '.')) + (Strata.Python.RegexAST.anychar)) + (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar))) +-/ +#guard_msgs in +#eval parseTop ".*\\.\\...*" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.anchor_start) (Strata.Python.RegexAST.char 'x')) + (Strata.Python.RegexAST.char 'n')) + (Strata.Python.RegexAST.char '-')) + (Strata.Python.RegexAST.char '-')) + (Strata.Python.RegexAST.star (Strata.Python.RegexAST.anychar))) +-/ +#guard_msgs in +#eval parseTop "^xn--.*" + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Invalid character range [x-c]: start character 'x' is greater than end character 'c'" + "[x-c]" + { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "[x-c]" + +/-- +info: Except.error (Strata.Python.ParseError.patternError + "Invalid character range [1-0]: start character '1' is greater than end character '0'" + "[51-08]" + { byteIdx := 2 }) +-/ +#guard_msgs in +#eval parseTop "[51-08]" + +/-- +info: Except.ok (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.char 'a') (Strata.Python.RegexAST.char 'b')) + (Strata.Python.RegexAST.char 'c'))) +-/ +#guard_msgs in +#eval parseTop "(abc)" + +/-- +info: Except.ok (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.char 'a') (Strata.Python.RegexAST.char 'b'))) +-/ +#guard_msgs in +#eval parseTop "(a|b)" + +/-- +info: Except.ok (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.anchor_start) (Strata.Python.RegexAST.char 'a')) + (Strata.Python.RegexAST.anchor_end)) + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.anchor_start) (Strata.Python.RegexAST.char 'b')) + (Strata.Python.RegexAST.anchor_end))) +-/ +#guard_msgs in +#eval parseTop "^a$|^b$" + +/-- +info: Except.ok (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.anchor_start) (Strata.Python.RegexAST.char 'a')) + (Strata.Python.RegexAST.anchor_end))) + (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.anchor_start) (Strata.Python.RegexAST.char 'b')) + (Strata.Python.RegexAST.anchor_end)))) +-/ +#guard_msgs in +#eval parseTop "(^a$)|(^b$)" + +/-- +info: Except.ok (Strata.Python.RegexAST.star + (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.char 'a') (Strata.Python.RegexAST.char 'b')) + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.char 'c') (Strata.Python.RegexAST.char 'd'))))) +-/ +#guard_msgs in +#eval parseTop "(ab|cd)*" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.char 'a') + (Strata.Python.RegexAST.optional (Strata.Python.RegexAST.char 'b'))) +-/ +#guard_msgs in +#eval parseTop "ab?" + +/-- info: Except.ok (Strata.Python.RegexAST.optional (Strata.Python.RegexAST.range 'a' 'z')) -/ +#guard_msgs in +#eval parseTop "[a-z]?" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented + "Positive lookahead (?=...) is not supported" + "(?=test)" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "(?=test)" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented + "Negative lookahead (?!...) is not supported" + "(?!silly-)" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "(?!silly-)" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented + "Extension notation (?...) is not supported" + "(?:abc)" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "(?:abc)" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented + "Extension notation (?...) is not supported" + "(?Ptest)" + { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "(?Ptest)" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Special sequence \\d is not supported" "\\d+" { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "\\d+" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Special sequence \\w is not supported" "\\w*" { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "\\w*" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Special sequence \\s is not supported" "\\s+" { byteIdx := 0 }) +-/ +#guard_msgs in +#eval parseTop "\\s+" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Escape sequence \\n is not supported" "test\\n" { byteIdx := 4 }) +-/ +#guard_msgs in +#eval parseTop "test\\n" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Backreference \\1 is not supported" "(a)\\1" { byteIdx := 3 }) +-/ +#guard_msgs in +#eval parseTop "(a)\\1" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Non-greedy quantifier *? is not supported" "a*?" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a*?" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Non-greedy quantifier +? is not supported" "a+?" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a+?" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Non-greedy quantifier ?? is not supported" "a??" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a??" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Possessive quantifier *+ is not supported" "a*+" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a*+" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Possessive quantifier ++ is not supported" "a++" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a++" + +/-- +info: Except.error (Strata.Python.ParseError.unimplemented "Possessive quantifier ?+ is not supported" "a?+" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "a?+" + +/-- +info: Except.ok (Strata.Python.RegexAST.union + (Strata.Python.RegexAST.empty) + (Strata.Python.RegexAST.concat (Strata.Python.RegexAST.char 'x') (Strata.Python.RegexAST.char 'y'))) +-/ +#guard_msgs in +#eval parseTop "|xy" + +/-- +info: Except.ok (Strata.Python.RegexAST.concat + (Strata.Python.RegexAST.char 'a') + (Strata.Python.RegexAST.group + (Strata.Python.RegexAST.union (Strata.Python.RegexAST.empty) (Strata.Python.RegexAST.char 'b')))) +-/ +#guard_msgs in +#eval parseTop "a(|b)" + +/-- +info: Except.error (Strata.Python.ParseError.patternError "Unbalanced parenthesis" "x)" { byteIdx := 1 }) +-/ +#guard_msgs in +#eval parseTop "x)" + +/-- +info: Except.error (Strata.Python.ParseError.patternError "Unbalanced parenthesis" "())" { byteIdx := 2 }) +-/ +#guard_msgs in +#eval parseTop "())" + +end Test.parseTop + +------------------------------------------------------------------------------- +end Strata.Python diff --git a/Strata/Languages/Python/Regex/ReToBoogie.lean b/Strata/Languages/Python/Regex/ReToBoogie.lean new file mode 100644 index 000000000..4ea29793f --- /dev/null +++ b/Strata/Languages/Python/Regex/ReToBoogie.lean @@ -0,0 +1,406 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Python.Regex.ReParser +import Strata.Languages.Boogie.Factory + +namespace Strata +namespace Python + +------------------------------------------------------------------------------- + +open Lambda.LExpr +open Boogie + +/-- +Python regexes can be interpreted differently based on the matching mode. + +Consider the regex pattern that does not contain any anchors: `x`. +For search, this is equivalent to `.*x.*`. +For match, this is equivalent to `x.*`. +For fullmatch, this is exactly `x`. + +Consider the regex pattern: `^x`. +For search, this is equivalent to `x.*`. +For match, this is equivalent to `x.*`. +Again for fullmatch, this is exactly `x`. + +Consider the regex pattern: `x$`. +For search, this is equivalent to `.*x`. +For match, this is equivalent to `x`. +Again for fullmatch, this is exactly `x`. + +Consider the regex pattern: `^x$`. +For search, match, and fullmatch, this is equivalent to `x`. +-/ +inductive MatchMode where + | search -- `re.search()` - match anywhere in string + | match -- `re.match()` - match at start of string + | fullmatch -- `re.fullmatch()` - match entire string + deriving Repr, BEq + +/-- +When `r` is definitely consuming, this function returns `true`. +Returns `false` otherwise (i.e., when it _may_ not be consuming). +-/ +def RegexAST.alwaysConsume (r : RegexAST) : Bool := + match r with + | .char _ => true + | .range _ _ => true + | .union r1 r2 => alwaysConsume r1 && alwaysConsume r2 + | .concat r1 r2 => alwaysConsume r1 || alwaysConsume r2 + | .anychar => true + | .star _ => false + | .plus r1 => alwaysConsume r1 + | .optional _ => false + | .loop r1 n _ => alwaysConsume r1 && n ≠ 0 + | .anchor_start => false + | .anchor_end => false + | .group r1 => alwaysConsume r1 + | .empty => false + | .complement _ => true + +/-- +Empty regex pattern; matches an empty string. +-/ +def Boogie.emptyRegex : Boogie.Expression.Expr := + mkApp () (.op () strToRegexFunc.name none) [strConst () ""] + +/-- +Unmatchable regex pattern. +-/ +def Boogie.unmatchableRegex : Boogie.Expression.Expr := + mkApp () (.op () reNoneFunc.name none) [] + +partial def RegexAST.toBoogie (r : RegexAST) (atStart atEnd : Bool) : + Boogie.Expression.Expr := + match r with + | .char c => + (mkApp () (.op () strToRegexFunc.name none) [strConst () (toString c)]) + | .range c1 c2 => + mkApp () (.op () reRangeFunc.name none) [strConst () (toString c1), strConst () (toString c2)] + | .anychar => + mkApp () (.op () reAllCharFunc.name none) [] + | .empty => Boogie.emptyRegex + | .complement r => + let rb := toBoogie r atStart atEnd + mkApp () (.op () reCompFunc.name none) [rb] + | .anchor_start => + if atStart then Boogie.emptyRegex else Boogie.unmatchableRegex + | .anchor_end => + if atEnd then Boogie.emptyRegex else Boogie.unmatchableRegex + | .plus r1 => + toBoogie (.concat r1 (.star r1)) atStart atEnd + | .star r1 => + let r1b := toBoogie r1 atStart atEnd + let r2b := + match (alwaysConsume r1) with + | true => + let r1b := toBoogie r1 atStart false -- r1 at the beginning + let r2b := toBoogie r1 false false -- r1s in the middle + let r3b := toBoogie r1 false atEnd -- r1 at the end + let r2b := mkApp () (.op () reStarFunc.name none) [r2b] + mkApp () (.op () reConcatFunc.name none) + [mkApp () (.op () reConcatFunc.name none) [r1b, r2b], r3b] + | false => + mkApp () (.op () reStarFunc.name none) [r1b] + mkApp () (.op () reUnionFunc.name none) + [mkApp () (.op () reUnionFunc.name none) [Boogie.emptyRegex, r1b], r2b] + | .optional r1 => + toBoogie (.union .empty r1) atStart atEnd + | .loop r1 n m => + match n, m with + | 0, 0 => Boogie.emptyRegex + | 0, 1 => toBoogie (.union .empty r1) atStart atEnd + | 0, m => -- Note: m >= 2 + let r1b := toBoogie r1 atStart atEnd + let r2b := match (alwaysConsume r1) with + | true => + let r1b := toBoogie r1 atStart false -- r1 at the beginning + let r2b := toBoogie r1 false false -- r1s in the middle + let r3b := toBoogie r1 false atEnd -- r1 at the end + let r2b := mkApp () (.op () reLoopFunc.name none) [r2b, intConst () 0, intConst () (m-2)] + mkApp () (.op () reConcatFunc.name none) [mkApp () (.op () reConcatFunc.name none) [r1b, r2b], r3b] + | false => + mkApp () (.op () reLoopFunc.name none) [r1b, intConst () 0, intConst () m] + mkApp () (.op () reUnionFunc.name none) + [mkApp () (.op () reUnionFunc.name none) [Boogie.emptyRegex, r1b], + r2b] + | _, _ => + toBoogie (.concat r1 (.loop r1 (n - 1) (m - 1))) atStart atEnd + | .group r1 => toBoogie r1 atStart atEnd + | .concat r1 r2 => + match (alwaysConsume r1), (alwaysConsume r2) with + | true, true => + let r1b := toBoogie r1 atStart false + let r2b := toBoogie r2 false atEnd + mkApp () (.op () reConcatFunc.name none) [r1b, r2b] + | true, false => + let r1b := toBoogie r1 atStart atEnd + let r2b := toBoogie r2 false atEnd + mkApp () (.op () reConcatFunc.name none) [r1b, r2b] + | false, true => + let r1b := toBoogie r1 atStart false + let r2b := toBoogie r2 true atEnd + mkApp () (.op () reConcatFunc.name none) [r1b, r2b] + | false, false => + let r1b := toBoogie r1 atStart atEnd + let r2b := toBoogie r2 atStart atEnd + mkApp () (.op () reConcatFunc.name none) [r1b, r2b] + | .union r1 r2 => + let r1b := toBoogie r1 atStart atEnd + let r2b := toBoogie r2 atStart atEnd + mkApp () (.op () reUnionFunc.name none) [r1b, r2b] + +def pythonRegexToBoogie (pyRegex : String) (mode : MatchMode := .fullmatch) : + Boogie.Expression.Expr × Option ParseError := + match parseTop pyRegex with + | .error err => (mkApp () (.op () reAllFunc.name none) [], some err) + | .ok ast => + let dotStar := (RegexAST.star (.anychar)) + -- Wrap with `.*` based on mode. + let ast := match mode with + | .fullmatch => ast + | .match => .concat ast dotStar + | .search => .concat dotStar (.concat ast dotStar) + let result := RegexAST.toBoogie ast true true + (result, none) + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) (~Str.ToRegEx #b))) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar))), + none) +-/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "ab.*" -- Encoded as `ab(|.|..*.)` + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) (~Str.ToRegEx #b))) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ((~Re.Concat (~Str.ToRegEx #c)) (~Str.ToRegEx #)))) ((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #c)) ~Re.None)) (~Re.Star ((~Re.Concat (~Str.ToRegEx #c)) ~Re.None)))) ((~Re.Concat (~Str.ToRegEx #c)) (~Str.ToRegEx #))))), + none) +-/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "ab(c$)*" + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) (~Str.ToRegEx #b))) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ((~Re.Concat ((~Re.Concat ~Re.None) (~Str.ToRegEx #c))) (~Str.ToRegEx #)))) ((~Re.Concat ((~Re.Concat ((~Re.Concat ((~Re.Concat ~Re.None) (~Str.ToRegEx #c))) ~Re.None)) (~Re.Star ((~Re.Concat ((~Re.Concat ~Re.None) (~Str.ToRegEx #c))) ~Re.None)))) ((~Re.Concat ((~Re.Concat ~Re.None) (~Str.ToRegEx #c))) (~Str.ToRegEx #))))), + none) +-/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "ab(^c$)*" + +/-- info: (((~Re.Concat (~Str.ToRegEx #a)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "ab" + +/-- info: (((~Re.Union (~Str.ToRegEx #a)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "a|b" + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #b)), none) +-/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "^ab" + +/-- +info: (((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #b))) (~Str.ToRegEx #)), + none) +-/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "^ab$" + +/-- info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format$ pythonRegexToBoogie "(a$)b" + +/-- +info: (((~Re.Concat ((~Re.Concat ((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #))) (~Str.ToRegEx #))) (~Str.ToRegEx #a))) (~Str.ToRegEx #))) (~Str.ToRegEx #)), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^^^a$$" + +/-- +info: (((~Re.Concat (~Str.ToRegEx #)) ((~Re.Concat ((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #))) (~Str.ToRegEx #a))) (~Str.ToRegEx #))) (~Str.ToRegEx #))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^(^^a$$)" + +/-- +info: (((~Re.Union ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #))) ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #b))) (~Str.ToRegEx #))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "(^a$)|(^b$)" + +/-- +info: (((~Re.Concat (~Str.ToRegEx #c)) ((~Re.Union ((~Re.Concat ~Re.None) (~Str.ToRegEx #a))) ((~Re.Concat ~Re.None) (~Str.ToRegEx #b)))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "c((^a)|(^b))" + +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) ((~Re.Concat (~Str.ToRegEx #b)) ~Re.None))) (~Str.ToRegEx #c)), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "((a$)|(b$))c" + +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) (~Str.ToRegEx #b))) (~Str.ToRegEx #c)), none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "((a$)|(b))c" + +/-- +info: (((~Re.Concat (~Str.ToRegEx #c)) ((~Re.Union ((~Re.Concat (~Str.ToRegEx #a)) (~Str.ToRegEx #))) ((~Re.Concat ((~Re.Concat ~Re.None) (~Str.ToRegEx #b))) (~Str.ToRegEx #)))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "c((a$)|(^b$))" + +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) (~Str.ToRegEx #b))) (~Str.ToRegEx #c)), none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "((a$)|(b))c" + +/-- info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) ~Re.None)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^$b" + +/-- +info: (((~Re.Union ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #))) ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) ~Re.None)) (~Str.ToRegEx #b))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^a$|^$b" + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #c)) ((~Re.Union ((~Re.Concat ~Re.None) (~Str.ToRegEx #a))) ((~Re.Concat (~Str.ToRegEx #b)) ~Re.None)))) (~Str.ToRegEx #d)), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "c(^a|b$)d" + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #c)) ((~Re.Union ((~Re.Concat ~Re.None) (~Str.ToRegEx #a))) ((~Re.Concat (~Str.ToRegEx #b)) ~Re.None)))) (~Str.ToRegEx #d)), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "(c(^a|b$))d" + +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) ((~Re.Concat (~Str.ToRegEx #b)) ~Re.None))) ((~Re.Union ((~Re.Concat ~Re.None) (~Str.ToRegEx #c))) ((~Re.Concat (~Str.ToRegEx #d)) (~Str.ToRegEx #)))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "(^a|b$)(^c|d$)" + +/-- +info: (((~Re.Concat ((~Re.Concat ((~Re.Union ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) ((~Re.Concat (~Str.ToRegEx #b)) ~Re.None))) ~Re.None)) (~Str.ToRegEx #c)), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "((^a|b$)^)c" + +/-- info: (((~Re.Concat ((~Re.Union (~Str.ToRegEx #)) ~Re.None)) (~Str.ToRegEx #c)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "(^|$)c" + +/-- info: (((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^^" + +/-- +info: (((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #))) (~Str.ToRegEx #))) (~Str.ToRegEx #)), none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^$$^" + +/-- info: (((~Re.Concat ((~Re.Union (~Str.ToRegEx #)) (~Str.ToRegEx #))) (~Str.ToRegEx #)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "(^|$)^" + +/-- +info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #)), none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^a$" .fullmatch + +/-- +info: (~Re.All, + some Pattern error at position 1: Invalid repeat bounds {100,2}: maximum 2 is less than minimum 100 in pattern 'x{100,2}') +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "x{100,2}" .fullmatch + +-- (unmatchable) +/-- info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "a^b" .fullmatch + +/-- +info: (((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) ~Re.None)) (~Str.ToRegEx #b)), none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^a^b" .fullmatch + +/-- info: (((~Re.Concat ((~Re.Concat (~Str.ToRegEx #a)) ~Re.None)) (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "a$b" .fullmatch + +/-- info: ((~Re.Comp (~Str.ToRegEx #b)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "[^b]" .fullmatch + +/-- info: ((~Re.Comp ((~Re.Range #A) #Z)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "[^A-Z]" .fullmatch + +/-- info: ((~Re.Comp (~Str.ToRegEx #^)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "[^^]" .fullmatch + +/-- info: ((~Str.ToRegEx #a), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "a" .fullmatch + +/-- +info: (((~Re.Concat (~Str.ToRegEx #a)) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "a" .match + +-- search mode tests +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar))) ((~Re.Concat (~Str.ToRegEx #a)) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar)))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "a" .search + +/-- +info: (((~Re.Concat ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar))) ((~Re.Concat ((~Re.Concat ((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a))) (~Str.ToRegEx #))) ((~Re.Union ((~Re.Union (~Str.ToRegEx #)) ~Re.AllChar)) ((~Re.Concat ((~Re.Concat ~Re.AllChar) (~Re.Star ~Re.AllChar))) ~Re.AllChar)))), + none) +-/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^a$" .search + +/-- info: (((~Re.Concat (~Str.ToRegEx #)) (~Str.ToRegEx #a)), none) -/ +#guard_msgs in +#eval Std.format $ pythonRegexToBoogie "^a" .fullmatch + +-- -- BAD +-- #eval Std.format $ pythonRegexToBoogie "a$.*" .fullmatch +-- +-- -- BAD +-- #eval Std.format $ pythonRegexToBoogie "a$" .match + + +------------------------------------------------------------------------------- diff --git a/Strata/Languages/Python/z3_configs.txt b/Strata/Languages/Python/z3_configs.txt new file mode 100644 index 000000000..ab7b6a429 --- /dev/null +++ b/Strata/Languages/Python/z3_configs.txt @@ -0,0 +1,2 @@ + +(set-option :smt.mbqi false) (set-option :auto_config false) diff --git a/Strata/Languages/Python/z3_parallel.py b/Strata/Languages/Python/z3_parallel.py new file mode 100755 index 000000000..5b28d01df --- /dev/null +++ b/Strata/Languages/Python/z3_parallel.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +# Copyright Strata Contributors + +# SPDX-License-Identifier: Apache-2.0 OR MIT + +# This file runs several configurations of Z3 in parallel, and returns SAT/UNSAT if +# any return the same, only returning unknown if all return unknown / timeout. +# Configurations can be added to z3_configs.txt, one per line. +# The solvers currently run in parallel until completion. This could be improved, but +# we currently use a 1s timeout, so it's not a high priority. + +import sys +import subprocess +import tempfile +from pathlib import Path +from concurrent.futures import ProcessPoolExecutor, as_completed + +def run_z3_config(smt_content, config_pair, timeout): + with tempfile.NamedTemporaryFile(mode='w', suffix='.smt2', delete=False) as f: + f.write(f"{config_pair[0]} {config_pair[1]}\n") + f.write(smt_content) + f.flush() + + process = None + try: + process = subprocess.Popen( + ['z3', f'-T:{timeout}', f.name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + stdout, stderr = process.communicate(timeout=timeout) + Path(f.name).unlink() + + output = stdout.strip() + first_line = output.split('\n')[0].lower() if output else '' + if first_line == 'sat': + return 'sat', output + elif first_line == 'unsat': + return 'unsat', output + return None, output + except subprocess.TimeoutExpired: + if process: + process.kill() + process.wait() + Path(f.name).unlink() + return None, "timeout" + except Exception as e: + Path(f.name).unlink() + return None, str(e) + +def main(): + if len(sys.argv) < 2: + print("Usage: z3_parallel.py [-v] [-c config_file] ") + sys.exit(1) + + verbose = False + config_file = None + args = sys.argv[1:] + + while args and args[0].startswith('-'): + if args[0] == '-v': + verbose = True + args = args[1:] + elif args[0] == '-c': + if len(args) < 2: + print("Usage: z3_parallel.py [-v] [-c config_file] ") + sys.exit(1) + config_file = args[1] + args = args[2:] + else: + break + + if len(args) != 1: + print("Usage: z3_parallel.py [-v] [-c config_file] ") + sys.exit(1) + + smt_file = args[0] + + if config_file is None: + script_dir = Path(__file__).parent + config_file = script_dir / 'z3_configs.txt' + + timeout = 1 + + configs = [] + with open(config_file) as f: + for line in f: + line = line.strip() + parts = line.split(maxsplit=1) + if len(parts) == 2: + configs.append(parts) + elif len(parts) == 0: + configs.append(('', '')) + else: + configs.append((parts[0], '')) + + with open(smt_file) as f: + smt_content = f.read() + + with ProcessPoolExecutor(max_workers=len(configs)) as executor: + futures = [executor.submit(run_z3_config, smt_content, cfg, timeout) for cfg in configs] + + sat_result = None + all_results = [] + for future in as_completed(futures): + result, output = future.result() + all_results.append((result, output)) + if result and not sat_result: + sat_result = (result, output) + + if verbose: + for i, (result, output) in enumerate(all_results): + print(f"Config {i}: {result or 'unknown'} - {output}") + + if sat_result: + print(sat_result[0]) + return + + print("unknown") + +if __name__ == '__main__': + main() diff --git a/Strata/Transform/BoogieTransform.lean b/Strata/Transform/BoogieTransform.lean new file mode 100644 index 000000000..ad5584f3d --- /dev/null +++ b/Strata/Transform/BoogieTransform.lean @@ -0,0 +1,234 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Statement +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Boogie.BoogieGen +import Strata.DL.Util.LabelGen + +/-! # Utility functions for program transformation in Boogie -/ + +namespace Boogie +namespace Transform + +open LabelGen + +def oldVarPrefix (id : String) : String := s!"old_{id}" +def tmpVarPrefix (id : String) : String := s!"tmp_{id}" + +def createHavoc (ident : Expression.Ident) + : Statement := Statement.havoc ident + +def createHavocs (ident : List Expression.Ident) + : List Statement := ident.map createHavoc + +def createFvar (ident : Expression.Ident) + : Expression.Expr + := Lambda.LExpr.fvar ((): ExpressionMetadata) ident none + +def createFvars (ident : List Expression.Ident) + : List Expression.Expr + := ident.map createFvar + +def genIdent (ident : Expression.Ident) (pf : String → String) + : BoogieGenM Expression.Ident := + BoogieGenState.gen (pf ident.name) + +/-- +Generate identifiers in the form of arg_... that can be used to reduce argument expressions to temporary variables. +-/ +def genArgExprIdent + : BoogieGenM Expression.Ident := + genIdent "arg" tmpVarPrefix + +def genArgExprIdents (n:Nat) + : BoogieGenM (List Expression.Ident) := + List.mapM (fun _ => genArgExprIdent) (List.replicate n ()) + +/-- +Retrieves a fresh identifier from the counter generator the given identifier "ident" within old(...), or retrieve an existing one from the exprMap +Assumes that ident contains no duplicates +-/ +def genOutExprIdent (ident : Expression.Ident) + : BoogieGenM Expression.Ident := + genIdent ident tmpVarPrefix + +def genOutExprIdents (idents : List Expression.Ident) + : BoogieGenM (List Expression.Ident) + := List.mapM genOutExprIdent idents + +/-- +Retrieves a fresh identifier from the counter generator the given identifier "ident" within old(...), or retrieve an existing one from the exprMap +Assumes that ident contains no duplicates +-/ +def genOldExprIdent (ident : Expression.Ident) + : BoogieGenM Expression.Ident := + genIdent ident oldVarPrefix + +def genOldExprIdents (idents : List Expression.Ident) + : BoogieGenM (List Expression.Ident) + := List.mapM genOldExprIdent idents + +/-- Checks whether a variable `ident` can be found in program `p` -/ +def isGlobalVar (p : Program) (ident : Expression.Ident) : Bool := + (p.find? .var ident).isSome + +abbrev Err := String + +abbrev BoogieTransformM := ExceptT Err BoogieGenM + +def getIdentTy? (p : Program) (id : Expression.Ident) := p.getVarTy? id + +def getIdentTy! (p : Program) (id : Expression.Ident) + : BoogieTransformM (Expression.Ty) := do + match getIdentTy? p id with + | none => throw s!"failed to find type for {Std.format id}" + | some ty => return ty + +def getIdentTys! (p : Program) (ids : List Expression.Ident) + : BoogieTransformM (List Expression.Ty) := do + match ids with + | [] => return [] + | id :: rest => + let ty ← getIdentTy! p id + return ty :: (← getIdentTys! p rest) + +/-- +returned list has the shape +((generated_name, ty), original_expr) +Only types of the 'inputs' parameter are used +-/ +def genArgExprIdentsTrip + (inputs : @Lambda.LTySignature Visibility) + (args : List Expression.Expr) + : BoogieTransformM (List ((Expression.Ident × Lambda.LTy) × Expression.Expr)) + := do + if inputs.length ≠ args.length then throw "input length and args length mismatch" + else let gen_idents ← genArgExprIdents args.length + return (gen_idents.zip inputs.unzip.2).zip args + +/-- +returned list has the shape +`((generated_name, ty), original_name)` +Only types of the 'outputs' parameter are used. +-/ +def genOutExprIdentsTrip + (outputs : @Lambda.LTySignature Visibility) + (lhs : List Expression.Ident) + : BoogieTransformM (List ((Expression.Ident × Expression.Ty) × Expression.Ident)) := do + if outputs.length ≠ lhs.length then throw "output length and lhs length mismatch" + else let gen_idents ← genOutExprIdents lhs + return (gen_idents.zip outputs.unzip.2).zip lhs + +/-- +returned list has the shape +`((generated_name, ty), original_name)` +-/ +def genOldExprIdentsTrip + (p : Program) + (ids : List Expression.Ident) + : BoogieTransformM (List ((Expression.Ident × Expression.Ty) × Expression.Ident)) := do + let gen_idents ← genOldExprIdents ids + let tys ← getIdentTys! p ids + return (gen_idents.zip tys).zip ids + +/-- +Generate an init statement with rhs as expression +-/ +def createInit (trip : (Expression.Ident × Expression.Ty) × Expression.Expr) + : Statement := + match trip with + | ((v', ty), e) => Statement.init v' ty e + +def createInits (trips : List ((Expression.Ident × Expression.Ty) × Expression.Expr)) + : List Statement := + trips.map createInit + +/-- +Generate an init statement with rhs as a free variable reference +-/ +def createInitVar (trip : (Expression.Ident × Expression.Ty) × Expression.Ident) + : Statement := + match trip with + | ((v', ty), v) => Statement.init v' ty (Lambda.LExpr.fvar () v none) + +def createInitVars (trips : List ((Expression.Ident × Expression.Ty) × Expression.Ident)) + : List Statement := + trips.map createInitVar + +/-- turns a list of preconditions into assumes with substitution -/ +def createAsserts + (pres : List Expression.Expr) + (subst : Map Expression.Ident Expression.Expr) + : List Statement + := pres |> List.mapIdx + (λ i pred ↦ + Statement.assert s!"assert_{i}" (Lambda.LExpr.substFvars pred subst)) + +/-- turns a list of preconditions into assumes with substitution -/ +def createAssumes + (posts : List Expression.Expr) + (subst : Map Expression.Ident Expression.Expr) + : List Statement + := posts |> List.mapIdx + (λ i pred ↦ + Statement.assume s!"assume_{i}" (Lambda.LExpr.substFvars pred subst)) + +/-- +Generate the substitution pairs needed for the body of the procedure +-/ +def createOldVarsSubst + (trips : List ((Expression.Ident × Expression.Ty) × Expression.Ident)) + : Map Expression.Ident Expression.Expr := + trips.map go where go + | ((v', _), v) => (v, createFvar v') + + + +/- Generic runner functions -/ + +def runStmts (f : Statement → Program → BoogieTransformM (List Statement)) + (ss : List Statement) (inputProg : Program) + : BoogieTransformM (List Statement) := do + match ss with + | [] => return [] + | s :: ss => + let s' := (f s inputProg) + let ss' := (runStmts f ss inputProg) + return (← s') ++ (← ss') + +def runProcedures (f : Statement → Program → BoogieTransformM (List Statement)) + (dcls : List Decl) (inputProg : Program) + : BoogieTransformM (List Decl) := do + match dcls with + | [] => return [] + | d :: ds => + match d with + | .proc p md => + return Decl.proc { p with body := ← (runStmts f p.body inputProg ) } md :: + (← (runProcedures f ds inputProg)) + | _ => return d :: (← (runProcedures f ds inputProg)) + +def runProgram (f : Statement → Program → BoogieTransformM (List Statement)) + (p : Program) : BoogieTransformM Program := do + let newDecls ← runProcedures f p.decls p + return { decls := newDecls } + + +@[simp] +def runWith {α : Type} (p : α) (f : α → BoogieTransformM β) + (s : BoogieGenState): + Except Err β × BoogieGenState := + (StateT.run (f p) s) + +@[simp] +def run {α : Type} (p : α) (f : α → BoogieTransformM β) + (s : BoogieGenState := .emp): + Except Err β := + (runWith p f s).fst + +end Transform +end Boogie diff --git a/Strata/Transform/CallElim.lean b/Strata/Transform/CallElim.lean index 3fa4f234a..cb1f4e8f1 100644 --- a/Strata/Transform/CallElim.lean +++ b/Strata/Transform/CallElim.lean @@ -4,198 +4,21 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -import Strata.DDM.Integration.Lean -import Strata.DDM.Util.Format -import Strata.Languages.Boogie.Statement -import Strata.Languages.Boogie.Verifier -import Strata.Languages.Boogie.Boogie -import Strata.Languages.Boogie.OldExpressions -import Strata.Languages.Boogie.ProgramWF -import Strata.DL.Util.ListUtils -import Strata.Languages.Boogie.BoogieGen -import Strata.DL.Util.LabelGen +import Strata.Transform.BoogieTransform /-! # Call Elimination Transformation -/ namespace Boogie namespace CallElim -open LabelGen - -def oldVarPrefix (id : String) : String := s!"old_{id}" -def tmpVarPrefix (id : String) : String := s!"tmp_{id}" - -def createHavoc (ident : Expression.Ident) - : Statement := Statement.havoc ident - -def createHavocs (ident : List Expression.Ident) - : List Statement := ident.map createHavoc - -def createFvar (ident : Expression.Ident) - : Expression.Expr - := Lambda.LExpr.fvar ident none - -def createFvars (ident : List Expression.Ident) - : List Expression.Expr - := ident.map createFvar - -def genIdent (ident : Expression.Ident) (pf : String → String) - : BoogieGenM Expression.Ident := - BoogieGenState.gen (pf ident.name) - -/-- -Generate identifiers in the form of arg_... that can be used to reduce argument expressions to temporary variables. --/ -def genArgExprIdent (_ : Expression.Expr) - : BoogieGenM Expression.Ident := - genIdent "arg" tmpVarPrefix - -def genArgExprIdents (exprs : List Expression.Expr) - : BoogieGenM (List Expression.Ident) - := List.mapM genArgExprIdent exprs - -/-- -Retrieves a fresh identifier from the counter generator the given identifier "ident" within old(...), or retrieve an existing one from the exprMap -Assumes that ident contains no duplicates --/ -def genOutExprIdent (ident : Expression.Ident) - : BoogieGenM Expression.Ident := - genIdent ident tmpVarPrefix - -def genOutExprIdents (idents : List Expression.Ident) - : BoogieGenM (List Expression.Ident) - := List.mapM genOutExprIdent idents - -/-- -Retrieves a fresh identifier from the counter generator the given identifier "ident" within old(...), or retrieve an existing one from the exprMap -Assumes that ident contains no duplicates --/ -def genOldExprIdent (ident : Expression.Ident) - : BoogieGenM Expression.Ident := - genIdent ident oldVarPrefix - -def genOldExprIdents (idents : List Expression.Ident) - : BoogieGenM (List Expression.Ident) - := List.mapM genOldExprIdent idents - -/-- Checks whether a variable `ident` can be found in program `p` -/ -def isGlobalVar (p : Program) (ident : Expression.Ident) : Bool := - (p.find? .var ident).isSome - -abbrev Err := String - -abbrev CallElimM := ExceptT Err BoogieGenM - -def getIdentTy? (p : Program) (id : Expression.Ident) := p.getVarTy? id - -def getIdentTy! (p : Program) (id : Expression.Ident) - : CallElimM (Expression.Ty) := do - match getIdentTy? p id with - | none => throw s!"failed to find type for {Std.format id}" - | some ty => return ty - -def getIdentTys! (p : Program) (ids : List Expression.Ident) - : CallElimM (List Expression.Ty) := do - match ids with - | [] => return [] - | id :: rest => - let ty ← getIdentTy! p id - return ty :: (← getIdentTys! p rest) - -/-- -returned list has the shape -((generated_name, ty), original_expr) --/ -def genArgExprIdentsTrip - (inputs : @Lambda.LTySignature Visibility) - (args : List Expression.Expr) - : CallElimM (List ((Expression.Ident × Lambda.LTy) × Expression.Expr)) - := do - if inputs.length ≠ args.length then throw "input length and args length mismatch" - else let gen_idents ← genArgExprIdents args - return (gen_idents.zip inputs.unzip.2).zip args - -/-- -returned list has the shape -`((generated_name, ty), original_name)` --/ -def genOutExprIdentsTrip - (outputs : @Lambda.LTySignature Visibility) - (lhs : List Expression.Ident) - : CallElimM (List ((Expression.Ident × Expression.Ty) × Expression.Ident)) := do - if outputs.length ≠ lhs.length then throw "output length and lhs length mismatch" - else let gen_idents ← genOutExprIdents lhs - return (gen_idents.zip outputs.unzip.2).zip lhs - -/-- -returned list has the shape -`((generated_name, ty), original_name)` --/ -def genOldExprIdentsTrip - (p : Program) - (ids : List Expression.Ident) - : CallElimM (List ((Expression.Ident × Expression.Ty) × Expression.Ident)) := do - let gen_idents ← genOldExprIdents ids - let tys ← getIdentTys! p ids - return (gen_idents.zip tys).zip ids - -/-- -Generate an init statement with rhs as expression --/ -def createInit (trip : (Expression.Ident × Expression.Ty) × Expression.Expr) - : Statement := - match trip with - | ((v', ty), e) => Statement.init v' ty e - -def createInits (trips : List ((Expression.Ident × Expression.Ty) × Expression.Expr)) - : List Statement := - trips.map createInit - -/-- -Generate an init statement with rhs as a free variable reference --/ -def createInitVar (trip : (Expression.Ident × Expression.Ty) × Expression.Ident) - : Statement := - match trip with - | ((v', ty), v) => Statement.init v' ty (Lambda.LExpr.fvar v none) - -def createInitVars (trips : List ((Expression.Ident × Expression.Ty) × Expression.Ident)) - : List Statement := - trips.map createInitVar - -/-- turns a list of preconditions into assumes with substitution -/ -def createAsserts - (pres : List Expression.Expr) - (subst : Map Expression.Ident Expression.Expr) - : List Statement - := pres |> List.mapIdx - (λ i pred ↦ - Statement.assert s!"assert_{i}" (Lambda.LExpr.substFvars pred subst)) - -/-- turns a list of preconditions into assumes with substitution -/ -def createAssumes - (posts : List Expression.Expr) - (subst : Map Expression.Ident Expression.Expr) - : List Statement - := posts |> List.mapIdx - (λ i pred ↦ - Statement.assume s!"assume_{i}" (Lambda.LExpr.substFvars pred subst)) - -/-- -Generate the substitution pairs needed for the body of the procedure --/ -def createOldVarsSubst - (trips : List ((Expression.Ident × Expression.Ty) × Expression.Ident)) - : Map Expression.Ident Expression.Expr := - trips.map go where go - | ((v', _), v) => (v, createFvar v') +open Boogie.Transform /-- The main call elimination transformation algorithm on a single statement. -The returned result is a sequence of statements if the +The returned result is a sequence of statements -/ def callElimStmt (st: Statement) (p : Program) - : CallElimM (List Statement) := do + : BoogieTransformM (List Statement) := do match st with | .call lhs procName args _ => @@ -212,12 +35,10 @@ def callElimStmt (st: Statement) (p : Program) let oldVars := oldVars.filter (isGlobalVar p) let genArgTrips := genArgExprIdentsTrip (Lambda.LMonoTySignature.toTrivialLTy proc.header.inputs) args - let argTrips : List ((Expression.Ident × Expression.Ty) × Expression.Expr) ← genArgTrips - -- Monadic operation, generate var mapping for each unique oldVars. let genOutTrips := genOutExprIdentsTrip (Lambda.LMonoTySignature.toTrivialLTy proc.header.outputs) lhs let outTrips : List ((Expression.Ident × Expression.Ty) × Expression.Ident) @@ -264,43 +85,16 @@ def callElimStmt (st: Statement) (p : Program) return argInit ++ outInit ++ oldInit ++ asserts ++ havocs ++ assumes | _ => return [ st ] -def callElimStmts (ss: List Statement) (prog : Program) - : CallElimM (List Statement) := do match ss with - | [] => return [] - | s :: ss => - let s' := (callElimStmt s prog) - let ss' := (callElimStmts ss prog) - return (← s') ++ (← ss') +def callElimStmts (ss: List Statement) (prog : Program) := + runStmts callElimStmt ss prog -def callElimL (dcls : List Decl) (prog : Program) - : CallElimM (List Decl) := - match dcls with - | [] => return [] - | d :: ds => - match d with - | .proc p => - return Decl.proc { p with body := ← (callElimStmts p.body prog ) } :: (← (callElimL ds prog)) - | _ => return d :: (← (callElimL ds prog)) +def callElimL (dcls : List Decl) (prog : Program) := + runProcedures callElimStmt dcls prog /-- Call Elimination for an entire program by walking through all procedure bodies -/ -def callElim' (p : Program) : CallElimM Program := return { decls := (← (callElimL p.decls p)) } - -@[simp] -def runCallElimWith' {α : Type} (p : α) (f : α → CallElimM β) (s : BoogieGenState): - Except Err β × BoogieGenState := - (StateT.run (f p) s) - -@[simp] -def runCallElimWith {α : Type} (p : α) (f : α → CallElimM β) (s : BoogieGenState): - Except Err β := - (runCallElimWith' p f s).fst - -/-- run call elimination with an empty counter state (e.g. starting from 0) -/ -@[simp] -def runCallElim {α : Type} (p : α) (f : α → CallElimM β): - Except Err β := - runCallElimWith p f .emp +def callElim' (p : Program) : BoogieTransformM Program := + runProgram callElimStmt p end CallElim end Boogie diff --git a/Strata/Transform/CallElimCorrect.lean b/Strata/Transform/CallElimCorrect.lean index 4329a24b1..351be77fb 100644 --- a/Strata/Transform/CallElimCorrect.lean +++ b/Strata/Transform/CallElimCorrect.lean @@ -12,6 +12,7 @@ import Strata.Languages.Boogie.Program import Strata.Languages.Boogie.ProgramType import Strata.Languages.Boogie.WF import Strata.DL.Lambda.Lambda +import Strata.Transform.BoogieTransform import Strata.Transform.CallElim import Strata.DL.Imperative.CmdSemantics import Strata.Languages.Boogie.StatementSemantics @@ -22,12 +23,12 @@ import Strata.DL.Util.ListUtils This file contains the main proof that the call elimination transformation is semantics preserving (see `callElimStatementCorrect`). - Additionally, `callElimStmtsNoExcept` shows that the call elimination + Additionally, `callElimBlockNoExcept` shows that the call elimination transformation always succeeds on well-formed statements. -/ namespace CallElimCorrect -open Boogie CallElim +open Boogie Boogie.Transform CallElim theorem BoogieIdent.isGlob_isGlobOrLocl : PredImplies (BoogieIdent.isGlob ·) (BoogieIdent.isGlobOrLocl ·) := by @@ -123,9 +124,9 @@ theorem getIdentTys!_store_same : theorem getIdentTy!_no_throw : (p.find? .var ident).isSome = true → - ∃ r, (runCallElimWith ident (getIdentTy! p) cs) = (Except.ok r) := by + ∃ r, (runWith ident (getIdentTy! p) cs).fst = (Except.ok r) := by intros H - simp [runCallElimWith, StateT.run, getIdentTy!] + simp [runWith, StateT.run, getIdentTy!] have Hsome := @getOldExprIdentTy_some p ident simp [H] at Hsome simp [Option.isSome] at Hsome @@ -138,7 +139,7 @@ theorem getIdentTys!_no_throw : {idents : List Expression.Ident} {cs : BoogieGenState}, (∀ ident ∈ idents, (p.find? .var ident).isSome = true) → - ∃ r, (runCallElimWith idents (getIdentTys! p) cs) = (Except.ok r) := by + ∃ r, (runWith idents (getIdentTys! p) cs).fst = (Except.ok r) := by intros p idents cs Hglob induction idents generalizing cs case nil => @@ -152,7 +153,7 @@ theorem getIdentTys!_no_throw : have Hhead := @getIdentTy!_no_throw _ _ cs Hsome cases Hhead with | intro T' Hok' => - simp [runCallElimWith, StateT.run] at Hok' + simp [runWith, StateT.run] at Hok' split <;> simp_all next err cs' Hres => specialize @ih cs' @@ -163,16 +164,16 @@ theorem getIdentTys!_no_throw : simp [pure, StateT.pure] -- Step 1. A theorem stating that given a well-formed program, call-elim will return no exception -theorem callElimStmtsNoExcept : +theorem callElimBlockNoExcept : ∀ (st : Boogie.Statement) (p : Boogie.Program), WF.WFStatementsProp p [st] → - ∃ sts, Except.ok sts = ((CallElim.runCallElim [st] (CallElim.callElimStmts · p))) + ∃ sts, Except.ok sts = ((run [st] (CallElim.callElimStmts · p))) -- NOTE: the generated variables will not be local, but temp. So it will not be well-formed -- ∧ WF.WFStatementsProp p sts := by intros st p wf - simp [CallElim.callElimStmts, CallElim.callElimStmt] + simp [Transform.run, runStmts, CallElim.callElimStmts, CallElim.callElimStmt] cases st with | block l b md => exists [.block l b md] | ite cd tb eb md => exists [.ite cd tb eb md] @@ -220,7 +221,7 @@ theorem callElimStmtsNoExcept : (List.map Procedure.Check.expr res'.spec.postconditions.values))).eraseDups) = eq at * have Hgen := @getIdentTys!_no_throw p eq (List.mapM.loop genOldExprIdent eq [] ss).snd ?_ - simp [runCallElimWith, StateT.run] at Hgen + simp [runWith, StateT.run] at Hgen . cases Hgen with | intro tys Hgen => simp_all @@ -238,6 +239,7 @@ theorem callElimStmtsNoExcept : exfalso apply Hne simp [Option.isSome] at df + unfold BoogieIdent.unres at * split at df <;> simp_all apply Hne simp [← ol, Lambda.LMonoTySignature.toTrivialLTy] @@ -253,6 +255,7 @@ theorem callElimStmtsNoExcept : exfalso apply Hne simp [Option.isSome] at df + unfold BoogieIdent.unres at * split at df <;> simp_all apply Hne simp [← al, Lambda.LMonoTySignature.toTrivialLTy] @@ -266,6 +269,7 @@ theorem callElimStmtsNoExcept : cases wf with | mk wf => simp [Program.Procedure.find?] at wf + unfold BoogieIdent.unres at * split at wf <;> simp_all . -- other case exfalso @@ -299,7 +303,7 @@ theorem postconditions_subst_unwrap : theorem prepostconditions_unwrap {ps : List (BoogieLabel × Procedure.Check)} : post ∈ List.map Procedure.Check.expr (ListMap.values ps) → -∃ label attr, (label, { expr := post, attr : Procedure.Check }) ∈ ps := by +∃ label attr md, (label, { expr := post, attr := attr, md := md : Procedure.Check }) ∈ ps := by intros H induction ps case nil => @@ -312,7 +316,7 @@ post ∈ List.map Procedure.Check.expr (ListMap.values ps) → cases Hc.1 with | inl Hin => simp_all - refine ⟨h.1, c.attr, ?_⟩ + refine ⟨h.1, c.attr, c.md, ?_⟩ left simp [← Hc, Hin] | inr Hin => @@ -321,9 +325,7 @@ post ∈ List.map Procedure.Check.expr (ListMap.values ps) → . simp [← Hc.2] refine ⟨c, ⟨Hin, rfl⟩⟩ . cases ih with - | intro label ih => cases ih with - | intro attr ih => - refine ⟨label, attr, Or.inr ih⟩ + | intro label ih => grind theorem updatedStateIsDefinedMono : (σ k').isSome = true → @@ -338,54 +340,39 @@ Imperative.WellFormedSemanticEvalVar δ → Boogie.WellFormedBoogieEvalCong δ → Imperative.WellFormedSemanticEvalVal δ → ¬ k ∈ (Imperative.HasVarsPure.getVars e) → -δ σ₀ σ e = some v' → -δ σ₀ (updatedState σ k v) e = some v' := by +δ σ e = some v' → +δ (updatedState σ k v) e = some v' := by intros Hwfv Hwfc Hwfvl Hnin Hsome simp [Imperative.WellFormedSemanticEvalVar, Imperative.HasFvar.getFvar] at Hwfv - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc simp [Imperative.WellFormedSemanticEvalVal] at Hwfvl have Hval := Hwfvl.2 simp [← Hsome] at * induction e <;> simp [Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * - case const c t | op o ty | bvar b => + case const c | op o ty | bvar b => rw [Hval]; rw [Hval]; constructor; constructor - case fvar n ty => + case fvar m n ty => simp [Hwfv] simp [updatedState] - intros Heq - simp [Heq] - simp_all - case mdata info e ih => - apply ((Hwfc e e σ₀ (updatedState σ k v) σ₀ σ) ?_).2.1 - apply ih ; simp_all - case abs ty e ih => - apply ((Hwfc e e σ₀ (updatedState σ k v) σ₀ σ) ?_).1 - apply ih ; simp_all - case quant kk ty tr e trih eih => - apply (((Hwfc tr tr σ₀ (updatedState σ k v) σ₀ σ) ?_).2.2 e e ?_).2.2.1 - apply trih ; simp_all - apply eih ; simp_all - case app fn e fnih eih => - apply (((Hwfc fn fn σ₀ (updatedState σ k v) σ₀ σ) ?_).2.2 e e ?_).1 - apply fnih ; simp_all - apply eih ; simp_all - case ite c t e cih tih eih => - apply (((Hwfc t t σ₀ (updatedState σ k v) σ₀ σ) ?_).2.2 e e ?_).2.2.2 c c ?_ - apply tih ; simp_all - apply eih ; simp_all - apply cih ; simp_all - case eq e1 e2 e1ih e2ih => - apply (((Hwfc e1 e1 σ₀ (updatedState σ k v) σ₀ σ) ?_).2.2 e2 e2 ?_).2.1 - apply e1ih ; simp_all - apply e2ih ; simp_all + grind + case abs m ty e ih => + apply ((Hwfc.1 (updatedState σ k v) σ)) + grind + case quant m kk ty tr e trih eih => + apply Hwfc.quantcongr <;> grind + case app m fn e fnih eih => + apply Hwfc.appcongr <;> grind + case ite m c t e cih tih eih => + apply Hwfc.itecongr <;> grind + case eq m e1 e2 e1ih e2ih => + apply Hwfc.eqcongr <;> grind theorem EvalExpressionsUpdatedState {δ : BoogieEval} : Imperative.WellFormedSemanticEvalVar δ → Boogie.WellFormedBoogieEvalCong δ → Imperative.WellFormedSemanticEvalVal δ → ¬ k ∈ es.flatMap Imperative.HasVarsPure.getVars → - EvalExpressions (P:=Boogie.Expression) δ σ₀ σ es vs → - EvalExpressions (P:=Boogie.Expression) δ σ₀ (updatedState σ k v) es vs := by + EvalExpressions (P:=Boogie.Expression) δ σ es vs → + EvalExpressions (P:=Boogie.Expression) δ (updatedState σ k v) es vs := by intros Hwfv Hwfc Hwfvl Hnin Heval have Hlen := EvalExpressionsLength Heval induction es generalizing vs σ @@ -411,8 +398,8 @@ theorem EvalExpressionUpdatedStates {δ : BoogieEval} : ks'.length = vs'.length → ks'.Nodup → ks'.Disjoint (Imperative.HasVarsPure.getVars e) → - δ σ₀ σ e = some v → - δ σ₀ (updatedStates σ ks' vs') e = some v := by + δ σ e = some v → + δ (updatedStates σ ks' vs') e = some v := by intros Hwfv Hwfc Hwfvl Hlen Hnd Hnin Heval induction ks' generalizing vs' σ case nil => @@ -442,8 +429,8 @@ theorem EvalExpressionsUpdatedStates {δ : BoogieEval} : ks'.length = vs'.length → ks'.Nodup → ks'.Disjoint (es.flatMap Imperative.HasVarsPure.getVars) → - EvalExpressions (P:=Boogie.Expression) δ σ₀ σ es vs → - EvalExpressions (P:=Boogie.Expression) δ σ₀ (updatedStates σ ks' vs') es vs := by + EvalExpressions (P:=Boogie.Expression) δ σ es vs → + EvalExpressions (P:=Boogie.Expression) δ (updatedStates σ ks' vs') es vs := by intros Hwfv Hwfc Hwfvl Hlen Hnd Hnin Heval have Hlen := EvalExpressionsLength Heval induction ks' generalizing vs' σ @@ -624,7 +611,7 @@ theorem EvalStatementContractInitVar : Imperative.WellFormedSemanticEvalVar δ → σ v = some vv → σ v' = none → - EvalStatementContract π δ σ₀ σ + EvalStatementContract π δ σ (createInitVar ((v', ty), v)) (updatedState σ v' vv) := by intros Hwf Hsome Hnone @@ -632,7 +619,7 @@ theorem EvalStatementContractInitVar : constructor constructor . apply Imperative.EvalCmd.eval_init <;> try assumption - have Hwfv := Hwf (Lambda.LExpr.fvar v none) v σ₀ σ + have Hwfv := Hwf (Lambda.LExpr.fvar () v none) v σ rw [Hwfv]; assumption simp [Imperative.HasFvar.getFvar] apply Imperative.InitState.init Hnone @@ -653,7 +640,7 @@ theorem EvalStatementsContractInitVars : List.Nodup ((trips.unzip.fst.unzip.fst) ++ (trips.unzip.snd)) → ReadValues σ (trips.unzip.snd) vvs → Imperative.isNotDefined σ (trips.unzip.fst.unzip.fst) → - EvalStatementsContract π δ σ₀ σ + EvalStatementsContract π δ σ (createInitVars trips) (updatedStates σ (trips.unzip.fst.unzip.fst) vvs) := by @@ -669,7 +656,7 @@ theorem EvalStatementsContractInitVars : | mk pair v => cases pair with | mk v' ty => - apply Imperative.EvalStmts.stmts_some_sem + apply Imperative.EvalBlock.stmts_some_sem apply EvalStatementContractInitVar <;> try assumption apply Hndef <;> simp_all unfold updatedStates @@ -692,9 +679,9 @@ theorem EvalStatementsContractInitVars : theorem EvalStatementContractInit : Imperative.WellFormedSemanticEvalVar δ → - δ σ₀ σ e = some vv → + δ σ e = some vv → σ v' = none → - EvalStatementContract π δ σ₀ σ + EvalStatementContract π δ σ (createInit ((v', ty), e)) (updatedState σ v' vv) := by intros Hwf Hsome Hnone @@ -721,10 +708,10 @@ theorem EvalStatementsContractInits : -- the generated old variable names shouldn't overlap with original variables trips.unzip.1.unzip.1.Disjoint (List.flatMap (Imperative.HasVarsPure.getVars (P:=Expression)) trips.unzip.2) → List.Nodup (trips.unzip.1.unzip.1) → - EvalExpressions (P:=Boogie.Expression) δ σ₀ σ (trips.unzip.2) vvs → + EvalExpressions (P:=Boogie.Expression) δ σ (trips.unzip.2) vvs → -- ReadValues σ (trips.unzip.2) vvs → Imperative.isNotDefined σ (trips.unzip.1.unzip.1) → - EvalStatementsContract π δ σ₀ σ + EvalStatementsContract π δ σ (createInits trips) (updatedStates σ (trips.unzip.1.unzip.1) vvs) := by @@ -740,7 +727,7 @@ theorem EvalStatementsContractInits : | mk pair v => cases pair with | mk v' ty => - apply Imperative.EvalStmts.stmts_some_sem + apply Imperative.EvalBlock.stmts_some_sem apply EvalStatementContractInit <;> try assumption apply Hndef <;> simp_all unfold updatedStates @@ -765,7 +752,7 @@ theorem EvalStatementContractHavocUpdated : ∀ vv, Imperative.WellFormedSemanticEvalVar δ → σ v = some vv' → - EvalStatementContract π δ σ₀ σ + EvalStatementContract π δ σ (createHavoc v) (updatedState σ v vv) := by intros vv Hwf Hsome @@ -814,7 +801,7 @@ theorem createFvarsSubstStores : Imperative.substDefined σ σA (ks1.zip ks2) → Imperative.substStores σ σA (ks1.zip ks2) → ReadValues σA ks2 argVals → - EvalExpressions (P:=Boogie.Expression) δ σ₀ σ (createFvars ks1) argVals := by + EvalExpressions (P:=Boogie.Expression) δ σ (createFvars ks1) argVals := by intros Hlen Hwfv Hdef Hsubst Hrd simp [createFvars] have Hlen2 := ReadValuesLength Hrd @@ -853,7 +840,7 @@ theorem EvalStatementsContractHavocVars : Imperative.WellFormedSemanticEvalVar δ → Imperative.isDefined σ vs → HavocVars σ vs σ' → - EvalStatementsContract π δ σ₀ σ + EvalStatementsContract π δ σ (createHavocs vs) σ' := by intros Hwfv Hdef Hhav simp [createHavocs] @@ -861,12 +848,12 @@ theorem EvalStatementsContractHavocVars : case nil => have Heq := HavocVarsEmpty Hhav simp_all - exact Imperative.EvalStmts.stmts_none_sem + exact Imperative.EvalBlock.stmts_none_sem case cons h t ih => simp [createHavoc] cases Hhav with | update_some Hup Hhav => - apply Imperative.EvalStmts.stmts_some_sem + apply Imperative.EvalBlock.stmts_some_sem apply EvalStmtRefinesContract apply Imperative.EvalStmt.cmd_sem apply EvalCommand.cmd_sem @@ -1134,10 +1121,10 @@ theorem Lambda.LExpr.substFvarCorrect : ((@Imperative.HasVarsPure.getVars Expression _ _ e).removeAll [fro]) → -- NOTE: the old store is irrelevant because we assume congruence on old expressions as well, -- More relation between the old store would be needed if we remove old expression congruence from WellFormedSemanticEvalVal - δ σ₀ σ e = δ σ₀' σ' (e.substFvar fro (createFvar to)) := by + δ σ e = δ σ' (e.substFvar fro (createFvar to)) := by intros Hwfc Hwfvr Hwfvl Hsubst2 Hinv induction e <;> simp [Lambda.LExpr.substFvar, createFvar] at * - case const c ty | op o ty | bvar x => + case const c | op o ty | bvar x => rw [Hwfvl.2] rw [Hwfvl.2] constructor @@ -1160,20 +1147,12 @@ theorem Lambda.LExpr.substFvarCorrect : exact Hinv simp [Imperative.HasFvar.getFvar] simp [Imperative.HasFvar.getFvar] - case mdata info e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + case abs m ty e ih => specialize ih Hinv - specialize Hwfc _ _ _ _ _ _ ih - have Hinfo := Hwfc.2.1 - specialize Hinfo info - simp [Hinfo] - case abs ty e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc - specialize ih Hinv - specialize Hwfc _ _ _ _ _ _ ih - apply Hwfc.1 - case quant k ty tr e trih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + have e2 := (e.substFvar fro (Lambda.LExpr.fvar () to none)) + have Hwfc := Hwfc.1 σ σ' e ((e.substFvar fro (Lambda.LExpr.fvar () to none))) + grind + case quant m k ty tr e trih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.app_removeAll, List.zip_append] at * @@ -1187,13 +1166,8 @@ theorem Lambda.LExpr.substFvarCorrect : rw [Hinv] left; assumption - specialize Hwfc _ _ _ _ _ _ trih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.1 - exact (Hfun k ty) - case app c fn fih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.quantcongr <;> grind + case app m c fn fih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.app_removeAll, List.zip_append] at * @@ -1205,13 +1179,8 @@ theorem Lambda.LExpr.substFvarCorrect : . intros k1 k2 Hin rw [Hinv] right; assumption - specialize Hwfc _ _ _ _ _ _ fih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.1 - exact Hfun - case ite c t e cih tih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.appcongr <;> grind + case ite m c t e cih tih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.app_removeAll, List.zip_append] at * @@ -1227,14 +1196,8 @@ theorem Lambda.LExpr.substFvarCorrect : . intros k1 k2 Hin rw [Hinv] right; right; assumption - specialize Hwfc _ _ _ _ _ _ tih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.2 - specialize Hfun _ _ cih - exact Hfun - case eq e1 e2 e1ih e2ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.itecongr <;> grind + case eq m e1 e2 e1ih e2ih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.app_removeAll, List.zip_append] at * @@ -1246,48 +1209,35 @@ theorem Lambda.LExpr.substFvarCorrect : . intros k1 k2 Hin rw [Hinv] right; assumption - specialize Hwfc _ _ _ _ _ _ e1ih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ e2ih - have Hfun := Hfun.2.1 - exact Hfun + apply Hwfc.eqcongr <;> grind theorem Lambda.LExpr.substFvarsCorrectZero : Boogie.WellFormedBoogieEvalCong δ → Imperative.WellFormedSemanticEvalVar δ → Imperative.WellFormedSemanticEvalVal δ → Imperative.invStores σ σ' (Imperative.HasVarsPure.getVars e) → - δ σ₀ σ e = δ σ₀' σ' e := by + δ σ e = δ σ' e := by intros Hwfc Hwfvr Hwfvl Hinv induction e <;> simp at * - case const c ty | op o ty | bvar x => + case const c | op o ty | bvar x => rw [Hwfvl.2] rw [Hwfvl.2] constructor constructor - case fvar name ty => + case fvar m name ty => simp [Imperative.WellFormedSemanticEvalVar] at Hwfvr - specialize Hwfvr (Lambda.LExpr.fvar name ty) name + specialize Hwfvr (Lambda.LExpr.fvar m name ty) name rw [Hwfvr] rw [Hwfvr] rw [Hinv] simp [Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] simp [Imperative.HasFvar.getFvar] simp [Imperative.HasFvar.getFvar] - case mdata info e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc - specialize ih Hinv - specialize Hwfc _ _ _ _ _ _ ih - have Hinfo := Hwfc.2.1 - specialize Hinfo info - simp [Hinfo] - case abs ty e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + case abs m ty e ih => specialize ih Hinv - specialize Hwfc _ _ _ _ _ _ ih - apply Hwfc.1 - case quant k ty tr e trih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + have Hwfc := Hwfc.abscongr σ σ' e e ih + apply Hwfc + case quant m k ty tr e trih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.zip_append] at * @@ -1299,13 +1249,8 @@ theorem Lambda.LExpr.substFvarsCorrectZero : . intros k1 k2 Hin rw [Hinv] right; assumption - specialize Hwfc _ _ _ _ _ _ trih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.1 - exact (Hfun k ty) - case app c fn fih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.quantcongr <;> grind + case app m fn e fih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.zip_append] at * @@ -1317,13 +1262,8 @@ theorem Lambda.LExpr.substFvarsCorrectZero : . intros k1 k2 Hin rw [Hinv] right; assumption - specialize Hwfc _ _ _ _ _ _ fih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.1 - exact Hfun - case ite c t e cih tih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.appcongr <;> grind + case ite m c t e cih tih eih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.zip_append] at * @@ -1339,14 +1279,8 @@ theorem Lambda.LExpr.substFvarsCorrectZero : . intros k1 k2 Hin rw [Hinv] right; right; assumption - specialize Hwfc _ _ _ _ _ _ tih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.2 - specialize Hfun _ _ cih - exact Hfun - case eq e1 e2 e1ih e2ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.itecongr <;> grind + case eq m e1 e2 e1ih e2ih => simp [Imperative.invStores, Imperative.substStores, Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * simp [List.zip_append] at * @@ -1358,11 +1292,7 @@ theorem Lambda.LExpr.substFvarsCorrectZero : . intros k1 k2 Hin rw [Hinv] right; assumption - specialize Hwfc _ _ _ _ _ _ e1ih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ e2ih - have Hfun := Hfun.2.1 - exact Hfun + apply Hwfc.eqcongr <;> grind theorem updatedStoresInvStores : ¬ k ∈ ks → @@ -1582,9 +1512,9 @@ theorem Lambda.LExpr.substFvarsCorrect : to.Disjoint (@Imperative.HasVarsPure.getVars Expression _ _ e) → Imperative.invStores σ σ' ((@Imperative.HasVarsPure.getVars Expression _ _ e).removeAll (fro ++ to)) → - δ σ₀ σ e = δ σ₀' σ' (e.substFvars (fro.zip $ createFvars to)) := by + δ σ e = δ σ' (e.substFvars (fro.zip $ createFvars to)) := by intros Hwfc Hwfvr Hwfvl Hlen Hdef Hnd Hsubst Hnin Hinv - induction fro generalizing to σ₀ σ σ' e + induction fro generalizing to σ σ' e case nil => simp_all have Hemp : to = [] := by @@ -1610,7 +1540,7 @@ theorem Lambda.LExpr.substFvarsCorrect : cases Hsubst1 with | intro Hsubst' Hsubst1 => -- the old store can stay unchanged since it is irrelevant - rw [substFvarCorrect (σ₀ := σ₀) (σ₀' := σ₀) (e := e) Hwfc Hwfvr Hwfvl Hsubst'] <;> simp_all + rw [substFvarCorrect (e := e) Hwfc Hwfvr Hwfvl Hsubst'] <;> simp_all rw [ih] <;> try simp_all . refine substDefined_updatedState ?_ exact substDefined_tail Hdef @@ -1652,11 +1582,11 @@ theorem createAssertsCorrect : Imperative.invStores σA σ' ((Imperative.HasVarsPure.getVars (P:=Expression) pre).removeAll (ks ++ ks')) ∧ ks'.Disjoint (Imperative.HasVarsPure.getVars (P:=Expression) pre) ∧ - δ σA σA pre = some Imperative.HasBool.tt) → - EvalExpressions δ σ₀ σ (createFvars ks') vals → + δ σA pre = some Imperative.HasBool.tt) → + EvalExpressions δ σ (createFvars ks') vals → ReadValues σA ks vals → Imperative.substStores σ' σA (ks'.zip ks) → - EvalStatementsContract π δ σ₀ σ' (createAsserts pres (ks.zip (createFvars ks'))) σ' := by + EvalStatementsContract π δ σ' (createAsserts pres (ks.zip (createFvars ks'))) σ' := by intros Hwfb Hwfvr Hwfvl Hwfc Hlen Hnd Hdef Hpres Heval Hrd Hsubst2 simp [createAsserts] -- Make index parameter `i` explicit so that we can induct generalizing `i`. @@ -1665,8 +1595,8 @@ theorem createAssertsCorrect : Imperative.invStores σA σ' ((Imperative.HasVarsPure.getVars (P:=Expression) pre).removeAll (ks ++ ks')) ∧ ks'.Disjoint (Imperative.HasVarsPure.getVars (P:=Expression) pre) ∧ - δ σA σA pre = some Imperative.HasBool.tt) → - EvalStatementsContract π δ σ₀ σ' + δ σA pre = some Imperative.HasBool.tt) → + EvalStatementsContract π δ σ' (List.mapIdx (fun j pred => Statement.assert s!"assert_{i + j}" (Lambda.LExpr.substFvars pred (ks.zip (createFvars ks')))) l) σ' by @@ -1679,7 +1609,7 @@ theorem createAssertsCorrect : case cons st sts ih => simp; constructor; constructor; constructor; constructor specialize Hl st (by simp) - . have Heq : δ σA σA st = δ σ₀ σ' (Lambda.LExpr.substFvars st (ks.zip (createFvars ks'))) := by + . have Heq : δ σA st = δ σ' (Lambda.LExpr.substFvars st (ks.zip (createFvars ks'))) := by apply Lambda.LExpr.substFvarsCorrect Hwfc Hwfvr Hwfvl Hlen Hdef Hnd ?_ Hl.2.1 Hl.1 . apply Imperative.substStoresFlip' simp [Imperative.substSwap, zip_swap] @@ -1710,9 +1640,9 @@ theorem createAssumesCorrect : Imperative.invStores σA σ' ((Imperative.HasVarsPure.getVars (P:=Expression) post).removeAll (ks ++ ks')) ∧ ks'.Disjoint (Imperative.HasVarsPure.getVars (P:=Expression) post) ∧ - δ σ₀' σA post = some Imperative.HasBool.tt) → + δ σA post = some Imperative.HasBool.tt) → Imperative.substStores σA σ' (ks.zip ks') → - EvalStatementsContract π δ σ₀ σ' (createAssumes posts (ks.zip (createFvars ks'))) σ' := by + EvalStatementsContract π δ σ' (createAssumes posts (ks.zip (createFvars ks'))) σ' := by intros Hwfb Hwfvr Hwfvl Hwfc Hlen Hnd Hdef Hposts Hsubst2 simp [createAssumes] -- Make index parameter `i` explicit so that we can induct generalizing `i`. @@ -1721,8 +1651,8 @@ theorem createAssumesCorrect : Imperative.invStores σA σ' ((Imperative.HasVarsPure.getVars (P:=Expression) post).removeAll (ks ++ ks')) ∧ ks'.Disjoint (Imperative.HasVarsPure.getVars (P:=Expression) post) ∧ - δ σ₀' σA post = some Imperative.HasBool.tt) → - EvalStatementsContract π δ σ₀ σ' + δ σA post = some Imperative.HasBool.tt) → + EvalStatementsContract π δ σ' (List.mapIdx (fun j pred => Statement.assume s!"assume_{i + j}" (Lambda.LExpr.substFvars pred (ks.zip (createFvars ks')))) l) σ' by @@ -1735,7 +1665,7 @@ theorem createAssumesCorrect : case cons st sts ih => simp ; constructor ; constructor ; constructor ; constructor specialize Hl st (by simp) - . have Heq : δ σ₀' σA st = δ σ₀ σ' (Lambda.LExpr.substFvars st (ks.zip (createFvars ks'))) := by + . have Heq : δ σA st = δ σ' (Lambda.LExpr.substFvars st (ks.zip (createFvars ks'))) := by apply Lambda.LExpr.substFvarsCorrect Hwfc Hwfvr Hwfvl Hlen Hdef Hnd Hsubst2 Hl.2.1 Hl.1 rw [← Heq] exact Hl.2.2 @@ -1794,93 +1724,30 @@ theorem substOldCorrect : Boogie.WellFormedBoogieEvalCong δ → Boogie.WellFormedBoogieEvalTwoState δ σ₀ σ → OldExpressions.NormalizedOldExpr e → - Imperative.invStores σ₀ σ₀' - ((OldExpressions.extractOldExprVars e).removeAll [fro]) → + --Imperative.invStores σ₀ σ + -- ((OldExpressions.extractOldExprVars e).removeAll [fro]) → Imperative.substDefined σ₀ σ [(fro, to)] → Imperative.substStores σ₀ σ [(fro, to)] → -- substitute the store and the expression simultaneously - δ σ₀ σ e = δ σ₀' σ (OldExpressions.substOld fro (createFvar to) e) := by - intros Hwfvr Hwfvl Hwfc Hwf2 Hnorm Hinv Hdef Hsubst + δ σ e = δ σ (OldExpressions.substOld fro (createFvar to) e) := by + intros Hwfvr Hwfvl Hwfc Hwf2 Hnorm Hdef Hsubst induction e <;> simp [OldExpressions.substOld] at * - case const c ty | op o ty | bvar x => - rw [Hwfvl.2] - rw [Hwfvl.2] - constructor - constructor - case fvar name ty => - simp [Imperative.WellFormedSemanticEvalVar] at Hwfvr - rw [Hwfvr] - rw [Hwfvr] - exact name - simp [Imperative.HasFvar.getFvar] - simp [Imperative.HasFvar.getFvar] - case mdata info e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc - cases Hnorm with - | mdata Hnorm => - specialize ih Hnorm Hinv - specialize Hwfc _ _ _ _ _ _ ih - have Hinfo := Hwfc.2.1 - specialize Hinfo info - simp [Hinfo] - case abs ty e ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + case abs m ty e ih => cases Hnorm with | abs Hnorm => - specialize ih Hnorm - specialize ih Hinv - specialize Hwfc _ _ _ _ _ _ ih - apply Hwfc.1 - case quant k ty tr e trih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.1 + apply ih Hnorm + case quant m k ty tr e trih eih => cases Hnorm with | quant Ht He => - specialize trih Ht ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - left; assumption - specialize eih He ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - right; assumption - specialize Hwfc _ _ _ _ _ _ trih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.1 - exact (Hfun k ty) - case app c fn fih eih => + specialize eih He + specialize trih Ht + apply Hwfc.quantcongr <;> grind + case app m c fn fih eih => cases Hnorm with | app Hc Hfn Hwf => - specialize fih Hc ?_ - . intros k1 k2 Hin - rw [Hinv] - unfold OldExpressions.extractOldExprVars at ⊢ - split <;> simp_all - . unfold OldExpressions.extractOldExprVars at Hin - simp_all - . unfold OldExpressions.extractOldExprVars at Hin - simp_all - . simp [List.app_removeAll, List.zip_append] - simp_all - specialize eih Hfn ?_ - . intros k1 k2 Hin - rw [Hinv] - unfold OldExpressions.extractOldExprVars at ⊢ - split <;> simp_all - . unfold OldExpressions.extractOldExprVars at Hin - simp_all - . specialize Hwf _ - constructor - cases Hwf - simp_all - . simp [List.app_removeAll, List.zip_append] - simp_all + specialize fih Hc + specialize eih Hfn split . -- is an old var split @@ -1895,12 +1762,12 @@ theorem substOldCorrect : cases Hwf2' with | intro σ₁ Hwf2' => by_cases Hin : fro ∈ vs - case pos e1 e2 ty' => + case pos => -- old var is modified - have HH := ((Hwf2.2.1 vs vs' σ₀ σ₁ σ Hwf2'.1 Hwf2'.2) fro).1 Hin + have HH:= Hwf2.2.1 vs vs' σ₀ σ₁ σ Hwf2'.1 Hwf2'.2 fro simp [OldExpressions.oldVar, OldExpressions.oldExpr, - BoogieIdent.unres] at HH + BoogieIdent.unres, Hin] at HH rw [HH] simp [createFvar] simp [Imperative.WellFormedSemanticEvalVar] at Hwfvr @@ -1908,7 +1775,7 @@ theorem substOldCorrect : apply Hsubst exact List.mem_singleton.mpr rfl simp [Imperative.HasFvar.getFvar] - case neg e1 e2 ty' => + case neg => -- old var is not modified have Hup := HavocVarsUpdateStates Hwf2'.1 cases Hup with @@ -1918,10 +1785,10 @@ theorem substOldCorrect : | intro bs Hinit => have Hsubst' := substStoresUpdatesInv' ?_ Hsubst Hup have Hsubst'' := substStoresInitsInv' ?_ Hsubst' Hinit - . have HH := ((Hwf2.2.1 _ _ _ _ _ Hwf2'.1 Hwf2'.2) fro).2 Hin + . have HH:= Hwf2.2.1 vs vs' σ₀ σ₁ σ Hwf2'.1 Hwf2'.2 fro simp [OldExpressions.oldVar, OldExpressions.oldExpr, - BoogieIdent.unres] at HH + BoogieIdent.unres, Hin] at HH simp [createFvar] simp [HH] simp [Imperative.WellFormedSemanticEvalVar] at Hwfvr @@ -1941,71 +1808,24 @@ theorem substOldCorrect : simp [Heq] at * contradiction . -- is an old var that is not substituted, use congruence - specialize Hwfc _ _ _ _ _ _ fih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.1 - exact Hfun + rename_i e1 e2 mOp ty0 mVar x ty1 h + simp at m mOp ty0 mVar x ty1 + apply Hwfc.appcongr <;> grind . -- is not an old var, use congruence - specialize Hwfc _ _ _ _ _ _ fih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.1 - exact Hfun - case ite c t e cih tih eih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + apply Hwfc.appcongr <;> grind + case ite m c t e cih tih eih => cases Hnorm with | ite Hc Ht He => - specialize cih Hc ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - left; assumption - specialize tih Ht ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - right; left; assumption - specialize eih He ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - right; right; assumption - specialize Hwfc _ _ _ _ _ _ tih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ eih - have Hfun := Hfun.2.2.2 - specialize Hfun _ _ cih - exact Hfun - case eq e1 e2 e1ih e2ih => - simp [Boogie.WellFormedBoogieEvalCong] at Hwfc + specialize cih Hc + specialize tih Ht + specialize eih He + apply Hwfc.itecongr <;> grind + case eq m e1 e2 e1ih e2ih => cases Hnorm with | eq He1 He2 => - specialize e1ih He1 ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - left; assumption - specialize e2ih He2 ?_ - . intros k1 k2 Hin - rw [Hinv] - simp [OldExpressions.extractOldExprVars, - List.app_removeAll, - List.zip_append] - right; assumption - specialize Hwfc _ _ _ _ _ _ e1ih - have Hfun := Hwfc.2.2 - specialize Hfun _ _ e2ih - have Hfun := Hfun.2.1 - exact Hfun + specialize e2ih He2 + apply Hwfc.eqcongr <;> grind + -- Needed from refinement theorem -- UpdateState P✝ σ id v✝ σ'✝ @@ -2196,7 +2016,7 @@ NormalizedOldExpr e → (extractOldExprVars e).removeAll [h] := by intros Hnorm induction Hnorm <;> simp [extractOldExprVars, createFvar, substOld] at * <;> try assumption - case app fn e Hnfn Hne Hwf ih1 ih2 => + case app fn e m Hnfn Hne Hwf ih1 ih2 => split . -- is old var next e1 e2 ty x ty => @@ -2210,7 +2030,10 @@ NormalizedOldExpr e → cases Hwf simp_all simp [BoogieIdent.unres] at HH - have Hnold' : ¬ IsOldPred (substOld h (Lambda.LExpr.fvar h' none) fn) := by + rename_i md tyy id v + have HH2 := HH md tyy () id v + simp_all + have Hnold' : ¬ IsOldPred (substOld h (Lambda.LExpr.fvar () h' none) fn) := by intros Hold apply Hnold apply substOldIsOldPred' ?_ Hold @@ -2223,7 +2046,13 @@ NormalizedOldExpr e → exfalso; apply Hnold'; constructor . -- old expr, contradiction exfalso; apply Hnold'; constructor - split <;> simp_all + split + · rename_i x1 x2 x3 m1 id ty2 x7 + have hI: Lambda.LExpr.op x2 ⟨"old", Visibility.unres⟩ x3 = Lambda.LExpr.op x2 ⟨"old", Visibility.unres⟩ x3 := by rfl + have hI2: Lambda.LExpr.fvar m1 id ty2 = Lambda.LExpr.fvar m1 id ty2 := by rfl + have h2 := HH x2 x3 m1 id ty2 hI hI2 + exfalso + contradiction . -- old expr, contradiction exfalso; apply Hnold; constructor . simp [List.app_removeAll] @@ -2256,9 +2085,9 @@ theorem substOldExpr_cons: unfold OldExpressions.substsOldExpr split <;> simp [*] simp_all [createOldVarsSubst, createFvar] - rename_i fn e _ _ H - generalize H1: (OldExpressions.substOld h.snd (Lambda.LExpr.fvar h.fst.fst none) fn) = fn' - generalize H2: (OldExpressions.substOld h.snd (Lambda.LExpr.fvar h.fst.fst none) e) = e' + rename_i _ fn e _ _ H + generalize H1: (OldExpressions.substOld h.snd (Lambda.LExpr.fvar () h.fst.fst none) fn) = fn' + generalize H2: (OldExpressions.substOld h.snd (Lambda.LExpr.fvar () h.fst.fst none) e) = e' rw (occs := [3]) [Boogie.OldExpressions.substsOldExpr.eq_def] simp; split simp_all [Map.isEmpty]; rename_i H; split at H <;> simp_all @@ -2267,7 +2096,7 @@ theorem substOldExpr_cons: unfold OldExpressions.substOld at H1 split at H1 <;> simp_all unfold OldExpressions.substOld at H2 - split at H2 <;> simp_all + split at H2 <;> simp_all; grind split at H2; split at H2 any_goals simp_all simp [← H2.left] at * @@ -2308,24 +2137,17 @@ theorem substsOldCorrect : Imperative.substDefined σ₀ σ (createOldStoreSubst oldTrips) → Imperative.substNodup (createOldStoreSubst oldTrips) → oldTrips.unzip.1.unzip.1.Disjoint (OldExpressions.extractOldExprVars e) → - δ σ₀ σ e = δ σ₀' σ (OldExpressions.substsOldExpr (createOldVarsSubst oldTrips) e) := by + δ σ e = δ σ (OldExpressions.substsOldExpr (createOldVarsSubst oldTrips) e) := by intros Hwfvr Hwfvl Hwfc Hwf2 Hnorm Hsubst Hdef Hnd Hdisj induction oldTrips generalizing e case nil => simp [createOldVarsSubst] at *; rw[OldExpressions.substOldExpr_nil] - cases Hwf2 with - | intro vs Hwf2 => - apply Lambda.LExpr.substFvarsCorrectZero Hwfc Hwfvr Hwfvl - intros k1 k2 Hin - simp [zip_self_eq Hin] case cons h t ih => have : OldExpressions.substsOldExpr (createOldVarsSubst (h :: t)) e = OldExpressions.substsOldExpr (createOldVarsSubst t) (OldExpressions.substOld h.snd (createFvar h.1.fst) e) :=by apply substOldExpr_cons Hnd rw[this, ← ih] - apply substOldCorrect <;> try simp_all - intro k1 k2 Hin - simp [zip_self_eq Hin] + apply substOldCorrect <;> try assumption intro k1 k2 Hin simp [Imperative.substDefined] at Hdef apply Hdef; simp_all [createOldStoreSubst, createOldStoreSubst.go] @@ -2348,7 +2170,7 @@ theorem substsOldCorrect : simp exact List.Disjoint_cons_tail H.right -theorem genArgExprIdent_len' : (List.mapM genArgExprIdent t s).fst.length = t.length := by +theorem genArgExprIdent_len' : (List.mapM (fun _ => genArgExprIdent) t s).fst.length = t.length := by induction t generalizing s <;> simp_all case nil => simp [pure, StateT.pure] @@ -2358,12 +2180,12 @@ theorem genArgExprIdent_len' : (List.mapM genArgExprIdent t s).fst.length = t.le simp [StateT.map, Functor.map] apply ih -theorem genArgExprIdent_len : List.mapM genArgExprIdent t s = (a, s') → t.length = a.length := by +theorem genArgExprIdent_len : List.mapM (fun _ => genArgExprIdent) t s = (a, s') → t.length = a.length := by intros Hgen - generalize Heq : List.mapM genArgExprIdent t s = res at Hgen + generalize Heq : List.mapM (fun _ => genArgExprIdent) t s = res at Hgen cases res with | mk fst snd => - have Heq' : (List.mapM genArgExprIdent t s).fst = fst := by simp [Heq] + have Heq' : (List.mapM (fun _ => genArgExprIdent) t s).fst = fst := by simp [Heq] cases Hgen simp [← Heq'] symm @@ -2438,7 +2260,7 @@ theorem genArgExprIdentsTrip_snd : simp [genArgExprIdents] at heq induction args <;> simp_all case cons h t ih => - simp [bind, StateT.bind, Functor.map, StateT.map] at heq + simp [bind, List.replicate, StateT.bind, StateT.map] at heq rw [List.map_snd_zip] simp split at heq @@ -2449,8 +2271,11 @@ theorem genArgExprIdentsTrip_snd : next a'' e'' heq'' => cases heq simp_all - rw [genArgExprIdent_len (t:=t) (a:=a'')] <;> try assumption - simp_all + have Hlen: t.length = (List.replicate t.length ()).length := by + solve | simp + rw [Hlen] + rw [genArgExprIdent_len (t:=List.replicate t.length ()) (a:=a'')] <;> try assumption + omega . simp [throw, throwThe, MonadExceptOf.throw, ExceptT.mk, pure, StateT.pure] at Hgen cases Hgen @@ -2521,17 +2346,19 @@ theorem Procedure.find.go_in_decls : theorem Procedure.find_in_decls : Program.Procedure.find? p name = some proc → - ∃ md, - .proc proc md ∈ p.decls := by + ∃ md, .proc proc md ∈ p.decls := by intros Hsome - simp [Program.Procedure.find?] at Hsome - split at Hsome <;> simp_all - simp [Decl.getProc] at Hsome - split at Hsome <;> simp_all - next md heq => - exists md - simp [Program.find?] at heq - exact find.go_in_decls heq + simp only [Program.Procedure.find?] at Hsome + split at Hsome + case h_1 => + grind + case h_2 d heq => + simp only [Decl.getProc, Option.some.injEq] at Hsome + split at Hsome + case h_1 _ _ proc' md _ => + exists md + simp only [Hsome] at heq + exact find.go_in_decls heq theorem Program.find.go_decl_kind_match : Program.find?.go d name decls = some decl → @@ -2651,7 +2478,7 @@ case cons h t ih => /--! Theorems about well-formedness of BoogieGen -/ theorem genArgExprIdentTemp : - genArgExprIdent e s = (l, s') → BoogieIdent.isTemp l := + genArgExprIdent s = (l, s') → BoogieIdent.isTemp l := fun Hgen => by exact genBoogieIdentTemp Hgen theorem genOutExprIdentTemp : @@ -2670,36 +2497,36 @@ theorem genIdentGeneratedWF : fun Hgen => genBoogieIdentGeneratedWF Hgen theorem genArgExprIdentGeneratedWF : - genArgExprIdent e s = (l, s') → s'.generated = l :: s.generated := + genArgExprIdent s = (l, s') → s'.generated = l :: s.generated := fun Hgen => genBoogieIdentGeneratedWF Hgen theorem genArgExprIdentsGeneratedWF : - genArgExprIdents es s = (ls, s') → + genArgExprIdents n s = (ls, s') → ls.reverse ++ s.generated = s'.generated := by intros Hgen simp [genArgExprIdents] at Hgen - induction es generalizing s ls s' <;> simp at Hgen - case nil => + induction n generalizing s ls s' + case zero => + rw [List.replicate_zero] at Hgen simp [StateT.pure, pure] at Hgen cases Hgen <;> simp_all - case cons h t ih => - simp [bind, StateT.bind, Functor.map, StateT.map, pure] at Hgen + case succ n => + simp only [List.replicate] at Hgen + simp [bind, StateT.bind, pure] at Hgen split at Hgen next a s₁ heq => split at Hgen next a' s₂ heq' => cases Hgen have HH := genArgExprIdentGeneratedWF heq - specialize ih heq' - simp [HH] at ih - simp_all + grind theorem genArgExprIdentsTripGeneratedWF { s s' : BoogieGenState } : genArgExprIdentsTrip outs xs s = (Except.ok trips, s') → trips.unzip.1.unzip.1.reverse ++ s.generated = s'.generated := by intros Hgen - apply genArgExprIdentsGeneratedWF (es:=xs) + apply genArgExprIdentsGeneratedWF (n:=xs.length) simp [genArgExprIdentsTrip] at * split at Hgen . simp [Functor.map, ExceptT.map, bind, @@ -2728,29 +2555,30 @@ theorem genArgExprIdentsTripGeneratedWF { s s' : BoogieGenState } : theorem genArgExprIdentWFMono : BoogieGenState.WF s → - genArgExprIdent e s = (l, s') → + genArgExprIdent s = (l, s') → BoogieGenState.WF s' := fun Hgen => BoogieGenState.WFMono' Hgen theorem genArgExprIdentsWFMono : BoogieGenState.WF s → - genArgExprIdents es s = (ls, s') → + genArgExprIdents n s = (ls, s') → BoogieGenState.WF s' := by intros Hwf Hgen simp [genArgExprIdents] at Hgen - induction es generalizing s ls s' <;> simp at Hgen - case nil => + induction n generalizing s ls s' + case zero => simp [StateT.pure, pure] at Hgen cases Hgen <;> simp_all - case cons h t ih => - simp [bind, StateT.bind, Functor.map, StateT.map, pure] at Hgen + case succ n' => + simp only [List.replicate] at Hgen + simp [bind, StateT.bind, pure] at Hgen split at Hgen next a s₁ heq => split at Hgen next a' s₂ heq' => cases Hgen have HH := genArgExprIdentWFMono Hwf heq - exact ih HH heq' + grind theorem genArgExprIdentsTripWFMono : BoogieGenState.WF s → @@ -2768,7 +2596,7 @@ theorem genArgExprIdentsTripWFMono : simp [pure, StateT.pure] at Hgen cases Hgen simp [StateT.map, Functor.map] at heq - generalize Hgen' : (genArgExprIdents xs s) = gen at heq + generalize Hgen' : (genArgExprIdents xs.length s) = gen at heq cases gen with | mk fst snd => simp at heq @@ -3057,7 +2885,7 @@ theorem extractedOldExprInVars : exact H2 intros x Hin simp_all - case mdata ih | abs ih => + case abs ih => cases Hnorm apply ih <;> assumption case quant trih eih => @@ -3253,12 +3081,35 @@ theorem extractedOldVarsInVars : open OldExpressions in theorem substOldPostSubset: (Imperative.HasVarsPure.getVars (P:=Expression) - (substOld h2 (Lambda.LExpr.fvar h1 ty) post)).Subset + (substOld h2 (Lambda.LExpr.fvar m h1 ty) post)).Subset (Imperative.HasVarsPure.getVars (P:=Expression) post ++ [h1]) := by induction post <;> simp [substOld] - case fvar | op | const | bvar => - intros x Hin ; simp_all - case mdata ih | abs ih => + case fvar => + intros x Hin + rename_i m name ty2 + simp at m + simp at name + simp at ty2 + + simp_all + case op => + intros x Hin + rename_i m name ty2 + simp at m + simp at name + simp at ty2 + simp_all + case const => + intros x Hin + rename_i m name + simp at m + simp_all + case bvar => + intros x Hin + rename_i m d + simp at m + simp_all + case abs ih => exact ih case ite cih tih eih => simp [Imperative.HasVarsPure.getVars, Lambda.LExpr.LExpr.getVars] at * @@ -3304,6 +3155,8 @@ theorem substOldPostSubset: . apply List.Subset.trans apply trih <;> assumption intros x Hin + rename_i m1 k1 ty1 trigger1 e1 + have assoc := List.append_assoc (Lambda.LExpr.LExpr.getVars trigger1) (Lambda.LExpr.LExpr.getVars e1) [h1] simp_all cases Hin <;> simp_all . apply List.Subset.trans @@ -3325,7 +3178,7 @@ theorem substOldPostSubset: theorem substOldExprPostSubset': (Imperative.HasVarsPure.getVars (P:=Expression) - (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar h1 ty))] post)).Subset + (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar m h1 ty))] post)).Subset (Imperative.HasVarsPure.getVars (P:=Expression) post ++ [h1]) := by rw [OldExpressions.substsOldExpr_singleton] apply substOldPostSubset @@ -3333,16 +3186,16 @@ theorem substOldExprPostSubset': theorem substOldExprPostSubset'': (Imperative.HasVarsPure.getVars (P:=Expression) post ++ [h1]) ⊆ S → (Imperative.HasVarsPure.getVars (P:=Expression) - (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar h1 ty))] post)) ⊆ S := by + (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar m h1 ty))] post)) ⊆ S := by have : (Imperative.HasVarsPure.getVars (P:=Expression) - (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar h1 ty))] post)).Subset + (OldExpressions.substsOldExpr [(h2, (Lambda.LExpr.fvar m h1 ty))] post)).Subset (Imperative.HasVarsPure.getVars (P:=Expression) post ++ [h1]) := substOldExprPostSubset' apply List.Subset.trans this open OldExpressions in theorem substOldExprPostSubset: (Imperative.HasVarsPure.getVars (P:=Expression) - (substsOldExpr ((h2, (Lambda.LExpr.fvar h1 ty))::t) post)).Subset + (substsOldExpr ((h2, (Lambda.LExpr.fvar m h1 ty))::t) post)).Subset (Imperative.HasVarsPure.getVars (P:=Expression) (substsOldExpr t post) ++ [h1]) := by induction post any_goals (simp only [Imperative.HasVarsPure.getVars, substsOldExpr, Map.isEmpty, Bool.false_eq_true, ↓reduceIte, ite_self] at *; try apply List.subset_append_left) @@ -3351,7 +3204,7 @@ theorem substOldExprPostSubset: any_goals (apply List.append_subset.mpr; constructor <;> try apply List.Subset.trans (by assumption); try apply List.append_subset.mpr; constructor) any_goals (apply List.append_subset.mpr; constructor) any_goals apply List.Subset.assoc.mp - any_goals (apply List.Subset.subset_app_of_or_3; simp) + any_goals (solve | apply List.Subset.subset_app_of_or_3; simp) split <;> try split any_goals (split <;> try split) any_goals split @@ -3405,7 +3258,7 @@ theorem substsOldPostSubset: have ih := @ih post Hdisj have : (Imperative.HasVarsPure.getVars - (substsOldExpr ((h.snd, Lambda.LExpr.fvar h.1.fst none) :: List.map createOldVarsSubst.go t) post)).Subset + (substsOldExpr ((h.snd, Lambda.LExpr.fvar () h.1.fst none) :: List.map createOldVarsSubst.go t) post)).Subset ((Imperative.HasVarsPure.getVars (substsOldExpr (List.map createOldVarsSubst.go t) post)) ++ [h.1.fst]) := by apply substOldExprPostSubset apply List.Subset.trans this @@ -3419,26 +3272,26 @@ theorem substsOldPostSubset: set_option maxHeartbeats 500000 -- Second, the program/statement returned by callElim has the same semantics as the pre-transformation program/statement -theorem callElimStatementCorrect : +theorem callElimStatementCorrect [LawfulBEq Expression.Expr] : -- procedure lookup function is well-behaved (∀ pname, π pname = (Program.Procedure.find? p (.unres pname))) → -- all global variables in p exist in σ (∀ gk, (p.find? .var gk).isSome → (σ gk).isSome) → - EvalStatementsContract π δ σ₀ σ [st] σ' → + EvalStatementsContract π δ σ [st] σ' → WellFormedBoogieEvalCong δ → WF.WFStatementsProp p [st] → WF.WFProgramProp p → BoogieGenState.WF γ → (∀ v, v ∈ γ.generated ↔ ((σ v).isSome ∧ BoogieIdent.isTemp v)) → - (Except.ok sts, γ') = (CallElim.runCallElimWith' [st] (CallElim.callElimStmts · p) γ) → + (Except.ok sts, γ') = (runWith [st] (CallElim.callElimStmts · p) γ) → -- NOTE: The theorem does not expect the same store due to inserting new temp variables exists σ'', Inits σ' σ'' ∧ - EvalStatementsContract π δ σ₀ σ sts σ'' + EvalStatementsContract π δ σ sts σ'' := by intros Hp Hgv Heval Hwfc Hwf Hwfp Hwfgen Hwfgenst Helim cases st <;> - simp [StateT.run, callElimStmts, callElimStmt, + simp [Transform.runWith, StateT.run, callElimStmts, runStmts, callElimStmt, pure, ExceptT.pure, ExceptT.mk, StateT.pure, bind, ExceptT.bind, ExceptT.bindCont, StateT.bind, ] at Helim @@ -3470,15 +3323,17 @@ theorem callElimStatementCorrect : cases Hwf with | mk Hwf => simp [Option.isSome] at Hwf split at Hwf <;> simp_all - next decl' proc Hfa Harglen Houtlen Hlhsdisj Hlhs Hwfargs Hfind => + next decl' proc Harglen Houtlen Hlhsdisj Hlhs Hwfargs Hfind => cases Heval with | stmts_some_sem Heval Heval2 => cases Heval with | cmd_sem Heval Hdef => cases Heval with | call_sem lkup Hevalargs Hevalouts Hwfval Hwfvars Hwfb Hwf2 Hwf Hinitin Hinitout Hpre Hhav1 Hhav2 Hpost Hrd Hupdate => next outVals argVals σA σAO σO σR p' modvals => + unfold BoogieIdent.unres at Hfind have Hsome : (Program.Procedure.find? p procName).isSome := by simp [Hfind] simp [Option.isSome] at Hsome + unfold BoogieIdent.unres at * have lkup' := lkup split at Hsome <;> try contradiction next x val Hfind => @@ -3591,7 +3446,7 @@ theorem callElimStatementCorrect : (argTrips.unzip.fst.unzip.fst ++ outTrips.unzip.fst.unzip.fst ++ oldTrips.unzip.fst.unzip.fst) := by - simp only [EvalStmtsEmpty Heval2] at * + simp only [EvalBlockEmpty Heval2] at * apply UpdateStatesNotDefMonotone ?_ Hupdate intros v Hin have Htemp : v.isTemp = true := by @@ -3971,6 +3826,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwf := (List.Forall_mem_iff.mp wfpre _ HH).glvars simp at Hwf exact Hwf @@ -3980,6 +3837,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => apply List.Disjoint_app.mp ⟨?_, ?_⟩ . apply List.PredDisjoint_Disjoint (P:=(BoogieIdent.isTemp ·)) @@ -4170,7 +4029,7 @@ theorem callElimStatementCorrect : generalize HσR₁ : (updatedStates (updatedStates σR (List.map (Prod.fst ∘ Prod.fst) outTrips) outVals)) (List.map (Prod.fst ∘ Prod.fst) oldTrips) oldVals = σR₁ - apply createAssumesCorrect (σ₀':=σ₁) (σA:=σR₁) Hwfb Hwfvars + apply createAssumesCorrect (σA:=σR₁) Hwfb Hwfvars . assumption . assumption . -- length @@ -4343,6 +4202,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hpost := (List.Forall_mem_iff.mp wfpost _ HH) have Hlcl := List.Forall_mem_iff.mp Hpost.lvars have Hgl := List.Forall_mem_iff.mp Hpost.glvars @@ -4417,8 +4278,8 @@ theorem callElimStatementCorrect : -- simp [Imperative.WellFormedSemanticEvalBool] at Hwfb -- apply (Hwfb _ _ _).1.1.mp have Hsubst' : - δ σO σR₁ post = - δ σ₁ σR₁ (OldExpressions.substsOldExpr (createOldVarsSubst oldTrips) (OldExpressions.normalizeOldExpr post)) + δ σR₁ post = + δ σR₁ (OldExpressions.substsOldExpr (createOldVarsSubst oldTrips) (OldExpressions.normalizeOldExpr post)) := by cases Hwf2 with | intro e Hwf2 => @@ -4473,9 +4334,8 @@ theorem callElimStatementCorrect : exact UpdateStatesNotDefMonotone' Hndef Hupdate . exact (List.nodup_append.mp Hgennd).2.1 . simp [Houttriplen] - . intros vs vs' σ₀ σ₁ σ Hhav Hinit - have HH := Hwf2.1 vs vs' σ₀ σ₁ σ ⟨Hhav,Hinit⟩ - apply HH + . intros vs vs' σ₀ σ₁ σ m Hhav Hinit + grind -- normalized . apply OldExpressions.normalizeOldExprSound have HH := prepostconditions_unwrap Hin.1 @@ -4483,6 +4343,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwfpost := (List.Forall_mem_iff.mp wfpost _ HH).oldexprs simp at Hwfpost exact Hwfpost @@ -4572,6 +4434,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwf := (List.Forall_mem_iff.mp wfpost _ HH).glvars simp at Hwf exact Hwf @@ -4582,6 +4446,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwf := (List.Forall_mem_iff.mp wfpost _ HH).oldexprs simp at Hwf exact Hwf @@ -4599,6 +4465,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwf := (List.Forall_mem_iff.mp wfpost _ HH).glvars simp at Hwf exact Hwf @@ -4615,6 +4483,8 @@ theorem callElimStatementCorrect : | intro label HH => cases HH with | intro attr HH => + cases HH with + | intro md HH => have Hwf := (List.Forall_mem_iff.mp wfpost _ HH).glvars simp at Hwf exact Hwf diff --git a/Strata/Transform/DetToNondet.lean b/Strata/Transform/DetToNondet.lean index 712093d66..37313a927 100644 --- a/Strata/Transform/DetToNondet.lean +++ b/Strata/Transform/DetToNondet.lean @@ -21,21 +21,21 @@ def StmtToNondetStmt {P : PureExpr} [Imperative.HasBool P] [HasNot P] Imperative.NondetStmt P (Cmd P) := match st with | .cmd cmd => .cmd cmd - | .block _ ⟨ bss ⟩ _ => StmtsToNondetStmt bss - | .ite cond ⟨ tss ⟩ ⟨ ess ⟩ md => + | .block _ bss _ => BlockToNondetStmt bss + | .ite cond tss ess md => .choice - (.seq (.assume "true_cond" cond md) (StmtsToNondetStmt tss)) - (.seq ((.assume "false_cond" (Imperative.HasNot.not cond) md)) (StmtsToNondetStmt ess)) - | .loop guard _measure _inv ⟨ bss ⟩ md => - .loop (.seq (.assume "guard" guard md) (StmtsToNondetStmt bss)) + (.seq (.assume "true_cond" cond md) (BlockToNondetStmt tss)) + (.seq ((.assume "false_cond" (Imperative.HasNot.not cond) md)) (BlockToNondetStmt ess)) + | .loop guard _measure _inv bss md => + .loop (.seq (.assume "guard" guard md) (BlockToNondetStmt bss)) | .goto _ _ => (.assume "skip" Imperative.HasBool.tt) /-- Deterministic-to-nondeterministic transformation for multiple (deterministic) statements -/ -def StmtsToNondetStmt {P : Imperative.PureExpr} [Imperative.HasBool P] [HasNot P] - (ss : Imperative.Stmts P (Cmd P)) : +def BlockToNondetStmt {P : Imperative.PureExpr} [Imperative.HasBool P] [HasNot P] + (ss : Imperative.Block P (Cmd P)) : Imperative.NondetStmt P (Cmd P) := match ss with | [] => (.assume "skip" Imperative.HasBool.tt) - | s :: ss => .seq (StmtToNondetStmt s) (StmtsToNondetStmt ss) + | s :: ss => .seq (StmtToNondetStmt s) (BlockToNondetStmt ss) end diff --git a/Strata/Transform/DetToNondetCorrect.lean b/Strata/Transform/DetToNondetCorrect.lean index eaec65d73..86c5e62d5 100644 --- a/Strata/Transform/DetToNondetCorrect.lean +++ b/Strata/Transform/DetToNondetCorrect.lean @@ -34,68 +34,62 @@ theorem StmtToNondetCorrect WellFormedSemanticEvalVal δ → (∀ st, Stmt.sizeOf st ≤ m → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ st σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtToNondetStmt st) σ') ∧ + EvalStmt P (Cmd P) (EvalCmd P) δ σ st σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (StmtToNondetStmt st) σ') ∧ (∀ ss, - Stmts.sizeOf ss ≤ m → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ ss σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtsToNondetStmt ss) σ') := by + Block.sizeOf ss ≤ m → + EvalBlock P (Cmd P) (EvalCmd P) δ σ ss σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (BlockToNondetStmt ss) σ') := by intros Hwfb Hwfvl apply Nat.strongRecOn (motive := λ m ↦ - ∀ σ₀ σ σ', + ∀ σ σ', (∀ st, Stmt.sizeOf st ≤ m → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ st σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtToNondetStmt st) σ') ∧ + EvalStmt P (Cmd P) (EvalCmd P) δ σ st σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (StmtToNondetStmt st) σ') ∧ (∀ ss, - Stmts.sizeOf ss ≤ m → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ ss σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtsToNondetStmt ss) σ') + Block.sizeOf ss ≤ m → + EvalBlock P (Cmd P) (EvalCmd P) δ σ ss σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (BlockToNondetStmt ss) σ') ) - intros n ih σ₀ σ σ' + intros n ih σ σ' refine ⟨?_, ?_⟩ . intros st Hsz Heval match st with | .cmd c => cases Heval constructor <;> simp_all - | .block _ ⟨ bss ⟩ => + | .block _ bss => cases Heval with | block_sem Heval => next label b => - cases Heval with - | block_sem Heval => - specialize ih (Stmts.sizeOf bss) (by simp_all; omega) - apply (ih _ _ _).2 + specialize ih (Block.sizeOf bss) (by simp_all; omega) + apply (ih _ _).2 omega assumption - | .ite c ⟨ tss ⟩ ⟨ ess ⟩ => + | .ite c tss ess => cases Heval with | ite_true_sem Htrue Hwfb Heval => - cases Heval with - | block_sem Heval => - specialize ih (Stmts.sizeOf tss) (by simp_all; omega) + specialize ih (Block.sizeOf tss) (by simp_all; omega) refine EvalNondetStmt.choice_left_sem Hwfb ?_ apply EvalNondetStmt.seq_sem . apply EvalNondetStmt.cmd_sem exact EvalCmd.eval_assume Htrue Hwfb simp [isDefinedOver, HasVarsImp.modifiedVars, Cmd.modifiedVars, isDefined] - . apply (ih _ _ _).2 + . apply (ih _ _).2 omega assumption | ite_false_sem Hfalse Hwfb Heval => next c t e => - cases Heval with - | block_sem Heval => - specialize ih (Stmts.sizeOf ess) (by simp_all; omega) + specialize ih (Block.sizeOf ess) (by simp_all; omega) refine EvalNondetStmt.choice_right_sem Hwfb ?_ apply EvalNondetStmt.seq_sem . apply EvalNondetStmt.cmd_sem refine EvalCmd.eval_assume ?_ Hwfb simp [WellFormedSemanticEvalBool] at Hwfb - exact (Hwfb σ₀ σ c).2.mp Hfalse + exact (Hwfb σ c).2.mp Hfalse simp [isDefinedOver, HasVarsImp.modifiedVars, Cmd.modifiedVars, isDefined] - . apply (ih _ _ _).2 + . apply (ih _ _).2 omega assumption | .goto _ => @@ -108,27 +102,27 @@ theorem StmtToNondetCorrect cases ss <;> cases Heval case stmts_none_sem => - simp [StmtsToNondetStmt] + simp [BlockToNondetStmt] constructor constructor next wfvl wffv wfb wfbv wfn => expose_names simp [WellFormedSemanticEvalVal] at Hwfvl have Hval := wfbv.bool_is_val.1 - have Hv := Hwfvl.2 HasBool.tt σ₀ σ Hval + have Hv := Hwfvl.2 HasBool.tt σ Hval exact Hv assumption intros id Hin simp [HasVarsImp.modifiedVars, Cmd.modifiedVars] at Hin case stmts_some_sem h t σ'' Heval Hevals => - simp [StmtsToNondetStmt] - simp [Stmts.sizeOf] at Hsz - specialize ih (h.sizeOf + Stmts.sizeOf t) (by omega) + simp [BlockToNondetStmt] + simp [Block.sizeOf] at Hsz + specialize ih (h.sizeOf + Block.sizeOf t) (by omega) constructor - . apply (ih _ _ _).1 + . apply (ih _ _).1 omega exact Heval - . apply (ih _ _ _).2 + . apply (ih _ _).2 omega exact Hevals @@ -138,18 +132,18 @@ theorem StmtToNondetStmtCorrect [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] : WellFormedSemanticEvalBool δ → WellFormedSemanticEvalVal δ → - EvalStmt P (Cmd P) (EvalCmd P) δ σ₀ σ st σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtToNondetStmt st) σ' := by + EvalStmt P (Cmd P) (EvalCmd P) δ σ st σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (StmtToNondetStmt st) σ' := by intros Hwfb Hwfv Heval apply (StmtToNondetCorrect Hwfb Hwfv (m:=st.sizeOf)).1 <;> simp_all /-- Proof that the Deterministic-to-nondeterministic transformation is correct for multiple (deterministic) statements -/ -theorem StmtsToNondetStmtCorrect +theorem BlockToNondetStmtCorrect [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] : WellFormedSemanticEvalBool δ → WellFormedSemanticEvalVal δ → - EvalStmts P (Cmd P) (EvalCmd P) δ σ₀ σ ss σ' → - EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ₀ σ (StmtsToNondetStmt ss) σ' := by + EvalBlock P (Cmd P) (EvalCmd P) δ σ ss σ' → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (BlockToNondetStmt ss) σ' := by intros Hwfb Hwfv Heval - apply (StmtToNondetCorrect Hwfb Hwfv (m:=Stmts.sizeOf ss)).2 <;> simp_all + apply (StmtToNondetCorrect Hwfb Hwfv (m:=Block.sizeOf ss)).2 <;> simp_all diff --git a/Strata/Transform/LoopElim.lean b/Strata/Transform/LoopElim.lean index 17725e949..1a5f435f2 100644 --- a/Strata/Transform/LoopElim.lean +++ b/Strata/Transform/LoopElim.lean @@ -25,51 +25,46 @@ def Stmt.removeLoopsM [HasNot P] [HasVarsImp P C] [HasHavoc P C] [HasPassiveCmds P C] (s : Stmt P C) : StateM Nat (Stmt P C) := match s with - | .loop guard _ invariant? ⟨ bss ⟩ _ => do + | .loop guard _ invariant? bss md => do let invariant := invariant?.getD HasBool.tt let loop_num ← StateT.modifyGet (fun x => (x, x + 1)) let neg_guard : P.Expr := HasNot.not guard - let assigned_vars := Stmts.modifiedVars bss + let assigned_vars := Block.modifiedVars bss let havocd : Stmt P C := - .block s!"loop_havoc_{loop_num}" { - ss := assigned_vars.map (λ n => Stmt.cmd (HasHavoc.havoc n)) - } {} + .block s!"loop_havoc_{loop_num}" (assigned_vars.map (λ n => Stmt.cmd (HasHavoc.havoc n))) {} let entry_invariant := - Stmt.cmd (HasPassiveCmds.assert s!"entry_invariant_{loop_num}" invariant) + Stmt.cmd (HasPassiveCmds.assert s!"entry_invariant_{loop_num}" invariant md) let first_iter_facts := - .block s!"first_iter_asserts_{loop_num}" {ss := [entry_invariant]} {} - let arbitrary_iter_assumes := .block s!"arbitrary_iter_assumes_{loop_num}" { - ss := [(Stmt.cmd (HasPassiveCmds.assume s!"assume_guard_{loop_num}" guard)), - (Stmt.cmd (HasPassiveCmds.assume s!"assume_invariant_{loop_num}" invariant))]} + .block s!"first_iter_asserts_{loop_num}" [entry_invariant] {} + let arbitrary_iter_assumes := .block s!"arbitrary_iter_assumes_{loop_num}" [(Stmt.cmd (HasPassiveCmds.assume s!"assume_guard_{loop_num}" guard md)), + (Stmt.cmd (HasPassiveCmds.assume s!"assume_invariant_{loop_num}" invariant md))] let maintain_invariant := - Stmt.cmd (HasPassiveCmds.assert s!"arbitrary_iter_maintain_invariant_{loop_num}" invariant) - let body_statements ← Stmts.removeLoopsM bss - let arbitrary_iter_facts := .block s!"arbitrary_iter_facts_{loop_num}" { - ss := [havocd, arbitrary_iter_assumes] ++ + Stmt.cmd (HasPassiveCmds.assert s!"arbitrary_iter_maintain_invariant_{loop_num}" invariant md) + let body_statements ← Block.removeLoopsM bss + let arbitrary_iter_facts := .block s!"arbitrary_iter_facts_{loop_num}" ([havocd, arbitrary_iter_assumes] ++ body_statements ++ - [maintain_invariant] - } {} - let not_guard := Stmt.cmd (HasPassiveCmds.assume s!"not_guard_{loop_num}" neg_guard) - let invariant := Stmt.cmd (HasPassiveCmds.assume s!"invariant_{loop_num}" invariant) - pure (.ite guard {ss := [first_iter_facts, arbitrary_iter_facts, havocd, not_guard, invariant]} { ss := [] } {}) - | .ite c ⟨ tss ⟩ ⟨ ess ⟩ md => do - let tss ← Stmts.removeLoopsM tss - let ess ← Stmts.removeLoopsM ess - pure (.ite c { ss := tss } { ss := ess } md) - | .block label ⟨ bss ⟩ md => do - let bss ← Stmts.removeLoopsM bss - pure (.block label { ss := bss } md) + [maintain_invariant]) {} + let not_guard := Stmt.cmd (HasPassiveCmds.assume s!"not_guard_{loop_num}" neg_guard md) + let invariant := Stmt.cmd (HasPassiveCmds.assume s!"invariant_{loop_num}" invariant md) + pure (.ite guard [first_iter_facts, arbitrary_iter_facts, havocd, not_guard, invariant] [] {}) + | .ite c tss ess md => do + let tss ← Block.removeLoopsM tss + let ess ← Block.removeLoopsM ess + pure (.ite c tss ess md) + | .block label bss md => do + let bss ← Block.removeLoopsM bss + pure (.block label bss md) | .cmd _ => pure s | .goto _ _ => pure s -def Stmts.removeLoopsM +def Block.removeLoopsM [HasNot P] [HasVarsImp P C] [HasHavoc P C] [HasPassiveCmds P C] (ss : List (Stmt P C)) : StateM Nat (List (Stmt P C)) := match ss with | [] => pure [] | s :: ss => do let s ← Stmt.removeLoopsM s - let ss ← Stmts.removeLoopsM ss + let ss ← Block.removeLoopsM ss pure (s :: ss) end diff --git a/Strata/Transform/ProcedureInlining.lean b/Strata/Transform/ProcedureInlining.lean new file mode 100644 index 000000000..d97142948 --- /dev/null +++ b/Strata/Transform/ProcedureInlining.lean @@ -0,0 +1,259 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Util.LabelGen +import Strata.DL.Util.ListUtils +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Boogie.BoogieGen +import Strata.Languages.Boogie.ProgramWF +import Strata.Languages.Boogie.Statement +import Strata.Transform.BoogieTransform + +/-! # Procedure Inlining Transformation -/ + +namespace Boogie +namespace ProcedureInlining + +open Transform + +mutual +partial def Block.substFvar (b : Block) (fr:Expression.Ident) + (to:Expression.Expr) : Block := + List.map (fun s => Statement.substFvar s fr to) b + +partial def Statement.substFvar (s : Boogie.Statement) + (fr:Expression.Ident) + (to:Expression.Expr) : Statement := + match s with + | .init lhs ty rhs metadata => + .init lhs ty (Lambda.LExpr.substFvar rhs fr to) metadata + | .set lhs rhs metadata => + .set lhs (Lambda.LExpr.substFvar rhs fr to) metadata + | .havoc _ _ => s + | .assert lbl b metadata => + .assert lbl (Lambda.LExpr.substFvar b fr to) metadata + | .assume lbl b metadata => + .assume lbl (Lambda.LExpr.substFvar b fr to) metadata + | .call lhs pname args metadata => + .call lhs pname (List.map (Lambda.LExpr.substFvar · fr to) args) metadata + + | .block lbl b metadata => + .block lbl (Block.substFvar b fr to) metadata + | .ite cond thenb elseb metadata => + .ite (Lambda.LExpr.substFvar cond fr to) (Block.substFvar thenb fr to) + (Block.substFvar elseb fr to) metadata + | .loop guard measure invariant body metadata => + .loop (Lambda.LExpr.substFvar guard fr to) + (Option.map (Lambda.LExpr.substFvar · fr to) measure) + (Option.map (Lambda.LExpr.substFvar · fr to) invariant) + (Block.substFvar body fr to) + metadata + | .goto _ _ => s +end + +mutual +partial def Block.renameLhs (b : Block) (fr: Lambda.Identifier Visibility) (to: Lambda.Identifier Visibility) : Block := + List.map (fun s => Statement.renameLhs s fr to) b + +partial def Statement.renameLhs (s : Boogie.Statement) (fr: Lambda.Identifier Visibility) (to: Lambda.Identifier Visibility) + : Statement := + match s with + | .init lhs ty rhs metadata => + .init (if lhs.name == fr then to else lhs) ty rhs metadata + | .set lhs rhs metadata => + .set (if lhs.name == fr then to else lhs) rhs metadata + | .call lhs pname args metadata => + .call (lhs.map (fun l => + if l.name == fr then to else l)) pname args metadata + | .block lbl b metadata => + .block lbl (Block.renameLhs b fr to) metadata + | .ite x thenb elseb m => + .ite x (Block.renameLhs thenb fr to) (Block.renameLhs elseb fr to) m + | .loop m g i b md => + .loop m g i (Block.renameLhs b fr to) md + | .havoc l md => .havoc (if l.name == fr then to else l) md + | .assert _ _ _ | .assume _ _ _ + | .goto _ _ => s +end + +-- Unlike Stmt.hasLabel, this gathers labels in assert and assume as well. +mutual +partial def Block.labels (b : Block): List String := + List.flatMap (fun s => Statement.labels s) b + +-- Assume and Assert's labels have special meanings, so they must not be +-- mangled during procedure inlining. +partial def Statement.labels (s : Boogie.Statement) : List String := + match s with + | .block lbl b _ => lbl :: (Block.labels b) + | .ite _ thenb elseb _ => (Block.labels thenb) ++ (Block.labels elseb) + | .loop _ _ _ body _ => Block.labels body + | .assume lbl _ _ => [lbl] + | .assert lbl _ _ => [lbl] + | _ => [] +end + +mutual +partial def Block.replaceLabels (b : Block) (map:Map String String) + : Block := + b.map (fun s => Statement.replaceLabels s map) + +partial def Statement.replaceLabels + (s : Boogie.Statement) (map:Map String String) : Boogie.Statement := + let app (s:String) := + match Map.find? map s with + | .none => s + | .some s' => s' + match s with + | .block lbl b m => .block (app lbl) (Block.replaceLabels b map) m + | .goto lbl m => .goto (app lbl) m + | .ite cond thenb elseb _ => + .ite cond (Block.replaceLabels thenb map) (Block.replaceLabels elseb map) + | .loop g measure inv body m => + .loop g measure inv (Block.replaceLabels body map) m + | .assume lbl e m => .assume (app lbl) e m + | .assert lbl e m => .assert (app lbl) e m + | _ => s +end + + +private def genOldToFreshIdMappings (old_vars : List Expression.Ident) + (prev_map : Map Expression.Ident Expression.Ident) (prefix_ : String) + : BoogieTransformM (Map Expression.Ident Expression.Ident) := do + let prev_map <- old_vars.foldlM + (fun var_map id => do + let new_name <- genIdent id (fun s => prefix_ ++ "_" ++ s) + return var_map.insert id new_name) + prev_map + return prev_map + +private def renameAllLocalNames (c:Procedure) + : BoogieTransformM (Procedure × Map Expression.Ident Expression.Ident) := do + let var_map: Map Expression.Ident Expression.Ident := [] + let proc_name := c.header.name.name + + -- Make a map for renaming local variables + let lhs_vars := List.flatMap (fun (s:Statement) => s.definedVars) c.body + let lhs_vars := lhs_vars ++ c.header.inputs.unzip.fst ++ + c.header.outputs.unzip.fst + let var_map <- genOldToFreshIdMappings lhs_vars var_map proc_name + + -- Make a map for renaming label names + let labels := List.flatMap (fun s => Statement.labels s) c.body + -- Reuse genOldToFreshIdMappings by introducing dummy data to Identifier + let label_ids:List Expression.Ident := labels.map + (fun s => { name:=s, metadata := Visibility.temp }) + let label_map_id <- genOldToFreshIdMappings label_ids [] proc_name + let label_map := label_map_id.map (fun (id1,id2) => (id1.name, id2.name)) + + -- Do substitution + let new_body := List.map (fun (s0:Statement) => + var_map.foldl (fun (s:Statement) (old_id,new_id) => + let s := Statement.substFvar s old_id (.fvar () new_id .none) + let s := Statement.renameLhs s old_id new_id + Statement.replaceLabels s label_map) + s0) c.body + let new_header := { c.header with + inputs := c.header.inputs.map (fun (id,ty) => + match var_map.find? id with + | .some id' => (id',ty) + | .none => panic! "unreachable"), + outputs := c.header.outputs.map (fun (id,ty) => + match var_map.find? id with + | .some id' => (id',ty) + | .none => panic! "unreachable") + } + return ({ c with body := new_body, header := new_header }, var_map) + + +/- +Procedure Inlining. + +If st is a call statement, inline the contents of the callee procedure. +To avoid conflicts between duplicated variable names in caller and callee, +every variables in callee are renamed. +This function does not update the specification because inlineCallStmt will not +use the specification. This will have to change if Strata also wants to support +the reachability query. +-/ +def inlineCallStmt (st: Statement) (p : Program) + : BoogieTransformM (List Statement) := + open Lambda in do + match st with + | .call lhs procName args _ => + + let some proc := Program.Procedure.find? p procName + | throw s!"Procedure {procName} not found in program" + + -- Create a copy of the procedure that has all input/output/local vars + -- replaced with fresh ones + let (proc,var_map) <- renameAllLocalNames proc + + let sigOutputs := LMonoTySignature.toTrivialLTy proc.header.outputs + let sigInputs := LMonoTySignature.toTrivialLTy proc.header.inputs + + -- Stuffs for the call statement: + -- call x1,x2, .. = f(v1,v2,...) + -- where 'procedure f(in1,in2,..) outputs(out1,out2,..)' + -- Insert + -- init in1 : ty := v1 --- inputInit + -- init in2 : ty := v2 + -- init out1 : ty := --- outputInit + -- init out2 : ty := + -- ... (f body) + -- set x1 := out1 --- outputSetStmts + -- set x2 := out2 + -- `init outN` is not necessary because calls are only allowed to use + -- already declared variables (per Boogie.typeCheck) + + -- Create a fresh var statement for each LHS + let outputTrips ← genOutExprIdentsTrip sigOutputs sigOutputs.unzip.fst + let outputInit := createInitVars + (outputTrips.map (fun ((tmpvar,ty),orgvar) => ((orgvar,ty),tmpvar))) + -- Create a var statement for each procedure input arguments. + -- The input parameter expression is assigned to these new vars. + --let inputTrips ← genArgExprIdentsTrip sigInputs args + let inputInit := createInits (sigInputs.zip args) + -- Assign the output variables in the signature to the actual output + -- variables used in the callee. + let outputSetStmts := + let out_vars := sigOutputs.unzip.fst + let out_vars := out_vars.map + (fun id => match var_map.find? id with + | .none => id | .some x => x) + let outs_lhs_and_sig := List.zip lhs out_vars + List.map + (fun (lhs_var,out_var) => + Statement.set lhs_var (.fvar () out_var (.none))) + outs_lhs_and_sig + + let stmts:List (Imperative.Stmt Boogie.Expression Boogie.Command) + := inputInit ++ outputInit ++ proc.body ++ outputSetStmts + + return [.block (procName ++ "$inlined") stmts] + | _ => return [st] + +def inlineCallStmts (ss: List Statement) (prog : Program) + : BoogieTransformM (List Statement) := do match ss with + | [] => return [] + | s :: ss => + let s' := (inlineCallStmt s prog) + let ss' := (inlineCallStmts ss prog) + return (← s') ++ (← ss') + +def inlineCallL (dcls : List Decl) (prog : Program) + : BoogieTransformM (List Decl) := + match dcls with + | [] => return [] + | d :: ds => + match d with + | .proc p md => + return Decl.proc { p with body := ← (inlineCallStmts p.body prog ) } md :: + (← (inlineCallL ds prog)) + | _ => return d :: (← (inlineCallL ds prog)) + +end ProcedureInlining +end Boogie diff --git a/Strata/Util/IO.lean b/Strata/Util/IO.lean new file mode 100644 index 000000000..10d5f469e --- /dev/null +++ b/Strata/Util/IO.lean @@ -0,0 +1,32 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public section +namespace Strata.Util + +/-- Read from stdin if path is "-", otherwise read from file -/ +def readInputSource (path : String) : IO String := do + if path == "-" then + let stdin ← IO.getStdin + stdin.readToEnd + else + IO.FS.readFile path + +/-- Read binary from stdin if path is "-", otherwise read from file -/ +def readBinInputSource (path : String) : IO ByteArray := do + if path == "-" then + let stdin ← IO.getStdin + stdin.readBinToEnd + else + IO.FS.readBinFile path + +/-- Get display name for error messages: "" if reading from stdin, otherwise the path -/ +def displayName (path : String) : String := + if path == "-" then "" else path + +end Strata.Util +end diff --git a/StrataMain.lean b/StrataMain.lean index 1ce06ef7d..53c5a9298 100644 --- a/StrataMain.lean +++ b/StrataMain.lean @@ -7,6 +7,11 @@ -- Executable with utilities for working with Strata files. import Strata.DDM.Elab import Strata.DDM.Ion +import Strata.Util.IO + +import Strata.Languages.Python.Python +import Strata.DDM.Integration.Java.Gen +import StrataTest.Transform.ProcedureInlining def exitFailure {α} (message : String) : IO α := do IO.eprintln (message ++ "\n\nRun strata --help for additional help.") @@ -45,14 +50,16 @@ def readStrataText (fm : Strata.DialectFileMap) (input : System.FilePath) (bytes if errors.size > 0 then exitFailure (← Strata.mkErrorReport input errors) match header with - | .program stx dialect => + | .program _ dialect => let dialects ← match ← Strata.Elab.loadDialect fm .builtin dialect with | (dialects, .ok _) => pure dialects | (_, .error msg) => exitFailure msg - match Strata.Elab.elabProgramRest dialects leanEnv inputContext stx dialect startPos with + let .isTrue mem := inferInstanceAs (Decidable (dialect ∈ dialects.dialects)) + | panic! "internal: loadDialect failed" + match Strata.Elab.elabProgramRest dialects leanEnv inputContext dialect mem startPos with | .ok program => pure (dialects, .program program) - | .error errors => exitFailure (← Strata.mkErrorReport input errors) + | .error errors => exitFailure (← Strata.mkErrorReport input errors) | .dialect stx dialect => let (loaded, d, s) ← Strata.Elab.elabDialectRest fm .builtin #[] inputContext stx dialect startPos @@ -85,22 +92,22 @@ def readStrataIon (fm : Strata.DialectFileMap) (path : System.FilePath) (bytes : match ← Strata.Elab.loadDialect fm .builtin dialect with | (loaded, .ok _) => pure loaded | (_, .error msg) => exitFailure msg - match Strata.Program.fromIonFragment frag dialects.dialects dialect with + let .isTrue mem := inferInstanceAs (Decidable (dialect ∈ dialects.dialects)) + | panic! "loadDialect failed" + let dm := dialects.dialects.importedDialects dialect mem + match Strata.Program.fromIonFragment frag dm dialect with | .ok pgm => pure (dialects, .program pgm) | .error msg => fileReadError path msg def readFile (fm : Strata.DialectFileMap) (path : System.FilePath) : IO (Strata.Elab.LoadedDialects × Strata.DialectOrProgram) := do - let bytes ← - match ← IO.FS.readBinFile path |>.toBaseIO with - | .error _ => - exitFailure s!"Error reading {path}." - | .ok c => pure c + let bytes ← Strata.Util.readBinInputSource path.toString + let displayPath : System.FilePath := Strata.Util.displayName path.toString if bytes.startsWith Ion.binaryVersionMarker then - readStrataIon fm path bytes + readStrataIon fm displayPath bytes else - readStrataText fm path bytes + readStrataText fm displayPath bytes structure Command where name : String @@ -136,7 +143,10 @@ def printCommand : Command where let (ld, pd) ← readFile searchPath v[0] match pd with | .dialect d => - IO.print <| d.format ld.dialects + let .isTrue mem := inferInstanceAs (Decidable (d.name ∈ ld.dialects)) + | IO.eprintln s!"Internal error reading file." + return + IO.print <| ld.dialects.format d.name mem | .program pgm => IO.print <| toString pgm @@ -149,16 +159,87 @@ def diffCommand : Command where let ⟨_, p2⟩ ← readFile fm v[1] match p1, p2 with | .program p1, .program p2 => - if p1 == p2 then return () - else exitFailure "Two programs are different" + if p1.dialect != p2.dialect then + exitFailure s!"Dialects differ: {p1.dialect} and {p2.dialect}" + let Decidable.isTrue eq := inferInstanceAs (Decidable (p1.commands.size = p2.commands.size)) + | exitFailure s!"Number of commands differ {p1.commands.size} and {p2.commands.size}" + for (c1, c2) in Array.zip p1.commands p2.commands do + if c1 != c2 then + exitFailure s!"Commands differ: {repr c1} and {repr c2}" | _, _ => exitFailure "Cannot compare dialect def with another dialect/program." +def readPythonStrata (path : String) : IO Strata.Program := do + let bytes ← Strata.Util.readBinInputSource path + if ! bytes.startsWith Ion.binaryVersionMarker then + exitFailure s!"pyAnalyze expected Ion file" + match Strata.Program.fromIon Strata.Python.Python_map Strata.Python.Python.name bytes with + | .ok p => pure p + | .error msg => exitFailure msg + +def pyTranslateCommand : Command where + name := "pyTranslate" + args := [ "file" ] + help := "Translate a Strata Python Ion file to Strata.Boogie. Write results to stdout." + callback := fun _ v => do + let pgm ← readPythonStrata v[0] + let preludePgm := Strata.Python.Internal.Boogie.prelude + let bpgm := Strata.pythonToBoogie Strata.Python.Internal.signatures pgm + let newPgm : Boogie.Program := { decls := preludePgm.decls ++ bpgm.decls } + IO.print newPgm + +def pyAnalyzeCommand : Command where + name := "pyAnalyze" + args := [ "file", "verbose" ] + help := "Analyze a Strata Python Ion file. Write results to stdout." + callback := fun _ v => do + let verbose := v[1] == "1" + let pgm ← readPythonStrata v[0] + if verbose then + IO.print pgm + let preludePgm := Strata.Python.Internal.Boogie.prelude + let bpgm := Strata.pythonToBoogie Strata.Python.Internal.signatures pgm + let newPgm : Boogie.Program := { decls := preludePgm.decls ++ bpgm.decls } + if verbose then + IO.print newPgm + let newPgm := runInlineCall newPgm + if verbose then + IO.println "Inlined: " + IO.print newPgm + let solverName : String := "Strata/Languages/Python/z3_parallel.py" + let vcResults ← EIO.toIO (fun f => IO.Error.userError (toString f)) + (Boogie.verify solverName newPgm { Options.default with stopOnFirstError := false, verbose, removeIrrelevantAxioms := true } + (moreFns := Strata.Python.ReFactory)) + let mut s := "" + for vcResult in vcResults do + s := s ++ s!"\n{vcResult.obligation.label}: {Std.format vcResult.result}\n" + IO.println s + +def javaGenCommand : Command where + name := "javaGen" + args := [ "dialect-file", "package", "output-dir" ] + help := "Generate Java classes from a DDM dialect file." + callback := fun fm v => do + let (ld, pd) ← readFile fm v[0] + match pd with + | .dialect d => + match Strata.Java.generateDialect d v[1] with + | .ok files => + Strata.Java.writeJavaFiles v[2] v[1] files + IO.println s!"Generated Java files for {d.name} in {v[2]}/{Strata.Java.packageToPath v[1]}" + | .error msg => + exitFailure s!"Error generating Java: {msg}" + | .program _ => + exitFailure "Expected a dialect file, not a program file." + def commandList : List Command := [ + javaGenCommand, checkCommand, toIonCommand, printCommand, diffCommand, + pyAnalyzeCommand, + pyTranslateCommand, ] def commandMap : Std.HashMap String Command := diff --git a/StrataTest/Backends/CBMC/BoogieToCProverGOTO.lean b/StrataTest/Backends/CBMC/BoogieToCProverGOTO.lean index dd59c0d35..ea51f369f 100644 --- a/StrataTest/Backends/CBMC/BoogieToCProverGOTO.lean +++ b/StrataTest/Backends/CBMC/BoogieToCProverGOTO.lean @@ -21,14 +21,16 @@ model-check a Strata-generated GOTO binary. ------------------------------------------------------------------------------- +abbrev BoogieParams : Lambda.LExprParams := ⟨Unit, Boogie.Visibility⟩ + abbrev Boogie.ExprStr : Imperative.PureExpr := - { Ident := String, - Expr := Lambda.LExpr Lambda.LMonoTy Unit, + { Ident := BoogieParams.Identifier, + Expr := Lambda.LExpr BoogieParams.mono, Ty := Lambda.LTy, - TyEnv := @Lambda.TEnv Visibility, - TyContext := @Lambda.LContext Visibility, - EvalEnv := Lambda.LState Visibility - EqIdent := instDecidableEqString } + TyEnv := @Lambda.TEnv BoogieParams.IDMeta, + TyContext := @Lambda.LContext BoogieParams, + EvalEnv := Lambda.LState BoogieParams + EqIdent := inferInstanceAs (DecidableEq BoogieParams.Identifier) } namespace BoogieToGOTO @@ -44,7 +46,7 @@ private def lookupType (T : Boogie.Expression.TyEnv) (i : Boogie.Expression.Iden private def updateType (T : Boogie.Expression.TyEnv) (i : Boogie.Expression.Ident) (ty : Boogie.Expression.Ty) : Boogie.Expression.TyEnv := - T.insertInContext i ty + @Lambda.TEnv.insertInContext ⟨Boogie.ExpressionMetadata, Boogie.Visibility⟩ _ T i ty instance : Imperative.ToGoto Boogie.Expression where lookupType := lookupType @@ -70,26 +72,25 @@ private def updateTypeStr (T : Boogie.ExprStr.TyEnv) (i : Boogie.ExprStr.Ident) instance : Imperative.ToGoto Boogie.ExprStr where lookupType := lookupTypeStr updateType := updateTypeStr - identToString := (fun x => x) + identToString := (fun x => x.name) toGotoType := (fun ty => Lambda.LMonoTy.toGotoType ty.toMonoTypeUnsafe) toGotoExpr := Lambda.LExpr.toGotoExpr open Lambda in -def substVarNames {IDMeta: Type} [DecidableEq IDMeta] - (e : LExpr LMonoTy IDMeta) (frto : Map String String) : (LExpr LMonoTy Unit) := +def substVarNames {Metadata IDMeta: Type} [DecidableEq IDMeta] + (e : LExpr ⟨⟨Metadata, IDMeta⟩, LMonoTy⟩) (frto : Map String String) : (LExpr ⟨⟨Unit, Boogie.Visibility⟩, LMonoTy⟩) := match e with - | .const c ty => .const c ty - | .bvar b => .bvar b - | .op o ty => .op o.name ty - | .fvar name ty => + | .const _ c => .const () c + | .bvar _ b => .bvar () b + | .op _ o ty => .op () (Lambda.Identifier.mk o.name Boogie.Visibility.unres) ty + | .fvar _ name ty => let name_alt := frto.find? name.name - .fvar (name_alt.getD name.name) ty - | .mdata info e' => .mdata info (substVarNames e' frto) - | .abs ty e' => .abs ty (substVarNames e' frto) - | .quant qk ty tr' e' => .quant qk ty (substVarNames tr' frto) (substVarNames e' frto) - | .app f e' => .app (substVarNames f frto) (substVarNames e' frto) - | .ite c t e' => .ite (substVarNames c frto) (substVarNames t frto) (substVarNames e' frto) - | .eq e1 e2 => .eq (substVarNames e1 frto) (substVarNames e2 frto) + .fvar () (Lambda.Identifier.mk (name_alt.getD name.name) Boogie.Visibility.unres) ty + | .abs _ ty e' => .abs () ty (substVarNames e' frto) + | .quant _ qk ty tr' e' => .quant () qk ty (substVarNames tr' frto) (substVarNames e' frto) + | .app _ f e' => .app () (substVarNames f frto) (substVarNames e' frto) + | .ite _ c t e' => .ite () (substVarNames c frto) (substVarNames t frto) (substVarNames e' frto) + | .eq _ e1 e2 => .eq () (substVarNames e1 frto) (substVarNames e2 frto) def Boogie.Cmd.renameVars (frto : Map String String) (c : Imperative.Cmd Boogie.Expression) : Imperative.Cmd Boogie.ExprStr := @@ -152,9 +153,9 @@ def CProverGOTO.Context.toJson (programName : String) (ctx : CProverGOTO.Context open Lambda.LTy.Syntax in def transformToGoto (boogie : Boogie.Program) : Except Format CProverGOTO.Context := do - let C := { Lambda.LContext.default with functions := Boogie.Factory, knownTypes := Boogie.KnownTypes } - let T := Lambda.TEnv.default - let (boogie, _T) ← Boogie.Program.typeCheck C T boogie + let Ctx := { Lambda.LContext.default with functions := Boogie.Factory, knownTypes := Boogie.KnownTypes } + let Env := Lambda.TEnv.default + let (boogie, _Env) ← Boogie.Program.typeCheck Ctx Env boogie dbg_trace f!"[Strata.Boogie] Type Checking Succeeded!" if h : boogie.decls.length = 1 then let decl := boogie.decls[0]'(by exact Nat.lt_of_sub_eq_succ h) @@ -184,7 +185,7 @@ def transformToGoto (boogie : Boogie.Program) : Except Format CProverGOTO.Contex let formals_renamed := formals.zip new_formals let formals_tys : Map String CProverGOTO.Ty := formals.zip formals_tys - let locals := (Imperative.Stmts.definedVars p.body).map Boogie.BoogieIdent.toPretty + let locals := (Imperative.Block.definedVars p.body).map Boogie.BoogieIdent.toPretty let new_locals := locals.map (fun l => CProverGOTO.mkLocalSymbol pname l) let locals_renamed := locals.zip new_locals @@ -192,7 +193,7 @@ def transformToGoto (boogie : Boogie.Program) : Except Format CProverGOTO.Contex let cmds := Boogie.Cmds.renameVars args_renamed cmds let ans ← @Imperative.Cmds.toGotoTransform Boogie.ExprStr - BoogieToGOTO.instToGotoExprStr T pname cmds (loc := 0) + BoogieToGOTO.instToGotoExprStr Env pname cmds (loc := 0) let ending_insts : Array CProverGOTO.Instruction := #[ -- (FIXME): Add lifetime markers. -- { type := .DEAD, locationNum := ans.nextLoc + 1, @@ -214,7 +215,7 @@ def transformToGoto (boogie : Boogie.Program) : Except Format CProverGOTO.Contex open Strata in def getGotoJson (programName : String) (env : Program) : IO CProverGOTO.Json := do - let (program, errors) := TransM.run (translateProgram env) + let (program, errors) := TransM.run Inhabited.default (translateProgram env) if errors.isEmpty then (match (BoogieToGOTO.transformToGoto program) with | .error e => diff --git a/StrataTest/Backends/CBMC/LambdaToCProverGOTO.lean b/StrataTest/Backends/CBMC/LambdaToCProverGOTO.lean index 1f6f3495a..e23446a65 100644 --- a/StrataTest/Backends/CBMC/LambdaToCProverGOTO.lean +++ b/StrataTest/Backends/CBMC/LambdaToCProverGOTO.lean @@ -11,6 +11,11 @@ namespace Lambda open Std (ToFormat Format format) ------------------------------------------------------------------------------- +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ + +private instance : Coe String TestParams.Identifier where + coe s := Identifier.mk s () + def LMonoTy.toGotoType (ty : LMonoTy) : Except Format CProverGOTO.Ty := match ty with | .bitvec n => .ok (CProverGOTO.Ty.UnsignedBV n) @@ -19,9 +24,9 @@ def LMonoTy.toGotoType (ty : LMonoTy) : Except Format CProverGOTO.Ty := | .string => .ok .String | _ => .error f!"[toGotoType] Not yet implemented: {ty}" -def LExprT.getGotoType {IDMeta} (e : LExprT IDMeta) : +def LExprT.getGotoType {T : LExprParamsT} (e : LExprT T) : Except Format CProverGOTO.Ty := do - let ty := toLMonoTy e + let ty := LExpr.toLMonoTy e ty.toGotoType def fnToGotoID (fn : String) : Except Format CProverGOTO.Expr.Identifier := @@ -34,55 +39,55 @@ def fnToGotoID (fn : String) : Except Format CProverGOTO.Expr.Identifier := Mapping `LExprT` (Lambda expressions obtained after the type inference transform) to GOTO expressions. -/ -def LExprT.toGotoExpr {IDMeta} [ToString IDMeta] (e : LExprT IDMeta) : +def LExprT.toGotoExpr {TBase: LExprParamsT} [ToString TBase.base.IDMeta] (e : LExprT TBase) : Except Format CProverGOTO.Expr := open CProverGOTO in do match e with -- Constants - | .const c ty => - let gty ← ty.toGotoType - return (Expr.constant c gty) + | .const m c => + let gty ← m.type.toGotoType + return (Expr.constant (toString c) gty) -- Variables - | .fvar v ty => - let gty ← ty.toGotoType + | .fvar m v _ => + let gty ← m.type.toGotoType return (Expr.symbol (toString v) gty) -- Binary Functions - | .app (.app (.op fn _) e1 _) e2 ty => + | .app m (.app _ (.op _ fn _) e1) e2 => let op ← fnToGotoID (toString fn) - let gty ← ty.toGotoType + let gty ← m.type.toGotoType let e1g ← toGotoExpr e1 let e2g ← toGotoExpr e2 return { id := op, type := gty, operands := [e1g, e2g] } -- Unary Functions - | .app (.op fn _) e1 ty => + | .app m (.op _ fn _) e1 => let op ← fnToGotoID (toString fn) - let gty ← ty.toGotoType + let gty ← m.type.toGotoType let e1g ← toGotoExpr e1 return { id := op, type := gty, operands := [e1g] } -- Equality - | .eq e1 e2 _ => + | .eq _ e1 e2 => let e1g ← toGotoExpr e1 let e2g ← toGotoExpr e2 return { id := .binary .Equal, type := .Boolean, operands := [e1g, e2g] } | _ => .error f!"[toGotoExpr] Not yet implemented: {e}" /-- -Mapping `LExpr` to GOTO expressions. +Mapping `LExpr` to GOTO expressions (for LMonoTy-typed expressions). -/ -def LExpr.toGotoExpr {IDMeta} [ToString IDMeta] (e : LExpr LMonoTy IDMeta) : +def LExpr.toGotoExpr {TBase: LExprParams} [ToString $ LExpr TBase.mono] (e : LExpr TBase.mono) : Except Format CProverGOTO.Expr := open CProverGOTO in do match e with -- Constants - | .const c (some ty) => - let gty ← ty.toGotoType - return (Expr.constant c gty) + | .const _ c => + let gty ← c.ty.toGotoType + return (Expr.constant (toString c) gty) -- Variables - | .fvar v (some ty) => + | .fvar _ v (some ty) => let gty ← ty.toGotoType return (Expr.symbol (toString v) gty) -- Binary Functions - | .app (.app (.op fn (some ty)) e1) e2 => + | .app _ (.app _ (.op _ fn (some ty)) e1) e2 => let op ← fnToGotoID (toString fn) let retty := ty.destructArrow.getLast! let gty ← retty.toGotoType @@ -90,18 +95,18 @@ def LExpr.toGotoExpr {IDMeta} [ToString IDMeta] (e : LExpr LMonoTy IDMeta) : let e2g ← toGotoExpr e2 return { id := op, type := gty, operands := [e1g, e2g] } -- Unary Functions - | .app (.op fn (some ty)) e1 => + | .app _ (.op _ fn (some ty)) e1 => let op ← fnToGotoID (toString fn) let retty := ty.destructArrow.getLast! let gty ← retty.toGotoType let e1g ← toGotoExpr e1 return { id := op, type := gty, operands := [e1g] } -- Equality - | .eq e1 e2 => + | .eq _ e1 e2 => let e1g ← toGotoExpr e1 let e2g ← toGotoExpr e2 return { id := .binary .Equal, type := .Boolean, operands := [e1g, e2g] } - | _ => .error f!"[toGotoExpr] Not yet implemented: {e}" + | _ => .error f!"[toGotoExpr] Not yet implemented: {toString e}" open LTy.Syntax LExpr.Syntax in /-- @@ -114,5 +119,5 @@ info: ok: { id := CProverGOTO.Expr.Identifier.nullary (CProverGOTO.Expr.Identifi namedFields := [] } -/ #guard_msgs in -#eval do let ans ← @LExprT.toGotoExpr String _ (.const "1" mty[int]) +#eval do let ans ← @LExprT.toGotoExpr TestParams.mono _ (.const ⟨(), mty[int]⟩ (LConst.intConst 1)) return repr ans diff --git a/StrataTest/Backends/CBMC/ToCProverGOTO.lean b/StrataTest/Backends/CBMC/ToCProverGOTO.lean index 0a3a15cc1..4ba14958f 100644 --- a/StrataTest/Backends/CBMC/ToCProverGOTO.lean +++ b/StrataTest/Backends/CBMC/ToCProverGOTO.lean @@ -15,14 +15,16 @@ instructions -/ section open Std (ToFormat Format format) -abbrev LExprTP : Imperative.PureExpr := - { Ident := String, - Expr := Lambda.LExprT String, +private abbrev TestParams : Lambda.LExprParams := ⟨Unit, Unit⟩ + +private abbrev LExprTP : Imperative.PureExpr := + { Ident := TestParams.Identifier, + Expr := Lambda.LExprT TestParams.mono, Ty := Lambda.LMonoTy, - TyEnv := @Lambda.TEnv String, - TyContext := @Lambda.LContext String, - EvalEnv := Lambda.LState String - EqIdent := instDecidableEqString } + TyEnv := @Lambda.TEnv TestParams.IDMeta, + TyContext := @Lambda.LContext TestParams, + EvalEnv := Lambda.LState TestParams + EqIdent := inferInstanceAs (DecidableEq TestParams.Identifier) } /-- Commands, parameterized by type-annotated Lambda expressions. @@ -30,11 +32,11 @@ Commands, parameterized by type-annotated Lambda expressions. We assume in this test that the Lambda expressions are well-typed. In practice, these should after Lambda's type inference pass. -/ -abbrev Cmd := Imperative.Cmd LExprTP +private abbrev Cmd := Imperative.Cmd LExprTP private def lookupType (T : LExprTP.TyEnv) (i : LExprTP.Ident) : Except Format CProverGOTO.Ty := match T.context.types.find? i with - | none => .error s!"Cannot find {i} in the type context!" + | none => .error f!"Cannot find {i} in the type context!" | some ty => if ty.isMonoType then let ty := ty.toMonoTypeUnsafe @@ -47,7 +49,7 @@ private def updateType (T : LExprTP.TyEnv) (i : LExprTP.Ident) (ty : LExprTP.Ty) instance : Imperative.ToGoto LExprTP where lookupType := lookupType updateType := updateType - identToString := (fun i => i) + identToString := (fun i => i.name) toGotoType := Lambda.LMonoTy.toGotoType toGotoExpr := Lambda.LExprT.toGotoExpr @@ -56,8 +58,8 @@ instance : Imperative.ToGoto LExprTP where open Lambda.LTy.Syntax def ExampleProgram1 : Imperative.Cmds LExprTP := - [.init "s" mty[bv32] (.const "0" mty[bv32]), - .set "s" (.const "100" mty[bv32])] + [.init (Lambda.Identifier.mk "s" ()) mty[bv32] (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 0)), + .set (Lambda.Identifier.mk "s" ()) (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 100))] /-- info: ok: #[DECL (decl (s : unsignedbv[32])), @@ -72,15 +74,15 @@ info: ok: #[DECL (decl (s : unsignedbv[32])), /- (100 : bv32) + (200 : bv32) -/ -private def addBV32LExpr (op1 op2 : Lambda.LExprT String) := - (Lambda.LExprT.app - (.app (.op "Bv32.Add" mty[bv32 → bv32 → bv32]) op1 mty[bv32 → bv32]) - op2 - mty[bv32]) +private def addBV32LExpr (op1 op2 : Lambda.LExprT TestParams.mono) : Lambda.LExprT TestParams.mono := + (Lambda.LExpr.app { underlying := (), type := mty[bv32] } + (Lambda.LExpr.app { underlying := (), type := mty[bv32 → bv32] } + (.op { underlying := (), type := mty[bv32 → bv32 → bv32] } (Lambda.Identifier.mk "Bv32.Add" ()) (some mty[bv32 → bv32 → bv32])) op1) + op2) def ExampleProgram2 : Imperative.Cmds LExprTP := - [.init "s" mty[bv32] (.const "0" mty[bv32]), - .set "s" (addBV32LExpr (.const "100" mty[bv32]) (.const "200" mty[bv32]))] + [.init (Lambda.Identifier.mk "s" ()) mty[bv32] (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 0)), + .set (Lambda.Identifier.mk "s" ()) (addBV32LExpr (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 100)) (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 200)))] /-- info: ok: #[DECL (decl (s : unsignedbv[32])), @@ -96,11 +98,11 @@ info: ok: #[DECL (decl (s : unsignedbv[32])), -- (FIXME) Is this the right way to deal with non-det. expressions? def ExampleProgram3 : Imperative.Cmds LExprTP := - [.init "x" mty[bv32] (.const "0" mty[bv32]), - .init "y" mty[bv32] (.const "0" mty[bv32]), - .havoc "x", - .havoc "y", - .init "z" mty[bv32] (addBV32LExpr (.fvar "x" mty[bv32]) (.fvar "y" mty[bv32]))] + [.init (Lambda.Identifier.mk "x" ()) mty[bv32] (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 0)), + .init (Lambda.Identifier.mk "y" ()) mty[bv32] (.const { underlying := (), type := mty[bv32] } (.bitvecConst 32 0)), + .havoc (Lambda.Identifier.mk "x" ()), + .havoc (Lambda.Identifier.mk "y" ()), + .init (Lambda.Identifier.mk "z" ()) mty[bv32] (addBV32LExpr (.fvar { underlying := (), type := mty[bv32] } (Lambda.Identifier.mk "x" ()) (some mty[bv32])) (.fvar { underlying := (), type := mty[bv32] } (Lambda.Identifier.mk "y" ()) (some mty[bv32])))] /-- info: ok: #[DECL (decl (x : unsignedbv[32])), diff --git a/StrataTest/DDM/Bool.lean b/StrataTest/DDM/Bool.lean new file mode 100644 index 000000000..c27f40002 --- /dev/null +++ b/StrataTest/DDM/Bool.lean @@ -0,0 +1,63 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean + +-- Test that Bool can be used as an inductive type with true/false operators +#dialect +dialect TestBool; + +category BoolExpr; + +op printBool (b : BoolExpr) : Command => "print " b ";"; +op wrappedBool (b: Bool): BoolExpr => b; + +op ifThenElse (cond : Bool, thenVal : BoolExpr, elseVal : BoolExpr) : BoolExpr => + "if " cond " then " thenVal " else " elseVal; + +#end + +-- Test parsing with true +def testTrue := #strata program TestBool; print true; #end + +/-- +info: "program TestBool;\nprint true;" +-/ +#guard_msgs in +#eval toString testTrue.format + +-- Test parsing with false +def testFalse := #strata program TestBool; print false; #end + +/-- +info: "program TestBool;\nprint false;" +-/ +#guard_msgs in +#eval toString testFalse.format + +-- Test parsing with if-then-else using booleans +def testIfThenElse := #strata +program TestBool; +print if true then false else true; +#end + +/-- +info: "program TestBool;\nprint if true then false else (true);" +-/ +#guard_msgs in +#eval toString testIfThenElse.format + +-- Test that we can use booleans in nested expressions +def testNested := #strata +program TestBool; +print if true then if false then true else false else true; +#end + +/-- +info: "program TestBool;\nprint if true then if false then true else (false) else (true);" +-/ +#guard_msgs in +#eval toString testNested.format diff --git a/StrataTest/DDM/ByteArray.lean b/StrataTest/DDM/ByteArray.lean new file mode 100644 index 000000000..cf6ddac5c --- /dev/null +++ b/StrataTest/DDM/ByteArray.lean @@ -0,0 +1,41 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +import Strata.DDM.Integration.Lean + +-- Minimal dialect to test dialects can be declared. +#guard_msgs in +#dialect +dialect Test; +op eval (b : ByteArray) : Command => "eval " b ";"; +#end + +#strata_gen Test + +def bvExample := #strata +program Test; +eval b"ab\x12\r\\"; +#end + +/-- +info: program Test; +eval b"ab\x12\r\\"; +-/ +#guard_msgs in +#eval IO.print bvExample + +#guard + match Command.ofAst bvExample.commands[0] with + | .ok (Command.eval _ bv) => bv.val == .mk ("ab\x12\r\\".toList.toArray.map Char.toUInt8) + | _ => false + +/-- +error: expected Invalid hex escape sequence +-/ +#guard_msgs in +#eval IO.print #strata +program Test; +eval b"\xgg"; +#end diff --git a/StrataTest/DDM/Elab.lean b/StrataTest/DDM/Elab.lean index 4c2198fe5..2667c8bc1 100644 --- a/StrataTest/DDM/Elab.lean +++ b/StrataTest/DDM/Elab.lean @@ -13,6 +13,8 @@ dialect Test; op assert : Command => "assert" ";"; op decimal (v : Decimal) : Command => "decimal " v ";"; op str (v : Str) : Command => "str " v ";\n"; +// Test whitepace only literals are counted correctly +op ws (i : Num, j : Num) : Command => "ws " i " " j ";"; #end def testProgram := #strata program Test; decimal 1e99; #end @@ -21,7 +23,14 @@ def testProgram := #strata program Test; decimal 1e99; #end info: "program Test;\ndecimal 1e99;" -/ #guard_msgs in -#eval toString testProgram.format +#eval toString testProgram + +/-- +info: program Test; +ws 1 2; +-/ +#guard_msgs in +#eval IO.println #strata program Test; ws 1 2; #end /-- error: P already declared. diff --git a/StrataTest/DDM/Integration/Java/TestGen.lean b/StrataTest/DDM/Integration/Java/TestGen.lean new file mode 100644 index 000000000..8dc4012e5 --- /dev/null +++ b/StrataTest/DDM/Integration/Java/TestGen.lean @@ -0,0 +1,323 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Java +import Strata.DDM.Integration.Lean.Env -- For dialectExt +import Strata.DDM.Integration.Lean.HashCommands -- For #load_dialect +import Strata.Languages.Boogie.DDMTransform.Parse -- Loads Boogie dialect into env + +namespace Strata.Java.Test + +open Strata.Java + +def check (s sub : String) : Bool := (s.splitOn sub).length > 1 + +-- Test 1: Basic dialect with 2 operators +#eval do + let testDialect : Strata.Dialect := { + name := "Test" + imports := #[] + declarations := #[ + .syncat { name := "Expr", argNames := #[] }, + .op { + name := "literal" + argDecls := .ofArray #[ + { ident := "value", kind := .cat (.atom .none ⟨"Init", "Num"⟩) } + ] + category := ⟨"Test", "Expr"⟩ + syntaxDef := { atoms := #[], prec := 0 } + }, + .op { + name := "add" + argDecls := .ofArray #[ + { ident := "lhs", kind := .cat (.atom .none ⟨"Test", "Expr"⟩) }, + { ident := "rhs", kind := .cat (.atom .none ⟨"Test", "Expr"⟩) } + ] + category := ⟨"Test", "Expr"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + assert! files.interfaces.any (fun i => check i.2 "sealed interface Expr") + assert! files.records.size = 2 + assert! files.records.any (fun r => check r.1 "Literal") + assert! files.records.any (fun r => check r.1 "Add") + pure () + +-- Test 2: Reserved word escaping for fields +#eval do + let testDialect : Strata.Dialect := { + name := "Reserved" + imports := #[] + declarations := #[ + .syncat { name := "Stmt", argNames := #[] }, + .op { + name := "int" + argDecls := .ofArray #[ + { ident := "public", kind := .cat (.atom .none ⟨"Init", "Ident"⟩) } + ] + category := ⟨"Reserved", "Stmt"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + assert! files.records.any (fun r => r.1 == "Int.java") + assert! files.records.any (fun r => check r.2 "public_") + pure () + +-- Test 3: Name collision (operator name matches category name) +#eval do + let testDialect : Strata.Dialect := { + name := "Collision" + imports := #[] + declarations := #[ + .syncat { name := "expr", argNames := #[] }, + .op { + name := "Expr" + argDecls := .ofArray #[] + category := ⟨"Collision", "expr"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + assert! files.interfaces.any (fun i => i.1 == "Expr.java") + assert! files.records.any (fun r => r.1 == "Expr_.java") + pure () + +-- Test 4: Duplicate operator names and reserved word collision +#eval do + let testDialect : Strata.Dialect := { + name := "Dup" + imports := #[] + declarations := #[ + .syncat { name := "A", argNames := #[] }, + .syncat { name := "B", argNames := #[] }, + .op { name := "foo", argDecls := .ofArray #[], category := ⟨"Dup", "A"⟩, syntaxDef := { atoms := #[], prec := 0 } }, + .op { name := "foo", argDecls := .ofArray #[], category := ⟨"Dup", "B"⟩, syntaxDef := { atoms := #[], prec := 0 } }, -- Duplicate + .op { name := "class", argDecls := .ofArray #[], category := ⟨"Dup", "A"⟩, syntaxDef := { atoms := #[], prec := 0 } }, + .op { name := "class_", argDecls := .ofArray #[], category := ⟨"Dup", "B"⟩, syntaxDef := { atoms := #[], prec := 0 } } -- Would clash after escaping + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + let recordNames := files.records.map Prod.fst + assert! recordNames.toList.eraseDups.length == recordNames.size + pure () + +-- Test 5: Category name collides with base class +#eval do + let testDialect : Strata.Dialect := { + name := "Base" + imports := #[] + declarations := #[ + .syncat { name := "Node", argNames := #[] }, -- Collides with base class + .op { name := "leaf", argDecls := .ofArray #[], category := ⟨"Base", "Node"⟩, syntaxDef := { atoms := #[], prec := 0 } } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + let allNames := #["Node.java", "SourceRange.java"] ++ files.interfaces.map Prod.fst ++ files.records.map Prod.fst + assert! allNames.toList.eraseDups.length == allNames.size + pure () + +-- Test 6: Snake_case to PascalCase conversion +#eval do + let testDialect : Strata.Dialect := { + name := "Snake" + imports := #[] + declarations := #[ + .syncat { name := "my_category", argNames := #[] }, + .op { + name := "my_operator" + argDecls := .ofArray #[] + category := ⟨"Snake", "my_category"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + assert! files.interfaces.any (fun i => i.1 == "MyCategory.java") + assert! files.records.any (fun r => r.1 == "MyOperator.java") + pure () + +-- Test 7: All DDM types map correctly +#eval! do + let testDialect : Strata.Dialect := { + name := "Types" + imports := #[] + declarations := #[ + .syncat { name := "Node", argNames := #[] }, + .op { + name := "allTypes" + argDecls := .ofArray #[ + { ident := "ident", kind := .cat (.atom .none ⟨"Init", "Ident"⟩) }, + { ident := "num", kind := .cat (.atom .none ⟨"Init", "Num"⟩) }, + { ident := "dec", kind := .cat (.atom .none ⟨"Init", "Decimal"⟩) }, + { ident := "str", kind := .cat (.atom .none ⟨"Init", "Str"⟩) }, + { ident := "b", kind := .cat (.atom .none ⟨"Init", "Bool"⟩) }, + { ident := "bytes", kind := .cat (.atom .none ⟨"Init", "ByteArray"⟩) }, + { ident := "opt", kind := .cat ⟨.none, ⟨"Init", "Option"⟩, #[.atom .none ⟨"Init", "Num"⟩]⟩ }, + { ident := "seq", kind := .cat ⟨.none, ⟨"Init", "Seq"⟩, #[.atom .none ⟨"Init", "Ident"⟩]⟩ } + ] + category := ⟨"Types", "Node"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + let record := files.records[0]!.2 + assert! check record "java.lang.String ident" + assert! check record "java.math.BigInteger num" + assert! check record "java.math.BigDecimal dec" + assert! check record "java.lang.String str" + assert! check record "boolean b" + assert! check record "byte[] bytes" + assert! check record "java.util.Optional opt" + assert! check record "java.util.List seq" + pure () + +-- Test 8: FQN usage (no imports that could conflict) +#eval! do + let testDialect : Strata.Dialect := { + name := "FQN" + imports := #[] + declarations := #[ + .syncat { name := "Node", argNames := #[] }, + .op { + name := "test" + argDecls := .ofArray #[] + category := ⟨"FQN", "Node"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + let record := files.records[0]!.2 + assert! !(check record "import java.") + assert! check record "java.lang.String operationName()" + pure () + +-- Test 9: Stub interfaces for referenced-but-empty categories +#eval do + let testDialect : Strata.Dialect := { + name := "Stub" + imports := #[] + declarations := #[ + .syncat { name := "Stmt", argNames := #[] }, + .op { + name := "eval" + argDecls := .ofArray #[ + { ident := "e", kind := .cat (.atom .none ⟨"Init", "Expr"⟩) } -- References Init.Expr + ] + category := ⟨"Stub", "Stmt"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + assert! files.interfaces.any (fun i => check i.2 "sealed interface Stmt") + assert! files.interfaces.any (fun i => check i.2 "non-sealed interface Expr") + pure () + +-- Test 10: Boogie dialect returns error (has type/function declarations not yet supported) +elab "#testBoogieError" : command => do + let env ← Lean.getEnv + let state := Strata.dialectExt.getState env + let some boogie := state.loaded.dialects["Boogie"]? + | Lean.logError "Boogie dialect not found"; return + match generateDialect boogie "com.strata.boogie" with + | .error msg => + if !(check msg "type declaration" || check msg "function declaration") then + Lean.logError s!"Expected error about type/function declaration, got: {msg}" + | .ok _ => Lean.logError "Expected error for Boogie dialect" + +#testBoogieError + +-- Test 11: Cross-dialect name collision (A.Num vs B.Num) +#eval do + let testDialect : Strata.Dialect := { + name := "A" + imports := #[] + declarations := #[ + .syncat { name := "Num", argNames := #[] }, + .op { + name := "lit" + argDecls := .ofArray #[ + { ident := "a", kind := .cat (.atom .none ⟨"A", "Num"⟩) }, + { ident := "b", kind := .cat (.atom .none ⟨"B", "Num"⟩) } + ] + category := ⟨"A", "Num"⟩ + syntaxDef := { atoms := #[], prec := 0 } + } + ] + } + let files := (generateDialect testDialect "com.test").toOption.get! + -- Should have 2 interfaces: one for A.Num, one stub for B.Num + assert! files.interfaces.size = 2 + let names : List String := files.interfaces.toList.map Prod.fst + assert! names.any (fun n => (n.splitOn "A").length > 1) + assert! names.any (fun n => (n.splitOn "B").length > 1) + pure () + +-- Test 12: Generated Java compiles (requires javac) +#load_dialect "testdata/Simple.dialect.st" + +elab "#testCompile" : command => do + let javacCheck ← IO.Process.output { cmd := "javac", args := #["--version"] } + if javacCheck.exitCode != 0 then + Lean.logError "Test 12 failed: javac not found" + return + + let env ← Lean.getEnv + let state := Strata.dialectExt.getState env + let some simple := state.loaded.dialects["Simple"]? + | Lean.logError "Simple dialect not found"; return + let files := (generateDialect simple "com.test").toOption.get! + + let dir : System.FilePath := "/tmp/strata-java-test" + writeJavaFiles dir "com.test" files + + let fileNames := #["SourceRange.java", "Node.java", files.builders.1] + ++ files.interfaces.map Prod.fst + ++ files.records.map Prod.fst + let pkgDir := (dir / "com" / "test").toString + let filePaths := fileNames.map fun f => pkgDir ++ "/" ++ f + + let result ← IO.Process.output { + cmd := "javac" + args := filePaths + } + + IO.FS.removeDirAll dir + + if result.exitCode != 0 then + Lean.logError s!"javac failed:\n{result.stderr}" + +#testCompile + +-- Test 13: Roundtrip - verify Lean can read Java-generated Ion +-- Depends on testdata/comprehensive.ion (generated by Tools/Java/regenerate-testdata.sh) +elab "#testRoundtrip" : command => do + let env ← Lean.getEnv + let state := Strata.dialectExt.getState env + let some simple := state.loaded.dialects["Simple"]? + | Lean.logError "Simple dialect not found"; return + let dm := Strata.DialectMap.ofList! [Strata.initDialect, simple] + let ionBytes ← IO.FS.readBinFile "StrataTest/DDM/Integration/Java/testdata/comprehensive.ion" + match Strata.Program.fromIon dm "Simple" ionBytes with + | .error e => Lean.logError s!"Roundtrip test failed: {e}" + | .ok prog => + if prog.commands.size != 1 then Lean.logError "Expected 1 command"; return + let cmd := prog.commands[0]! + if cmd.name != (⟨"Simple", "block"⟩ : Strata.QualifiedIdent) then Lean.logError "Expected block command"; return + if let .seq _ stmts := cmd.args[0]! then + if stmts.size != 4 then Lean.logError s!"Expected 4 statements, got {stmts.size}" + else Lean.logError "Expected seq argument" + +#testRoundtrip + +end Strata.Java.Test diff --git a/StrataTest/DDM/Integration/Java/regenerate-testdata.sh b/StrataTest/DDM/Integration/Java/regenerate-testdata.sh new file mode 100755 index 000000000..d4acc3130 --- /dev/null +++ b/StrataTest/DDM/Integration/Java/regenerate-testdata.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Regenerate Java roundtrip test data +set -e +cd "$(dirname "$0")" + +STRATA_ROOT="$(cd ../../../.. && pwd)" +TESTDATA="testdata" +GEN_DIR="testdata/generated" +JAR="testdata/ion-java-1.11.9.jar" + +# Download ion-java if needed +if [ ! -f "$JAR" ]; then + echo "=== Downloading ion-java ===" + curl -sLO --output-dir testdata "https://repo1.maven.org/maven2/com/amazon/ion/ion-java/1.11.9/ion-java-1.11.9.jar" +fi + +echo "=== Generating Java classes from dialect ===" +(cd "$STRATA_ROOT" && lake exe strata javaGen "$STRATA_ROOT/StrataTest/DDM/Integration/Java/$TESTDATA/Simple.dialect.st" com.strata.simple "$STRATA_ROOT/StrataTest/DDM/Integration/Java/$GEN_DIR") + +echo "=== Compiling Java ===" +javac -cp "$JAR" $GEN_DIR/com/strata/simple/*.java $TESTDATA/GenerateTestData.java + +echo "=== Generating test data ===" +java -cp "$JAR:$GEN_DIR:$TESTDATA" GenerateTestData "$TESTDATA/comprehensive.ion" + +echo "=== Cleaning up ===" +rm -rf "$GEN_DIR" +rm -f $TESTDATA/*.class + +echo "=== Verifying with Lean ===" +(cd "$STRATA_ROOT" && lake exe strata print --include "$STRATA_ROOT/StrataTest/DDM/Integration/Java/$TESTDATA" "$STRATA_ROOT/StrataTest/DDM/Integration/Java/$TESTDATA/comprehensive.ion" 2>&1 | tail -1) + +echo "" +echo "Done! Regenerated $TESTDATA/comprehensive.ion" diff --git a/StrataTest/DDM/Integration/Java/testdata/.gitignore b/StrataTest/DDM/Integration/Java/testdata/.gitignore new file mode 100644 index 000000000..a3ac359fa --- /dev/null +++ b/StrataTest/DDM/Integration/Java/testdata/.gitignore @@ -0,0 +1,4 @@ +# Generated during regenerate-testdata.sh +generated/ +*.class +ion-java-*.jar diff --git a/StrataTest/DDM/Integration/Java/testdata/GenerateTestData.java b/StrataTest/DDM/Integration/Java/testdata/GenerateTestData.java new file mode 100644 index 000000000..e451b183e --- /dev/null +++ b/StrataTest/DDM/Integration/Java/testdata/GenerateTestData.java @@ -0,0 +1,35 @@ +import static com.strata.simple.Simple.*; +import com.strata.simple.*; +import com.amazon.ion.*; +import com.amazon.ion.system.*; +import java.io.*; +import java.util.*; + +/** Generates comprehensive.ion covering all DDM types. */ +public class GenerateTestData { + public static void main(String[] args) throws Exception { + var ion = IonSystemBuilder.standard().build(); + var serializer = new IonSerializer(ion); + + // AST covering: Num, Str, Ident, Bool, Decimal, ByteArray, Option, Seq, nesting + Node ast = block(List.of( + assign("x", add(num(1), neg(num(2)))), + print("hello"), + ifStmt(true, data(new byte[]{0x01, (byte)0xFF}), Optional.of(decimal(3.14))), + ifStmt(false, block(List.of()), Optional.empty()))); + + IonList program = ion.newEmptyList(); + IonSexp header = ion.newEmptySexp(); + header.add(ion.newSymbol("program")); + header.add(ion.newString("Simple")); + program.add(header); + program.add(serializer.serializeCommand(ast)); + + try (var out = new FileOutputStream(args[0])) { + var writer = IonBinaryWriterBuilder.standard().build(out); + program.writeTo(writer); + writer.close(); + } + System.out.println("Generated: " + args[0]); + } +} diff --git a/StrataTest/DDM/Integration/Java/testdata/README.md b/StrataTest/DDM/Integration/Java/testdata/README.md new file mode 100644 index 000000000..35dc36f4e --- /dev/null +++ b/StrataTest/DDM/Integration/Java/testdata/README.md @@ -0,0 +1,27 @@ +# Java Roundtrip Test Data + +`comprehensive.ion` is a Java-generated Ion file that tests all DDM types. + +## To regenerate + +From this directory: + +```bash +./regenerate-testdata.sh +``` + +This will: +1. Generate Java classes from `Simple.dialect.st` +2. Build and run `GenerateTestData.java` to produce `comprehensive.ion` +3. Clean up generated classes +4. Verify the output with Lean + +## What's tested + +The test file covers all DDM types in a single AST: +- Num, Str, Ident +- Bool (true and false) +- Decimal, ByteArray +- Option (some and none) +- Seq (with items and empty) +- Nested operations (3 levels deep) diff --git a/StrataTest/DDM/Integration/Java/testdata/Simple.dialect.st b/StrataTest/DDM/Integration/Java/testdata/Simple.dialect.st new file mode 100644 index 000000000..b4d3e7ea2 --- /dev/null +++ b/StrataTest/DDM/Integration/Java/testdata/Simple.dialect.st @@ -0,0 +1,15 @@ +dialect Simple; + +category Expr; +op num (value : Num) : Simple.Expr => value; +op add (left : Simple.Expr, right : Simple.Expr) : Simple.Expr => left "+" right; +op neg (inner : Simple.Expr) : Simple.Expr => "-" inner; + +category Stmt; +op print (msg : Str) : Simple.Stmt => "print" msg; +op assign (name : Ident, value : Simple.Expr) : Simple.Stmt => name ":=" value; +op block (stmts : Seq Simple.Stmt) : Simple.Stmt => "{" stmts "}"; +op ifStmt (cond : Bool, thenBranch : Simple.Stmt, elseBranch : Option Simple.Stmt) : Simple.Stmt => + "if" cond "then" thenBranch elseBranch; +op data (bytes : ByteArray) : Simple.Stmt => "data" bytes; +op decimal (value : Decimal) : Simple.Stmt => "decimal" value; diff --git a/StrataTest/DDM/Integration/Java/testdata/comprehensive.ion b/StrataTest/DDM/Integration/Java/testdata/comprehensive.ion new file mode 100644 index 000000000..6ee448d58 Binary files /dev/null and b/StrataTest/DDM/Integration/Java/testdata/comprehensive.ion differ diff --git a/StrataTest/DDM/Ion.lean b/StrataTest/DDM/Ion.lean index 387326756..702b42b5c 100644 --- a/StrataTest/DDM/Ion.lean +++ b/StrataTest/DDM/Ion.lean @@ -21,26 +21,8 @@ def testRoundTrip {α} [FromIon α] [BEq α] [Inhabited α] (toF : α → ByteAr def testDialectRoundTrip (d : Dialect) : Bool := testRoundTrip Dialect.toIon d -#dialect -dialect Bool; -// Introduce Boolean type -type Bool; - -// Introduce literals as constants. -fn true_lit : Bool => "true"; -fn false_lit : Bool => "false"; - -// Introduce basic Boolean operations. -fn not_expr (tp : Type) : tp => tp; -fn and (a : Bool, b : Bool) : Bool => @[prec(10), leftassoc] a " && " b; -fn or (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " || " b; -fn imp (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " ==> " b; - -// Introduce equality operations that work for arbitrary types. -// The type is inferred. -fn equal (tp : Type, a : tp, b : tp) : Bool => @[prec(15)] a " == " b; -fn not_equal (tp : Type, a : tp, b : tp) : Bool => @[prec(15)] a " != " b; -#end +-- Load the actual Bool dialect from Examples +#load_dialect "../../Examples/dialects/Bool.dialect.st" #guard testDialectRoundTrip Bool diff --git a/StrataTest/DDM/LoadDialect.lean b/StrataTest/DDM/LoadDialect.lean index ba430a7be..94732cd76 100644 --- a/StrataTest/DDM/LoadDialect.lean +++ b/StrataTest/DDM/LoadDialect.lean @@ -31,6 +31,14 @@ error: 1 error(s) in ../../Examples/dialects/Arith.dialect.st: namespace Bool #strata_gen Bool + +-- Test that boolLit has the expected signature +/-- +info: Strata.Test.Bool.Expr.boolLit {α : Type} : α → (b : Ann _root_.Bool α) → Expr α +-/ +#guard_msgs in +#check Expr.boolLit + end Bool #load_dialect "../../Examples/dialects/Arith.dialect.st" diff --git a/StrataTest/DDM/PipeIdent.lean b/StrataTest/DDM/PipeIdent.lean new file mode 100644 index 000000000..e25764126 --- /dev/null +++ b/StrataTest/DDM/PipeIdent.lean @@ -0,0 +1,172 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean + +open Strata + +-- Test dialect for pipe-delimited identifiers (SMT-LIB 2.6 syntax) +#dialect +dialect PipeIdent; + +category Expression; + +op var (name : Ident) : Expression => name; +op assign (lhs : Ident, rhs : Expression) : Command => lhs:0 " := " rhs ";"; +op add (a : Expression, b : Expression) : Expression => @[prec(10), leftassoc] a " + " b; +op or (a : Expression, b : Expression) : Expression => @[prec(5), leftassoc] a " || " b; +op bitwiseOr (a : Expression, b : Expression) : Expression => @[prec(6), leftassoc] a " | " b; +op intLit (n : Num) : Expression => @[prec(0)] n; + +#end + +namespace PipeIdent + +#strata_gen PipeIdent + +end PipeIdent + +-- Various special characters in pipe-delimited identifiers +-- Including «» which tests that Lean's «» notation is properly stripped +/-- +info: program PipeIdent; +result := |special-name| + |name with spaces| + |name@with#special$chars| + |123numeric| + |name-with-émojis-🎉| + |name«with»guillemets| + regularName; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := |special-name| + |name with spaces| + |name@with#special$chars| + |123numeric| + |name-with-émojis-🎉| + |name«with»guillemets| + regularName; +#end).format + +-- || operator is not confused with pipe-delimited identifiers +/-- +info: program PipeIdent; +result := |special-name| || regularName; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := |special-name| || regularName; +#end).format + +-- Operator-like identifiers +/-- +info: program PipeIdent; +result := |++| + |--| + |**|; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := |++| + |--| + + |**|; +#end).format + +-- Escape sequences (SMT-LIB 2.6 spec) +/-- +info: program PipeIdent; +result := |name\|with\|pipes| + |path\\to\\file|; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := |name\|with\|pipes| + + |path\\to\\file|; +#end).format + +-- Single | operator coexists with |identifier| +/-- +info: program PipeIdent; +result := |x-value| | |y-value| | regularVar; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := |x-value| | |y-value| | regularVar; +#end).format + +-- Identifiers with dots don't require pipe delimiters +/-- +info: program PipeIdent; +result := qualified.name + another.dotted.identifier + x.y; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := qualified.name + another.dotted.identifier + x.y; +#end).format + +-- Identifiers with consecutive dots +/-- +info: program PipeIdent; +result := a..b + x...y + trailing..end; +-/ +#guard_msgs in +#eval (#strata +program PipeIdent; +result := a..b + x...y + trailing..end; +#end).format + +-- Verify escape sequences are unescaped in AST (not just round-trip) +def testEscapeAST := #strata +program PipeIdent; +x := |name\|with\|pipes|; +y := |path\\to\\file|; +#end + +-- Extract identifier from var operation in RHS +def getRHSIdent (op : Operation) : String := + match op.args[1]! with + | .op varOp => + match varOp.args[0]! with + | .ident _ s => s + | _ => "" + | _ => "" + +-- Verify: \| is unescaped to | in AST (stored with Lean's «» notation) +#guard (getRHSIdent testEscapeAST.commands[0]!) == "«name|with|pipes»" + +-- Verify: \\ is unescaped to single \ in AST (stored with Lean's «» notation) +#guard (getRHSIdent testEscapeAST.commands[1]!) == "«path\\to\\file»" + +-- Verify dots are preserved in AST +def testDotIdent := #strata +program PipeIdent; +x := qualified.name; +y := another.dotted.identifier; +z := a..b; +w := x...y; +v := trailing..end; +#end + +-- Verify: dots are preserved in identifier names in AST (stored with Lean's «» notation) +#guard (getRHSIdent testDotIdent.commands[0]!) == "«qualified.name»" +#guard (getRHSIdent testDotIdent.commands[1]!) == "«another.dotted.identifier»" +#guard (getRHSIdent testDotIdent.commands[2]!) == "«a..b»" +#guard (getRHSIdent testDotIdent.commands[3]!) == "«x...y»" +#guard (getRHSIdent testDotIdent.commands[4]!) == "«trailing..end»" + +-- Test dialect with | operator that has NO spaces in syntax definition +#dialect +dialect PipeIdentNoSpace; + +category Expression; + +op var (name : Ident) : Expression => name; +op bitwiseOr (a : Expression, b : Expression) : Expression => @[prec(6), leftassoc] a "|" b; +op exprStmt (e : Expression) : Command => e ";"; + +#end + +-- Edge case: | operator without spaces can create ambiguous output +-- "normalId|pipe" is parsed as normalId followed by unterminated pipe-delimited identifier +/-- +error: unterminated pipe-delimited identifier +-/ +#guard_msgs in +#eval (#strata +program PipeIdentNoSpace; +normalId|pipe; +#end).format diff --git a/StrataTest/DDM/TestGrammar.lean b/StrataTest/DDM/TestGrammar.lean new file mode 100644 index 000000000..a6b28260c --- /dev/null +++ b/StrataTest/DDM/TestGrammar.lean @@ -0,0 +1,114 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Elab +import Strata.DDM.Parser +import Strata.DDM.Format + +/- +Allows testing whether a DDM dialect can parse and print a given program without losing information. +-/ +open Strata + +namespace StrataTest.DDM + +/-- Remove C-style comments (// and /* */) from a string -/ +def stripComments (s : String) : String := + let rec stripMultiLine (str : String) (startIdx : Nat) (acc : String) : String := + if startIdx >= str.length then acc + else + let remaining := str.drop startIdx + match remaining.splitOn "/*" with + | [] => acc + | [rest] => acc ++ rest + | beforeComment :: afterStart => + let afterStartStr := "/*".intercalate afterStart + match afterStartStr.splitOn "*/" with + | [] => acc ++ beforeComment + | afterComment :: _ => + let newIdx := startIdx + beforeComment.length + 2 + afterComment.length + 2 + stripMultiLine str newIdx (acc ++ beforeComment) + termination_by str.length - startIdx + + let withoutMultiLine := stripMultiLine s 0 "" + let lines := withoutMultiLine.splitOn "\n" + let withoutSingleLine := lines.map fun line => + match line.splitOn "//" with + | [] => line + | first :: _ => first + "\n".intercalate withoutSingleLine + +/-- Normalize whitespace in a string by splitting on whitespace and rejoining with single spaces -/ +def normalizeWhitespace (s : String) : String := + let words := (s.splitToList Char.isWhitespace).filter (·.isEmpty.not) + " ".intercalate words + +/-- Result of a grammar test -/ +structure GrammarTestResult where + parseSuccess : Bool + normalizedInput : String + normalizedOutput : String + normalizedMatch : Bool + errorMessages : List String := [] + +/-- Test parsing and formatting a file with a given dialect. + + Takes: + - dialect: The dialect to use for parsing + - filePath: Path to the source file to test + + Returns: + - GrammarTestResult with parse/format results -/ +def testGrammarFile (dialect: Dialect) (ctx : Lean.Parser.InputContext) : IO GrammarTestResult := do + try + let loaded := .ofDialects! #[initDialect, dialect] + let ddmProgram ← Strata.Elab.parseStrataProgramFromDialect loaded dialect.name ctx + let formatted := ddmProgram.format.render + let normalizedInput := normalizeWhitespace <| stripComments <| + s!"program {dialect.name}; " ++ ctx.inputString + let normalizedOutput := normalizeWhitespace formatted + + let isMatch := normalizedInput == normalizedOutput + + return { + parseSuccess := true + normalizedInput := normalizedInput + normalizedOutput := normalizedOutput + normalizedMatch := isMatch + errorMessages := [] + } + catch e => + return { + parseSuccess := false + normalizedInput := "" + normalizedOutput := "" + normalizedMatch := false + errorMessages := [toString e] + } + +def printTestResult (result : GrammarTestResult) (showFormatted : Bool := true) : IO Unit := do + + if !result.parseSuccess then + IO.println s!"✗ Parse failed: {result.errorMessages.length} error(s)" + for msg in result.errorMessages do + IO.println s!" {msg}" + else + IO.println "✓ Parse succeeded!\n" + + if showFormatted then + IO.println "=== Formatted input ===\n" + IO.println result.normalizedInput + IO.println "=== Formatted output ===\n" + IO.println result.normalizedOutput + + IO.println "\n=== Comparison ===\n" + if result.normalizedMatch then + IO.println "✓ Formatted output matches input (modulo whitespace)!" + else + IO.println "✗ Formatted output differs from input" + IO.println "(This is expected when comments are present in the source)" + +end StrataTest.DDM diff --git a/StrataTest/DDM/UnwrapSimple.lean b/StrataTest/DDM/UnwrapSimple.lean new file mode 100644 index 000000000..8f6ff0bdb --- /dev/null +++ b/StrataTest/DDM/UnwrapSimple.lean @@ -0,0 +1,116 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean + +open Strata + +#dialect +dialect TestUnwrap; + +category Expression; + +op var (name : Ident) : Expression => name; +op index (@[unwrap] id : Num) : Expression => id; +op index_nounwrap (id : Num) : Expression => id; +op name (@[unwrap] n : Ident) : Expression => n; +op text (@[unwrap] s : Str) : Expression => s; +op decimal_val (@[unwrap] d : Decimal) : Expression => d; +op bytes_val (@[unwrap] b : ByteArray) : Expression => b; +op bool_unwrapped (@[unwrap] b : Bool) : Expression => b; +op bool_wrapped (b : Bool) : Expression => b; + +#end + +namespace TestUnwrap + +#strata_gen TestUnwrap + +end TestUnwrap + +/-- +info: TestUnwrap.Expression (α : Type) : Type +-/ +#guard_msgs in +#check TestUnwrap.Expression + +/-- +info: TestUnwrap.Expression.var {α : Type} : α → (name : Ann String α) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.var + +/-- +info: TestUnwrap.Expression.index {α : Type} : α → (id : Nat) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.index + +/-- +info: TestUnwrap.Expression.index_nounwrap {α : Type} : α → (id : Ann Nat α) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.index_nounwrap + +/-- +info: TestUnwrap.Expression.name {α : Type} : α → (n : String) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.name + +/-- +info: TestUnwrap.Expression.text {α : Type} : α → (s : String) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.text + +/-- +info: TestUnwrap.Expression.decimal_val {α : Type} : α → (d : Decimal) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.decimal_val + +/-- +info: TestUnwrap.Expression.bytes_val {α : Type} : α → (b : ByteArray) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.bytes_val + +/-- +info: TestUnwrap.Expression.bool_unwrapped {α : Type} : α → (b : Bool) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.bool_unwrapped + +/-- +info: TestUnwrap.Expression.bool_wrapped {α : Type} : α → (b : Ann Bool α) → TestUnwrap.Expression α +-/ +#guard_msgs in +#check TestUnwrap.Expression.bool_wrapped + +-- Verify that index uses unwrapped Nat (not Ann Nat α) +example : TestUnwrap.Expression Unit := .index () 42 + +-- Verify that index_nounwrap uses wrapped Ann Nat +example : TestUnwrap.Expression Unit := .index_nounwrap () ⟨(), 42⟩ + +-- Verify that name uses unwrapped String +example : TestUnwrap.Expression Unit := .name () "foo" + +-- Verify that text uses unwrapped String +example : TestUnwrap.Expression Unit := .text () "bar" + +-- Verify that decimal_val uses unwrapped Decimal +example : TestUnwrap.Expression Unit := .decimal_val () { mantissa := 123, exponent := -2 } + +-- Verify that bytes_val uses unwrapped ByteArray +example : TestUnwrap.Expression Unit := .bytes_val () (ByteArray.mk #[0x48, 0x69]) + +-- Verify that bool_unwrapped uses unwrapped Bool +example : TestUnwrap.Expression Unit := .bool_unwrapped () true + +-- Verify that bool_wrapped uses wrapped Ann Bool +example : TestUnwrap.Expression Unit := .bool_wrapped () ⟨(), false⟩ diff --git a/StrataTest/DDM/Util/Ion.lean b/StrataTest/DDM/Util/Ion.lean index a98ed59ac..1895e8799 100644 --- a/StrataTest/DDM/Util/Ion.lean +++ b/StrataTest/DDM/Util/Ion.lean @@ -32,12 +32,26 @@ def testRoundtrip (v : List (Ion SymbolId)) : Bool := #guard testRoundtrip [.bool false, .bool true] #guard testRoundtrip [.int 0, .int 1, .int (-1), .int 256, .int (-256)] #guard testRoundtrip [.float 1e-3, .float 3] -#guard testRoundtrip [.decimal ⟨0, 0 ⟩, .decimal ⟨1, 3 ⟩, .decimal ⟨0, 0 ⟩] +#guard testRoundtrip [.decimal ⟨0, 0⟩] +#guard testRoundtrip [.decimal ⟨0, 1⟩] +#guard testRoundtrip [.decimal ⟨0, -1⟩] +#guard testRoundtrip [.decimal ⟨0, 65⟩] +#guard (serialize #[.decimal ⟨0, 256⟩]).asHex = "e00100ea520280" +#guard (serialize #[.decimal ⟨0, -256⟩]).asHex = "e00100ea524280" +#guard testRoundtrip [.decimal ⟨0, 256⟩] +#guard testRoundtrip [.decimal ⟨0, -256⟩] +#guard testRoundtrip [.decimal ⟨258, 0⟩] +#guard testRoundtrip [.decimal ⟨-258, 0⟩] +#guard testRoundtrip [.decimal ⟨1, 3⟩] + +#guard testRoundtrip [.symbol (.mk 0), .symbol (.mk 1)] + #guard testRoundtrip [.string "", .string "⟨"] #guard testRoundtrip [.string "this_is_a_long_name"] -#guard testRoundtrip [.symbol (.mk 0), .symbol (.mk 1)] +#guard testRoundtrip [.blob <| Strata.ByteArray.zeros 20000] #guard testRoundtrip [.list #[], .list #[.int 1]] +#guard testRoundtrip [.list (Array.ofFn (n := 8000) fun i => .int i.val)] #guard testRoundtrip [.sexp #[], .sexp #[.int 1]] #guard testRoundtrip [.struct #[], .struct #[(.mk 1, .int 1)]] #guard testRoundtrip [.annotation #[.mk 1] (.int 1)] diff --git a/StrataTest/DL/Imperative/Arith.lean b/StrataTest/DL/Imperative/Arith.lean index bd8733bb5..5f9a1008d 100644 --- a/StrataTest/DL/Imperative/Arith.lean +++ b/StrataTest/DL/Imperative/Arith.lean @@ -25,7 +25,7 @@ private def testProgram1 : Commands := /-- info: ok: Commands: init (x : Num) := (y : Num) -#[] havoc x +havoc x assert [x_value_eq] ($__x0 : Num) = (y : Num) State: diff --git a/StrataTest/DL/Imperative/ArithEval.lean b/StrataTest/DL/Imperative/ArithEval.lean index d7bfbf435..f9ee3f0f6 100644 --- a/StrataTest/DL/Imperative/ArithEval.lean +++ b/StrataTest/DL/Imperative/ArithEval.lean @@ -204,7 +204,7 @@ private def testProgram2 : Cmds PureExpr := /-- info: Commands: init (x : Num) := y -#[] havoc x +havoc x assert [x_value_eq] ($__x0 : Num) = 100 State: diff --git a/StrataTest/DL/Imperative/ArithType.lean b/StrataTest/DL/Imperative/ArithType.lean index 7826e2091..e66f6a5b6 100644 --- a/StrataTest/DL/Imperative/ArithType.lean +++ b/StrataTest/DL/Imperative/ArithType.lean @@ -92,7 +92,7 @@ def unifyTypes (T : TEnv) (constraints : List (Ty × Ty)) : Except Format TEnv : /-- Instantiation of `TypeContext` for `ArithPrograms`. -/ -instance : TypeContext PureExpr Unit TEnv where +instance : TypeContext PureExpr Unit TEnv Std.Format where isBoolType := Arith.TypeCheck.isBoolType freeVars := (fun e => (Arith.Expr.freeVars e).map (fun (v, _) => v)) preprocess := fun _ => Arith.TypeCheck.preprocess @@ -101,6 +101,7 @@ instance : TypeContext PureExpr Unit TEnv where lookup := Arith.TypeCheck.lookup inferType := fun _ => Arith.TypeCheck.inferType unifyTypes := Arith.TypeCheck.unifyTypes + typeErrorFmt := id instance : ToFormat (Cmds PureExpr × TEnv) where format arg := @@ -140,9 +141,7 @@ private def testProgram2 : Cmds Arith.PureExpr := private def testProgram3 : Cmds Arith.PureExpr := [.init "x" .Bool (.Var "x" .none)] -/-- -info: error: Type Checking [init (x : .Bool) := x]: Variable x cannot appear in its own initialization expression! --/ +/-- info: error: Variable x cannot appear in its own initialization expression! -/ #guard_msgs in #eval do let (cs, τ) ← Cmds.typeCheck () TEnv.init testProgram3 return format (cs, τ) @@ -168,9 +167,7 @@ private def testProgram5 : Cmds Arith.PureExpr := [.init "x" .Num (.Num 5), .init "x" .Bool (.Eq (.Num 1) (.Num 2))] -/-- -info: error: Type Checking [init (x : .Bool) := 1 = 2]: Variable x of type Num already in context. --/ +/-- info: error: Variable x of type Num already in context. -/ #guard_msgs in #eval do let (cs, τ) ← Cmds.typeCheck () TEnv.init testProgram5 return format (cs, τ) diff --git a/StrataTest/DL/Lambda/LExprEvalTests.lean b/StrataTest/DL/Lambda/LExprEvalTests.lean index 198879bc9..5b793fc60 100644 --- a/StrataTest/DL/Lambda/LExprEvalTests.lean +++ b/StrataTest/DL/Lambda/LExprEvalTests.lean @@ -4,6 +4,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +import Strata.DL.Lambda.Semantics import Strata.DL.Lambda.LExprEval --------------------------------------------------------------------- @@ -19,88 +20,228 @@ section EvalTest open LTy.Syntax LExpr.SyntaxMono open Std (ToFormat Format format) -/-- info: (λ (if (%0 == #1) then #10 else (_minit %0))) -/ -#guard_msgs in -#eval format $ Lambda.LExpr.eval 100 - {Lambda.LState.init with state := - [[("m", (mty[int → int], esM[_minit]))]] } - esM[λ (if (%0 == #1) then #10 else (m %0))] +/- +Each test is a pair of +1. Lambda.LExpr.eval invocation, and +2. Its equivalent Lambda.LExpr.Step version. +-/ -/-- info: #42 -/ +-- A helper tactic for proving 'isCanonicalValue e = b'. +macro "discharge_isCanonicalValue": tactic => `(tactic| + conv => + lhs; reduce; unfold isCanonicalValue; reduce; unfold isCanonicalValue + ) +-- Take a small step. +macro "take_step": tactic => `(tactic | + (conv => lhs; reduce) <;> apply ReflTrans.step + ) +-- Finish taking small steps! +macro "take_refl": tactic => `(tactic | + (conv => lhs; reduce) <;> apply ReflTrans.refl + ) +-- Do beta reduction. +macro "reduce_beta": tactic => `(tactic | + apply Step.beta <;> discharge_isCanonicalValue + ) +-- A helper tactic to exhibit an instance of Metadata (which is Unit) +macro "inhabited_metadata": tactic => `(tactic | + solve | (simp; apply ()) + ) + +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ + +private instance : Coe String TestParams.Identifier where + coe s := Identifier.mk s () + + +/- Test cases -/ + +structure TestCase where + -- Input state + σ: LState TestParams + -- Input expression + e: LExpr (TestParams.mono) + -- Reduced output + e_out: LExpr (TestParams.mono) + +def check (t:TestCase) (n:=100) := (Lambda.LExpr.eval n t.σ t.e) == t.e_out + +/-- The two kinds of propositions we would like to test! -/ +abbrev steps_well (t:TestCase):Prop := + Lambda.StepStar (Tbase:=TestParams) + t.σ.config.factory (Scopes.toEnv t.σ.state) t.e t.e_out + +abbrev stuck (t:TestCase):Prop := + ∀ eres, ¬ Lambda.Step (Tbase:=TestParams) + t.σ.config.factory (Scopes.toEnv t.σ.state) t.e eres + + +-------------------------------- Tests ------------------------------ + +def test1 := TestCase.mk + ({Lambda.LState.init with state := [[("m", (mty[int → int], esM[_minit]))]] }) + (esM[λ (if (%0 == #1) then #10 else (m %0))]) + (esM[λ (if (%0 == #1) then #10 else (_minit %0))]) + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval 100 - { LState.init with state := [[("x", (mty[int], esM[(#32 : int)]))]] } - esM[((λ (if (%0 == #23) then #17 else #42)) (x : int))] +#eval (check test1) -/-- info: (f #true) -/ -#guard_msgs in -#eval format $ LExpr.eval 10 ∅ esM[(f #true)] +-- Small step stucks because abstraction is a value. +example: stuck test1 := by + intros e H + contradiction -/-- info: (minit #24) -/ + +def test2 := TestCase.mk + { LState.init with state := [[("x", (mty[int], esM[#32]))]] } + esM[((λ (if (%0 == #23) then #17 else #42)) (x : int))] + esM[#42] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval 100 - { LState.init with state := - [[("m", (none, esM[(λ (minit %0))]))], -- most recent scope - [("m", (none, (.const "12" none)))]] } - esM[((λ (if (%0 == #23) then #17 else (m %0)) #24))] +#eval (check test2) + +example: steps_well test2 := by + unfold steps_well Scopes.toEnv test2 + take_step; apply Step.reduce_2 <;> try inhabited_metadata + · discharge_isCanonicalValue + · repeat constructor + take_step; reduce_beta + take_step; constructor <;> try inhabited_metadata + · apply Step.eq_reduce <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.ite_reduce_else + apply ReflTrans.refl + + +def test3 := TestCase.mk + ∅ + esM[(f #true)] + esM[(f #true)] + +/-- info: true -/ +#guard_msgs in +#eval check test3 + +example: stuck test3 := by + intros e H + contradiction -/-- info: (minit #24) -/ + +def test4 := TestCase.mk + { LState.init with state := + [[("m", (none, esM[(λ (minit %0))]))], -- most recent scope + [("m", (none, (.intConst () 12)))]] } + esM[((λ (if (%0 == #23) then #17 else (m %0)) #24))] + esM[(minit #24)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval 100 - { LState.init with state := [[("m", (none, esM[minit]))]] } - esM[((λ (if (%0 == #23) then #17 else (m %0))) #24)] +#eval check test4 + +example: steps_well test4 := by + unfold steps_well Scopes.toEnv test4 + take_step; reduce_beta + take_step; apply Step.ite_reduce_cond <;> try inhabited_metadata + · apply Step.eq_reduce <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.ite_reduce_else + take_step; apply Step.reduce_1; inhabited_metadata; apply Step.expand_fvar; rfl + take_step; reduce_beta + take_refl + + +def test5 := TestCase.mk + { LState.init with state := [[("m", (none, esM[minit]))]] } + esM[((λ (if (%0 == #23) then #17 else (m %0))) #24)] + esM[(minit #24)] + +/-- info: true -/ +#guard_msgs in +#eval check test5 + +example: steps_well test5 := by + unfold steps_well Scopes.toEnv test5 + take_step; reduce_beta + take_step; apply Step.ite_reduce_cond; inhabited_metadata + · apply Step.eq_reduce <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.ite_reduce_else + take_step; apply Step.reduce_1; inhabited_metadata; apply Step.expand_fvar; rfl + take_refl -/-- info: x -/ + +def test6 := TestCase.mk + ∅ + esM[if #true then x else y] + esM[x] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval 10 ∅ esM[if #true then x else y] +#eval check test6 + +example: steps_well test6 := by + unfold steps_well Scopes.toEnv test6 + take_step + · constructor + take_refl + -- Ill-formed `abs` is returned as-is in this Curry style... -/-- info: (λ %1) -/ -#guard_msgs in -#eval format $ LExpr.eval 10 ∅ esM[(λ %1)] +def test7 := TestCase.mk + ∅ + esM[(λ %1)] + esM[(λ %1)] -/-- info: ((λ %1) #true) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 ∅ (.app (.mdata ⟨"x"⟩ (.abs .none (.bvar 1))) (.const "true" none)) +#eval check test7 + +example: stuck test7 := by + intros e H + contradiction + /- Tests for evaluation of BuiltInFunctions. -/ open LTy.Syntax -private def testBuiltIn : @Factory Unit := +private def testBuiltIn : @Factory TestParams := #[{ name := "Int.Add", inputs := [("x", mty[int]), ("y", mty[int])], output := mty[int], - concreteEval := some (fun e args => match args with + concreteEval := some (fun _e args => match args with | [e1, e2] => let e1i := LExpr.denoteInt e1 let e2i := LExpr.denoteInt e2 match e1i, e2i with - | some x, some y => (.const (toString (x + y)) mty[int]) - | _, _ => e - | _ => e) }, + | some x, some y => .some (.intConst e1.metadata (x + y)) + | _, _ => .none + | _ => .none) }, { name := "Int.Div", inputs := [("x", mty[int]), ("y", mty[int])], output := mty[int], - concreteEval := some (fun e args => match args with + concreteEval := some (fun _e args => match args with | [e1, e2] => let e1i := LExpr.denoteInt e1 let e2i := LExpr.denoteInt e2 match e1i, e2i with | some x, some y => - if y == 0 then e else (.const (toString (x / y)) mty[int]) - | _, _ => e - | _ => e) }, + if y == 0 then .none + else .some (.intConst e1.metadata (x / y)) + | _, _ => .none + | _ => .none) }, { name := "Int.Neg", inputs := [("x", mty[int])], output := mty[int], - concreteEval := some (fun e args => match args with + concreteEval := some (fun _e args => match args with | [e1] => let e1i := LExpr.denoteInt e1 match e1i with - | some x => (.const (toString (- x)) mty[int]) - | _ => e - | _ => e) }, + | some x => .some (.intConst e1.metadata (- x)) + | _ => .none + | _ => .none) }, { name := "IntAddAlias", attr := #["inline"], @@ -109,88 +250,387 @@ private def testBuiltIn : @Factory Unit := body := some esM[((~Int.Add x) y)] }] -private def testState : LState Unit := +private def testState : LState TestParams := let ans := LState.addFactory LState.init testBuiltIn match ans with | .error e => panic s!"{e}" | .ok ok => ok -/-- info: (#50 : int) -/ -#guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~IntAddAlias #20) #30)] -/-- info: ((~Int.Add #20) x) -/ -#guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~IntAddAlias #20) x)] +def test8 := TestCase.mk + testState + esM[((~IntAddAlias #20) #30)] + esM[(#50)] -/-- info: ((~Int.Add ((~Int.Add #5) #100)) x) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 LState.init esM[(( ((λλ (~Int.Add %1) %0)) ((λ ((~Int.Add %0) #100)) #5)) x)] +#eval check test8 -/-- info: (#50 : int) -/ -#guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Add #20) #30)] +example: steps_well test8 := by + unfold steps_well Scopes.toEnv test8 + take_step; apply Step.expand_fn <;> discharge_isCanonicalValue + take_step; apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_refl -/-- info: ((~Int.Add (#105 : int)) x) -/ -#guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((((λλ (~Int.Add %1) %0)) ((λ ((~Int.Add %0) #100)) #5)) x)] -/-- info: ((#f #20) (#-5 : int)) -/ -#guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[( ((λλ (#f %1) %0) #20) ((λ (~Int.Neg %0)) (#5 : int)))] +def test9 := TestCase.mk + testState + esM[((~IntAddAlias #20) x)] + esM[((~Int.Add #20) x)] -/-- info: ((~Int.Add #20) (~Int.Neg x)) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[( ((λλ (~Int.Add %1) %0) #20) ((λ (~Int.Neg %0)) x))] +#eval check test9 + +-- Note: this case diverges from concrete evaluator, because 'x' is not a +-- canonical value! Small step reduces only when the arguments are values, +-- to avoid nondeterminism in the small-step semantics (unless this becomes +-- explicitly allowed in the future). +example: stuck test9 := by + intro e H; cases H + case reduce_2 => contradiction + case reduce_1 => contradiction + case expand_fn => + rename_i Hlfunc Hfv + conv at Hlfunc => lhs; reduce + cases Hlfunc + rename_i Hconst Htmp + conv at Hconst => lhs; reduce; unfold isCanonicalValue; reduce + contradiction + case eval_fn => + rename_i Hlfunc _ + conv at Hlfunc => lhs; reduce + cases Hlfunc + rename_i Hconst Htmp + conv at Hconst => lhs; reduce; unfold isCanonicalValue; reduce + contradiction + + +-- A sanity check that confirms the parse tree of λλ x y +/-- info: true -/ +#guard_msgs in +#eval esM[(λλ (~Int.Add %1) %0)] = esM[((λ(λ (~Int.Add %1))) %0)] + -/-- info: ((~Int.Add #20) (~Int.Neg x)) -/ +def test10 := TestCase.mk + LState.init + esM[(( ((λ(λ ((~Int.Add %1) %0)))) ((λ ((~Int.Add %0) #100)) #5)) x)] + esM[((~Int.Add ((~Int.Add #5) #100)) x)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Add #20) (~Int.Neg x))] +#eval check test10 + +-- The small step semantics of this example will stuck in the middle because +-- 'Int.Add %0 100' cannot be evaluated because the definition of Int.Add is +-- not available in LState.init . -/-- info: ((~Int.Add x) (#-30 : int)) -/ + +def test11 := TestCase.mk + testState + esM[((~Int.Add #20) #30)] + esM[#50] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Add x) (~Int.Neg #30))] +#eval check test11 + +example: steps_well test11 := by + unfold steps_well Scopes.toEnv test11 + take_step; apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_refl -/-- info: (#50 : int) -/ + +def test12 := TestCase.mk + testState + esM[((((λ(λ (~Int.Add %1) %0))) ((λ ((~Int.Add %0) #100)) #5)) x)] + esM[((~Int.Add #105) x)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((λ %0) ((~Int.Add #20) #30))] +#eval check test12 + +example: steps_well test12 := by + unfold steps_well Scopes.toEnv test12 + take_step; apply Step.reduce_1; inhabited_metadata; apply Step.reduce_2 + · inhabited_metadata; + · discharge_isCanonicalValue + · reduce_beta + take_step; apply Step.reduce_1; inhabited_metadata; apply Step.reduce_2; + · inhabited_metadata; + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.reduce_1; inhabited_metadata; reduce_beta + take_step; apply Step.reduce_1; inhabited_metadata; reduce_beta + take_refl + +/-- info: false -/ +#guard_msgs in +#eval LExpr.isCanonicalValue testState.config.factory esM[((~Int.Add #100) #200)] -/-- info: (#100 : int) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Div #300) ((~Int.Add #2) #1))] +#eval LExpr.isCanonicalValue testState.config.factory esM[(~Int.Add #100)] + -/-- info: (#0 : int) -/ +def test13 := TestCase.mk + testState + esM[( ((λ(λ (#f %1) %0) #20)) ((λ (~Int.Neg %0)) #5))] + esM[((#f #20) #-5)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Add #3) (~Int.Neg #3))] +#eval check test13 + +-- The small step semantics of this example will stuck in the middle because +-- '(#f 20) e' cannot be evaluated because the definition of #f is +-- not available. + + +def test14 := TestCase.mk + testState + esM[( ((λ(λ (~Int.Add %1) %0)) #20) ((λ (~Int.Neg %0)) x))] + esM[((~Int.Add #20) (~Int.Neg x))] -/-- info: (#0 : int) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Add (~Int.Neg #3)) #3)] +#eval check test14 -/-- info: ((~Int.Div #300) (#0 : int)) -/ +-- The result stops at (.. ((λ (~Int.Neg %0)) x)) because definition of +-- x is not available. +example: steps_well { test14 with e_out := esM[((~Int.Add #20) ((λ (~Int.Neg %0)) x))] } + := by + unfold steps_well Scopes.toEnv test14 + take_step; apply Step.reduce_1; inhabited_metadata; reduce_beta + take_step; apply Step.reduce_1; inhabited_metadata; reduce_beta + take_refl + + +def test15 := TestCase.mk + testState + esM[((~Int.Add #20) (~Int.Neg x))] + esM[((~Int.Add #20) (~Int.Neg x))] + +/-- info: true -/ +#guard_msgs in +#eval check test15 + +example: stuck test15 := by + intros e H + cases H <;> try contradiction + case reduce_2 => + rename_i a + cases a <;> try contradiction + · rename_i a a2 _ + cases a2; cases a + · rename_i a a2 a3 _ + cases a3 + conv at a => lhs ; reduce; unfold isCanonicalValue; reduce + contradiction + case expand_fn => + rename_i a a2 a3 + cases a2 + contradiction + case eval_fn => + rename_i a a2 a3 _ + cases a3 + conv at a => lhs ; reduce; unfold isCanonicalValue; reduce + contradiction + + +def test16 := TestCase.mk + testState + esM[((~Int.Add x) (~Int.Neg #30))] + esM[((~Int.Add x) #-30)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Div #300) ((~Int.Add #3) (~Int.Neg #3)))] +#eval check test16 + +-- test16 stucks because '~Int.Add x' isn't canonical value. +example: stuck test16 := by + intros e H + cases H <;> try contradiction + case reduce_2 => + rename_i a a2 + conv at a => lhs; unfold isCanonicalValue; reduce; unfold isCanonicalValue; reduce + contradiction + case expand_fn => + rename_i a a2 a3 + cases a2 + contradiction + case eval_fn => + rename_i a a2 a3 _ + cases a3 + conv at a => lhs ; reduce; unfold isCanonicalValue; reduce + contradiction + + +def test17 := TestCase.mk + testState + esM[((λ %0) ((~Int.Add #20) #30))] + esM[(#50)] + +/-- info: true -/ +#guard_msgs in +#eval check test17 + +example: steps_well test17 := by + unfold steps_well Scopes.toEnv test17 + take_step; apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; reduce_beta + take_refl + + +def test18 := TestCase.mk + testState + esM[((~Int.Div #300) ((~Int.Add #2) #1))] + esM[(#100)] -/-- info: ((~Int.Div x) (#3 : int)) -/ +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 10 testState esM[((~Int.Div x) ((~Int.Add #2) #1))] +#eval check test18 + +example: steps_well test18 := by + unfold steps_well Scopes.toEnv test18 + take_step; apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.eval_fn <;> try discharge_isCanonicalValue + · simp; rfl + · inhabited_metadata + take_refl + + +def test19 := TestCase.mk + testState + esM[((~Int.Add #3) (~Int.Neg #3))] + esM[(#0)] + +/-- info: true -/ +#guard_msgs in +#eval check test19 + +example: steps_well test19 := by + unfold steps_well Scopes.toEnv test19 + take_step + · apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step + · apply Step.eval_fn <;> try rfl + · conv => lhs; reduce; unfold isCanonicalValue; reduce + · inhabited_metadata + take_refl + + +def test20 := TestCase.mk + testState + esM[((~Int.Add (~Int.Neg #3)) #3)] + esM[(#0)] + +/-- info: true -/ +#guard_msgs in +#eval check test20 + +example: steps_well test20 := by + unfold steps_well Scopes.toEnv test20 + take_step; apply Step.reduce_1 + · inhabited_metadata + · apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_refl + + +def test21 := TestCase.mk + testState + esM[((~Int.Div #300) ((~Int.Add #3) (~Int.Neg #3)))] + esM[((~Int.Div #300) #0)] + +/-- info: true -/ +#guard_msgs in +#eval check test21 + +example: steps_well test21 := by + unfold steps_well Scopes.toEnv test21 + take_step; apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_step; apply Step.reduce_2 + · inhabited_metadata + · discharge_isCanonicalValue + · apply Step.eval_fn <;> try discharge_isCanonicalValue + · inhabited_metadata + take_refl + + +def test22 := TestCase.mk + testState + esM[((~Int.Div x) ((~Int.Add #2) #1))] + esM[((~Int.Div x) #3)] + +/-- info: true -/ +#guard_msgs in +#eval check test22 -/-- info: ((~Int.Le (#100 : int)) x) -/ +-- TODO: steps_well proof of test22 + + +def test23 := TestCase.mk + testState + esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) #1))) x)] + esM[((~Int.Le #100) x)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 200 testState - esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) #1))) x)] +#eval check test23 -/-- -info: ((~Int.Le ((~Int.Div #300) ((~Int.Add #2) y))) x) --/ +-- TODO: steps_well proof of test23 + + +def test24 := TestCase.mk + testState + esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) y))) x)] + esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) y))) x)] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 200 testState - esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) y))) x)] +#eval check test24 + +-- TODO: stuck proof of test24 -/-- info: ((~Int.Div x) x) -/ + +def test25 := TestCase.mk + testState + esM[((~Int.Div x) x)] + esM[((~Int.Div x) x) ] + +/-- info: true -/ #guard_msgs in -#eval format $ LExpr.eval (IDMeta:=Unit) 200 testState - esM[((~Int.Div x) x)] +#eval check test25 + +-- TODO: stuck proof of test25 end EvalTest diff --git a/StrataTest/DL/Lambda/LExprTTests.lean b/StrataTest/DL/Lambda/LExprTTests.lean index 352ecb2a0..3cf790ee8 100644 --- a/StrataTest/DL/Lambda/LExprTTests.lean +++ b/StrataTest/DL/Lambda/LExprTTests.lean @@ -15,91 +15,98 @@ section Tests open LTy.Syntax LExpr.SyntaxMono LExpr LMonoTy +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ + +private instance : Coe String TestParams.Identifier where + coe s := Identifier.mk s () + /-- info: error: Cannot infer the type of this bvar: %2 -/ #guard_msgs in -- Ill-formed terms, like those that contain dangling bound variables, do not -- type check. -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλ %2] return (format $ ans) /-- info: ok: (((λ (%0 : $__ty3)) : (arrow $__ty3 $__ty3)) (y : $__ty3)) : $__ty3) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default (TEnv.default.updateContext { types := [[("y", t[∀x. %x])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default (TEnv.default.updateContext { types := [[("y", t[∀x. %x])]] }) esM[((λ %0) y)] return (format $ ans.fst) -/-- info: error: Cannot unify differently named type constructors bool and int! -/ +/-- info: error: Impossible to unify bool with int. -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default (TEnv.default.updateContext { types := [[("x", t[bool])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default (TEnv.default.updateContext { types := [[("x", t[bool])]] }) esM[if #true then (x == #5) else (x == #6)] return format ans -/-- info: ok: (if (#true : bool) then ((x : int) == (#5 : int)) else ((x : int) == (#6 : int))) -/ +/-- info: ok: (if #true then ((x : int) == #5) else ((x : int) == #6)) -/ #guard_msgs in -#eval do let ans ← LExpr.annotate LContext.default (TEnv.default.updateContext { types := [[("x", t[∀x. %x])]] }) +#eval do let ans ← LExpr.annotate (T:=TestParams) LContext.default (TEnv.default.updateContext { types := [[("x", t[∀x. %x])]] }) esM[if #true then (x == #5) else (x == #6)] return (format $ ans.fst) /-- info: ok: (λ %0) -/ #guard_msgs in -#eval do let ans ← LExpr.annotate LContext.default TEnv.default esM[λ(%0)] +#eval do let ans ← LExpr.annotate (T:=TestParams) LContext.default TEnv.default esM[λ(%0)] return format ans.fst -/-- info: ok: (∀ (%0 == (#5 : int))) -/ +/-- info: ok: (∀ (%0 == #5)) -/ #guard_msgs in -#eval do let ans ← LExpr.annotate LContext.default TEnv.default esM[∀ (%0 == #5)] +#eval do let ans ← LExpr.annotate (T:=TestParams) LContext.default TEnv.default esM[∀ (%0 == #5)] return format ans.fst /-- info: ok: (λ ((succ : (arrow int int)) %0)) -/ #guard_msgs in -#eval do let ans ← LExpr.annotate LContext.default ( TEnv.default.updateContext { types := [[("succ", t[int → int])]] }) +#eval do let ans ← LExpr.annotate (T:=TestParams) LContext.default ( TEnv.default.updateContext { types := [[("succ", t[int → int])]] }) esM[λ(succ %0)] return (format $ ans.fst) /-- info: ok: (∀(int) ((%0 : int) == (#5 : int)) : bool)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[∀ (%0 == #5)] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[∀ (%0 == #5)] return (format $ ans.fst) /-- info: ok: ((λ (%0 : $__ty0)) : (arrow $__ty0 $__ty0)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[λ(%0)] - return (format $ ans.fst) +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λ(%0)] + return (LExprT.format $ ans.fst) /-- info: ok: (#5 : int) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[#5] - return (format $ ans.fst) +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[#5] + return (LExprT.format $ ans.fst) /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[((λ %0) #5)] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[((λ %0) #5)] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow $__ty0 int) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[λ #5] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λ #5] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow (arrow int $__ty2) $__ty2) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[λ(%0 #5)] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λ(%0 #5)] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow $__ty0 (arrow (arrow $__ty0 $__ty4) $__ty4)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[λλ(%0 %1)] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλ(%0 %1)] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow (arrow int $__ty4) $__ty4) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default esM[((λλ (%0 %1)) #5)] +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[((λλ (%0 %1)) #5)] return (format ans.fst.toLMonoTy) -/-- info: error: Ftvar $__ty0 is in the free variables of (arrow $__ty0 $__ty3)! -/ +/-- +info: error: Failed occurs check: $__ty0 cannot be unified with (arrow $__ty0 $__ty3) because it would create a circular dependency during unification. +-/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λ(%0 %0)] return (format $ ans.fst) @@ -107,7 +114,7 @@ open LTy.Syntax LExpr.SyntaxMono LExpr LMonoTy #guard_msgs in -- Term: fun f -> (fun g -> (fun x -> (f (g x)))) -- Expected type: ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλλ(%2 (%1 %0))] return (format $ ans.fst.toLMonoTy) @@ -115,7 +122,7 @@ open LTy.Syntax LExpr.SyntaxMono LExpr LMonoTy #guard_msgs in -- Term: fun f -> (fun x -> (f (f x))) -- Expected type: ('a -> 'a) -> 'a -> 'a -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλ (%1 (%1 %0))] return (format $ ans.fst.toLMonoTy) @@ -125,17 +132,19 @@ info: ok: (arrow (arrow $__ty2 (arrow $__ty8 $__ty9)) (arrow (arrow $__ty2 $__ty #guard_msgs in -- Function: fun f -> (fun g -> (fun x -> ((f x) (g x)))) -- Expected type: ('a -> 'b -> 'c) -> ('a -> 'b) -> 'a -> 'c -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλλ ((%2 %0) (%1 %0))] return (format $ ans.fst.toLMonoTy) -/-- info: error: Ftvar $__ty1 is in the free variables of (arrow $__ty1 $__ty5)! -/ +/-- +info: error: Failed occurs check: $__ty1 cannot be unified with (arrow $__ty1 $__ty5) because it would create a circular dependency during unification. +-/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr LContext.default TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) LContext.default TEnv.default esM[λλ(%1 (%0 %0))] return (format $ ans.fst) -private def testIntFns : (@Factory Unit) := +private def testIntFns : (@Factory TestParams) := #[{ name := "unit", inputs := [], output := mty[unit]}, @@ -158,70 +167,73 @@ info: error: Type unit is not an instance of a previously registered type! Known Types: [∀[0, 1]. (arrow 0 1), string, int, bool] -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} TEnv.default esM[~unit] return (format $ ans.fst) /-- info: ok: (~unit : unit) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr { LContext.default with functions := testIntFns, knownTypes := makeKnownTypes [t[unit].toKnownType!] } TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) { LContext.default with functions := testIntFns, knownTypes := makeKnownTypes [t[unit].toKnownType!] } TEnv.default esM[~unit] return (format $ ans.fst) /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} - ((@TEnv.default Unit).updateContext { aliases := [{typeArgs := [], name := "myInt", type := mty[int]}]}) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} + ((@TEnv.default TestParams.IDMeta).updateContext { aliases := [{typeArgs := [], name := "myInt", type := mty[int]}]}) esM[((~SynonymTest #20) #30)] return (format $ ans.fst.toLMonoTy) -/-- info: error: Cannot unify differently named type constructors int and bool! -/ +/-- +info: error: Impossible to unify (arrow int int) with (arrow bool $__ty0). +First mismatch: int with bool. +-/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr { LContext.default with functions := testIntFns } TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) { LContext.default with functions := testIntFns } TEnv.default esM[(~Int.Neg #true)] return (format $ ans) /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr { LContext.default with functions := testIntFns } TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) { LContext.default with functions := testIntFns } TEnv.default esM[(~Int.Neg #100)] return (format $ ans.fst.toLMonoTy) /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr { LContext.default with functions := testIntFns } TEnv.default +#eval do let ans ← LExpr.resolve (T:=TestParams) { LContext.default with functions := testIntFns } TEnv.default esM[((λ %0) ((~Int.Add #20) #30))] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow int (arrow int int)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("x", t[int])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("x", t[int])]] }) esM[(λ (~Int.Add %0))] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow int (arrow int int)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("x", t[int])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("x", t[int])]] }) esM[λλ ((~Int.Add %0) %1)] return (format $ ans.fst.toLMonoTy) /-- info: ok: (arrow int (arrow int int)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("x", t[int])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("x", t[int])]] }) esM[(λλ ((~Int.Add %1) %0))] return (format $ ans.fst.toLMonoTy); /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("x", t[int])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("x", t[int])]] }) esM[((~Int.Add x) (~Int.Neg #30))] return (format $ ans.fst.toLMonoTy) /-- -info: ok: (((~Int.Add : (arrow int (arrow int int))) (x : int)) ((~Int.Neg : (arrow int int)) (#30 : int))) +info: ok: (((~Int.Add : (arrow int (arrow int int))) (x : int)) ((~Int.Neg : (arrow int int)) #30)) -/ #guard_msgs in -#eval do let ans ← LExpr.annotate {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("x", t[int])]] }) +#eval do let ans ← LExpr.annotate (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("x", t[int])]] }) esM[((~Int.Add x) (~Int.Neg #30))] return (format $ ans.fst) @@ -229,17 +241,16 @@ info: ok: (((~Int.Add : (arrow int (arrow int int))) (x : int)) ((~Int.Neg : (ar info: ok: ((λ ((%0 : (arrow bool $__ty4)) ((fn : (arrow bool bool)) (#true : bool)) : bool)) : $__ty4)) : (arrow (arrow bool $__ty4) $__ty4)) -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("fn", t[∀a. %a → %a])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("fn", t[∀a. %a → %a])]] }) esM[(λ (%0 (fn #true)))] return format ans.fst /-- info: ok: int -/ #guard_msgs in -#eval do let ans ← LExprT.fromLExpr {LContext.default with functions := testIntFns} ((@TEnv.default Unit).updateContext { types := [[("fn", t[∀a. %a → %a])]] }) +#eval do let ans ← LExpr.resolve (T:=TestParams) {LContext.default with functions := testIntFns} ((@TEnv.default TestParams.IDMeta).updateContext { types := [[("fn", t[∀a. %a → %a])]] }) esM[(fn #3)] return (format $ ans.fst.toLMonoTy) - end Tests --------------------------------------------------------------------- diff --git a/StrataTest/DL/Lambda/Lambda.lean b/StrataTest/DL/Lambda/Lambda.lean index 6afd5ef8f..5ae49e4ff 100644 --- a/StrataTest/DL/Lambda/Lambda.lean +++ b/StrataTest/DL/Lambda/Lambda.lean @@ -16,6 +16,7 @@ namespace Lambda open Std (ToFormat Format format) open LExpr LTy +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ section Test open LState LExpr LExpr.SyntaxMono @@ -26,42 +27,44 @@ Existing Function: func Int.Add : ((x : int) (y : int)) → int; New Function:func Int.Add : () → int; -/ #guard_msgs in -#eval do let F ← IntBoolFactory.addFactoryFunc { name := "Int.Add", - inputs := [], - output := .tcons "int" [] } - let ans ← typeCheckAndPartialEval F esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) #1))) #100)] - return format ans +#eval do + let F ← (IntBoolFactory : @Factory TestParams).addFactoryFunc ( + { name := "Int.Add", + inputs := [], + output := .tcons "int" [] } : LFunc TestParams) + let ans ← typeCheckAndPartialEval TypeFactory.default F esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) #1))) #100)] + return format ans /-- info: Annotated expression: -(((~Int.Le : (arrow int (arrow int bool))) (((~Int.Div : (arrow int (arrow int int))) (#300 : int)) (((~Int.Add : (arrow int (arrow int int))) (#2 : int)) (#1 : int)))) (#100 : int)) +(((~Int.Le : (arrow int (arrow int bool))) (((~Int.Div : (arrow int (arrow int int))) #300) (((~Int.Add : (arrow int (arrow int int))) #2) #1))) #100) --- -info: (#true : bool) +info: #true -/ #guard_msgs in -#eval format $ typeCheckAndPartialEval IntBoolFactory +#eval format $ typeCheckAndPartialEval TypeFactory.default (IntBoolFactory : @Factory TestParams) esM[((~Int.Le ((~Int.Div #300) ((~Int.Add #2) #1))) #100)] /-- info: Annotated expression: -((~Int.Div : (arrow int (arrow int int))) (((~Int.Add : (arrow int (arrow int int))) (#2 : int)) (#1 : int))) +((~Int.Div : (arrow int (arrow int int))) (((~Int.Add : (arrow int (arrow int int))) #2) #1)) --- -info: (λ (((~Int.Div : (arrow int (arrow int int))) (#3 : int)) %0)) +info: (λ (((~Int.Div : (arrow int (arrow int int))) #3) %0)) -/ #guard_msgs in -#eval format $ typeCheckAndPartialEval IntBoolFactory +#eval format $ typeCheckAndPartialEval TypeFactory.default (IntBoolFactory : @Factory TestParams) esM[((~Int.Div ((~Int.Add #2) #1)))] /-- info: Annotated expression: -((λ (%0 (#2 : int))) ((~Int.Div : (arrow int (arrow int int))) (#300 : int))) +((λ (%0 #2)) ((~Int.Div : (arrow int (arrow int int))) #300)) --- -info: (#150 : int) +info: #150 -/ #guard_msgs in -#eval format $ typeCheckAndPartialEval IntBoolFactory +#eval format $ typeCheckAndPartialEval TypeFactory.default (IntBoolFactory : @Factory TestParams) esM[((λ (%0 #2)) (~Int.Div #300))] end Test diff --git a/StrataTest/DL/Lambda/TypeFactoryTests.lean b/StrataTest/DL/Lambda/TypeFactoryTests.lean new file mode 100644 index 000000000..0a4eba152 --- /dev/null +++ b/StrataTest/DL/Lambda/TypeFactoryTests.lean @@ -0,0 +1,593 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + + + +import Strata.DL.Lambda.Lambda +import Strata.DL.Lambda.IntBoolFactory +import Strata.DL.Lambda.TypeFactory + +--------------------------------------------------------------------- + +namespace Lambda + +open Std (ToFormat Format format) +open LExpr LTy + +private abbrev TestParams : LExprParams := ⟨Unit, Unit⟩ + +private instance : Coe String TestParams.Identifier where + coe s := Identifier.mk s () + +private def absMulti' (n: Nat) (body: LExpr TestParams.mono) : LExpr TestParams.mono := + List.foldr (fun _ e => .abs () .none e) body (List.range n) + +/- +We write the tests as pattern matches, even though we use eliminators +-/ + +-- Test 1: Enum + +/- +type day = Su | M | T | W | Th | F | Sa + +match W with +| Su => 0 +| M => 1 +| T => 2 +| W => 3 +| Th => 4 +| F => 5 +| Sa => 6 +end ==> 3 + +-/ + +def weekTy : LDatatype Unit := {name := "Day", typeArgs := [], constrs := List.map (fun (x: String) => {name := x, args := [], testerName := "Day$is" ++ x}) ["Su", "M", "T", "W", "Th", "F", "Sa"], constrs_ne := rfl} + +/-- +info: Annotated expression: +(((((((((~Day$Elim : (arrow Day (arrow int (arrow int (arrow int (arrow int (arrow int (arrow int (arrow int int))))))))) (~W : Day)) #0) #1) #2) #3) #4) #5) #6) + +--- +info: #3 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[weekTy] (Factory.default : @Factory TestParams) ((LExpr.op () ("Day$Elim" : TestParams.Identifier) .none).mkApp () (.op () ("W" : TestParams.Identifier) (.some (.tcons "Day" [])) :: (List.range 7).map (intConst () ∘ Int.ofNat))) + + +/-- +info: Annotated expression: +((~Day$isW : (arrow Day bool)) (~W : Day)) + +--- +info: #true +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[weekTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("Day$isW" : TestParams.Identifier) .none).mkApp () [.op () ("W" : TestParams.Identifier) (.some (.tcons "Day" []))]) + +/-- +info: Annotated expression: +((~Day$isW : (arrow Day bool)) (~M : Day)) + +--- +info: #false +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[weekTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("Day$isW" : TestParams.Identifier) .none).mkApp () [.op () ("M" : TestParams.Identifier) (.some (.tcons "Day" []))]) + + +-- Test 2: Polymorphic tuples + +/- +type Tup a b = Prod a b + +fst e = match e with | (x, y) => x +snd e = match e with | (x, y) => y + +fst (3, "a") ==> 3 +snd (3, "a") ==> "a" +fst (snd ("a", (1, "b"))) ==> 1 + +-/ + +def tupTy : LDatatype Unit := {name := "Tup", typeArgs := ["a", "b"], constrs := [{name := "Prod", args := [("x", .ftvar "a"), ("y", .ftvar "b")], testerName := "Tup$isProd"}], constrs_ne := rfl} + +def fst (e: LExpr TestParams.mono) := (LExpr.op () ("Tup$Elim" : TestParams.Identifier) .none).mkApp () [e, .abs () .none (.abs () .none (.bvar () 1))] + +def snd (e: LExpr TestParams.mono) := (LExpr.op () ("Tup$Elim" : TestParams.Identifier) .none).mkApp () [e, .abs () .none (.abs () .none (.bvar () 0))] + +def prod (e1 e2: LExpr TestParams.mono) : LExpr TestParams.mono := (LExpr.op () ("Prod" : TestParams.Identifier) .none).mkApp () [e1, e2] + +/-- +info: Annotated expression: +(((~Tup$Elim : (arrow (Tup int string) (arrow (arrow int (arrow string int)) int))) (((~Prod : (arrow int (arrow string (Tup int string)))) #3) #a)) (λ (λ %1))) + +--- +info: #3 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[tupTy] Factory.default (fst (prod (intConst () 3) (strConst () "a"))) + +/-- +info: Annotated expression: +(((~Tup$Elim : (arrow (Tup int string) (arrow (arrow int (arrow string string)) string))) (((~Prod : (arrow int (arrow string (Tup int string)))) #3) #a)) (λ (λ %0))) + +--- +info: #a +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[tupTy] Factory.default (snd (prod (intConst () 3) (strConst () "a"))) + + +/-- +info: Annotated expression: +(((~Tup$Elim : (arrow (Tup int string) (arrow (arrow int (arrow string int)) int))) (((~Tup$Elim : (arrow (Tup string (Tup int string)) (arrow (arrow string (arrow (Tup int string) (Tup int string))) (Tup int string)))) (((~Prod : (arrow string (arrow (Tup int string) (Tup string (Tup int string))))) #a) (((~Prod : (arrow int (arrow string (Tup int string)))) #1) #b))) (λ (λ %0)))) (λ (λ %1))) + +--- +info: #1 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[tupTy] Factory.default (fst (snd (prod (strConst () "a") (prod (intConst () 1) (strConst () "b"))))) + + +-- Test 3: Polymorphic Lists + +/- +type List a = | Nil | Cons a (List a) + +match Nil with | Nil => 1 | Cons _ _ => 0 end ==> 1 +match [2] with | Nil => 0 | Cons x _ => x end ==> 2 + +-/ + +def nilConstr : LConstr Unit := {name := "Nil", args := [], testerName := "isNil"} +def consConstr : LConstr Unit := {name := "Cons", args := [("hd", .ftvar "a"), ("tl", .tcons "List" [.ftvar "a"])], testerName:= "isCons"} +def listTy : LDatatype Unit := {name := "List", typeArgs := ["a"], constrs := [nilConstr, consConstr], constrs_ne := rfl} + +-- Syntactic sugar +def cons (e1 e2: LExpr TestParams.mono) : LExpr TestParams.mono := .app () (.app () (.op () ("Cons" : TestParams.Identifier) .none) e1) e2 +def nil : LExpr TestParams.mono := .op () ("Nil" : TestParams.Identifier) .none + +def listExpr (l: List (LExpr TestParams.mono)) : LExpr TestParams.mono := + List.foldr cons nil l + +/-- info: Annotated expression: +((((~List$Elim : (arrow (List $__ty5) (arrow int (arrow (arrow $__ty5 (arrow (List $__ty5) (arrow int int))) int)))) (~Nil : (List $__ty5))) #1) (λ (λ (λ #1)))) + +--- +info: #1 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) ((LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () [nil, (intConst () 1), .abs () .none (.abs () .none (.abs () .none (intConst () 1)))]) + +-- Test: elim(cons 1 nil, 0, fun x y => x) -> (fun x y => x) 1 nil + + + +/-- info: Annotated expression: +((((~List$Elim : (arrow (List int) (arrow int (arrow (arrow int (arrow (List int) (arrow int int))) int)))) (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (~Nil : (List int)))) #0) (λ (λ (λ %2)))) + +--- +info: #2 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) ((LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () [listExpr [intConst () 2], intConst () 0, .abs () .none (.abs () .none (.abs () .none (bvar () 2)))]) + +-- Test testers (isNil and isCons) + +/-- info: Annotated expression: +((~isNil : (arrow (List $__ty11) bool)) (~Nil : (List $__ty11))) + +--- +info: #true +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("isNil" : TestParams.Identifier) .none).mkApp () [nil]) + +/-- info: Annotated expression: +((~isNil : (arrow (List int) bool)) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (~Nil : (List int)))) + +--- +info: #false +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("isNil" : TestParams.Identifier) .none).mkApp () [cons (intConst () 1) nil]) + +/-- info: Annotated expression: +((~isCons : (arrow (List $__ty11) bool)) (~Nil : (List $__ty11))) + +--- +info: #false +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("isCons" : TestParams.Identifier) .none).mkApp () [nil]) + +/-- info: Annotated expression: +((~isCons : (arrow (List int) bool)) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (~Nil : (List int)))) + +--- +info: #true +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("isCons" : TestParams.Identifier) .none).mkApp () [cons (intConst () 1) nil]) + +-- But a non-value should NOT reduce + +def ex_list : LFunc TestParams := + {name := "l", inputs := [], output := (.tcons "List" [.int])} + +/-- info: Annotated expression: +((~isCons : (arrow (List int) bool)) (~l : (List int))) + +--- +info: ((~isCons : (arrow (List int) bool)) (~l : (List int))) +-/ +#guard_msgs in +#eval format $ do + let f ← ((Factory.default : @Factory TestParams).addFactoryFunc ex_list) + (typeCheckAndPartialEval (T:=TestParams) #[listTy] f + ((LExpr.op () ("isCons" : TestParams.Identifier) (some (LMonoTy.arrow (.tcons "List" [.int]) .bool))).mkApp () [.op () "l" .none])) + +-- Test destructors + +/-- +info: Annotated expression: +((~hd : (arrow (List int) int)) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (~Nil : (List int)))) + +--- +info: #1 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("hd" : TestParams.Identifier) .none).mkApp () [cons (intConst () 1) nil]) + +/-- +info: Annotated expression: ((~tl : (arrow (List int) (List int))) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (~Nil : (List int))))) + +--- +info: (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (~Nil : (List int))) +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("tl" : TestParams.Identifier) .none).mkApp () [cons (intConst () 1) (cons (intConst () 2) nil)]) + +-- Destructor does not evaluate on a different constructor + +/-- +info: Annotated expression: ((~tl : (arrow (List $__ty1) (List $__ty1))) (~Nil : (List $__ty1))) + +--- +info: ((~tl : (arrow (List $__ty1) (List $__ty1))) (~Nil : (List $__ty1)))-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (Factory.default : @Factory TestParams) + ((LExpr.op () ("tl" : TestParams.Identifier) .none).mkApp () [nil]) + + +-- Test 4: Multiple types and Factories + +/- +match [(3, "a"), (4, "b")] with +| (x1, y1) :: (x2, y2) :: _ => x1 + x2 +| (x, y) :: nil => 1 +| nil => 0 +end ==> 7 +-/ + +def addOp (e1 e2: LExpr TestParams.mono) : LExpr TestParams.mono := .app () (.app () (.op () ("Int.Add" : TestParams.Identifier) .none) e1) e2 + +/-- info: Annotated expression: +((((~List$Elim : (arrow (List (Tup int string)) (arrow int (arrow (arrow (Tup int string) (arrow (List (Tup int string)) (arrow int int))) int)))) (((~Cons : (arrow (Tup int string) (arrow (List (Tup int string)) (List (Tup int string))))) (((~Prod : (arrow int (arrow string (Tup int string)))) #3) #a)) (((~Cons : (arrow (Tup int string) (arrow (List (Tup int string)) (List (Tup int string))))) (((~Prod : (arrow int (arrow string (Tup int string)))) #4) #b)) (~Nil : (List (Tup int string)))))) #0) (λ (λ (λ (((~Int.Add : (arrow int (arrow int int))) (((~Tup$Elim : (arrow (Tup int string) (arrow (arrow int (arrow string int)) int))) %2) (λ (λ %1)))) ((((~List$Elim : (arrow (List (Tup int string)) (arrow int (arrow (arrow (Tup int string) (arrow (List (Tup int string)) (arrow int int))) int)))) %1) #1) (λ (λ (λ (((~Tup$Elim : (arrow (Tup int string) (arrow (arrow int (arrow string int)) int))) %2) (λ (λ %1)))))))))))) + +--- +info: #7 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy, tupTy] (IntBoolFactory : @Factory TestParams) + ((LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () + [listExpr [(prod (intConst () 3) (strConst () "a")), (prod (intConst () 4) (strConst () "b"))], + intConst () 0, + .abs () .none (.abs () .none (.abs () .none + (addOp (fst (.bvar () 2)) + ((LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () + [.bvar () 1, intConst () 1, .abs () .none (.abs () .none (.abs () .none (fst (.bvar () 2))))]))))]) + +-- Recursive tests + +-- 1. List length and append + +def length (x: LExpr TestParams.mono) := + (LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () [x, intConst () 0, absMulti' 3 (addOp (intConst () 1) (.bvar () 0))] + +/-- info: Annotated expression: +((((~List$Elim : (arrow (List string) (arrow int (arrow (arrow string (arrow (List string) (arrow int int))) int)))) (((~Cons : (arrow string (arrow (List string) (List string)))) #a) (((~Cons : (arrow string (arrow (List string) (List string)))) #b) (((~Cons : (arrow string (arrow (List string) (List string)))) #c) (~Nil : (List string)))))) #0) (λ (λ (λ (((~Int.Add : (arrow int (arrow int int))) #1) %0))))) + +--- +info: #3 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (IntBoolFactory : @Factory TestParams) (length (listExpr [strConst () "a", strConst () "b", strConst () "c"])) + + +/-- info: Annotated expression: +((((~List$Elim : (arrow (List int) (arrow int (arrow (arrow int (arrow (List int) (arrow int int))) int)))) (((~Cons : (arrow int (arrow (List int) (List int)))) #0) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (((~Cons : (arrow int (arrow (List int) (List int)))) #3) (((~Cons : (arrow int (arrow (List int) (List int)))) #4) (((~Cons : (arrow int (arrow (List int) (List int)))) #5) (((~Cons : (arrow int (arrow (List int) (List int)))) #6) (((~Cons : (arrow int (arrow (List int) (List int)))) #7) (((~Cons : (arrow int (arrow (List int) (List int)))) #8) (((~Cons : (arrow int (arrow (List int) (List int)))) #9) (((~Cons : (arrow int (arrow (List int) (List int)))) #10) (((~Cons : (arrow int (arrow (List int) (List int)))) #11) (((~Cons : (arrow int (arrow (List int) (List int)))) #12) (((~Cons : (arrow int (arrow (List int) (List int)))) #13) (((~Cons : (arrow int (arrow (List int) (List int)))) #14) (~Nil : (List int)))))))))))))))))) #0) (λ (λ (λ (((~Int.Add : (arrow int (arrow int int))) #1) %0))))) + +--- +info: #15 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (IntBoolFactory : @Factory TestParams) (length (listExpr ((List.range 15).map (intConst () ∘ Int.ofNat)))) + +/- +Append is trickier since it takes in two arguments, so the eliminator returns +a function. We can write it as (using nicer syntax): +l₁ ++ l₂ := (@List$Elim (List α → List α) l₁ (fun x => x) (fun x xs rec => fun l₂ => x :: rec l₂)) l₂ +-/ + +def append (l1 l2: LExpr TestParams.mono) : LExpr TestParams.mono := + .app () ((LExpr.op () ("List$Elim" : TestParams.Identifier) .none).mkApp () [l1, .abs () .none (.bvar () 0), absMulti' 3 (.abs () .none (cons (.bvar () 3) (.app () (.bvar () 1) (.bvar () 0))))]) l2 + +def list1 :LExpr TestParams.mono := listExpr [intConst () 2, intConst () 4, intConst () 6] +def list2 :LExpr TestParams.mono := listExpr [intConst () 1, intConst () 3, intConst () 5] + +-- The output is difficult to read, but gives [2, 4, 6, 1, 3, 5], as expected + +/-- info: Annotated expression: +(((((~List$Elim : (arrow (List int) (arrow (arrow (List int) (List int)) (arrow (arrow int (arrow (List int) (arrow (arrow (List int) (List int)) (arrow (List int) (List int))))) (arrow (List int) (List int)))))) (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (((~Cons : (arrow int (arrow (List int) (List int)))) #4) (((~Cons : (arrow int (arrow (List int) (List int)))) #6) (~Nil : (List int)))))) (λ %0)) (λ (λ (λ (λ (((~Cons : (arrow int (arrow (List int) (List int)))) %3) (%1 %0))))))) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (((~Cons : (arrow int (arrow (List int) (List int)))) #3) (((~Cons : (arrow int (arrow (List int) (List int)))) #5) (~Nil : (List int)))))) + +--- +info: (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (((~Cons : (arrow int (arrow (List int) (List int)))) #4) (((~Cons : (arrow int (arrow (List int) (List int)))) #6) (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (((~Cons : (arrow int (arrow (List int) (List int)))) #3) (((~Cons : (arrow int (arrow (List int) (List int)))) #5) (~Nil : (List int)))))))) +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy] (IntBoolFactory : @Factory TestParams) (append list1 list2) + +-- 2. Preorder traversal of binary tree + +/- +type binTree a = | Leaf | Node a (binTree a) (binTree a) + +def toList (t: binTree a) = + match t with + | Leaf => [] + | Node a l r => a :: (toList l) ++ (toList r) + +-/ + +def leafConstr : LConstr Unit := {name := "Leaf", args := [], testerName := "isLeaf"} +def nodeConstr : LConstr Unit := {name := "Node", args := [("x", .ftvar "a"), ("l", .tcons "binTree" [.ftvar "a"]), ("r", .tcons "binTree" [.ftvar "a"])], testerName := "isNode"} +def binTreeTy : LDatatype Unit := {name := "binTree", typeArgs := ["a"], constrs := [leafConstr, nodeConstr], constrs_ne := rfl} + +-- syntactic sugar +def node (x l r: LExpr TestParams.mono) : LExpr TestParams.mono := (LExpr.op () ("Node" : TestParams.Identifier) .none).mkApp () [x, l, r] +def leaf : LExpr TestParams.mono := LExpr.op () ("Leaf" : TestParams.Identifier) .none + +def toList (t: LExpr TestParams.mono) : LExpr TestParams.mono := + (LExpr.op () ("binTree$Elim" : TestParams.Identifier) .none).mkApp () [t, nil, absMulti' 5 (cons (.bvar () 4) (append (.bvar () 1) (.bvar () 0)))] + +/- +tree: + 1 + 2 4 + 3 5 + 6 7 + +toList gives [1; 2; 3; 4; 5; 6; 7] +-/ +def tree1 : LExpr TestParams.mono := + node (intConst () 1) + (node (intConst () 2) + (node (intConst () 3) leaf leaf) + leaf) + (node (intConst () 4) + leaf + (node (intConst () 5) + (node (intConst () 6) leaf leaf) + (node (intConst () 7) leaf leaf))) + +/-- info: Annotated expression: +((((~binTree$Elim : (arrow (binTree int) (arrow (List int) (arrow (arrow int (arrow (binTree int) (arrow (binTree int) (arrow (List int) (arrow (List int) (List int)))))) (List int))))) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #1) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #2) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #3) (~Leaf : (binTree int))) (~Leaf : (binTree int)))) (~Leaf : (binTree int)))) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #4) (~Leaf : (binTree int))) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #5) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #6) (~Leaf : (binTree int))) (~Leaf : (binTree int)))) ((((~Node : (arrow int (arrow (binTree int) (arrow (binTree int) (binTree int))))) #7) (~Leaf : (binTree int))) (~Leaf : (binTree int))))))) (~Nil : (List int))) (λ (λ (λ (λ (λ (((~Cons : (arrow int (arrow (List int) (List int)))) %4) (((((~List$Elim : (arrow (List int) (arrow (arrow (List int) (List int)) (arrow (arrow int (arrow (List int) (arrow (arrow (List int) (List int)) (arrow (List int) (List int))))) (arrow (List int) (List int)))))) %1) (λ %0)) (λ (λ (λ (λ (((~Cons : (arrow int (arrow (List int) (List int)))) %3) (%1 %0))))))) %0)))))))) + +--- +info: (((~Cons : (arrow int (arrow (List int) (List int)))) #1) (((~Cons : (arrow int (arrow (List int) (List int)))) #2) (((~Cons : (arrow int (arrow (List int) (List int)))) #3) (((~Cons : (arrow int (arrow (List int) (List int)))) #4) (((~Cons : (arrow int (arrow (List int) (List int)))) #5) (((~Cons : (arrow int (arrow (List int) (List int)))) #6) (((~Cons : (arrow int (arrow (List int) (List int)))) #7) (~Nil : (List int))))))))) +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[listTy, binTreeTy] (IntBoolFactory : @Factory TestParams) (toList tree1) + +-- 3. Infinite-ary trees +namespace Tree + +/- +type tree a = | Leaf a | Node (Nat -> tree a) + +-- Find the length of the n-indexed chain in the tree +def height (n: Nat) (t: tree a) : int = +match t with +| Leaf => 0 +| Node f => 1 + height (f n) + +Example tree: Node (fun x => Node (fun y => if x + y == 0 then Node (fun _ => Leaf 3) else Leaf 4)) has zero-height 3 (and all other heights 2) + +-/ + +def leafConstr : LConstr Unit := {name := "Leaf", args := [("x", .ftvar "a")], testerName := "isLeaf"} +def nodeConstr : LConstr Unit := {name := "Node", args := [("f", .arrow .int (.tcons "tree" [.ftvar "a"]))], testerName := "isNode"} +def treeTy : LDatatype Unit := {name := "tree", typeArgs := ["a"], constrs := [leafConstr, nodeConstr], constrs_ne := rfl} + +-- syntactic sugar +def node (f: LExpr TestParams.mono) : LExpr TestParams.mono := (LExpr.op () ("Node" : TestParams.Identifier) .none).mkApp () [f] +def leaf (x: LExpr TestParams.mono) : LExpr TestParams.mono := (LExpr.op () ("Leaf" : TestParams.Identifier) .none).mkApp () [x] + +def tree1 : LExpr TestParams.mono := node (.abs () .none (node (.abs () .none + (.ite () (.eq () (addOp (.bvar () 1) (.bvar () 0)) (intConst () 0)) + (node (.abs () .none (leaf (intConst () 3)))) + (leaf (intConst () 4)) + )))) + +def height (n: Nat) (t: LExpr TestParams.mono) : LExpr TestParams.mono := + (LExpr.op () ("tree$Elim" : TestParams.Identifier) .none).mkApp () [t, .abs () .none (intConst () 0), absMulti' 2 (addOp (intConst () 1) (.app () (.bvar () 0) (intConst () n)))] + +/--info: Annotated expression: +((((~tree$Elim : (arrow (tree int) (arrow (arrow int int) (arrow (arrow (arrow int (tree int)) (arrow (arrow int int) int)) int)))) ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ (if ((((~Int.Add : (arrow int (arrow int int))) %1) %0) == #0) then ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ ((~Leaf : (arrow int (tree int))) #3))) else ((~Leaf : (arrow int (tree int))) #4))))))) (λ #0)) (λ (λ (((~Int.Add : (arrow int (arrow int int))) #1) (%0 #0))))) + +--- +info: #3 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[treeTy] (IntBoolFactory : @Factory TestParams) (height 0 tree1) + +/--info: Annotated expression: +((((~tree$Elim : (arrow (tree int) (arrow (arrow int int) (arrow (arrow (arrow int (tree int)) (arrow (arrow int int) int)) int)))) ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ (if ((((~Int.Add : (arrow int (arrow int int))) %1) %0) == #0) then ((~Node : (arrow (arrow int (tree int)) (tree int))) (λ ((~Leaf : (arrow int (tree int))) #3))) else ((~Leaf : (arrow int (tree int))) #4))))))) (λ #0)) (λ (λ (((~Int.Add : (arrow int (arrow int int))) #1) (%0 #1))))) + +--- +info: #2 +-/ +#guard_msgs in +#eval format $ + typeCheckAndPartialEval #[treeTy] (IntBoolFactory : @Factory TestParams) (height 1 tree1) + +end Tree + +-- Typechecking tests + +/- +1. Non-positive type +type Bad := | C (Bad -> Bad) +-/ + +def badConstr1: LConstr Unit := {name := "C", args := [⟨"x", .arrow (.tcons "Bad" []) (.tcons "Bad" [])⟩], testerName := "isC"} +def badTy1 : LDatatype Unit := {name := "Bad", typeArgs := [], constrs := [badConstr1], constrs_ne := rfl} + +/-- info: Error in constructor C: Non-strictly positive occurrence of Bad in type (arrow Bad Bad) +-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[badTy1] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +2.Non-strictly positive type +type Bad a := | C ((Bad a -> int) -> int) +-/ + +def badConstr2: LConstr Unit := {name := "C", args := [⟨"x", .arrow (.arrow (.tcons "Bad" [.ftvar "a"]) .int) .int⟩], testerName := "isC"} +def badTy2 : LDatatype Unit := {name := "Bad", typeArgs := ["a"], constrs := [badConstr2], constrs_ne := rfl} + +/-- info: Error in constructor C: Non-strictly positive occurrence of Bad in type (arrow (arrow (Bad a) int) int)-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[badTy2] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +3. Non-strictly positive type 2 +type Bad a := | C (int -> (Bad a -> int)) +-/ + +def badConstr3: LConstr Unit := {name := "C", args := [⟨"x", .arrow .int (.arrow (.tcons "Bad" [.ftvar "a"]) .int)⟩], testerName := "isC"} +def badTy3 : LDatatype Unit := {name := "Bad", typeArgs := ["a"], constrs := [badConstr3], constrs_ne := rfl} + +/--info: Error in constructor C: Non-strictly positive occurrence of Bad in type (arrow (Bad a) int)-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[badTy3] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +4. Strictly positive type +type Good := | C (int -> (int -> Good)) +-/ + +def goodConstr1: LConstr Unit := {name := "C", args := [⟨"x", .arrow .int (.arrow .int (.tcons "Good" [.ftvar "a"]))⟩], testerName := "isC"} +def goodTy1 : LDatatype Unit := {name := "Good", typeArgs := ["a"], constrs := [goodConstr1], constrs_ne := rfl} + +/-- info: Annotated expression: +#0 + +--- +info: #0 +-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[goodTy1] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +5. Non-uniform type +type Nonunif a := | C (int -> Nonunif (List a)) +-/ +def nonUnifConstr1: LConstr Unit := {name := "C", args := [⟨"x", .arrow .int (.arrow .int (.tcons "Nonunif" [.tcons "List" [.ftvar "a"]]))⟩], testerName := "isC"} +def nonUnifTy1 : LDatatype Unit := {name := "Nonunif", typeArgs := ["a"], constrs := [nonUnifConstr1], constrs_ne := rfl} + +/-- info: Error in constructor C: Non-uniform occurrence of Nonunif, which is applied to [(List a)] when it should be applied to [a]-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[listTy, nonUnifTy1] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +6. Nested types are allowed, though they won't produce a useful elimination principle +type Nest a := | C (List (Nest a)) +-/ +def nestConstr1: LConstr Unit := {name := "C", args := [⟨"x", .tcons "List" [.tcons "Nest" [.ftvar "a"]]⟩], testerName := "isC"} +def nestTy1 : LDatatype Unit := {name := "Nest", typeArgs := ["a"], constrs := [nestConstr1], constrs_ne := rfl} + +/-- info: Annotated expression: +#0 + +--- +info: #0 +-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[listTy, nestTy1] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +7. 2 constructors with the same name: +type Bad = | C (int) | C (Bad) +-/ + +def badConstr4: LConstr Unit := {name := "C", args := [⟨"x", .int⟩], testerName := "isC1"} +def badConstr5: LConstr Unit := {name := "C", args := [⟨"x", .tcons "Bad" [.ftvar "a"]⟩], testerName := "isC"} +def badTy4 : LDatatype Unit := {name := "Bad", typeArgs := ["a"], constrs := [badConstr4, badConstr5], constrs_ne := rfl} + +/-- +info: A function of name C already exists! Redefinitions are not allowed. +Existing Function: func C : ∀[a]. ((x : int)) → (Bad a); +New Function:func C : ∀[a]. ((x : (Bad a))) → (Bad a); +-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[badTy4] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +/- +8. Constructor with same name as function not allowed +type Bad = | Int.add (int) +-/ +def badConstr6: LConstr Unit := {name := "Int.Add", args := [⟨"x", .int⟩], testerName := "isAdd"} +def badTy5 : LDatatype Unit := {name := "Bad", typeArgs := [], constrs := [badConstr6], constrs_ne := rfl} + +/-- info: A function of name Int.Add already exists! Redefinitions are not allowed. +Existing Function: func Int.Add : ((x : int) (y : int)) → int; +New Function:func Int.Add : ((x : int)) → Bad;-/ +#guard_msgs in +#eval format $ typeCheckAndPartialEval #[badTy5] (IntBoolFactory : @Factory TestParams) (intConst () 0) + +end Lambda diff --git a/StrataTest/Internal/InternalBoogiePrelude.lean b/StrataTest/Internal/InternalBoogiePrelude.lean new file mode 100644 index 000000000..f44231686 --- /dev/null +++ b/StrataTest/Internal/InternalBoogiePrelude.lean @@ -0,0 +1,21 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Elab +import Strata.DDM.AST +import Strata.Languages.Boogie.DDMTransform.Parse +import Strata.Languages.Boogie.Verifier +import Strata.Languages.Python.BoogiePrelude + +namespace Strata +namespace Python +namespace Internal + +def Boogie.prelude : Boogie.Program := Strata.Boogie.prelude + +end Internal +end Python +end Strata diff --git a/StrataTest/Internal/InternalFunctionSignatures.lean b/StrataTest/Internal/InternalFunctionSignatures.lean new file mode 100644 index 000000000..a286a750c --- /dev/null +++ b/StrataTest/Internal/InternalFunctionSignatures.lean @@ -0,0 +1,18 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Python.FunctionSignatures + +namespace Strata +namespace Python +namespace Internal + +protected def signatures : Signatures := Strata.Python.coreSignatures + +end Internal +end Python +end Strata diff --git a/StrataTest/Languages/B3/DDMConversionErrorTests.lean b/StrataTest/Languages/B3/DDMConversionErrorTests.lean new file mode 100644 index 000000000..4536e3e95 --- /dev/null +++ b/StrataTest/Languages/B3/DDMConversionErrorTests.lean @@ -0,0 +1,117 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 Conversion Error Tests + +Tests for error handling in CST↔AST conversion. +-/ + +namespace StrataTest.B3.ConversionErrors + +open Strata +open Strata.B3CST +open Strata.B3AST + +/-- Convert CST expression to AST and return formatted error messages -/ +def checkCSTToASTErrors (expr : B3CST.Expression Nat) : IO Unit := do + let ctx := B3.FromCSTContext.empty + let (_ast, errors) := B3.expressionFromCST ctx expr + + if errors.isEmpty then + IO.println "No errors" + else + for err in errors do + match err with + | .unresolvedIdentifier name _metadata => + IO.println s!"Unresolved identifier '{name}'" + +/-- Create a ToCSTContext from a list of variable names (in declaration order) -/ +def mkContext (vars : List String) (inProcedure : Bool := false) : B3.ToCSTContext := + { vars := vars.reverse, inProcedure := inProcedure } + +/-- Convert AST expression to CST and return formatted error messages -/ +def checkASTToCSTErrors (ctx : B3.ToCSTContext) (expr : B3AST.Expression Nat) : IO Unit := do + let (_cst, errors) := B3.expressionToCST ctx expr + + if errors.isEmpty then + IO.println "No errors" + else + for err in errors do + match err with + | .variableOutOfBounds idx size _metadata => + IO.println s!"Variable @{idx} out of bounds (context size: {size})" + | .unsupportedVariableReference idx _metadata => + IO.println s!"Variable @{idx} not supported in concrete syntax" + +/-- +info: Unresolved identifier 'undefinedVar' +-/ +#guard_msgs in +#eval checkCSTToASTErrors (.id 42 "undefinedVar") + +/-- +info: Unresolved identifier 'foo' +Unresolved identifier 'bar' +-/ +#guard_msgs in +#eval checkCSTToASTErrors (.add 5 (.id 10 "foo") (.id 20 "bar")) + +/-- +info: Unresolved identifier 'x' +Unresolved identifier 'y' +Unresolved identifier 'z' +-/ +#guard_msgs in +#eval checkCSTToASTErrors (.add 0 (.mul 0 (.id 1 "x") (.id 2 "y")) (.id 3 "z")) + +/-- +info: No errors +-/ +#guard_msgs in +#eval checkCSTToASTErrors (.natLit 100 42) + +/-- +info: No errors +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "y", "z"]) (.id 100 2) + +/-- +info: Variable @1 not supported in concrete syntax +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "x"]) (.id 120 1) + +/-- +info: No errors +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "x"] (inProcedure := true)) (.id 125 1) + +/-- +info: Variable @1 not supported in concrete syntax +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "x", "x"]) (.id 130 1) + +/-- +info: Variable @5 out of bounds (context size: 3) +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "y", "z"]) (.id 200 5) + +/-- +info: Variable @1 not supported in concrete syntax +Variable @1 not supported in concrete syntax +-/ +#guard_msgs in +#eval checkASTToCSTErrors (mkContext ["x", "x", "x"]) + (.binaryOp 0 (.add 0) (.id 10 1) (.id 20 1)) + +end StrataTest.B3.ConversionErrors diff --git a/StrataTest/Languages/B3/DDMFormatDeclarationsTests.lean b/StrataTest/Languages/B3/DDMFormatDeclarationsTests.lean new file mode 100644 index 000000000..6e8971574 --- /dev/null +++ b/StrataTest/Languages/B3/DDMFormatDeclarationsTests.lean @@ -0,0 +1,837 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Languages.B3.DDMFormatTests +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 Declaration Formatting Tests + +Tests for round-trip conversion and formatting of B3 declarations (types, functions, axioms, procedures). +Verifies that DDM AST → B3 AST → B3 CST → formatted output preserves structure and catches conversion errors. +-/ + +namespace B3 + +open Std (Format) +open Strata +open Strata.B3CST + +partial def doRoundtripDecl (decl : OperationF SourceRange) (ctx : FormatContext) (state : FormatState) : Format := + match B3CST.Decl.ofAst decl with + | .ok cstDecl => + let (b3Decl, cstToAstErrors) := B3.declFromCST B3.FromCSTContext.empty cstDecl + let (cstDecl', astToCstErrors) := B3.declToCST B3.ToCSTContext.empty b3Decl + -- Convert to Unit metadata for repr + let b3DeclUnit := B3AST.Decl.mapMetadata (fun _ => ()) b3Decl + let reprStr := (repr b3DeclUnit).pretty + let reprStr := cleanupDeclRepr reprStr + let reprStr := cleanupUnitRepr reprStr + let errorStr := if cstToAstErrors.isEmpty && astToCstErrors.isEmpty then "" + else + let cstErrs := cstToAstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let astErrs := astToCstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let parts := [ + if cstToAstErrors.isEmpty then "" else s!"\nCST→AST Errors:\n {cstErrs}", + if astToCstErrors.isEmpty then "" else s!"\nAST→CST Errors:\n {astErrs}" + ] + String.join parts + dbg_trace f!"B3: {reprStr}{errorStr}" + let cstAst := cstDecl'.toAst + (mformat (ArgF.op cstAst) ctx state).format + | .error msg => s!"Parse error: {msg}" + +partial def doRoundtripProgram (prog : OperationF SourceRange) (ctx : FormatContext) (state : FormatState) (printIntermediate: Bool := true) : Format := + match B3CST.Program.ofAst prog with + | .ok cstProg => + let (b3Prog, cstToAstErrors) := B3.programFromCST B3.FromCSTContext.empty cstProg + let (cstProg', astToCstErrors) := B3.programToCST B3.ToCSTContext.empty b3Prog + let errorStr := if cstToAstErrors.isEmpty && astToCstErrors.isEmpty then "" + else + let cstErrs := cstToAstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let astErrs := astToCstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let parts := [ + if cstToAstErrors.isEmpty then "" else s!"\nCST→AST Errors:\n {cstErrs}", + if astToCstErrors.isEmpty then "" else s!"\nAST→CST Errors:\n {astErrs}" + ] + String.join parts + dbg_trace (if printIntermediate then + -- Convert to Unit metadata for repr + let b3ProgUnit := B3AST.Program.mapMetadata (fun _ => ()) b3Prog + let reprStr := (repr b3ProgUnit).pretty + let reprStr := cleanupDeclRepr reprStr + let reprStr := cleanupUnitRepr reprStr + f!"B3: {reprStr}{errorStr}" + else + f!"{errorStr}") + let cstAst := cstProg'.toAst + (mformat (ArgF.op cstAst) ctx state).format + | .error msg => s!"Parse error: {msg}" + +def roundtripDecl (p : Program) : Format := + let ctx := FormatContext.ofDialects p.dialects p.globalContext {} + let state : FormatState := { openDialects := p.dialects.toList.foldl (init := {}) fun a d => a.insert d.name } + match p.commands.toList with + | [op] => + if op.name.name == "command_program" then + match op.args.toList with + | [ArgF.op prog] => doRoundtripProgram prog ctx state + | _ => "Error: expected program op" + else s!"Error: expected command_program, got {op.name.name}" + | _ => "Error: expected single command" + + + +section DeclarationRoundtripTests + +-- Type declaration +/-- +info: B3: .program () u #[.typeDecl () u "MyType"] +--- +info: +type MyType +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; type MyType #end + +-- Tagger declaration +/-- +info: B3: .program + () + u #[.tagger () u "MyTagger" u "MyType"] +--- +info: +tagger MyTagger for MyType +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; tagger MyTagger for MyType #end + +-- Simple axiom +/-- +info: B3: .program + () + u #[.axiom + () + u #[] + (.literal () (.boolLit () true))] +--- +info: +axiom true +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; axiom true #end + +/-- +info: B3: .program + () + u #[.function + () + u "F" + u #[.fParameter + () + u false + u "x" + u "int"] + u "int" + u none + u some (.functionBody + () + u #[] + (.literal () (.intLit () 1)))] +--- +info: +function F(x : int) : int { + 1 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; function F(x: int) : int { 1 } #end + +-- Function with multiple parameters +/-- +info: B3: .program + () + u #[.function + () + u "add" + u #[.fParameter + () + u false + u "x" + u "int", + .fParameter + () + u false + u "y" + u "int"] + u "int" + u none + u some (.functionBody + () + u #[] + (.binaryOp + () + (.add ()) + (.id () 1) + (.id () 0)))] +--- +info: +function add(x : int, y : int) : int { + x + y +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; function add(x: int, y: int) : int { x + y } #end + +-- Function with injective parameter +/-- +info: B3: .program + () + u #[.function + () + u "id" + u #[.fParameter + () + u true + u "x" + u "int"] + u "int" + u none + u some (.functionBody + () + u #[] + (.id () 0))] +--- +info: +function id(injective x : int) : int { + x +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; function id(injective x: int) : int { x } #end + +-- Function with tag +/-- +info: B3: .program + () + u #[.function + () + u "tagged" + u #[.fParameter + () + u false + u "x" + u "int"] + u "int" + u some u "mytag" + u some (.functionBody + () + u #[] + (.id () 0))] +--- +info: +function tagged(x : int) : int tag mytag { + x +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; function tagged(x: int) : int tag mytag { x } #end + +-- Function with when clause +/-- +info: B3: .program + () + u #[.function + () + u "conditional" + u #[.fParameter + () + u false + u "x" + u "int"] + u "int" + u none + u some (.functionBody + () + u #[.when + () + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal () (.intLit () 0)))] + (.id () 0))] +--- +info: +function conditional(x : int) : int + when x > 0 { + x +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; function conditional(x: int) : int when x > 0 { x } #end + +-- Simple procedure with no parameters +/-- +info: B3: .program + () + u #[.procedure + () + u "noop" + u #[] + u #[] + u some (.blockStmt + () + u #[.returnStmt ()])] +--- +info: +procedure noop() +{ + return +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure noop() { return } #end + +-- Procedure with in parameter +/-- +info: B3: .program + () + u #[.procedure + () + u "process" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u none] + u #[] + u some (.blockStmt + () + u #[.check + () + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal + () + (.intLit () 0)))])] +--- +info: +procedure process(x : int) +{ + check x > 0 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure process(x: int) { check x > 0 } #end + +-- Procedure with out parameter +/-- +info: B3: .program + () + u #[.procedure + () + u "getResult" + u #[.pParameter + () + (.paramModeOut ()) + u "result" + u "int" + u none] + u #[] + u some (.blockStmt + () + u #[.assign + () + u 0 + (.literal () (.intLit () 42))])] +--- +info: +procedure getResult(out result : int) +{ + result := 42 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure getResult(out result: int) { result := 42 } #end + +-- Procedure with inout parameter +/-- +info: B3: .program + () + u #[.procedure + () + u "increment" + u #[.pParameter + () + (.paramModeInout ()) + u "x" + u "int" + u none] + u #[.specEnsures + () + (.binaryOp + () + (.eq ()) + (.id () 0) + (.binaryOp + () + (.add ()) + (.id () 1) + (.literal () (.intLit () 1))))] + u some (.blockStmt + () + u #[.assert + () + (.binaryOp + () + (.eq ()) + (.id () 0) + (.id () 1)), + .assign + () + u 0 + (.binaryOp + () + (.add ()) + (.id () 0) + (.literal () (.intLit () 1))), + .assert + () + (.binaryOp + () + (.eq ()) + (.id () 0) + (.binaryOp + () + (.add ()) + (.id () 1) + (.literal + () + (.intLit () 1))))])] +--- +info: +procedure increment(inout x : int) + ensures x == old x + 1 +{ + assert x == old x + x := x + 1 + assert x == old x + 1 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure increment(inout x: int) ensures x == old x + 1 { assert x == old x + x := x + 1 + assert x == old x + 1 +} #end + +-- Procedure with mixed parameters +/-- +info: B3: .program + () + u #[.procedure + () + u "compute" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u none, + .pParameter + () + (.paramModeOut ()) + u "y" + u "int" + u none, + .pParameter + () + (.paramModeInout ()) + u "z" + u "int" + u none] + u #[] + u some (.blockStmt + () + u #[.assign + () + u 2 + (.binaryOp + () + (.add ()) + (.id () 3) + (.id () 0)), + .assign + () + u 0 + (.binaryOp + () + (.add ()) + (.id () 0) + (.literal + () + (.intLit () 1)))])] +--- +info: +procedure compute(x : int, out y : int, inout z : int) +{ + y := x + z + z := z + 1 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure compute(x: int, out y: int, inout z: int) { y := x + z z := z + 1 } #end + +-- Procedure with requires spec +/-- +info: B3: .program + () + u #[.procedure + () + u "safe" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u none] + u #[.specRequires + () + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal () (.intLit () 0)))] + u some (.blockStmt + () + u #[.check + () + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal + () + (.intLit () 0)))])] +--- +info: +procedure safe(x : int) + requires x > 0 +{ + check x > 0 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure safe(x: int) requires x > 0 { check x > 0 } #end + +-- Procedure with ensures spec +/-- +info: B3: .program + () + u #[.procedure + () + u "positive" + u #[.pParameter + () + (.paramModeOut ()) + u "x" + u "int" + u none] + u #[.specEnsures + () + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal () (.intLit () 0)))] + u some (.blockStmt + () + u #[.assign + () + u 0 + (.literal () (.intLit () 1))])] +--- +info: +procedure positive(out x : int) + ensures x > 0 +{ + x := 1 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure positive(out x: int) ensures x > 0 { x := 1 } #end + +-- Procedure with both requires and ensures +/-- +info: B3: .program + () + u #[.procedure + () + u "bounded" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u none, + .pParameter + () + (.paramModeOut ()) + u "y" + u "int" + u none] + u #[.specRequires + () + (.binaryOp + () + (.ge ()) + (.id () 1) + (.literal () (.intLit () 0))), + .specEnsures + () + (.binaryOp + () + (.ge ()) + (.id () 0) + (.literal () (.intLit () 0)))] + u some (.blockStmt + () + u #[.assign + () + u 0 + (.id () 1)])] +--- +info: +procedure bounded(x : int, out y : int) + requires x >= 0 + ensures y >= 0 +{ + y := x +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure bounded(x: int, out y: int) requires x >= 0 ensures y >= 0 { y := x } #end + +-- Procedure with parameter autoinv +/-- +info: B3: .program + () + u #[.procedure + () + u "withAutoinv" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u some (.binaryOp + () + (.ge ()) + (.binaryOp + () + (.add ()) + (.id () 1) + (.id () 0)) + (.literal () (.intLit () 0))), + .pParameter + () + (.paramModeIn ()) + u "y" + u "int" + u some (.binaryOp + () + (.ge ()) + (.id () 0) + (.unaryOp + () + (.neg ()) + (.id () 1)))] + u #[] + u some (.blockStmt + () + u #[.check + () + (.binaryOp + () + (.ge ()) + (.id () 1) + (.literal + () + (.intLit () 0)))])] +--- +info: +procedure withAutoinv(x : int autoinv x + y >= 0, y : int autoinv y >= -(x)) +{ + check x >= 0 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure withAutoinv(x: int autoinv x + y >= 0, y: int autoinv y >= -x) { check x >= 0 } #end + +-- Procedure with body containing multiple statements +/-- +info: B3: .program + () + u #[.procedure + () + u "multi" + u #[.pParameter + () + (.paramModeIn ()) + u "x" + u "int" + u none, + .pParameter + () + (.paramModeOut ()) + u "y" + u "int" + u none] + u #[] + u some (.blockStmt + () + u #[.varDecl + () + u "temp" + u some u "int" + u none + u none, + .assign + () + u 0 + (.binaryOp + () + (.add ()) + (.id () 2) + (.literal () (.intLit () 1))), + .assign + () + u 1 + (.binaryOp + () + (.mul ()) + (.id () 0) + (.literal + () + (.intLit () 2)))])] +--- +info: +procedure multi(x : int, out y : int) +{ + var temp : int + temp := x + 1 + y := temp * 2 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; procedure multi(x: int, out y: int) { var temp : int temp := x + 1 y := temp * 2 } #end + +-- Multiple declarations in a program +/-- +info: B3: .program + () + u #[.typeDecl () u "T", + .axiom + () + u #[] + (.literal () (.boolLit () true)), + .function + () + u "f" + u #[.fParameter + () + u false + u "x" + u "int"] + u "int" + u none + u some (.functionBody + () + u #[] + (.id () 0))] +--- +info: +type T +axiom true +function f(x : int) : int { + x +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; type T axiom true function f(x: int) : int { x } #end + +-- Procedure with inout parameter using old values in spec and body +/-- +info: B3: .program + () + u #[.procedure + () + u "incrementWithOld" + u #[.pParameter + () + (.paramModeInout ()) + u "x" + u "int" + u none] + u #[.specEnsures + () + (.binaryOp + () + (.eq ()) + (.id () 0) + (.binaryOp + () + (.add ()) + (.id () 1) + (.literal () (.intLit () 1))))] + u some (.blockStmt + () + u #[.assign + () + u 0 + (.binaryOp + () + (.add ()) + (.id () 0) + (.literal () (.intLit () 1))), + .assert + () + (.binaryOp + () + (.eq ()) + (.id () 0) + (.binaryOp + () + (.add ()) + (.id () 1) + (.literal + () + (.intLit () 1))))])] +--- +info: +procedure incrementWithOld(inout x : int) + ensures x == old x + 1 +{ + x := x + 1 + assert x == old x + 1 +} +-/ +#guard_msgs in +#eval roundtripDecl $ #strata program B3CST; +procedure incrementWithOld(inout x: int) + ensures x == old x + 1 +{ + x := x + 1 + assert x == old x + 1 +} +#end + +end DeclarationRoundtripTests + +end B3 diff --git a/StrataTest/Languages/B3/DDMFormatExpressionsTests.lean b/StrataTest/Languages/B3/DDMFormatExpressionsTests.lean new file mode 100644 index 000000000..6c5d12c31 --- /dev/null +++ b/StrataTest/Languages/B3/DDMFormatExpressionsTests.lean @@ -0,0 +1,584 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Languages.B3.DDMFormatTests +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 Expression Formatting Tests + +Tests for round-trip conversion and formatting of B3 expressions. +Verifies that DDM AST → B3 AST → B3 CST → formatted output preserves structure and catches conversion errors. + +## Note on Test Syntax + +Expressions are wrapped in `check` statements (e.g., `check 5 + 3`) because: +- our encoding of the B3 grammar doesn't allow bare expressions at the top level. +- Commands can only contain statements and declarations, not expressions +- The test extracts only the expression from the `check` statement for round-trip testing +- The `check` wrapper itself is not part of the tested AST - only the expression `5 + 3` is tested +-/ + +namespace B3 + +open Std (Format) +open Strata +open Strata.B3CST + +-- Helper to perform the round-trip transformation and format +-- DDM OperationF → B3 AST → DDM → formatted output +partial def doRoundtrip (e : OperationF SourceRange) (ctx : FormatContext) (state : FormatState) : Format := + match B3CST.Expression.ofAst e with + | .ok cstExpr => + let (b3Expr, cstToAstErrors) := B3.expressionFromCST B3.FromCSTContext.empty cstExpr + let (cstExpr', astToCstErrors) := B3.expressionToCST B3.ToCSTContext.empty b3Expr + -- Convert to Unit metadata for repr + let b3ExprUnit := B3AST.Expression.mapMetadata (fun _ => ()) b3Expr + let reprStr := (repr b3ExprUnit).pretty + let reprStr := cleanupExprRepr reprStr + let reprStr := cleanupUnitRepr reprStr + let errorStr := if cstToAstErrors.isEmpty && astToCstErrors.isEmpty then "" + else + let cstErrs := cstToAstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let astErrs := astToCstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let parts := [ + if cstToAstErrors.isEmpty then "" else s!"\nCST→AST Errors:\n {cstErrs}", + if astToCstErrors.isEmpty then "" else s!"\nAST→CST Errors:\n {astErrs}" + ] + String.join parts + dbg_trace f!"B3: {reprStr}{errorStr}" + let cstAst := cstExpr'.toAst + (mformat (ArgF.op cstAst) ctx state).format + | .error msg => s!"Parse error: {msg}" + +-- Helper to extract expression from a program and apply round-trip transformation +def roundtripExpr (p : Program) : Format := + let ctx := FormatContext.ofDialects p.dialects p.globalContext {} + let state : FormatState := { openDialects := p.dialects.toList.foldl (init := {}) fun a d => a.insert d.name } + match p.commands.toList with + | [op] => + if op.name.name == "command_stmt" then + match op.args.toList with + | [ArgF.op stmt] => + if stmt.name.name == "check" then + match stmt.args.toList with + | [ArgF.op e] => doRoundtrip e ctx state + | _ => s!"Error: expected op in check, got {repr stmt.args.toList}" + else s!"Error: expected check statement, got {stmt.name.name}" + | _ => "Error: expected statement op" + else if op.name.name == "command_decl" then + match op.args.toList with + | [ArgF.op decl] => + if decl.name.name == "axiom_decl" then + match decl.args.toList with + | [ArgF.op body] => + if body.name.name == "axiom" then + match body.args.toList with + | [ArgF.op e] => doRoundtrip e ctx state + | _ => s!"Error: expected op in axiom body, got {repr body.args.toList}" + else if body.name.name == "explain_axiom" then + match body.args.toList with + | [_, ArgF.op e] => doRoundtrip e ctx state + | _ => s!"Error: expected names and op in explain_axiom, got {repr body.args.toList}" + else s!"Error: expected axiom or explain_axiom body, got {body.name.name}" + | _ => s!"Error: expected AxiomBody in axiom_decl, got {repr decl.args.toList}" + else s!"Error: expected axiom declaration, got {decl.name.name}" + | _ => "Error: expected axiom op" + else + s!"Error: expected command_stmt or command_decl, got {op.name.name}" + | _ => "Error: expected single command" + +section ExpressionRoundtripTests + +-- We are losing the context so this is why it's printing that way. +/-- +info: B3: .id () 0 +CST→AST Errors: + Unresolved identifier 'x' +AST→CST Errors: + Variable index @0 is out of bounds (context has 0 variables) +--- +info: |@0| +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check x #end + +/-- +info: B3: .binaryOp + () + (.add ()) + (.literal () (.intLit () 5)) + (.literal () (.intLit () 3)) +--- +info: 5 + 3 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 5 + 3 #end + +/-- +info: B3: .literal () (.boolLit () true) +--- +info: true +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check true #end + +/-- +info: B3: .literal () (.boolLit () false) +--- +info: false +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check false #end + +/-- +info: B3: .unaryOp + () + (.not ()) + (.literal () (.boolLit () true)) +--- +info: !true +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check !true #end + +/-- +info: B3: .binaryOp + () + (.sub ()) + (.literal () (.intLit () 10)) + (.literal () (.intLit () 3)) +--- +info: 10 - 3 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 10 - 3 #end + +/-- +info: B3: .binaryOp + () + (.mul ()) + (.literal () (.intLit () 4)) + (.literal () (.intLit () 5)) +--- +info: 4 * 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 4 * 5 #end + +/-- +info: B3: .binaryOp + () + (.div ()) + (.literal () (.intLit () 20)) + (.literal () (.intLit () 4)) +--- +info: 20 div 4 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 20 div 4 #end + +/-- +info: B3: .binaryOp + () + (.mod ()) + (.literal () (.intLit () 17)) + (.literal () (.intLit () 5)) +--- +info: 17 mod 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 17 mod 5 #end + +/-- +info: B3: .binaryOp + () + (.eq ()) + (.literal () (.intLit () 5)) + (.literal () (.intLit () 5)) +--- +info: 5 == 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 5 == 5 #end + +/-- +info: B3: .binaryOp + () + (.neq ()) + (.literal () (.intLit () 3)) + (.literal () (.intLit () 7)) +--- +info: 3 != 7 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 3 != 7 #end + +/-- +info: B3: .binaryOp + () + (.le ()) + (.literal () (.intLit () 3)) + (.literal () (.intLit () 5)) +--- +info: 3 <= 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 3 <= 5 #end + +/-- +info: B3: .binaryOp + () + (.lt ()) + (.literal () (.intLit () 2)) + (.literal () (.intLit () 8)) +--- +info: 2 < 8 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 2 < 8 #end + +/-- +info: B3: .binaryOp + () + (.ge ()) + (.literal () (.intLit () 10)) + (.literal () (.intLit () 5)) +--- +info: 10 >= 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 10 >= 5 #end + +/-- +info: B3: .binaryOp + () + (.gt ()) + (.literal () (.intLit () 15)) + (.literal () (.intLit () 3)) +--- +info: 15 > 3 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 15 > 3 #end + +/-- +info: B3: .binaryOp + () + (.add ()) + (.literal () (.intLit () 2)) + (.binaryOp + () + (.mul ()) + (.literal () (.intLit () 3)) + (.literal () (.intLit () 4))) +--- +info: 2 + 3 * 4 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 2 + 3 * 4 #end + +/-- +info: B3: .binaryOp + () + (.mul ()) + (.binaryOp + () + (.add ()) + (.literal () (.intLit () 2)) + (.literal () (.intLit () 3))) + (.literal () (.intLit () 4)) +--- +info: (2 + 3) * 4 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check (2 + 3) * 4 #end + +/-- +info: B3: .binaryOp + () + (.add ()) + (.binaryOp + () + (.add ()) + (.literal () (.intLit () 1)) + (.literal () (.intLit () 2))) + (.literal () (.intLit () 3)) +--- +info: 1 + 2 + 3 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 1 + 2 + 3 #end + +/-- +info: B3: .binaryOp + () + (.lt ()) + (.binaryOp + () + (.add ()) + (.literal () (.intLit () 1)) + (.literal () (.intLit () 2))) + (.literal () (.intLit () 5)) +--- +info: 1 + 2 < 5 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 1 + 2 < 5 #end + +/-- +info: B3: .binaryOp + () + (.add ()) + (.binaryOp + () + (.sub ()) + (.literal () (.intLit () 10)) + (.literal () (.intLit () 3))) + (.literal () (.intLit () 2)) +--- +info: 10 - 3 + 2 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 10 - 3 + 2 #end + +/-- +info: B3: .binaryOp + () + (.mul ()) + (.binaryOp + () + (.div ()) + (.literal () (.intLit () 20)) + (.literal () (.intLit () 4))) + (.literal () (.intLit () 3)) +--- +info: 20 div 4 * 3 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 20 div 4 * 3 #end + +/-- +info: B3: .binaryOp + () + (.lt ()) + (.literal () (.intLit () 1)) + (.binaryOp + () + (.add ()) + (.binaryOp + () + (.mul ()) + (.literal () (.intLit () 2)) + (.literal () (.intLit () 3))) + (.literal () (.intLit () 4))) +--- +info: 1 < 2 * 3 + 4 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check 1 < 2 * 3 + 4 #end + +/-- +info: B3: .ite + () + (.literal () (.boolLit () true)) + (.literal () (.intLit () 1)) + (.literal () (.intLit () 0)) +--- +info: if true then 1 else 0 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check if true then 1 else 0 #end + +/-- +info: B3: .quantifierExpr + () + (.forall ()) + u #[.quantVarDecl () u "i" u "int"] + u #[] + (.binaryOp + () + (.ge ()) + (.id () 0) + (.literal () (.intLit () 0))) +--- +info: forall i : int i >= 0 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check forall i : int i >= 0 #end + +/-- +info: B3: .quantifierExpr + () + (.exists ()) + u #[.quantVarDecl () u "y" u "bool"] + u #[] + (.binaryOp + () + (.or ()) + (.id () 0) + (.unaryOp () (.not ()) (.id () 0))) +--- +info: exists y : bool y || !y +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check exists y : bool y || !y #end + +/-- +info: B3: .quantifierExpr + () + (.forall ()) + u #[.quantVarDecl () u "x" u "int"] + u #[.pattern + () + u #[.functionCall + () + u "f" + u #[.id () 0], + .functionCall + () + u "f" + u #[.id () 0]]] + (.binaryOp + () + (.gt ()) + (.functionCall + () + u "f" + u #[.id () 0]) + (.literal () (.intLit () 0))) +--- +info: forall x : int pattern f(x), f(x) f(x) > 0 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check forall x : int pattern f(x), f(x) f(x) > 0 #end + +/-- +info: B3: .quantifierExpr + () + (.exists ()) + u #[.quantVarDecl () u "y" u "bool"] + u #[.pattern () u #[.id () 0], + .pattern + () + u #[.unaryOp + () + (.not ()) + (.id () 0)]] + (.binaryOp + () + (.or ()) + (.id () 0) + (.unaryOp () (.not ()) (.id () 0))) +--- +info: exists y : bool pattern y pattern !y y || !y +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check exists y : bool pattern y pattern !y y || !y #end + +/-- +info: B3: .quantifierExpr + () + (.forall ()) + u #[.quantVarDecl () u "z" u "int"] + u #[.pattern () u #[.id () 0], + .pattern + () + u #[.binaryOp + () + (.add ()) + (.id () 0) + (.literal () (.intLit () 1))], + .pattern + () + u #[.binaryOp + () + (.mul ()) + (.id () 0) + (.literal () (.intLit () 2))]] + (.binaryOp + () + (.gt ()) + (.id () 0) + (.literal () (.intLit () 0))) +--- +info: forall z : int pattern z pattern z + 1 pattern z * 2 z > 0 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check forall z : int pattern z pattern z + 1 pattern z * 2 z > 0 #end + +/-- +info: B3: .quantifierExpr + () + (.forall ()) + u #[.quantVarDecl () u "x" u "int", + .quantVarDecl () u "y" u "int"] + u #[] + (.binaryOp + () + (.le ()) + (.id () 1) + (.id () 0)) +--- +info: forall x : int, y : int x <= y +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check forall x : int, y : int x <= y #end + +/-- +info: B3: .quantifierExpr + () + (.exists ()) + u #[.quantVarDecl () u "a" u "bool", + .quantVarDecl () u "b" u "bool", + .quantVarDecl () u "c" u "bool"] + u #[] + (.binaryOp + () + (.and ()) + (.binaryOp + () + (.and ()) + (.id () 2) + (.id () 1)) + (.id () 0)) +--- +info: exists a : bool, b : bool, c : bool a && b && c +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check exists a : bool, b : bool, c : bool a && b && c #end + +/-- +info: B3: .quantifierExpr + () + (.forall ()) + u #[.quantVarDecl () u "i" u "int", + .quantVarDecl () u "j" u "int"] + u #[.pattern + () + u #[.binaryOp + () + (.add ()) + (.id () 1) + (.id () 0)]] + (.binaryOp + () + (.ge ()) + (.binaryOp + () + (.add ()) + (.id () 1) + (.id () 0)) + (.literal () (.intLit () 0))) +--- +info: forall i : int, j : int pattern i + j i + j >= 0 +-/ +#guard_msgs in +#eval roundtripExpr $ #strata program B3CST; check forall i : int, j : int pattern i + j i + j >= 0 #end + +end ExpressionRoundtripTests + +end B3 diff --git a/StrataTest/Languages/B3/DDMFormatProgramsTests.lean b/StrataTest/Languages/B3/DDMFormatProgramsTests.lean new file mode 100644 index 000000000..7bf2f21fc --- /dev/null +++ b/StrataTest/Languages/B3/DDMFormatProgramsTests.lean @@ -0,0 +1,385 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Languages.B3.DDMFormatDeclarationsTests +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 Program Formatting Tests + +Tests for round-trip conversion and formatting of complete B3 programs. +Verifies that DDM AST → B3 AST → B3 CST → formatted output preserves structure and catches conversion errors. +-/ + +namespace B3 + +open Std (Format) +open Strata +open Strata.B3CST + +def roundtripProgram (p : Program) : Format := + let ctx := FormatContext.ofDialects p.dialects p.globalContext {} + let state : FormatState := { openDialects := p.dialects.toList.foldl (init := {}) fun a d => a.insert d.name } + match p.commands.toList with + | [op] => + if op.name.name == "command_program" then + match op.args.toList with + | [ArgF.op prog] => doRoundtripProgram prog ctx state false + | _ => "Error: expected program op" + else s!"Error: expected command_program, got {op.name.name}" + | _ => "Error: expected single command" + +section ProgramRoundtripTests + +-- Type declaration +/-- +info: +CST→AST Errors: + Unresolved identifier '«myFileSystemName: string»' + Unresolved identifier '«BlockPublicAcls: string»' + Unresolved identifier '«bucket: string»' + Unresolved identifier '«is-blocked: string»' + Unresolved identifier '«bucket: string»' + Unresolved identifier '«is-not-blocked: string»' +AST→CST Errors: + Variable index @2 is out of bounds (context has 2 variables) + Variable index @12 is out of bounds (context has 12 variables) + Variable index @12 is out of bounds (context has 12 variables) + Variable index @12 is out of bounds (context has 12 variables) + Variable index @12 is out of bounds (context has 12 variables) + Variable index @12 is out of bounds (context has 12 variables) +--- +info: +procedure Good(out result : XResult) +{ + var cresult : CResult + CreateClient(|@2|, out cresult) + if !CIsSuccess(cresult) ⏎ + { + result := XFailure(CFailure..msg(cresult)) + return + } + var fileSystem := CSuccess..value(cresult) + var aresult : AResult + ListBuckets(fileSystem, out aresult) + if !AIsSuccess(aresult) ⏎ + { + result := XFailure(AFailure..msg(aresult)) + return + } + var aresponse := ASuccess..value(aresult) + var buckets := AResponse..buckets(aresponse) + var i := 0 + loop + invariant 0 <= i && i <= length(buckets) ⏎ + { + if i == length(buckets) ⏎ + { + exit ⏎ + } + check 0 <= i && i < length(buckets) + var bucket := select(buckets, i) + var bucketName := Bucket..name(bucket) + var bresult : BResult + GetPublicAccessBlock(fileSystem, bucketName, out bresult) + if !BIsSuccess(bresult) ⏎ + { + result := XFailure(BFailure..msg(bresult)) + return + } + var bresponse := BSuccess..value(bresult) + var isBlocked := GetAttributeValue(BResponse..getConfig(bresponse), |@12|) + if isBlocked ⏎ + { + Print(|@12|, bucketName, |@12|) + } + else ⏎ + { + Print(|@12|, bucketName, |@12|) + } + i := i + 1 + } + var x : X + result := XSuccess(x) +} +procedure CreateClient(name : string, out result : CResult) +function UserOwnsBucket(name : string) : bool +type Client +procedure ListBuckets(c : Client, out aresult : AResult) + ensures AIsSuccess(aresult) ==> (forall bucket : Bucket pattern Bucket..name(bucket) pattern in(bucket, AResponse..buckets(ASuccess..value(aresult))) in(bucket, AResponse..buckets(ASuccess..value(aresult))) ==> UserOwnsBucket(Bucket..name(bucket))) +procedure GetPublicAccessBlock(c : Client, Bucket : string, out result : BResult) + requires UserOwnsBucket(Bucket) +type AResponse +function AResponse(injective buckets : BucketSeq) : AResponse +type BResponse +function BResponse(injective getConfig : BlockConfig) : BResponse +type Bucket +function Bucket(injective name : string) : Bucket +type BlockConfig +function GetAttributeValue(config : BlockConfig, attribute : string) : bool +type X +type XResult +tagger XResultTag for XResult +function XSuccess(injective value : X) : XResult tag XResultTag +function XFailure(injective msg : string) : XResult tag XResultTag +function XIsSuccess(r : XResult) : bool { + XResultTag(r) == XSuccess..tag() +} +type CResult +tagger CResultTag for CResult +function CSuccess(injective value : Client) : CResult tag CResultTag +function CFailure(injective msg : string) : CResult tag CResultTag +function CIsSuccess(r : CResult) : bool { + CResultTag(r) == CSuccess..tag() +} +type AResult +tagger AResultTag for AResult +function ASuccess(injective value : AResponse) : AResult tag AResultTag +function AFailure(injective msg : string) : AResult tag AResultTag +function AIsSuccess(r : AResult) : bool { + AResultTag(r) == ASuccess..tag() +} +type BResult +tagger BResultTag for BResult +function BSuccess(injective value : BResponse) : BResult tag BResultTag +function BFailure(injective msg : string) : BResult tag BResultTag +function BIsSuccess(r : BResult) : bool { + BResultTag(r) == BSuccess..tag() +} +type BucketSeq +function select(s : BucketSeq, i : int) : Bucket +function length(s : BucketSeq) : int +axiom explains length + forall s : BucketSeq pattern length(s) 0 <= length(s) +function in(b : Bucket, s : BucketSeq) : bool { + exists i : int pattern select(s, i) 0 <= i && i < length(s) && select(s, i) == b +} +type string +procedure Print(a : string, b : string, c : string) +-/ +#guard_msgs in +#eval roundtripProgram $ #strata program B3CST; +// This example program shows many B3 features in use. The main point is to prove +// that GetPublicAccessBlock is called with a bucket name that satisfies UserOwnsBucket. +// This properties is guaranteed by the postcondition of ListBuckets, which, upon +// success, returns a sequence where every bucket name satisfies GetPublicAccessBlock. +// +// Here is the program shown in the syntax of a Dafny-like programming-language: +// +// var fileSystem :- CreateClient("myFileSystemName") +// var aresponse :- fileSystem.ListBuckets() +// var buckets := aresponse.buckets +// for i := 0 to |buckets| { +// var bucket := buckets[i] +// var bucketName := bucket.name +// var bresponse :- fileSystem.GetPublicAccessBlock(bucketName) +// var isBlocked := bresponse.getConfig.GetAttributeValue("BlockPublicAcls") +// if isBlocked { +// print "bucket", bucketName, "is blocked" +// } else { +// print "bucket", bucketName, "is not blocked" +// } +// } +// +// Note that B3 identifiers may contain "." characters. B3 uses ".." as part of the +// name when it generates functions (for example, the function names generated as a result +// of declaring a parameter to be "injective"). + +procedure Good(out result: XResult) { + var cresult: CResult + CreateClient(|myFileSystemName: string|, out cresult) + if !CIsSuccess(cresult) { + result := XFailure(CFailure..msg(cresult)) + return + } + var fileSystem := CSuccess..value(cresult) + + var aresult: AResult + ListBuckets(fileSystem, out aresult) + if !AIsSuccess(aresult) { + result := XFailure(AFailure..msg(aresult)) + return + } + var aresponse := ASuccess..value(aresult) + + var buckets := AResponse..buckets(aresponse) + + var i := 0 + loop + invariant 0 <= i && i <= length(buckets) + { + if i == length(buckets) { + exit + } + + check 0 <= i && i < length(buckets) + var bucket := select(buckets, i) + + var bucketName := Bucket..name(bucket) + + var bresult: BResult + GetPublicAccessBlock(fileSystem, bucketName, out bresult) + if !BIsSuccess(bresult) { + result := XFailure(BFailure..msg(bresult)) + return + } + var bresponse := BSuccess..value(bresult) + + var isBlocked := GetAttributeValue(BResponse..getConfig(bresponse), |BlockPublicAcls: string|) + + if isBlocked { + Print(|bucket: string|, bucketName, |is-blocked: string|) + } else { + Print(|bucket: string|, bucketName, |is-not-blocked: string|) + } + + i := i + 1 + } + + var x: X + result := XSuccess(x) +} + +// -------------------------------------------------------------------- + +// The file-system API is the following: + +procedure CreateClient(name: string, out result: CResult) + +// This predicate says whether or not the given bucket name is owned by the user. +// It is used in the postcondition of ListBuckets and in the precondition of +// GetPublicAccessBlock. +function UserOwnsBucket(name: string): bool + +type Client + +procedure ListBuckets(c: Client, out aresult: AResult) + ensures AIsSuccess(aresult) ==> + forall bucket: Bucket + pattern Bucket..name(bucket) + pattern in(bucket, AResponse..buckets(ASuccess..value(aresult))) + in(bucket, AResponse..buckets(ASuccess..value(aresult))) ==> + UserOwnsBucket(Bucket..name(bucket)) + +procedure GetPublicAccessBlock(c: Client, Bucket: string, out result: BResult) + requires UserOwnsBucket(Bucket) + +// -------------------------------------------------------------------- + +// The example program uses an API model where each API entry point returns a "response". +// This is typical in many code bases, for example in Java, because it allows the API +// to evolve to add more attributes of the response in the future. What that means for +// the example program is that there are different response types. Here, those are modeled +// as records with one just field. + +// datatype AResponse = AResponse(buckets: BucketSeq) +type AResponse +function AResponse(injective buckets: BucketSeq): AResponse + +// datatype BResponse = BResponse(getConfig: BlockConfig) +type BResponse +function BResponse(injective getConfig: BlockConfig): BResponse + +// -------------------------------------------------------------------- + +// For the purpose of the example, a bucket is something that has a name. In a full API, +// buckets would also have some data, of course. + +// datatype Bucket = Bucket(name: string) +type Bucket +function Bucket(injective name: string): Bucket + +// -------------------------------------------------------------------- + +// In the example, a block configuration is a set of named attributes, each of which can +// be false or true. + +type BlockConfig + +function GetAttributeValue(config: BlockConfig, attribute: string): bool + +// -------------------------------------------------------------------- + +// The example program is written in the style of Rust, Go, and Dafny, where a failure +// is reported as a special return value that have to be tested by the caller. In Go, +// such querying and propagation of failures is done manually, whereas Rust has the +// "?" operator and Dafny has the ":-" operator for doing this. Such code is translated +// into B3 by doing the checking explicitly. +// +// Using datatypes with type parameters, such Result types can be defined as +// +// datatype Result = Success(value: X) | Failure(msg: string) +// +// Since B3 does not support polymorphism, there is one Result type for each success +// type. + +type X +type XResult // Result<()> +tagger XResultTag for XResult +function XSuccess(injective value: X): XResult tag XResultTag +function XFailure(injective msg: string): XResult tag XResultTag +function XIsSuccess(r: XResult): bool { + XResultTag(r) == XSuccess..tag() +} + +type CResult // Result +tagger CResultTag for CResult +function CSuccess(injective value: Client): CResult tag CResultTag +function CFailure(injective msg: string): CResult tag CResultTag +function CIsSuccess(r: CResult): bool { + CResultTag(r) == CSuccess..tag() +} + +type AResult // Result +tagger AResultTag for AResult +function ASuccess(injective value: AResponse): AResult tag AResultTag +function AFailure(injective msg: string): AResult tag AResultTag +function AIsSuccess(r: AResult): bool { + AResultTag(r) == ASuccess..tag() +} + +type BResult // Result +tagger BResultTag for BResult +function BSuccess(injective value: BResponse): BResult tag BResultTag +function BFailure(injective msg: string): BResult tag BResultTag +function BIsSuccess(r: BResult): bool { + BResultTag(r) == BSuccess..tag() +} + +// -------------------------------------------------------------------- + +// Finally, we have a type BucketSeq that models a sequence of buckets +// and a(n uninterpreted) string type whose values can be printed. +// +// In a source programming language, the "select" operation on a sequence +// has a precondition that the given index is in range. The example B3 code +// above uses a "check" statement to enforce that precondition. + +type BucketSeq + +function select(s: BucketSeq, i: int): Bucket + +function length(s: BucketSeq): int + +axiom explains length + forall s: BucketSeq + pattern length(s) + 0 <= length(s) + +function in(b: Bucket, s: BucketSeq): bool { + exists i: int + pattern select(s, i) + 0 <= i && i < length(s) && select(s, i) == b +} + +type string + +procedure Print(a: string, b: string, c: string) +#end + +end ProgramRoundtripTests + +end B3 diff --git a/StrataTest/Languages/B3/DDMFormatStatementsTests.lean b/StrataTest/Languages/B3/DDMFormatStatementsTests.lean new file mode 100644 index 000000000..3286fb100 --- /dev/null +++ b/StrataTest/Languages/B3/DDMFormatStatementsTests.lean @@ -0,0 +1,626 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Languages.B3.DDMFormatTests +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 Statement Formatting Tests + +Tests for round-trip conversion and formatting of B3 statements. +Verifies that DDM AST → B3 AST → B3 CST → formatted output preserves structure and catches conversion errors. +-/ + +namespace B3 + +open Std (Format) +open Strata +open Strata.B3CST + +-- Helper to perform the round-trip transformation for statements +-- DDM OperationF → B3 Stmt → DDM → formatted output +partial def doRoundtripStmt (stmt : OperationF SourceRange) (ctx : FormatContext) (state : FormatState) : Format := + match B3CST.Statement.ofAst stmt with + | .ok cstStmt => + let (b3Stmt, cstToAstErrors) := B3.stmtFromCST B3.FromCSTContext.empty cstStmt + let (cstStmt', astToCstErrors) := B3.stmtToCST B3.ToCSTContext.empty b3Stmt + -- Convert to Unit metadata for repr + let b3StmtUnit := B3AST.Statement.mapMetadata (fun _ => ()) b3Stmt + let reprStr := (repr b3StmtUnit).pretty + let reprStr := cleanupStmtRepr reprStr + let reprStr := cleanupUnitRepr reprStr + let errorStr := if cstToAstErrors.isEmpty && astToCstErrors.isEmpty then "" + else + let cstErrs := cstToAstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let astErrs := astToCstErrors.map Std.format |> List.map (·.pretty) |> String.intercalate "\n " + let parts := [ + if cstToAstErrors.isEmpty then "" else s!"\nCST→AST Errors:\n {cstErrs}", + if astToCstErrors.isEmpty then "" else s!"\nAST→CST Errors:\n {astErrs}" + ] + String.join parts + dbg_trace f!"B3: {reprStr}{errorStr}" + let cstAst := cstStmt'.toAst + (mformat (ArgF.op cstAst) ctx state).format + | .error msg => s!"Parse error: {msg}" + +-- Helper to extract statement from a program and apply round-trip transformation +def roundtripStmt (p : Program) : Format := + let ctx := FormatContext.ofDialects p.dialects p.globalContext {} + let state : FormatState := { openDialects := p.dialects.toList.foldl (init := {}) fun a d => a.insert d.name } + match p.commands.toList with + | [op] => + if op.name.name == "command_stmt" then + match op.args.toList with + | [ArgF.op stmt] => doRoundtripStmt stmt ctx state + | _ => "Error: expected statement op" + else s!"Error: expected command_stmt, got {op.name.name}" + | _ => "Error: expected single command" + +section StatementRoundtripTests + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .assign + () + u 0 + (.literal () (.intLit () 42))] +--- +info: +{ + var x : int + x := 42 +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; {var x: int x := 42} #end + +/-- +info: B3: .check + () + (.binaryOp + () + (.gt ()) + (.literal () (.intLit () 5)) + (.literal () (.intLit () 0))) +--- +info: +check 5 > 0 +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; check 5 > 0 #end + +/-- +info: B3: .assume + () + (.binaryOp + () + (.ge ()) + (.literal () (.intLit () 10)) + (.literal () (.intLit () 0))) +--- +info: +assume 10 >= 0 +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; assume 10 >= 0 #end + +/-- +info: B3: .assert + () + (.binaryOp + () + (.gt ()) + (.literal () (.intLit () 5)) + (.literal () (.intLit () 0))) +--- +info: +assert 5 > 0 +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; assert 5 > 0 #end + +/-- +info: B3: .reach + () + (.binaryOp + () + (.eq ()) + (.literal () (.intLit () 5)) + (.literal () (.intLit () 5))) +--- +info: +reach 5 == 5 +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; reach 5 == 5 #end + +/-- +info: B3: .returnStmt () +--- +info: +return +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; return #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .varDecl + () + u "y" + u some u "int" + u none + u none, + .blockStmt + () + u #[.assign + () + u 1 + (.literal () (.intLit () 1)), + .assign + () + u 0 + (.literal () (.intLit () 2))]] +--- +info: +{ + var x : int + var y : int + { + x := 1 + y := 2 + } +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var x: int var y: int { x := 1 y := 2 } } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "flag" + u some u "bool" + u none + u none, + .varDecl + () + u "x" + u some u "int" + u none + u none, + .ifStmt + () + (.id () 1) + (.assign + () + u 0 + (.literal () (.intLit () 1))) + u some (.blockStmt + () + u #[.assign + () + u 0 + (.literal () (.intLit () 0))])] +--- +info: +{ + var flag : bool + var x : int + if flag ⏎ + x := 1 + else ⏎ + { + x := 0 + } +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST;{ var flag: bool var x: int if flag x := 1 else { x := 0 } } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "i" + u some u "int" + u none + u none, + .loop + () + u #[] + (.blockStmt + () + u #[.assign + () + u 0 + (.binaryOp + () + (.add ()) + (.id () 0) + (.literal () (.intLit () 1)))])] +--- +info: +{ + var i : int + loop ⏎ + { + i := i + 1 + } +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var i: int loop { i := i + 1 } } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "i" + u some u "int" + u none + u none, + .varDecl + () + u "n" + u some u "int" + u none + u none, + .loop + () + u #[.binaryOp + () + (.ge ()) + (.id () 1) + (.literal () (.intLit () 0)), + .binaryOp + () + (.le ()) + (.id () 1) + (.id () 0)] + (.blockStmt + () + u #[.assign + () + u 1 + (.binaryOp + () + (.add ()) + (.id () 1) + (.literal () (.intLit () 1)))])] +--- +info: +{ + var i : int + var n : int + loop + invariant i >= 0 + invariant i <= n ⏎ + { + i := i + 1 + } +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var i: int var n: int loop invariant i >= 0 invariant i <= n { i := i + 1 } } #end + +/-- +info: B3: .exit () u some u "loop_start" +--- +info: +exit loop_start +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; exit loop_start #end + +/-- +info: B3: .labeledStmt + () + u "labeled_block" + (.blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .assign + () + u 0 + (.literal () (.intLit () 0))]) +--- +info: labeled_block: ⏎ +{ + var x : int + x := 0 +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; labeled_block: {var x: int x := 0} #end + +/-- +info: B3: .probe () u "debug_point" +--- +info: +probe debug_point +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; probe debug_point #end + +/-- +info: B3: .varDecl + () + u "x" + u some u "int" + u none + u none +--- +info: +var x : int +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; var x : int #end + +/-- +info: B3: .varDecl + () + u "x" + u some u "bool" + u none + u some (.literal () (.boolLit () true)) +--- +info: +var x : bool := true +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; val x : bool := true #end + +/-- +info: B3: .varDecl + () + u "y" + u some u "bool" + u none + u some (.literal () (.boolLit () true)) +--- +info: +var y : bool := true +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; var y : bool := true #end + +/-- +info: B3: .varDecl + () + u "z" + u some u "int" + u some (.binaryOp + () + (.ge ()) + (.id () 0) + (.literal () (.intLit () 0))) + u none +--- +info: +var z : int autoinv z >= 0 +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; var z : int autoinv z >= 0 #end + +/-- +info: B3: .aForall + () + u "x" + u "int" + (.blockStmt + () + u #[.check + () + (.binaryOp + () + (.ge ()) + (.id () 0) + (.literal () (.intLit () 0)))]) +--- +info: +forall x : int ⏎ +{ + check x >= 0 +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; forall x : int { check x >= 0 } #end + +/-- +info: B3: .choose + () + u #[.blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .assign + () + u 0 + (.literal () (.intLit () 2))], + .blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .assign + () + u 0 + (.literal () (.intLit () 1))]] +--- +info: +choose ⏎ +{ + var x : int + x := 1 +} or ⏎ +{ + var x : int + x := 2 +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; choose { var x: int x := 1 } or { var x: int x := 2 } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .varDecl + () + u "y" + u some u "int" + u none + u none, + .ifCase + () + u #[.oneIfCase + () + (.binaryOp + () + (.eq ()) + (.id () 1) + (.literal () (.intLit () 1))) + (.blockStmt + () + u #[.assign + () + u 0 + (.literal () (.intLit () 10))]), + .oneIfCase + () + (.binaryOp + () + (.eq ()) + (.id () 1) + (.literal () (.intLit () 2))) + (.blockStmt + () + u #[.assign + () + u 0 + (.literal + () + (.intLit () 20))])]] +--- +info: +{ + var x : int + var y : int + if + case x == 1 ⏎ + { + y := 10 + } + case x == 2 ⏎ + { + y := 20 + } +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var x: int var y: int if case x == 1 { y := 10 } case x == 2 { y := 20 } } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "a" + u some u "int" + u none + u none, + .varDecl + () + u "b" + u some u "int" + u none + u none, + .call + () + u "compute" + u #[.callArgOut () u "result", + .callArgExpr () (.id () 1), + .callArgExpr () (.id () 0)]] +--- +info: +{ + var a : int + var b : int + compute(out result, a, b) +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var a: int var b: int compute(out result, a, b) } #end + +/-- +info: B3: .blockStmt + () + u #[.varDecl + () + u "x" + u some u "int" + u none + u none, + .varDecl + () + u "y" + u some u "int" + u none + u none, + .call + () + u "modify" + u #[.callArgInout () u "x", + .callArgOut () u "y"]] +--- +info: +{ + var x : int + var y : int + modify(inout x, out y) +} +-/ +#guard_msgs in +#eval roundtripStmt $ #strata program B3CST; { var x: int var y: int modify(inout x, out y) } #end + +end StatementRoundtripTests + +end B3 diff --git a/StrataTest/Languages/B3/DDMFormatTests.lean b/StrataTest/Languages/B3/DDMFormatTests.lean new file mode 100644 index 000000000..2f321f143 --- /dev/null +++ b/StrataTest/Languages/B3/DDMFormatTests.lean @@ -0,0 +1,262 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.B3.DDMTransform.ParseCST +import Strata.Languages.B3.DDMTransform.DefinitionAST +import Strata.Languages.B3.DDMTransform.Conversion + +/-! +# B3 DDM Formatting Test Utilities + +Common utilities and helper functions for B3 formatting tests. +Provides string cleanup functions and shared formatting infrastructure used across expression, statement, declaration, and program formatting tests. +-/ + +namespace B3 + +open Std (Format) +open Strata +open Strata.B3CST + +/-- +info: inductive Strata.B3CST.Expression : Type → Type +number of parameters: 1 +constructors: +Strata.B3CST.Expression.not : {α : Type} → α → Expression α → Expression α +Strata.B3CST.Expression.natLit : {α : Type} → α → Nat → Expression α +Strata.B3CST.Expression.strLit : {α : Type} → α → String → Expression α +Strata.B3CST.Expression.btrue : {α : Type} → α → Expression α +Strata.B3CST.Expression.bfalse : {α : Type} → α → Expression α +Strata.B3CST.Expression.old_id : {α : Type} → α → Ann String α → Expression α +Strata.B3CST.Expression.id : {α : Type} → α → String → Expression α +Strata.B3CST.Expression.letExpr : {α : Type} → α → Ann String α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.labeledExpr : {α : Type} → α → Ann String α → Expression α → Expression α +Strata.B3CST.Expression.ite : {α : Type} → α → Expression α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.iff : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.implies : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.impliedBy : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.and : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.or : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.equal : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.not_equal : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.le : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.lt : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.ge : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.gt : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.neg : {α : Type} → α → Expression α → Expression α +Strata.B3CST.Expression.add : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.sub : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.mul : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.div : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.mod : {α : Type} → α → Expression α → Expression α → Expression α +Strata.B3CST.Expression.paren : {α : Type} → α → Expression α → Expression α +Strata.B3CST.Expression.functionCall : {α : Type} → α → Ann String α → Ann (Array (Expression α)) α → Expression α +Strata.B3CST.Expression.forall_expr : {α : Type} → + α → Ann (Array (VarDecl α)) α → Ann (Array (Pattern α)) α → Expression α → Expression α +Strata.B3CST.Expression.exists_expr : {α : Type} → + α → Ann (Array (VarDecl α)) α → Ann (Array (Pattern α)) α → Expression α → Expression α +-/ +#guard_msgs in +#print B3CST.Expression + +/-- +info: inductive Strata.B3AST.Expression : Type → Type +number of parameters: 1 +constructors: +Strata.B3AST.Expression.literal : {α : Type} → α → B3AST.Literal α → B3AST.Expression α +Strata.B3AST.Expression.id : {α : Type} → α → Nat → B3AST.Expression α +Strata.B3AST.Expression.ite : {α : Type} → + α → B3AST.Expression α → B3AST.Expression α → B3AST.Expression α → B3AST.Expression α +Strata.B3AST.Expression.binaryOp : {α : Type} → + α → B3AST.BinaryOp α → B3AST.Expression α → B3AST.Expression α → B3AST.Expression α +Strata.B3AST.Expression.unaryOp : {α : Type} → α → B3AST.UnaryOp α → B3AST.Expression α → B3AST.Expression α +Strata.B3AST.Expression.functionCall : {α : Type} → + α → Ann String α → Ann (Array (B3AST.Expression α)) α → B3AST.Expression α +Strata.B3AST.Expression.labeledExpr : {α : Type} → α → Ann String α → B3AST.Expression α → B3AST.Expression α +Strata.B3AST.Expression.letExpr : {α : Type} → + α → Ann String α → B3AST.Expression α → B3AST.Expression α → B3AST.Expression α +Strata.B3AST.Expression.quantifierExpr : {α : Type} → + α → + B3AST.QuantifierKind α → + Ann (Array (B3AST.VarDecl α)) α → Ann (Array (B3AST.Pattern α)) α → B3AST.Expression α → B3AST.Expression α +-/ +#guard_msgs in +#print B3AST.Expression + +/-- +info: inductive Strata.B3CST.Pattern : Type → Type +number of parameters: 1 +constructors: +Strata.B3CST.Pattern.pattern : {α : Type} → α → Ann (Array (Expression α)) α → Pattern α +-/ +#guard_msgs in +#print B3CST.Pattern + +-- Helpers to convert Unit annotations to SourceRange +mutual + partial def exprFUnitToSourceRange : ExprF Unit → ExprF SourceRange + | .bvar () idx => .bvar default idx + | .fvar () idx => .fvar default idx + | .fn () f => .fn default f + | .app () f a => .app default (exprFUnitToSourceRange f) (argFUnitToSourceRange a) + + partial def argFUnitToSourceRange : ArgF Unit → ArgF SourceRange + | .op op => .op { op with ann := default, args := op.args.map argFUnitToSourceRange } + | .expr e => .expr (exprFUnitToSourceRange e) + | .type t => .type (typeExprFUnitToSourceRange t) + | .cat c => .cat (syntaxCatFUnitToSourceRange c) + | .ident () x => .ident default x + | .num () x => .num default x + | .decimal () v => .decimal default v + | .strlit () s => .strlit default s + | .bytes () v => .bytes default v + | .option () ma => .option default (ma.map argFUnitToSourceRange) + | .seq () entries => .seq default (entries.map argFUnitToSourceRange) + | .commaSepList () entries => .commaSepList default (entries.map argFUnitToSourceRange) + + partial def typeExprFUnitToSourceRange : TypeExprF Unit → TypeExprF SourceRange + | .ident () tp a => .ident default tp (a.map typeExprFUnitToSourceRange) + | .bvar () idx => .bvar default idx + | .fvar () idx a => .fvar default idx (a.map typeExprFUnitToSourceRange) + | .arrow () a r => .arrow default (typeExprFUnitToSourceRange a) (typeExprFUnitToSourceRange r) + + partial def syntaxCatFUnitToSourceRange : SyntaxCatF Unit → SyntaxCatF SourceRange + | ⟨(), name, args⟩ => ⟨default, name, args.map syntaxCatFUnitToSourceRange⟩ +end + +-- Create a minimal B3 program to get the dialect context +def b3Program : Program := #strata program B3CST; #end + +-- Helper to convert OperationF Unit to OperationF SourceRange +def operationFUnitToSourceRange (op : OperationF Unit) : OperationF SourceRange := + { op with ann := default, args := op.args.map argFUnitToSourceRange } + +/-- +Clean up Unit annotation repr output for better readability. +Step 1: Remove `{ ann := (), val := X }` constructs via brace matching, keeping just u X +Step 2: Reduce excessive indentation (more than 2 spaces difference) to 2 spaces +-/ +partial def cleanupUnitRepr (s : String) : String := + -- Step 1: Remove { ann := (), val := X } constructs + let rec removeAnnStructs (chars : List Char) (acc : String) : String := + match chars with + | [] => acc + | _ => + let pattern := "{ ann := (),".toList + if chars.take pattern.length == pattern then + -- Found "{ ann := (),", now find matching closing brace for the whole structure + let rec findClose (cs : List Char) (depth : Nat) (acc : List Char) : Option (List Char × List Char) := + match cs with + | [] => none + | _ :: [] => none + | c :: d :: rest => + if c == '{' then findClose (d :: rest) (depth + 1) (c :: acc) + else if c == ' ' && d == '}' then + if depth == 0 then some (acc.reverse, rest) + else findClose (d :: rest) (depth - 1) (c :: acc) + else findClose (d :: rest) depth (c :: acc) + match findClose (chars.drop 1) 0 [] with + | none => removeAnnStructs (chars.drop 1) (acc ++ String.ofList [chars.head!]) + | some (innerChars, afterClose) => + -- innerChars contains everything between { and }, like "ann := (),\n val := X" or "ann := (), val := X" + -- Find "val := " and extract everything after it + let valPattern := "val := ".toList + let rec findValStart (cs : List Char) : Option (List Char) := + match cs with + | [] => none + | _ => + if cs.take valPattern.length == valPattern then + some (cs.drop valPattern.length) + else + match cs with + | [] => none + | _ :: rest => findValStart rest + match findValStart innerChars with + | none => removeAnnStructs (chars.drop 1) (acc ++ String.ofList [chars.head!]) + | some valueOnly => removeAnnStructs afterClose (acc ++ "u " ++ String.ofList valueOnly) + else + removeAnnStructs (chars.drop 1) (acc ++ String.ofList [chars.head!]) + + -- Apply removal 10 times to handle nested structures up to depth 10 + let rec applyNTimes (n : Nat) (str : String) : String := + if n == 0 then str + else applyNTimes (n - 1) (removeAnnStructs str.toList "") + + let step1 := applyNTimes 10 s + + -- Step 2: Remove trailing spaces and normalize indentation using a stack + let lines := step1.splitOn "\n" + let rec processLines (lines : List String) (indentStack : List Nat) (acc : List String) : List String := + match lines with + | [] => acc.reverse + | line :: rest => + -- Remove trailing spaces + let line := line.dropRightWhile (· == ' ') + let indent := line.takeWhile (· == ' ') |>.length + let content := line.dropWhile (· == ' ') + if content.isEmpty then + processLines rest indentStack ("" :: acc) + else + -- Update indent stack based on current indent + let newStack := + match indentStack with + | [] => [indent] + | prevIndent :: _ => + if indent > prevIndent then + -- Deeper nesting - push current indent + indent :: indentStack + else if indent == prevIndent then + -- Same level - keep stack + indentStack + else + -- Dedent - pop stack until we find matching or smaller indent + let rec popUntil (stack : List Nat) : List Nat := + match stack with + | [] => [indent] + | h :: t => + if h <= indent then stack + else popUntil t + popUntil indentStack + -- New indent is (stack depth - 1) * 2 + let newIndent := (newStack.length - 1) * 2 + let newLine := String.ofList (List.replicate newIndent ' ') ++ content + processLines rest newStack (newLine :: acc) + + String.intercalate "\n" (processLines lines [] []) + +/-- Remove Strata.B3AST namespace prefixes for expression types -/ +def cleanupExprRepr (s : String) : String := + let s := s.replace "Strata.B3AST.Expression." "." + let s := s.replace "Strata.B3AST.QuantifierKind." "." + let s := s.replace "Strata.B3AST.Literal." "." + let s := s.replace "Strata.B3AST.UnaryOp." "." + let s := s.replace "Strata.B3AST.BinaryOp." "." + let s := s.replace "Strata.B3AST.Pattern." "." + let s := s.replace "Strata.B3AST.VarDecl." "." + s + +/-- Remove Strata.B3AST namespace prefixes for statement types -/ +def cleanupStmtRepr (s : String) : String := + let s := cleanupExprRepr s + let s := s.replace "Strata.B3AST.Statement." "." + let s := s.replace "Strata.B3AST.CallArg." "." + let s := s.replace "Strata.B3AST.OneIfCase." "." + s + +/-- Remove Strata.B3AST namespace prefixes for declaration types -/ +def cleanupDeclRepr (s : String) : String := + let s := cleanupStmtRepr s + let s := s.replace "Strata.B3AST.Program." "." + let s := s.replace "Strata.B3AST.Decl." "." + let s := s.replace "Strata.B3AST.FParameter." "." + let s := s.replace "Strata.B3AST.PParameter." "." + let s := s.replace "Strata.B3AST.Spec." "." + let s := s.replace "Strata.B3AST.ParamMode." "." + let s := s.replace "Strata.B3AST.FunctionBody." "." + let s := s.replace "Strata.B3AST.When." "." + s + +end B3 diff --git a/StrataTest/Languages/Boogie/DatatypeVerificationTests.lean b/StrataTest/Languages/Boogie/DatatypeVerificationTests.lean new file mode 100644 index 000000000..68630e880 --- /dev/null +++ b/StrataTest/Languages/Boogie/DatatypeVerificationTests.lean @@ -0,0 +1,617 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Boogie.Verifier +import Strata.DL.Lambda.TypeFactory + +/-! +# Datatype Verification Integration Tests + +Verify Boogie programs with datatypes, encoding with declare-datatype +-/ + +namespace Boogie.DatatypeVerificationTests + +open Lambda +open Std (ToFormat Format) +open Imperative + +/-! ## Test Datatypes -/ + +/-- Simple Option datatype: Option a = None | Some(OptionVal: a) + Testers: isNone, isSome + Destructors: OptionVal -/ +def optionDatatype : LDatatype Visibility := + { name := "Option" + typeArgs := ["a"] + constrs := [ + { name := ⟨"None", .unres⟩, args := [], testerName := "isNone" }, + { name := ⟨"Some", .unres⟩, args := [(⟨"OptionVal", .unres⟩, .ftvar "a")], testerName := "isSome" } + ] + constrs_ne := by decide } + +/-- Recursive List datatype: List a = Nil | Cons(hd: a, tl: List a) + Testers: isNil, isCons + Destructors: hd, tl -/ +def listDatatype : LDatatype Visibility := + { name := "List" + typeArgs := ["a"] + constrs := [ + { name := ⟨"Nil", .unres⟩, args := [], testerName := "isNil" }, + { name := ⟨"Cons", .unres⟩, args := [ + (⟨"hd", .unres⟩, .ftvar "a"), + (⟨"tl", .unres⟩, .tcons "List" [.ftvar "a"]) + ], testerName := "isCons" } + ] + constrs_ne := by decide } + +/-- Hidden datatype that is never directly used in the program -/ +def hiddenDatatype : LDatatype Visibility := + { name := "Hidden" + typeArgs := ["a"] + constrs := [ + { name := ⟨"HiddenValue", .unres⟩, args := [ + (⟨"hiddenField", .unres⟩, .ftvar "a") + ], testerName := "isHiddenValue" } + ] + constrs_ne := by decide } + +/-- Container datatype that references Hidden but we never use Hidden directly -/ +def containerDatatype : LDatatype Visibility := + { name := "Container" + typeArgs := ["a"] + constrs := [ + { name := ⟨"Empty", .unres⟩, args := [], testerName := "isEmpty" }, + { name := ⟨"WithHidden", .unres⟩, args := [ + (⟨"hiddenPart", .unres⟩, .tcons "Hidden" [.ftvar "a"]), + (⟨"visiblePart", .unres⟩, .ftvar "a") + ], testerName := "isWithHidden" } + ] + constrs_ne := by decide } + +/-! ## Helper Functions -/ + +/-- +Create a Boogie program with datatypes and a procedure. +-/ +def mkProgramWithDatatypes + (datatypes : List (LDatatype Visibility)) + (procName : String) + (body : List Statement) + : Except Format Program := do + let proc : Procedure := { + header := { + name := BoogieIdent.unres procName + typeArgs := [] + inputs := [] + outputs := [] + } + spec := { + modifies := [] + preconditions := [] + postconditions := [] + } + body := body + } + + let decls := datatypes.map (fun d => Decl.type (.data d) .empty) + return { decls := decls ++ [Decl.proc proc .empty] } + +/-! ## Helper for Running Tests -/ + +/-- +Run verification and return a summary string. +-/ +def runVerificationTest (testName : String) (program : Program) : IO String := do + try + match ← EIO.toIO' (Boogie.verify "cvc5" program Options.quiet) with + | .error err => + return s!"{testName}: FAILED\n Error: {err}" + | .ok results => + let mut output := s!"{testName}: PASSED\n" + output := output ++ s!" Verified {results.size} obligation(s)\n" + for result in results do + if result.result != .unsat then + output := output ++ s!" WARNING: {result.obligation.label}: {Std.format result.result}\n" + return output + catch e => + return s!"{testName}: FAILED (exception)\n Error: {e}" + +/-! ## Test 1: Constructor Application -/ + +/-- +Test that constructor applications are properly encoded. + +datatype Option a = None | Some a + +procedure testConstructors () { + x := None; + y := Some 42; + assert true; +} +-/ +def test1_constructorApplication : IO String := do + let statements : List Statement := [ + -- Create None value + Statement.init (BoogieIdent.unres "x") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.op () (BoogieIdent.unres "None") (.some (LMonoTy.tcons "Option" [.int]))), + + -- Create Some value + Statement.init (BoogieIdent.unres "y") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 42)), + + -- Trivial assertion to verify the program + Statement.assert "trivial" (LExpr.boolConst () true) + ] + + match mkProgramWithDatatypes [optionDatatype] "testConstructors" statements with + | .error err => + return s!"Test 1 - Constructor Application: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 1 - Constructor Application" program + +/-! ## Test 2: Tester Functions -/ + +/-- +Test that tester functions (is-None, is-Some) are properly encoded. + +datatype Option a = None | Some a + +procedure testTesters () { + x := None; + assert (isNone x); + y := Some 42; + assert (isSome y); +} + +-/ +def test2_testerFunctions : IO String := do + let statements : List Statement := [ + -- Create None value + Statement.init (BoogieIdent.unres "x") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.op () (BoogieIdent.unres "None") (.some (LMonoTy.tcons "Option" [.int]))), + + -- Assert that x is None + Statement.assert "x_is_none" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isNone") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "x") (.some (LMonoTy.tcons "Option" [.int])))), + + -- Create Some value + Statement.init (BoogieIdent.unres "y") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 42)), + + -- Assert that y is Some + Statement.assert "y_is_some" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isSome") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "y") (.some (LMonoTy.tcons "Option" [.int])))) + ] + + match mkProgramWithDatatypes [optionDatatype] "testTesters" statements with + | .error err => + return s!"Test 2 - Tester Functions: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 2 - Tester Functions" program + +/-! ## Test 3: Destructor Functions -/ + +/-- +Test that destructor functions are properly encoded. + +datatype Option a = None | Some a +datatype List a = Nil | Cons a (List a) + +procedure testDestructors () { + opt := Some 42; + val := value opt; + assert (val == 42); + + list := [1] + head := hd list; + assert(head == 1); +} + +-/ +def test3_destructorFunctions : IO String := do + let statements : List Statement := [ + -- Create Some(42) + Statement.init (BoogieIdent.unres "opt") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 42)), + + -- Extract value from Some + Statement.init (BoogieIdent.unres "value") (.forAll [] LMonoTy.int) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "OptionVal") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .int))) + (LExpr.fvar () (BoogieIdent.unres "opt") (.some (LMonoTy.tcons "Option" [.int])))), + + -- Assert that val == 42 + Statement.assert "val_is_42" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "value") (.some .int)) + (LExpr.intConst () 42)), + + -- Create Cons(1, Nil) + Statement.init (BoogieIdent.unres "list") (.forAll [] (LMonoTy.tcons "List" [.int])) + (LExpr.app () + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Cons") + (.some (LMonoTy.arrow .int (LMonoTy.arrow (LMonoTy.tcons "List" [.int]) (LMonoTy.tcons "List" [.int]))))) + (LExpr.intConst () 1)) + (LExpr.op () (BoogieIdent.unres "Nil") (.some (LMonoTy.tcons "List" [.int])))), + + -- Extract head + Statement.init (BoogieIdent.unres "head") (.forAll [] LMonoTy.int) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "hd") + (.some (LMonoTy.arrow (LMonoTy.tcons "List" [.int]) .int))) + (LExpr.fvar () (BoogieIdent.unres "list") (.some (LMonoTy.tcons "List" [.int])))), + + -- Assert head == 1 + Statement.assert "head_is_1" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "head") (.some .int)) + (LExpr.intConst () 1)) + ] + + match mkProgramWithDatatypes [optionDatatype, listDatatype] "testDestructors" statements with + | .error err => + return s!"Test 3 - Destructor Functions: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 3 - Destructor Functions" program + +/-! ## Test 4: Nested Datatypes -/ + +/-- +Test nested datatypes (List of Option). + +datatype Option a = None | Some a +datatype List a = Nil | Cons a (List a) + +procedure testNested () { + listOfOpt := [Some 42]; + assert (isCons listOfOpt); + headOpt := hd listOfOpt; + value := Option$ConsProj0 headOpt; + assert (value == 42); +} + +-/ +def test4_nestedDatatypes : IO String := do + let statements : List Statement := [ + -- Create a List of Option: Cons(Some(42), Nil) + Statement.init (BoogieIdent.unres "listOfOpt") + (.forAll [] (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]])) + (LExpr.app () + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Cons") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) + (LMonoTy.arrow (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]]) + (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]]))))) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 42))) + (LExpr.op () (BoogieIdent.unres "Nil") + (.some (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]])))), + + -- Assert that the list is Cons + Statement.assert "list_is_cons" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isCons") + (.some (LMonoTy.arrow (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "listOfOpt") + (.some (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]])))), + + -- Extract the head of the list (which is an Option) + Statement.init (BoogieIdent.unres "headOpt") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "hd") + (.some (LMonoTy.arrow (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]]) (LMonoTy.tcons "Option" [.int])))) + (LExpr.fvar () (BoogieIdent.unres "listOfOpt") + (.some (LMonoTy.tcons "List" [LMonoTy.tcons "Option" [.int]])))), + + -- Extract the value from the Option + Statement.init (BoogieIdent.unres "value") (.forAll [] LMonoTy.int) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "OptionVal") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .int))) + (LExpr.fvar () (BoogieIdent.unres "headOpt") + (.some (LMonoTy.tcons "Option" [.int])))), + + -- Assert that the extracted value is 42 + Statement.assert "value_is_42" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "value") (.some .int)) + (LExpr.intConst () 42)) + ] + + match mkProgramWithDatatypes [listDatatype, optionDatatype] "testNested" statements with + | .error err => + return s!"Test 4 - Nested Datatypes: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 4 - Nested Datatypes" program + +/-! ## Test 5: Tester with Havoc (requires SMT) -/ + +/-- +Test tester functions with havoc'd values that require SMT solving and cannot +be solved only with partial evaluation. + +datatype Option a = None | Some a + +procedure testTesterHavoc () { + x := None; + x := havoc(); + assume (isSome x); + assert (not (isNone x)); +} + +-/ +def test5_testerWithHavoc : IO String := do + let statements : List Statement := [ + -- Havoc an Option value (non-deterministic) + Statement.init (BoogieIdent.unres "x") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.op () (BoogieIdent.unres "None") (.some (LMonoTy.tcons "Option" [.int]))), + Statement.havoc (BoogieIdent.unres "x"), + + -- Assume x is Some + Statement.assume "x_is_some" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isSome") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "x") (.some (LMonoTy.tcons "Option" [.int])))), + + -- Assert x is not None (should follow from assumption) + Statement.assert "x_not_none" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Bool.Not") + (.some (LMonoTy.arrow .bool .bool))) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isNone") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "x") (.some (LMonoTy.tcons "Option" [.int]))))) + ] + + match mkProgramWithDatatypes [optionDatatype] "testTesterHavoc" statements with + | .error err => + return s!"Test 5 - Tester with Havoc: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 5 - Tester with Havoc" program + +/-! ## Test 6: Destructor with Havoc (requires SMT) -/ + +/-- +Test destructor functions with havoc'd values. + +datatype Option a = None | Some a + +procedure testDestructorHavoc () { + opt := Some 0; + opt := havoc(); + assume (opt == Some 42); + value := val opt; + assert (value == 42); +} + +-/ +def test6_destructorWithHavoc : IO String := do + let statements : List Statement := [ + -- Havoc an Option value + Statement.init (BoogieIdent.unres "opt") (.forAll [] (LMonoTy.tcons "Option" [.int])) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 0)), + Statement.havoc (BoogieIdent.unres "opt"), + + -- Assume opt is Some(42) + Statement.assume "opt_is_some_42" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "opt") (.some (LMonoTy.tcons "Option" [.int]))) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Some") + (.some (LMonoTy.arrow .int (LMonoTy.tcons "Option" [.int])))) + (LExpr.intConst () 42))), + + -- Extract value + Statement.init (BoogieIdent.unres "value") (.forAll [] LMonoTy.int) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "OptionVal") + (.some (LMonoTy.arrow (LMonoTy.tcons "Option" [.int]) .int))) + (LExpr.fvar () (BoogieIdent.unres "opt") (.some (LMonoTy.tcons "Option" [.int])))), + + -- Assert val == 42 + Statement.assert "val_is_42" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "value") (.some .int)) + (LExpr.intConst () 42)) + ] + + match mkProgramWithDatatypes [optionDatatype] "testDestructorHavoc" statements with + | .error err => + return s!"Test 6 - Destructor with Havoc: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 6 - Destructor with Havoc" program + +/-! ## Test 7: List Constructor with Havoc (requires SMT) -/ + +/-- +Test list operations with havoc'd values. + +datatype List a = Nil | Cons a (List a) + +procedure testListHavoc () { + xs := Nil; + xs := havoc(); + assume (isCons xs); + assert (not (isNil xs)); +} + +-/ +def test7_listWithHavoc : IO String := do + let statements : List Statement := [ + -- Havoc a list + Statement.init (BoogieIdent.unres "xs") (.forAll [] (LMonoTy.tcons "List" [.int])) + (LExpr.op () (BoogieIdent.unres "Nil") (.some (LMonoTy.tcons "List" [.int]))), + Statement.havoc (BoogieIdent.unres "xs"), + + -- Assume xs is Cons + Statement.assume "xs_is_cons" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isCons") + (.some (LMonoTy.arrow (LMonoTy.tcons "List" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "xs") (.some (LMonoTy.tcons "List" [.int])))), + + -- Assert xs is not Nil + Statement.assert "xs_not_nil" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Bool.Not") + (.some (LMonoTy.arrow .bool .bool))) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isNil") + (.some (LMonoTy.arrow (LMonoTy.tcons "List" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "xs") (.some (LMonoTy.tcons "List" [.int]))))) + ] + + match mkProgramWithDatatypes [listDatatype] "testListHavoc" statements with + | .error err => + return s!"Test 7 - List with Havoc: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 7 - List with Havoc" program + +/-! ## Test 8: Hidden Type Recursive Addition -/ + +/-- +Test that SMT.Context.addType correctly handles the recursive case where +a datatype constructor has another datatype as an argument, but this +argument datatype is NEVER directly referenced in the program. + +datatype Hidden a = HiddenValue a +datatype Container a = Empty | WithHidden (Hidden a) a + +procedure testHiddenTypeRecursion () { + // We ONLY use Container, never Hidden directly + container := Empty; + havoc container; + assume (not (isEmpty container)); + visiblePart := visiblePart container; + assume (visiblePart == 42); + assert (isWithHidden container); +} +-/ +def test8_hiddenTypeRecursion : IO String := do + let statements : List Statement := [ + -- Initialize with Empty Container - note we NEVER use Hidden directly + Statement.init (BoogieIdent.unres "container") + (.forAll [] (LMonoTy.tcons "Container" [.int])) + (LExpr.op () (BoogieIdent.unres "Empty") (.some (LMonoTy.tcons "Container" [.int]))), + + -- Havoc the container to make it non-deterministic + Statement.havoc (BoogieIdent.unres "container"), + + -- Assume container is not Empty (so it must be WithHidden) + Statement.assume "container_not_empty" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "Bool.Not") + (.some (LMonoTy.arrow .bool .bool))) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isEmpty") + (.some (LMonoTy.arrow (LMonoTy.tcons "Container" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "container") (.some (LMonoTy.tcons "Container" [.int]))))), + + -- Extract the visible part + Statement.init (BoogieIdent.unres "visiblePart") (.forAll [] LMonoTy.int) + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "visiblePart") + (.some (LMonoTy.arrow (LMonoTy.tcons "Container" [.int]) .int))) + (LExpr.fvar () (BoogieIdent.unres "container") (.some (LMonoTy.tcons "Container" [.int])))), + + -- Assume the visible part has a specific value + Statement.assume "visible_part_is_42" + (LExpr.eq () + (LExpr.fvar () (BoogieIdent.unres "visiblePart") (.some .int)) + (LExpr.intConst () 42)), + + -- Assert that container is WithHidden + Statement.assert "container_is_with_hidden" + (LExpr.app () + (LExpr.op () (BoogieIdent.unres "isWithHidden") + (.some (LMonoTy.arrow (LMonoTy.tcons "Container" [.int]) .bool))) + (LExpr.fvar () (BoogieIdent.unres "container") (.some (LMonoTy.tcons "Container" [.int])))) + ] + + match mkProgramWithDatatypes [hiddenDatatype, containerDatatype] "testHiddenTypeRecursion" statements with + | .error err => + return s!"Test 8 - Hidden Type Recursion: FAILED (program creation)\n Error: {err.pretty}" + | .ok program => + runVerificationTest "Test 8 - Hidden Type Recursion" program + + + +/-- +info: "Test 1 - Constructor Application: PASSED\n Verified 1 obligation(s)\n" +-/ +#guard_msgs in +#eval test1_constructorApplication + +/-- +info: "Test 2 - Tester Functions: PASSED\n Verified 2 obligation(s)\n" +-/ +#guard_msgs in +#eval test2_testerFunctions + +/-- +info: "Test 3 - Destructor Functions: PASSED\n Verified 2 obligation(s)\n" +-/ +#guard_msgs in +#eval test3_destructorFunctions + +/-- +info: "Test 4 - Nested Datatypes: PASSED\n Verified 2 obligation(s)\n" +-/ +#guard_msgs in +#eval test4_nestedDatatypes + +/-- +info: "Test 5 - Tester with Havoc: PASSED\n Verified 1 obligation(s)\n" +-/ +#guard_msgs in +#eval test5_testerWithHavoc + +/-- +info: "Test 6 - Destructor with Havoc: PASSED\n Verified 1 obligation(s)\n" +-/ +#guard_msgs in +#eval test6_destructorWithHavoc + +/-- +info: "Test 7 - List with Havoc: PASSED\n Verified 1 obligation(s)\n" +-/ +#guard_msgs in +#eval test7_listWithHavoc + +/-- +info: "Test 8 - Hidden Type Recursion: PASSED\n Verified 1 obligation(s)\n" +-/ +#guard_msgs in +#eval test8_hiddenTypeRecursion + + +end Boogie.DatatypeVerificationTests diff --git a/Strata/Languages/Boogie/Examples/AdvancedMaps.lean b/StrataTest/Languages/Boogie/Examples/AdvancedMaps.lean similarity index 79% rename from Strata/Languages/Boogie/Examples/AdvancedMaps.lean rename to StrataTest/Languages/Boogie/Examples/AdvancedMaps.lean index 3103d0038..87065230b 100644 --- a/Strata/Languages/Boogie/Examples/AdvancedMaps.lean +++ b/StrataTest/Languages/Boogie/Examples/AdvancedMaps.lean @@ -51,7 +51,7 @@ spec { /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram mapPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram mapPgm) |>.snd |>.isEmpty /-- info: type MapII := (Map int int) @@ -61,25 +61,25 @@ var (b : (Map bool int)) := init_b_1 var (c : (Map int MapII)) := init_c_2 (procedure P : () → ()) modifies: [a, b, c] -preconditions: (P_requires_3, ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#0 : int))) (P_requires_4, ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) (#0 : int)) == (a : MapII))) +preconditions: (P_requires_3, ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #0)) (P_requires_4, ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) #0) == (a : MapII))) postconditions: ⏎ -body: assert [c_0_eq_a] ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) (#0 : int)) == (a : MapII)) -c := ((((~update : (arrow (Map int MapII) (arrow int (arrow MapII (Map int MapII))))) (c : (Map int MapII))) (#1 : int)) (a : MapII)) -assert [c_1_eq_a] ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) (#1 : int)) == (a : MapII)) -assert [a0eq0] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#0 : int)) -a := ((((~update : (arrow (Map int int) (arrow int (arrow int (Map int int))))) (a : MapII)) (#1 : int)) (#1 : int)) -assert [a1eq1] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#1 : int)) == (#1 : int)) -a := ((((~update : (arrow (Map int int) (arrow int (arrow int (Map int int))))) (a : MapII)) (#0 : int)) (#1 : int)) -assert [a0eq1] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#1 : int)) -assert [a0neq2] ((~Bool.Not : (arrow bool bool)) ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#2 : int))) -b := ((((~update : (arrow (Map bool int) (arrow bool (arrow int (Map bool int))))) (b : (Map bool int))) (#true : bool)) ((~Int.Neg : (arrow int int)) (#1 : int))) -assert [bTrueEqTrue] ((((~select : (arrow (Map bool int) (arrow bool int))) (b : (Map bool int))) (#true : bool)) == ((~Int.Neg : (arrow int int)) (#1 : int))) -assert [mix] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#1 : int)) == ((~Int.Neg : (arrow int int)) (((~select : (arrow (Map bool int) (arrow bool int))) (b : (Map bool int))) (#true : bool)))) +body: assert [c_0_eq_a] ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) #0) == (a : MapII)) +c := ((((~update : (arrow (Map int MapII) (arrow int (arrow MapII (Map int MapII))))) (c : (Map int MapII))) #1) (a : MapII)) +assert [c_1_eq_a] ((((~select : (arrow (Map int MapII) (arrow int MapII))) (c : (Map int MapII))) #1) == (a : MapII)) +assert [a0eq0] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #0) +a := ((((~update : (arrow (Map int int) (arrow int (arrow int (Map int int))))) (a : MapII)) #1) #1) +assert [a1eq1] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #1) == #1) +a := ((((~update : (arrow (Map int int) (arrow int (arrow int (Map int int))))) (a : MapII)) #0) #1) +assert [a0eq1] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #1) +assert [a0neq2] ((~Bool.Not : (arrow bool bool)) ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #2)) +b := ((((~update : (arrow (Map bool int) (arrow bool (arrow int (Map bool int))))) (b : (Map bool int))) #true) ((~Int.Neg : (arrow int int)) #1)) +assert [bTrueEqTrue] ((((~select : (arrow (Map bool int) (arrow bool int))) (b : (Map bool int))) #true) == ((~Int.Neg : (arrow int int)) #1)) +assert [mix] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #1) == ((~Int.Neg : (arrow int int)) (((~select : (arrow (Map bool int) (arrow bool int))) (b : (Map bool int))) #true))) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram mapPgm) +#eval TransM.run Inhabited.default (translateProgram mapPgm) /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/AdvancedQuantifiers.lean b/StrataTest/Languages/Boogie/Examples/AdvancedQuantifiers.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/AdvancedQuantifiers.lean rename to StrataTest/Languages/Boogie/Examples/AdvancedQuantifiers.lean diff --git a/Strata/Languages/Boogie/Examples/AssertionDefaultNames.lean b/StrataTest/Languages/Boogie/Examples/AssertionDefaultNames.lean similarity index 78% rename from Strata/Languages/Boogie/Examples/AssertionDefaultNames.lean rename to StrataTest/Languages/Boogie/Examples/AssertionDefaultNames.lean index 6b471eeda..4e758ead2 100644 --- a/Strata/Languages/Boogie/Examples/AssertionDefaultNames.lean +++ b/StrataTest/Languages/Boogie/Examples/AssertionDefaultNames.lean @@ -26,19 +26,19 @@ spec { /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram assertionNames) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram assertionNames) |>.snd |>.isEmpty /-- info: (procedure Test : ((x : int)) → ()) modifies: [] -preconditions: (Test_requires_0, ((x : int) == (#1 : int))) +preconditions: (Test_requires_0, ((x : int) == #1)) postconditions: ⏎ -body: assert [assert_0] ((x : int) == (#1 : int)) +body: assert [assert_0] ((x : int) == #1) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram assertionNames) +#eval TransM.run Inhabited.default (translateProgram assertionNames) /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/Axioms.lean b/StrataTest/Languages/Boogie/Examples/Axioms.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/Axioms.lean rename to StrataTest/Languages/Boogie/Examples/Axioms.lean diff --git a/Strata/Languages/Boogie/Examples/BitVecParse.lean b/StrataTest/Languages/Boogie/Examples/BitVecParse.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/BitVecParse.lean rename to StrataTest/Languages/Boogie/Examples/BitVecParse.lean diff --git a/Strata/Languages/Boogie/Examples/DDMAxiomsExtraction.lean b/StrataTest/Languages/Boogie/Examples/DDMAxiomsExtraction.lean similarity index 81% rename from Strata/Languages/Boogie/Examples/DDMAxiomsExtraction.lean rename to StrataTest/Languages/Boogie/Examples/DDMAxiomsExtraction.lean index 1787a18f2..f94a90621 100644 --- a/Strata/Languages/Boogie/Examples/DDMAxiomsExtraction.lean +++ b/StrataTest/Languages/Boogie/Examples/DDMAxiomsExtraction.lean @@ -36,7 +36,7 @@ def extractAxiomsDecl (prg: Boogie.Program) : (List Boogie.Decl) := /-- Extract the body LExpr from the axiom declaration -/ -def extractExpr (axDecl: Boogie.Decl): (Lambda.LExpr Lambda.LMonoTy Boogie.Visibility) := +def extractExpr (axDecl: Boogie.Decl): Boogie.Expression.Expr := match axDecl with | .ax a _ => a.e | _ => panic "Can be called only on axiom declaration" @@ -61,25 +61,24 @@ def transformSimpleTypeToFreeVariable (ty: Lambda.LMonoTy) (to_replace: List Str Transform all occurences of types of the form LMonoTy.tcons name [] into ftvar name, if name is in to_replace in the given expression -/ -def replaceTypesByFTV (expr: Lambda.LExpr Lambda.LMonoTy Boogie.Visibility) (to_replace: List String): Lambda.LExpr Lambda.LMonoTy Boogie.Visibility := +def replaceTypesByFTV (expr: Boogie.Expression.Expr) (to_replace: List String): Boogie.Expression.Expr := match expr with - | .const c oty => .const c (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) - | .op o oty => .op o (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) - | .fvar name oty => .fvar name (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) - | .mdata info e => .mdata info (replaceTypesByFTV e to_replace) - | .abs oty e => .abs (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) (replaceTypesByFTV e to_replace) - | .quant k oty tr e => .quant k (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) (replaceTypesByFTV tr to_replace) (replaceTypesByFTV e to_replace) - | .app fn e => .app (replaceTypesByFTV fn to_replace) (replaceTypesByFTV e to_replace) - | .ite c t e => .ite (replaceTypesByFTV c to_replace) (replaceTypesByFTV t to_replace) (replaceTypesByFTV e to_replace) - | .eq e1 e2 => .eq (replaceTypesByFTV e1 to_replace) (replaceTypesByFTV e2 to_replace) - | _ => expr + | .const m c => .const m c + | .op m o oty => .op m o (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) + | .fvar m name oty => .fvar m name (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) + | .bvar m i => .bvar m i + | .abs m oty e => .abs m (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) (replaceTypesByFTV e to_replace) + | .quant m k oty tr e => .quant m k (oty.map (fun t => transformSimpleTypeToFreeVariable t to_replace)) (replaceTypesByFTV tr to_replace) (replaceTypesByFTV e to_replace) + | .app m fn e => .app m (replaceTypesByFTV fn to_replace) (replaceTypesByFTV e to_replace) + | .ite m c t e => .ite m (replaceTypesByFTV c to_replace) (replaceTypesByFTV t to_replace) (replaceTypesByFTV e to_replace) + | .eq m e1 e2 => .eq m (replaceTypesByFTV e1 to_replace) (replaceTypesByFTV e2 to_replace) /-- Extract all axioms from the given environment by first translating it into a Boogie Program. It then extracts LExpr body from the axioms, and replace all occurences of the typeArgs by a ftvar with the same name -/ -def extractAxiomsWithFreeTypeVars (pgm: Program) (typeArgs: List String): (List (Lambda.LExpr Lambda.LMonoTy Boogie.Visibility)) := - let prg: Boogie.Program := (TransM.run (translateProgram pgm)).fst +def extractAxiomsWithFreeTypeVars (pgm: Program) (typeArgs: List String): (List Boogie.Expression.Expr) := + let prg: Boogie.Program := (TransM.run Inhabited.default (translateProgram pgm)).fst let axiomsDecls := extractAxiomsDecl prg let axioms := axiomsDecls.map extractExpr axioms.map (fun a => replaceTypesByFTV a typeArgs) @@ -396,101 +395,71 @@ info: #[{ ann := { start := { byteIdx := 295 }, stop := { byteIdx := 302 } }, #eval examplePgm.commands /-- -info: [Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"])) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.ftvar "k")) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.ftvar "v")) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.eq - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.op - { name := "select", metadata := Boogie.Visibility.unres } - (some (Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], - Lambda.LMonoTy.tcons "arrow" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]]))) - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.op - { name := "update", metadata := Boogie.Visibility.unres } - (some (Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], - Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.ftvar "k", - Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.ftvar "v", - Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]]]]))) - (Lambda.LExpr.bvar 2)) - (Lambda.LExpr.bvar 1)) - (Lambda.LExpr.bvar 0))) - (Lambda.LExpr.bvar 1)) - (Lambda.LExpr.bvar 0)))), - Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"])) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.ftvar "k")) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.ftvar "k")) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.quant - (Lambda.QuantifierKind.all) - (some (Lambda.LMonoTy.ftvar "v")) - (Lambda.LExpr.bvar 0) - (Lambda.LExpr.eq - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.op - { name := "select", metadata := Boogie.Visibility.unres } - (some (Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], - Lambda.LMonoTy.tcons "arrow" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]]))) - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.op - { name := "update", metadata := Boogie.Visibility.unres } - (some (Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], - Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.ftvar "k", - Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.ftvar "v", - Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]]]]))) - (Lambda.LExpr.bvar 3)) - (Lambda.LExpr.bvar 1)) - (Lambda.LExpr.bvar 0))) - (Lambda.LExpr.bvar 2)) - (Lambda.LExpr.app - (Lambda.LExpr.app - (Lambda.LExpr.op - { name := "select", metadata := Boogie.Visibility.unres } - (some (Lambda.LMonoTy.tcons - "arrow" - [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], - Lambda.LMonoTy.tcons "arrow" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]]))) - (Lambda.LExpr.bvar 3)) - (Lambda.LExpr.bvar 2))))))] +info: [LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.tcons + "Map" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]) (LExpr.bvar () 0) (LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.ftvar + "k") (LExpr.bvar () 0) (LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.ftvar + "v") (LExpr.bvar () 0) (LExpr.eq () (LExpr.app () (LExpr.app () (LExpr.op () { name := "select", + metadata := Boogie.Visibility.unres } (some Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]])) (LExpr.app () (LExpr.app () (LExpr.app () (LExpr.op () { name := "update", + metadata := Boogie.Visibility.unres } (some Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "v", + Lambda.LMonoTy.tcons + "Map" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]]]])) (LExpr.bvar () 2)) (LExpr.bvar () 1)) (LExpr.bvar () 0))) (LExpr.bvar () 1)) (LExpr.bvar () 0)))), + LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.tcons + "Map" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]) (LExpr.bvar () 0) (LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.ftvar + "k") (LExpr.bvar () 0) (LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.ftvar + "k") (LExpr.bvar () 0) (LExpr.quant () QuantifierKind.all (some Lambda.LMonoTy.ftvar + "v") (LExpr.bvar () 0) (LExpr.eq () (LExpr.app () (LExpr.app () (LExpr.op () { name := "select", + metadata := Boogie.Visibility.unres } (some Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]])) (LExpr.app () (LExpr.app () (LExpr.app () (LExpr.op () { name := "update", + metadata := Boogie.Visibility.unres } (some Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "v", + Lambda.LMonoTy.tcons + "Map" + [Lambda.LMonoTy.ftvar "k", + Lambda.LMonoTy.ftvar + "v"]]]])) (LExpr.bvar () 3)) (LExpr.bvar () 1)) (LExpr.bvar () 0))) (LExpr.bvar () 2)) (LExpr.app () (LExpr.app () (LExpr.op () { name := "select", + metadata := Boogie.Visibility.unres } (some Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.tcons "Map" [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"], + Lambda.LMonoTy.tcons + "arrow" + [Lambda.LMonoTy.ftvar "k", Lambda.LMonoTy.ftvar "v"]])) (LExpr.bvar () 3)) (LExpr.bvar () 2))))))] -/ #guard_msgs in #eval diff --git a/Strata/Languages/Boogie/Examples/DDMTransform.lean b/StrataTest/Languages/Boogie/Examples/DDMTransform.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/DDMTransform.lean rename to StrataTest/Languages/Boogie/Examples/DDMTransform.lean diff --git a/Strata/Languages/Boogie/Examples/FailingAssertion.lean b/StrataTest/Languages/Boogie/Examples/FailingAssertion.lean similarity index 77% rename from Strata/Languages/Boogie/Examples/FailingAssertion.lean rename to StrataTest/Languages/Boogie/Examples/FailingAssertion.lean index e8e9ce6ac..ec5a5e99a 100644 --- a/Strata/Languages/Boogie/Examples/FailingAssertion.lean +++ b/StrataTest/Languages/Boogie/Examples/FailingAssertion.lean @@ -32,21 +32,21 @@ spec { /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram failing) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram failing) |>.snd |>.isEmpty /-- info: type MapII := (Map int int) var (a : MapII) := init_a_0 (procedure P : () → ()) modifies: [a] -preconditions: (P_requires_1, ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#0 : int))) +preconditions: (P_requires_1, ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #0)) postconditions: ⏎ -body: assert [assert_0] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) (#0 : int)) == (#1 : int)) +body: assert [assert_0] ((((~select : (arrow (Map int int) (arrow int int))) (a : MapII)) #0) == #1) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram failing) +#eval TransM.run Inhabited.default (translateProgram failing) /-- info: [Strata.Boogie] Type checking succeeded. @@ -63,16 +63,17 @@ Proof Obligation: Wrote problem to vcs/assert_0.smt2. -Obligation assert_0: solver error! +Obligation assert_0: could not be proved! -Error: Cannot find model for id: f1 +Result: failed +CEx: ⏎ Evaluated program: type MapII := (Map int int) var (a : (Map int int)) := init_a_0 (procedure P : () → ()) modifies: [a] -preconditions: (P_requires_1, ((((~select : (arrow (Map int int) (arrow int int))) (a : (Map int int))) (#0 : int)) == (#0 : int))) +preconditions: (P_requires_1, ((((~select : (arrow (Map int int) (arrow int int))) (a : (Map int int))) #0) == #0)) postconditions: ⏎ body: assume [P_requires_1] (((~select $__a0) #0) == #0) assert [assert_0] (((~select $__a0) #0) == #1) @@ -80,7 +81,8 @@ assert [assert_0] (((~select $__a0) #0) == #1) --- info: Obligation: assert_0 -Result: err Cannot find model for id: f1 +Result: failed +CEx: -/ #guard_msgs in #eval verify "cvc5" failing @@ -108,32 +110,26 @@ info: Obligation assert_0: could not be proved! Result: failed -CEx: ($__x0, (- 1)) Obligation assert_1: could not be proved! Result: failed -CEx: ($__x0, (- 1)) Obligation assert_2: could not be proved! Result: failed -CEx: ($__x0, 7) --- info: Obligation: assert_0 Result: failed -CEx: ($__x0, (- 1)) Obligation: assert_1 Result: failed -CEx: ($__x0, (- 1)) Obligation: assert_2 Result: failed -CEx: ($__x0, 7) -/ #guard_msgs in -#eval verify "cvc5" failingThrice Options.quiet +#eval verify "cvc5" failingThrice Inhabited.default Options.quiet diff --git a/Strata/Languages/Boogie/Examples/FreeRequireEnsure.lean b/StrataTest/Languages/Boogie/Examples/FreeRequireEnsure.lean similarity index 84% rename from Strata/Languages/Boogie/Examples/FreeRequireEnsure.lean rename to StrataTest/Languages/Boogie/Examples/FreeRequireEnsure.lean index 0e90fba3f..6009bed32 100644 --- a/Strata/Languages/Boogie/Examples/FreeRequireEnsure.lean +++ b/StrataTest/Languages/Boogie/Examples/FreeRequireEnsure.lean @@ -74,19 +74,18 @@ Evaluated program: var (g : int) := init_g_0 (procedure Proc : () → ()) modifies: [g] -preconditions: (g_eq_15, ((g : int) == (#15 : int)) (Attribute: Boogie.Procedure.CheckAttr.Free)) -postconditions: (g_lt_10, (((~Int.Lt : (arrow int (arrow int bool))) (g : int)) (#10 : int)) (Attribute: Boogie.Procedure.CheckAttr.Free)) +preconditions: (g_eq_15, ((g : int) == #15) (Attribute: Boogie.Procedure.CheckAttr.Free)) +postconditions: (g_lt_10, (((~Int.Lt : (arrow int (arrow int bool))) (g : int)) #10) (Attribute: Boogie.Procedure.CheckAttr.Free)) body: assume [g_eq_15] ($__g0 == #15) assert [g_gt_10_internal] ((~Int.Gt $__g0) #10) g := ((~Int.Add $__g0) #1) -#[<[g_lt_10]: (((~Int.Lt : (arrow int (arrow int bool))) (g : int)) (#10 : int))>, - <[g_lt_10]: FreePostCondition>] assert [g_lt_10] #true +assert [g_lt_10] #true (procedure ProcCaller : () → ((x : int))) modifies: [] preconditions: ⏎ postconditions: ⏎ -body: #[] call Proc([]) +body: call Proc([]) assert [g_eq_15_internal] ($__g2 == #15) --- diff --git a/Strata/Languages/Boogie/Examples/Functions.lean b/StrataTest/Languages/Boogie/Examples/Functions.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/Functions.lean rename to StrataTest/Languages/Boogie/Examples/Functions.lean diff --git a/Strata/Languages/Boogie/Examples/GeneratedLabels.lean b/StrataTest/Languages/Boogie/Examples/GeneratedLabels.lean similarity index 96% rename from Strata/Languages/Boogie/Examples/GeneratedLabels.lean rename to StrataTest/Languages/Boogie/Examples/GeneratedLabels.lean index 7c9a89159..e55d370c9 100644 --- a/Strata/Languages/Boogie/Examples/GeneratedLabels.lean +++ b/StrataTest/Languages/Boogie/Examples/GeneratedLabels.lean @@ -46,11 +46,11 @@ axiom axiom_3: (∀ (∀ (∀ ((((~select : (arrow (Map Ref Struct) (arrow Ref S modifies: [] preconditions: ⏎ postconditions: ⏎ -body: init (newH : Heap) := ((((~update : (arrow (Map Ref Struct) (arrow Ref (arrow Struct (Map Ref Struct))))) (h : Heap)) (ref : Ref)) ((((~update : (arrow (Map Field int) (arrow Field (arrow int (Map Field int))))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field)) (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) (#1 : int)))) -assert [assert_0] ((((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (newH : Heap)) (ref : Ref))) (field : Field)) == (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) (#1 : int))) +body: init (newH : Heap) := ((((~update : (arrow (Map Ref Struct) (arrow Ref (arrow Struct (Map Ref Struct))))) (h : Heap)) (ref : Ref)) ((((~update : (arrow (Map Field int) (arrow Field (arrow int (Map Field int))))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field)) (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) #1))) +assert [assert_0] ((((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (newH : Heap)) (ref : Ref))) (field : Field)) == (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) #1)) -/ #guard_msgs in -#eval (TransM.run (translateProgram genLabelsPgm) |>.fst) +#eval (TransM.run Inhabited.default (translateProgram genLabelsPgm) |>.fst) /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/Goto.lean b/StrataTest/Languages/Boogie/Examples/Goto.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/Goto.lean rename to StrataTest/Languages/Boogie/Examples/Goto.lean diff --git a/Strata/Languages/Boogie/Examples/Havoc.lean b/StrataTest/Languages/Boogie/Examples/Havoc.lean similarity index 85% rename from Strata/Languages/Boogie/Examples/Havoc.lean rename to StrataTest/Languages/Boogie/Examples/Havoc.lean index 640952ee0..95d817068 100644 --- a/Strata/Languages/Boogie/Examples/Havoc.lean +++ b/StrataTest/Languages/Boogie/Examples/Havoc.lean @@ -24,7 +24,7 @@ procedure S() returns () /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram havocPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram havocPgm) |>.snd |>.isEmpty /-- info: (procedure S : () → ()) @@ -32,14 +32,14 @@ modifies: [] preconditions: ⏎ postconditions: ⏎ body: init (x : int) := init_x_0 -x := (#1 : int) +x := #1 havoc x -assert [x_eq_1] ((x : int) == (#1 : int)) +assert [x_eq_1] ((x : int) == #1) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram havocPgm) +#eval TransM.run Inhabited.default (translateProgram havocPgm) /-- info: [Strata.Boogie] Type checking succeeded. @@ -68,7 +68,7 @@ preconditions: ⏎ postconditions: ⏎ body: init (x : int) := init_x_0 x := #1 -#[] havoc x +havoc x assert [x_eq_1] ($__x0 == #1) --- diff --git a/Strata/Languages/Boogie/Examples/Loops.lean b/StrataTest/Languages/Boogie/Examples/Loops.lean similarity index 98% rename from Strata/Languages/Boogie/Examples/Loops.lean rename to StrataTest/Languages/Boogie/Examples/Loops.lean index e75ace691..c7f74eaa6 100644 --- a/Strata/Languages/Boogie/Examples/Loops.lean +++ b/StrataTest/Languages/Boogie/Examples/Loops.lean @@ -124,4 +124,4 @@ Obligation: arbitrary_iter_maintain_invariant_0 Result: verified -/ #guard_msgs in -#eval verify "cvc5" nestedPgm Options.quiet +#eval verify "cvc5" nestedPgm Inhabited.default Options.quiet diff --git a/Strata/Languages/Boogie/Examples/Map.lean b/StrataTest/Languages/Boogie/Examples/Map.lean similarity index 86% rename from Strata/Languages/Boogie/Examples/Map.lean rename to StrataTest/Languages/Boogie/Examples/Map.lean index 56d9bc194..0fd608ee6 100644 --- a/Strata/Languages/Boogie/Examples/Map.lean +++ b/StrataTest/Languages/Boogie/Examples/Map.lean @@ -26,7 +26,7 @@ procedure P() returns () /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram mapPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram mapPgm) |>.snd |>.isEmpty /-- info: func a : () → (Map int bool); @@ -34,14 +34,14 @@ info: func a : () → (Map int bool); modifies: [] preconditions: ⏎ postconditions: ⏎ -body: assume [a_zero_true_assumption] ((((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) (#0 : int)) == (#true : bool)) -assert [a_zero_true] (((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) (#0 : int)) -assert [a_one_true] (((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) (#1 : int)) +body: assume [a_zero_true_assumption] ((((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) #0) == #true) +assert [a_zero_true] (((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) #0) +assert [a_one_true] (((~select : (arrow (Map int bool) (arrow int bool))) (~a : (Map int bool))) #1) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram mapPgm) +#eval TransM.run Inhabited.default (translateProgram mapPgm) /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/Min.lean b/StrataTest/Languages/Boogie/Examples/Min.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/Min.lean rename to StrataTest/Languages/Boogie/Examples/Min.lean diff --git a/Strata/Languages/Boogie/Examples/OldExpressions.lean b/StrataTest/Languages/Boogie/Examples/OldExpressions.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/OldExpressions.lean rename to StrataTest/Languages/Boogie/Examples/OldExpressions.lean diff --git a/Strata/Languages/Boogie/Examples/PrecedenceCheck.lean b/StrataTest/Languages/Boogie/Examples/PrecedenceCheck.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/PrecedenceCheck.lean rename to StrataTest/Languages/Boogie/Examples/PrecedenceCheck.lean diff --git a/Strata/Languages/Boogie/Examples/ProcedureCall.lean b/StrataTest/Languages/Boogie/Examples/ProcedureCall.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/ProcedureCall.lean rename to StrataTest/Languages/Boogie/Examples/ProcedureCall.lean diff --git a/Strata/Languages/Boogie/Examples/Quantifiers.lean b/StrataTest/Languages/Boogie/Examples/Quantifiers.lean similarity index 98% rename from Strata/Languages/Boogie/Examples/Quantifiers.lean rename to StrataTest/Languages/Boogie/Examples/Quantifiers.lean index f69ec10f4..bfa37d299 100644 --- a/Strata/Languages/Boogie/Examples/Quantifiers.lean +++ b/StrataTest/Languages/Boogie/Examples/Quantifiers.lean @@ -105,7 +105,7 @@ Result: failed CEx: ($__x0, 0) -/ #guard_msgs in -#eval verify "cvc5" quantPgm Options.default +#eval verify "cvc5" quantPgm Inhabited.default Options.default /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean b/StrataTest/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean similarity index 94% rename from Strata/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean rename to StrataTest/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean index 6f425084a..191fe3ab2 100644 --- a/Strata/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean +++ b/StrataTest/Languages/Boogie/Examples/QuantifiersWithTypeAliases.lean @@ -33,7 +33,7 @@ procedure test(h: Heap, ref: Ref, field: Field) returns () #end -#guard TransM.run (translateProgram QuantTypeAliases) |>.snd |>.isEmpty +#guard TransM.run Inhabited.default (translateProgram QuantTypeAliases) |>.snd |>.isEmpty /-- info: type Boogie.Boundedness.Infinite Ref [] @@ -48,13 +48,13 @@ axiom axiom_3: (∀ (∀ (∀ ((((~select : (arrow (Map Ref Struct) (arrow Ref S modifies: [] preconditions: ⏎ postconditions: ⏎ -body: init (newH : Heap) := ((((~update : (arrow (Map Ref Struct) (arrow Ref (arrow Struct (Map Ref Struct))))) (h : Heap)) (ref : Ref)) ((((~update : (arrow (Map Field int) (arrow Field (arrow int (Map Field int))))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field)) (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) (#1 : int)))) -assert [assert0] ((((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (newH : Heap)) (ref : Ref))) (field : Field)) == (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) (#1 : int))) +body: init (newH : Heap) := ((((~update : (arrow (Map Ref Struct) (arrow Ref (arrow Struct (Map Ref Struct))))) (h : Heap)) (ref : Ref)) ((((~update : (arrow (Map Field int) (arrow Field (arrow int (Map Field int))))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field)) (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) #1))) +assert [assert0] ((((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (newH : Heap)) (ref : Ref))) (field : Field)) == (((~Int.Add : (arrow int (arrow int int))) (((~select : (arrow (Map Field int) (arrow Field int))) (((~select : (arrow (Map Ref Struct) (arrow Ref Struct))) (h : Heap)) (ref : Ref))) (field : Field))) #1)) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram QuantTypeAliases) +#eval TransM.run Inhabited.default (translateProgram QuantTypeAliases) /-- diff --git a/Strata/Languages/Boogie/Examples/RealBitVector.lean b/StrataTest/Languages/Boogie/Examples/RealBitVector.lean similarity index 81% rename from Strata/Languages/Boogie/Examples/RealBitVector.lean rename to StrataTest/Languages/Boogie/Examples/RealBitVector.lean index c4f79cd5b..62377d5c9 100644 --- a/Strata/Languages/Boogie/Examples/RealBitVector.lean +++ b/StrataTest/Languages/Boogie/Examples/RealBitVector.lean @@ -29,24 +29,24 @@ procedure P() returns () /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram realPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram realPgm) |>.snd |>.isEmpty /-- info: func x : () → real; func y : () → real; -axiom real_x_ge_1: (((~Real.Ge : (arrow real (arrow real bool))) (~x : real)) (#1.0 : real)); -axiom real_y_ge_2: (((~Real.Ge : (arrow real (arrow real bool))) (~y : real)) (#2.0 : real)); +axiom real_x_ge_1: (((~Real.Ge : (arrow real (arrow real bool))) (~x : real)) #1); +axiom real_y_ge_2: (((~Real.Ge : (arrow real (arrow real bool))) (~y : real)) #2); (procedure P : () → ()) modifies: [] preconditions: ⏎ postconditions: ⏎ -body: assert [real_add_ge_good] (((~Real.Ge : (arrow real (arrow real bool))) (((~Real.Add : (arrow real (arrow real real))) (~x : real)) (~y : real))) (#3.0 : real)) -assert [real_add_ge_bad] (((~Real.Ge : (arrow real (arrow real bool))) (((~Real.Add : (arrow real (arrow real real))) (~x : real)) (~y : real))) (#4.0 : real)) +body: assert [real_add_ge_good] (((~Real.Ge : (arrow real (arrow real bool))) (((~Real.Add : (arrow real (arrow real real))) (~x : real)) (~y : real))) #3) +assert [real_add_ge_bad] (((~Real.Ge : (arrow real (arrow real bool))) (((~Real.Add : (arrow real (arrow real real))) (~x : real)) (~y : real))) #4) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram realPgm) +#eval TransM.run Inhabited.default (translateProgram realPgm) /-- info: [Strata.Boogie] Type checking succeeded. @@ -56,18 +56,18 @@ VCs: Label: real_add_ge_good Assumptions: -(real_x_ge_1, ((~Real.Ge ~x) #1.0)) -(real_y_ge_2, ((~Real.Ge ~y) #2.0)) +(real_x_ge_1, ((~Real.Ge ~x) #1)) +(real_y_ge_2, ((~Real.Ge ~y) #2)) Proof Obligation: -((~Real.Ge ((~Real.Add ~x) ~y)) #3.0) +((~Real.Ge ((~Real.Add ~x) ~y)) #3) Label: real_add_ge_bad Assumptions: -(real_x_ge_1, ((~Real.Ge ~x) #1.0)) -(real_y_ge_2, ((~Real.Ge ~y) #2.0)) +(real_x_ge_1, ((~Real.Ge ~x) #1)) +(real_y_ge_2, ((~Real.Ge ~y) #2)) Proof Obligation: -((~Real.Ge ((~Real.Add ~x) ~y)) #4.0) +((~Real.Ge ((~Real.Add ~x) ~y)) #4) Wrote problem to vcs/real_add_ge_good.smt2. Wrote problem to vcs/real_add_ge_bad.smt2. @@ -81,14 +81,14 @@ CEx: ⏎ Evaluated program: func x : () → real; func y : () → real; -axiom real_x_ge_1: (((~Real.Ge : (arrow real (arrow real bool))) (~x : real)) (#1.0 : real)); -axiom real_y_ge_2: (((~Real.Ge : (arrow real (arrow real bool))) (~y : real)) (#2.0 : real)); +axiom real_x_ge_1: (((~Real.Ge : (arrow real (arrow real bool))) (~x : real)) #1); +axiom real_y_ge_2: (((~Real.Ge : (arrow real (arrow real bool))) (~y : real)) #2); (procedure P : () → ()) modifies: [] preconditions: ⏎ postconditions: ⏎ -body: assert [real_add_ge_good] ((~Real.Ge ((~Real.Add ~x) ~y)) #3.0) -assert [real_add_ge_bad] ((~Real.Ge ((~Real.Add ~x) ~y)) #4.0) +body: assert [real_add_ge_good] ((~Real.Ge ((~Real.Add ~x) ~y)) #3) +assert [real_add_ge_bad] ((~Real.Ge ((~Real.Add ~x) ~y)) #4) --- info: @@ -130,13 +130,13 @@ spec { /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram bvPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram bvPgm) |>.snd |>.isEmpty /-- info: func x : () → bv8; func y : () → bv8; -axiom bv_x_ge_1: (((~Bv8.ULe : (arrow bv8 (arrow bv8 bool))) (#1 : bv8)) (~x : bv8)); -axiom bv_y_ge_2: (((~Bv8.ULe : (arrow bv8 (arrow bv8 bool))) (#2 : bv8)) (~y : bv8)); +axiom bv_x_ge_1: (((~Bv8.ULe : (arrow bv8 (arrow bv8 bool))) #1) (~x : bv8)); +axiom bv_y_ge_2: (((~Bv8.ULe : (arrow bv8 (arrow bv8 bool))) #2) (~y : bv8)); (procedure P : () → ()) modifies: [] preconditions: ⏎ @@ -152,7 +152,7 @@ body: r := (((~Bv1.Add : (arrow bv1 (arrow bv1 bv1))) (x : bv1)) (x : bv1)) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram bvPgm) +#eval TransM.run Inhabited.default (translateProgram bvPgm) /-- info: [Strata.Boogie] Type checking succeeded. @@ -212,7 +212,6 @@ info: Obligation bad_shift: could not be proved! Result: failed -CEx: ($__x0, #b10011001) ($__y1, #b00000010) --- info: Obligation: add_comm @@ -235,7 +234,6 @@ Result: verified Obligation: bad_shift Result: failed -CEx: ($__x0, #b10011001) ($__y1, #b00000010) -/ #guard_msgs in -#eval verify "cvc5" bvMoreOpsPgm Options.quiet +#eval verify "cvc5" bvMoreOpsPgm Inhabited.default Options.quiet diff --git a/Strata/Languages/Boogie/Examples/RecursiveProcIte.lean b/StrataTest/Languages/Boogie/Examples/RecursiveProcIte.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/RecursiveProcIte.lean rename to StrataTest/Languages/Boogie/Examples/RecursiveProcIte.lean diff --git a/Strata/Languages/Boogie/Examples/Regex.lean b/StrataTest/Languages/Boogie/Examples/Regex.lean similarity index 55% rename from Strata/Languages/Boogie/Examples/Regex.lean rename to StrataTest/Languages/Boogie/Examples/Regex.lean index dbff68430..d79b02378 100644 --- a/Strata/Languages/Boogie/Examples/Regex.lean +++ b/StrataTest/Languages/Boogie/Examples/Regex.lean @@ -17,6 +17,10 @@ function cannot_end_with_period () : regex { re.comp(re.concat (re.* (re.all()), str.to.re("."))) } +function optionally_a () : regex { + re.loop(str.to.re("a"), 0, 1) +} + function ok_chars_regex () : regex { re.loop( re.union(re.range("a", "z"), @@ -37,6 +41,8 @@ procedure main() returns () { assert [has_to_be_at_least_1_char]: (!(str.in.re("", ok_chars_regex()))); assert [cannot_exceed_10_chars]: (!(str.in.re("0123456789a", ok_chars_regex()))); + assert [optionally_a_check1]: (str.in.re("a", optionally_a())); + assert [optionally_a_check2]: (!(str.in.re("b", optionally_a()))); }; #end @@ -95,6 +101,20 @@ Assumptions: Proof Obligation: (~Bool.Not ((~Str.InRegEx #0123456789a) ~ok_chars_regex)) +Label: optionally_a_check1 +Assumptions: + + +Proof Obligation: +((~Str.InRegEx #a) ~optionally_a) + +Label: optionally_a_check2 +Assumptions: + + +Proof Obligation: +(~Bool.Not ((~Str.InRegEx #b) ~optionally_a)) + Wrote problem to vcs/hello_dot_ends_with_period.smt2. Wrote problem to vcs/dot_ends_with_period.smt2. Wrote problem to vcs/bye_exclaim_no_end_with_period.smt2. @@ -102,6 +122,8 @@ Wrote problem to vcs/ok_chars_str.smt2. Wrote problem to vcs/cannot_contain_exclaim.smt2. Wrote problem to vcs/has_to_be_at_least_1_char.smt2. Wrote problem to vcs/cannot_exceed_10_chars.smt2. +Wrote problem to vcs/optionally_a_check1.smt2. +Wrote problem to vcs/optionally_a_check2.smt2. --- info: Obligation: hello_dot_ends_with_period @@ -124,6 +146,12 @@ Result: verified Obligation: cannot_exceed_10_chars Result: verified + +Obligation: optionally_a_check1 +Result: verified + +Obligation: optionally_a_check2 +Result: verified -/ #guard_msgs in #eval verify "cvc5" regexPgm1 @@ -140,8 +168,16 @@ function bad_re_loop (n : int) : regex { procedure main(n : int) returns () { + var n1 : int; + n1 := 1; + assert (!(str.in.re("0123456789a", bad_re_loop(n)))); + // NOTE: If `bad_re_loop` was inlined, we wouldn't get this + // SMT encoding error because then `n1` would be replaced by + // `1` by the time `re.loop` is encoded. + assert (str.in.re("a", bad_re_loop(n1))); + }; #end @@ -157,17 +193,44 @@ Assumptions: Proof Obligation: (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +Label: assert_1 +Assumptions: + + +Proof Obligation: +((~Str.InRegEx #a) (~bad_re_loop #1)) + [Error] SMT Encoding error for obligation assert_0: ⏎ Natural numbers expected as indices for re.loop. Original expression: (((~Re.Loop ((~Re.Range #a) #z)) #1) %0) Evaluated program: func bad_re_loop : ((n : int)) → regex := - (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) (#a : string)) (#z : string))) (#1 : int)) (n : int))) + (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) #a) #z)) #1) (n : int))) (procedure main : ((n : int)) → ()) modifies: [] preconditions: ⏎ postconditions: ⏎ -body: assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +body: init (n1 : int) := init_n1_0 +n1 := #1 +assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +assert [assert_1] ((~Str.InRegEx #a) (~bad_re_loop #1)) + + + +[Error] SMT Encoding error for obligation assert_1: ⏎ +Natural numbers expected as indices for re.loop. +Original expression: (((~Re.Loop ((~Re.Range #a) #z)) #1) %0) + +Evaluated program: func bad_re_loop : ((n : int)) → regex := + (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) #a) #z)) #1) (n : int))) +(procedure main : ((n : int)) → ()) +modifies: [] +preconditions: ⏎ +postconditions: ⏎ +body: init (n1 : int) := init_n1_0 +n1 := #1 +assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +assert [assert_1] ((~Str.InRegEx #a) (~bad_re_loop #1)) @@ -179,12 +242,71 @@ Natural numbers expected as indices for re.loop. Original expression: (((~Re.Loop ((~Re.Range #a) #z)) #1) %0) Evaluated program: func bad_re_loop : ((n : int)) → regex := - (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) (#a : string)) (#z : string))) (#1 : int)) (n : int))) + (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) #a) #z)) #1) (n : int))) (procedure main : ((n : int)) → ()) modifies: [] preconditions: ⏎ postconditions: ⏎ -body: assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +body: init (n1 : int) := init_n1_0 +n1 := #1 +assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +assert [assert_1] ((~Str.InRegEx #a) (~bad_re_loop #1)) + + + + +Obligation: assert_1 +Result: err [Error] SMT Encoding error for obligation assert_1: ⏎ +Natural numbers expected as indices for re.loop. +Original expression: (((~Re.Loop ((~Re.Range #a) #z)) #1) %0) + +Evaluated program: func bad_re_loop : ((n : int)) → regex := + (((((~Re.Loop : (arrow regex (arrow int (arrow int regex)))) (((~Re.Range : (arrow string (arrow string regex))) #a) #z)) #1) (n : int))) +(procedure main : ((n : int)) → ()) +modifies: [] +preconditions: ⏎ +postconditions: ⏎ +body: init (n1 : int) := init_n1_0 +n1 := #1 +assert [assert_0] (~Bool.Not ((~Str.InRegEx #0123456789a) (~bad_re_loop $__n0))) +assert [assert_1] ((~Str.InRegEx #a) (~bad_re_loop #1)) -/ #guard_msgs in #eval verify "cvc5" regexPgm2 + +--------------------------------------------------------------------- + +def regexPgm3 := +#strata +program Boogie; + +procedure main(n : int) returns () { + + var s : string; + assert (!(str.in.re(s, re.none()))); + +}; +#end + +/-- +info: [Strata.Boogie] Type checking succeeded. + + +VCs: +Label: assert_0 +Assumptions: + + +Proof Obligation: +(~Bool.Not ((~Str.InRegEx init_s_0) ~Re.None)) + +Wrote problem to vcs/assert_0.smt2. +--- +info: +Obligation: assert_0 +Result: verified +-/ +#guard_msgs in +#eval verify "cvc5" regexPgm3 + +--------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean b/StrataTest/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean similarity index 89% rename from Strata/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean rename to StrataTest/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean index 805c1ad89..16ff84ac8 100644 --- a/Strata/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean +++ b/StrataTest/Languages/Boogie/Examples/RemoveIrrelevantAxioms.lean @@ -101,49 +101,41 @@ Result: unknown Obligation assert_4: could not be proved! Result: failed -CEx: ($__x0, 3) Obligation assert_5: could not be proved! Result: failed -CEx: ($__x0, 3) Obligation assert_6: could not be proved! Result: failed -CEx: ($__x1, 3) Obligation assert_7: could not be proved! Result: failed -CEx: ($__x1, 3) Obligation assert_8: could not be proved! Result: failed -CEx: ($__x2, 3) Obligation assert_9: could not be proved! Result: failed -CEx: ($__x2, 3) Obligation assert_10: could not be proved! Result: failed -CEx: ($__x3, 3) Obligation assert_11: could not be proved! Result: failed -CEx: ($__x3, 3) --- info: Obligation: assert_0 @@ -160,38 +152,30 @@ Result: unknown Obligation: assert_4 Result: failed -CEx: ($__x0, 3) Obligation: assert_5 Result: failed -CEx: ($__x0, 3) Obligation: assert_6 Result: failed -CEx: ($__x1, 3) Obligation: assert_7 Result: failed -CEx: ($__x1, 3) Obligation: assert_8 Result: failed -CEx: ($__x2, 3) Obligation: assert_9 Result: failed -CEx: ($__x2, 3) Obligation: assert_10 Result: failed -CEx: ($__x3, 3) Obligation: assert_11 Result: failed -CEx: ($__x3, 3) -/ #guard_msgs in -#eval verify "z3" irrelevantAxiomsTestPgm {Options.quiet with removeIrrelevantAxioms := true} +#eval verify "z3" irrelevantAxiomsTestPgm Inhabited.default {Options.quiet with removeIrrelevantAxioms := true} --------------------------------------------------------------------- @@ -285,6 +269,6 @@ Obligation: assert_11 Result: unknown -/ #guard_msgs in -#eval verify "z3" irrelevantAxiomsTestPgm {Options.quiet with removeIrrelevantAxioms := false} +#eval verify "z3" irrelevantAxiomsTestPgm Inhabited.default {Options.quiet with removeIrrelevantAxioms := false} --------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/Examples/SimpleProc.lean b/StrataTest/Languages/Boogie/Examples/SimpleProc.lean similarity index 91% rename from Strata/Languages/Boogie/Examples/SimpleProc.lean rename to StrataTest/Languages/Boogie/Examples/SimpleProc.lean index 3c81fe4b4..c2358d21d 100644 --- a/Strata/Languages/Boogie/Examples/SimpleProc.lean +++ b/StrataTest/Languages/Boogie/Examples/SimpleProc.lean @@ -29,7 +29,7 @@ spec { /-- info: true -/ #guard_msgs in -- No errors in translation. -#eval TransM.run (translateProgram simpleProcPgm) |>.snd |>.isEmpty +#eval TransM.run Inhabited.default (translateProgram simpleProcPgm) |>.snd |>.isEmpty /-- info: var (g : bool) := init_g_0 @@ -42,7 +42,7 @@ body: y := (((~Bool.Or : (arrow bool (arrow bool bool))) (x : bool)) (x : bool)) Errors: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram simpleProcPgm) +#eval TransM.run Inhabited.default (translateProgram simpleProcPgm) /-- info: [Strata.Boogie] Type checking succeeded. diff --git a/Strata/Languages/Boogie/Examples/String.lean b/StrataTest/Languages/Boogie/Examples/String.lean similarity index 61% rename from Strata/Languages/Boogie/Examples/String.lean rename to StrataTest/Languages/Boogie/Examples/String.lean index 3ba51ee01..bd0d64eff 100644 --- a/Strata/Languages/Boogie/Examples/String.lean +++ b/StrataTest/Languages/Boogie/Examples/String.lean @@ -29,6 +29,10 @@ procedure main() returns () { assume [s1_s2_concat_eq_s3]: str.concat(s1, s2) == s3; assert [s1_s2_len_sum_eq_s3_len]: str.len(s1) + str.len(s2) == str.len(s3); + + assert [substr_of_concat]: (str.substr(str.concat(s1,s2), 0, str.len(s1)) == s1); + + assert [substr_of_concat_concrete_test]: (str.substr("testing123", 2, 0) == ""); }; #end @@ -52,7 +56,25 @@ Assumptions: Proof Obligation: (((~Int.Add (~Str.Length init_s1_0)) (~Str.Length init_s2_1)) == (~Str.Length init_s3_2)) +Label: substr_of_concat +Assumptions: +(s1_len, ((~Str.Length init_s1_0) == #3)) +(s2_len, ((~Str.Length init_s2_1) == #3)) (s1_s2_concat_eq_s3, (((~Str.Concat init_s1_0) init_s2_1) == init_s3_2)) + +Proof Obligation: +((((~Str.Substr ((~Str.Concat init_s1_0) init_s2_1)) #0) (~Str.Length init_s1_0)) == init_s1_0) + +Label: substr_of_concat_concrete_test +Assumptions: +(s1_len, ((~Str.Length init_s1_0) == #3)) +(s2_len, ((~Str.Length init_s2_1) == #3)) (s1_s2_concat_eq_s3, (((~Str.Concat init_s1_0) init_s2_1) == init_s3_2)) + +Proof Obligation: +((((~Str.Substr #testing123) #2) #0) == #) + Wrote problem to vcs/s1_s2_len_sum_eq_s3_len.smt2. +Wrote problem to vcs/substr_of_concat.smt2. +Wrote problem to vcs/substr_of_concat_concrete_test.smt2. --- info: Obligation: concrete_string_test @@ -60,6 +82,12 @@ Result: verified Obligation: s1_s2_len_sum_eq_s3_len Result: verified + +Obligation: substr_of_concat +Result: verified + +Obligation: substr_of_concat_concrete_test +Result: verified -/ #guard_msgs in #eval verify "cvc5" strPgm diff --git a/Strata/Languages/Boogie/Examples/TypeAlias.lean b/StrataTest/Languages/Boogie/Examples/TypeAlias.lean similarity index 71% rename from Strata/Languages/Boogie/Examples/TypeAlias.lean rename to StrataTest/Languages/Boogie/Examples/TypeAlias.lean index 2c14418f3..9ba1df034 100644 --- a/Strata/Languages/Boogie/Examples/TypeAlias.lean +++ b/StrataTest/Languages/Boogie/Examples/TypeAlias.lean @@ -54,7 +54,7 @@ procedure P () returns () { /-- info: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram goodTypeAlias) |>.snd +#eval TransM.run Inhabited.default (translateProgram goodTypeAlias) |>.snd /-- info: type Boogie.Boundedness.Infinite Foo [_, _] @@ -72,7 +72,7 @@ assume [fooConst2_value] ((~fooConst2 : (Foo int bool)) == (~fooVal : (FooAlias2 assert [fooAssertion] ((~fooConst1 : (Foo int bool)) == (~fooConst2 : (Foo int bool))) -/ #guard_msgs in -#eval TransM.run (translateProgram goodTypeAlias) |>.fst +#eval TransM.run Inhabited.default (translateProgram goodTypeAlias) |>.fst /-- info: [Strata.Boogie] Type checking succeeded. @@ -97,3 +97,45 @@ Result: verified #eval verify "cvc5" goodTypeAlias -------------------------------------------------------------------- + +def funcAndTypeAliasesPgm : Program := +#strata +program Boogie; + +type MapInt := Map int int; + +function MapInt_get (d : MapInt, k : int) : int; +function MapGetEq (d: MapInt, k: int, v : int) : bool { + MapInt_get (d, k) == v +} + +procedure test () returns () { + var d : MapInt, k : int, v : int, b : bool; + b := MapGetEq(d, k, v); + assume (v == 0); + assert (b == MapGetEq(d, k, 0)); +}; +#end + +/-- +info: [Strata.Boogie] Type checking succeeded. + + +VCs: +Label: assert_0 +Assumptions: +(assume_0, (init_v_2 == #0)) + +Proof Obligation: +((((~MapGetEq init_d_0) init_k_1) init_v_2) == (((~MapGetEq init_d_0) init_k_1) #0)) + +Wrote problem to vcs/assert_0.smt2. +--- +info: +Obligation: assert_0 +Result: verified +-/ +#guard_msgs in +#eval verify "cvc5" funcAndTypeAliasesPgm + +-------------------------------------------------------------------- diff --git a/Strata/Languages/Boogie/Examples/TypeDecl.lean b/StrataTest/Languages/Boogie/Examples/TypeDecl.lean similarity index 90% rename from Strata/Languages/Boogie/Examples/TypeDecl.lean rename to StrataTest/Languages/Boogie/Examples/TypeDecl.lean index bbbcc691e..749841018 100644 --- a/Strata/Languages/Boogie/Examples/TypeDecl.lean +++ b/StrataTest/Languages/Boogie/Examples/TypeDecl.lean @@ -25,7 +25,7 @@ procedure P () returns () { /-- info: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram typeDeclPgm1) |>.snd +#eval TransM.run Inhabited.default (translateProgram typeDeclPgm1) |>.snd /-- info: [Strata.Boogie] Type checking succeeded. @@ -86,7 +86,7 @@ procedure P () returns () { /-- info: #[] -/ #guard_msgs in -#eval TransM.run (translateProgram typeDeclPgm3) |>.snd +#eval TransM.run Inhabited.default (translateProgram typeDeclPgm3) |>.snd /-- info: [Strata.Boogie] Type checking succeeded. @@ -120,7 +120,8 @@ type int := bool; #end /-- -error: [Strata.Boogie] Type checking error: This type declaration's name is reserved! +error: [Strata.Boogie] Type checking error. +(0, (0-0)) This type declaration's name is reserved! int := bool KnownTypes' names: [arrow, TriggerGroup, real, string, bitvec, Triggers, int, bool, Map, regex] diff --git a/Strata/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean b/StrataTest/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean similarity index 90% rename from Strata/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean rename to StrataTest/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean index 3a5c39c45..d84cf20fa 100644 --- a/Strata/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean +++ b/StrataTest/Languages/Boogie/Examples/TypeVarImplicitlyQuantified.lean @@ -30,7 +30,7 @@ axiom [a2]: (forall l_0: bool, l_1: int, l_2: int, y: int :: ); #end -def boogie_pgm := TransM.run (translateProgram pgm) +def boogie_pgm := TransM.run Inhabited.default (translateProgram pgm) /-- info: true -/ #guard_msgs in @@ -41,7 +41,7 @@ info: [Strata.Boogie] Type checking succeeded. --- info: ok: type set := (Map int bool) -func diff : ((a : set) (b : set)) → set; +func diff : ((a : (Map int bool)) (b : (Map int bool))) → (Map int bool); func lambda_0 : ((l_0 : bool) (l_1 : int) (l_2 : int)) → (Map int int); axiom a1: (∀ (∀ ((((~diff : (arrow (Map int bool) (arrow (Map int bool) (Map int bool)))) %1) %0) == (((~diff : (arrow (Map int bool) (arrow (Map int bool) (Map int bool)))) %0) %1)))); axiom a2: (∀ (∀ (∀ (∀ ((((~select : (arrow (Map int int) (arrow int int))) ((((~lambda_0 : (arrow bool (arrow int (arrow int (Map int int))))) %3) %2) %1)) %0) == (((~select : (arrow (Map int int) (arrow int int))) ((((~lambda_0 : (arrow bool (arrow int (arrow int (Map int int))))) %3) %1) %2)) %0)))))); diff --git a/Strata/Languages/Boogie/Examples/UnreachableAssert.lean b/StrataTest/Languages/Boogie/Examples/UnreachableAssert.lean similarity index 100% rename from Strata/Languages/Boogie/Examples/UnreachableAssert.lean rename to StrataTest/Languages/Boogie/Examples/UnreachableAssert.lean diff --git a/StrataTest/Languages/Boogie/ExprEvalTest.lean b/StrataTest/Languages/Boogie/ExprEvalTest.lean new file mode 100644 index 000000000..59c06d97f --- /dev/null +++ b/StrataTest/Languages/Boogie/ExprEvalTest.lean @@ -0,0 +1,218 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Lambda.Lambda +import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.LState +import Strata.DL.Lambda.LTy +import Strata.DL.SMT.Term +import Strata.DL.SMT.Encoder +import Strata.Languages.Boogie.Env +import Strata.Languages.Boogie.Factory +import Strata.Languages.Boogie.Identifiers +import Strata.Languages.Boogie.Options +import Strata.Languages.Boogie.SMTEncoder +import Strata.Languages.Boogie.Verifier +import Strata.DL.Lambda.TestGen +import Strata.DL.Lambda.PlausibleHelpers +import Plausible.Gen + +/-! This file does random testing of Boogie operations registered in factory, by +(1) choosing random constant inputs to the operations +(2) doing concrete evaluation and getting the results, +(3) SMT encoding the expression, and +(4) checking using the SMT solver whether the concrete output is equal to +the SMT expression. +-/ + +namespace Boogie + +section Tests + +open Lambda +open Std + +def encode (e:LExpr BoogieLParams.mono) + (tenv:TEnv Visibility) + (init_state:LState BoogieLParams): + Except Format (Option (Strata.SMT.Term × SMT.Context)) + := do + let init_state ← init_state.addFactory Boogie.Factory + let lcont := { Lambda.LContext.default with + functions := Boogie.Factory, knownTypes := Boogie.KnownTypes } + let (e,_T) ← LExpr.annotate lcont tenv e + let e_res := LExpr.eval init_state.config.fuel init_state e + match e_res with + | .const _ _ => + let env := Boogie.Env.init + let (smt_term_lhs,ctx) ← Boogie.toSMTTerm env [] e SMT.Context.default + let (smt_term_rhs,ctx) ← Boogie.toSMTTerm env [] e_res ctx + let smt_term_eq := Strata.SMT.Factory.eq smt_term_lhs smt_term_rhs + return (.some (smt_term_eq, ctx)) + | _ => return .none + +/-- +Check whether concrete evaluation of e matches the SMT encoding of e. +Returns false if e did not reduce to a constant. +-/ +def checkValid (e:LExpr BoogieLParams.mono): IO Bool := do + let tenv := TEnv.default + let init_state := LState.init + match encode e tenv init_state with + | .error msg => throw (IO.userError s!"error: {msg}") + | .ok (.none) => return false + | .ok (.some (smt_term, ctx)) => + let ans ← Boogie.dischargeObligation + { Options.default with verbose := false } + (LExpr.freeVars e) "z3" s!"exprEvalTest.smt2" + [smt_term] ctx + match ans with + | .ok (.sat _,_) => return true + | _ => + IO.println s!"Test failed on {e}" + IO.println s!"The query: {repr smt_term}" + throw (IO.userError "- failed") + +/-- +If a randomly chosen value is <= odd / 10, pick from interesting vals, +otherwise fallback. +-/ +private def pickInterestingValue {α} [Inhabited α] + (odd: Nat) (interesting_vals:List α) (fallback:IO α): IO α + := do + if interesting_vals.isEmpty then + fallback + else + let n := interesting_vals.length + let k <- IO.rand 0 9 + if k <= odd then + let idx <- IO.rand 0 (n - 1) + return interesting_vals.getD idx Inhabited.default + else + fallback + +private def pickRandInt (abs_bound:Nat): IO Int := do + let rand_sign <- IO.rand 0 1 + let rand_size <- IO.rand 0 abs_bound + return (if rand_sign = 0 then rand_size else - (Int.ofNat rand_size)) + +private def mkRandConst (ty:LMonoTy): IO (Option (LExpr BoogieLParams.mono)) + := do + match ty with + | .tcons "int" [] => + let i <- pickInterestingValue 1 [0,1,-1] (pickRandInt 2147483648) + return (.some (.intConst () i)) + | .tcons "bool" [] => + let rand_flag <- IO.rand 0 1 + let rand_flag := rand_flag == 0 + return (.some (.boolConst () rand_flag)) + | .tcons "real" [] => + let i <- pickInterestingValue 1 [0,1,-1] (pickRandInt 2147483648) + let n <- IO.rand 1 2147483648 + return (.some (.realConst () (mkRat i n))) + | .tcons "string" [] => + -- TODO: random string generator + return (.some (.strConst () "a")) + | .tcons "regex" [] => + -- TODO: random regex generator + return (.some (.app () + (.op () (BoogieIdent.unres "Str.ToRegEx") .none) (.strConst () ".*"))) + | .bitvec n => + let specialvals := + [0, 1, -1, Int.ofNat n, (Int.pow 2 (n-1)) - 1, -(Int.pow 2 (n-1))] + let i <- pickInterestingValue 3 specialvals (IO.rand 0 ((Nat.pow 2 n) - 1)) + return (.some (.bitvecConst () n (BitVec.ofInt n i))) + | _ => + return .none + +def checkFactoryOps (verbose:Bool): IO Unit := do + let arr:Array (LFunc BoogieLParams) := Boogie.Factory + let print (f:Format): IO Unit := + if verbose then IO.println f + else return () + for e in arr do + print f!"\nOp: {e.name} {e.inputs}" + if ¬ e.typeArgs.isEmpty then + print "- Has non-empty type arguments, skipping..." + continue + else + let cnt := 50 + let mut unsupported := false + let mut cnt_skipped := 0 + for _ in [0:cnt] do + let args:List (Option (LExpr BoogieLParams.mono)) + <- e.inputs.mapM (fun t => do + let res <- mkRandConst t.snd + match res with + | .some x => return (.some x) + | .none => + print s!"- Don't know how to create a constant for {t.snd}" + return .none) + if args.any (· == .none) then + unsupported := true + break + else + let args := List.map (Option.get!) args + let expr := List.foldl (fun e arg => (.app () e arg)) + (LExpr.op () (BoogieIdent.unres e.name.name) .none) args + let res <- checkValid expr + if ¬ res then + if cnt_skipped = 0 then + print f!"- did not evaluate to a constant; inputs: {args}" + print " (will omit printing other skipped cases)" + cnt_skipped := cnt_skipped + 1 + continue + if not unsupported then + print s!"- Total {cnt} tests passed, {cnt_skipped} tests skipped" + + +open Lambda.LExpr.SyntaxMono +open Lambda.LExpr.Syntax +open Lambda.LTy.Syntax + +/-- info: true -/ +#guard_msgs in #eval (checkValid eb[#100]) +/-- info: true -/ +#guard_msgs in #eval (checkValid eb[#true]) +/-- info: true -/ +#guard_msgs in #eval (checkValid eb[#1 == #2]) +/-- info: true -/ +#guard_msgs in #eval (checkValid eb[if #1 == #2 then #false else #true]) +/-- info: true -/ +#guard_msgs in #eval (checkValid + (.app () (.app () (.op () (BoogieIdent.unres "Int.Add") .none) eb[#100]) eb[#50])) + + +-- This may take a while (~ 1min) +#eval (checkFactoryOps false) + +open Plausible TestGen + +deriving instance Arbitrary for Visibility + +def test_lctx : LContext BoogieLParams := +{ + LContext.empty with + functions := Boogie.Factory + knownTypes := Boogie.KnownTypes +} + +def test_ctx : TContext Visibility := ⟨[[]], []⟩ + +abbrev test_ty : LTy := .forAll [] <| .tcons "bool" [] + +#guard_msgs(drop all) in +#eval do + let P : LExpr BoogieLParams.mono → Prop := fun t => HasType test_lctx test_ctx t test_ty + let t ← Gen.runUntil .none (ArbitrarySizedSuchThat.arbitrarySizedST P 5) 5 + IO.println s!"Generated {t}" + let b ← checkValid t + if ¬ b then + IO.println s!"Invalid!" + +end Tests + +end Boogie diff --git a/StrataTest/Languages/Boogie/ProcedureEvalTests.lean b/StrataTest/Languages/Boogie/ProcedureEvalTests.lean index 5f049d3ac..2e529367f 100644 --- a/StrataTest/Languages/Boogie/ProcedureEvalTests.lean +++ b/StrataTest/Languages/Boogie/ProcedureEvalTests.lean @@ -15,7 +15,7 @@ open Std (ToFormat Format format) open Procedure Statement Lambda Lambda.LTy.Syntax Lambda.LExpr.SyntaxMono Boogie.Syntax /-- -info: ok: Error: +info: Error: none Subst Map: (x, ($__x0 : int)) (y, ($__y1 : int)) @@ -54,6 +54,7 @@ func Bool.Equiv : ((x : bool) (y : bool)) → bool; func Bool.Not : ((x : bool)) → bool; func Str.Length : ((x : string)) → int; func Str.Concat : ((x : string) (y : string)) → string; +func Str.Substr : ((x : string) (i : int) (n : int)) → string; func Str.ToRegEx : ((x : string)) → regex; func Str.InRegEx : ((x : string) (y : regex)) → bool; func Re.All : () → regex; @@ -66,6 +67,7 @@ func Re.Loop : ((x : regex) (n1 : int) (n2 : int)) → regex; func Re.Union : ((x : regex) (y : regex)) → regex; func Re.Inter : ((x : regex) (y : regex)) → regex; func Re.Comp : ((x : regex)) → regex; +func Re.None : () → regex; func old : ∀[a]. ((x : a)) → a; func select : ∀[k, v]. ((m : (Map k v)) (i : k)) → v; func update : ∀[k, v]. ((m : (Map k v)) (i : k) (x : v)) → (Map k v); @@ -202,6 +204,8 @@ func Bv64.SGt : ((x : bv64) (y : bv64)) → bool; func Bv64.SGe : ((x : bv64) (y : bv64)) → bool; +Datatypes: + Path Conditions: @@ -215,7 +219,7 @@ Proof Obligation: ((~Int.Lt (~Int.Neg ($__x0 : int))) #0) -/ #guard_msgs in -#eval do let E ← Env.init.addFactory Boogie.Factory +#eval do let E := Env.init let (_proc, E) := evalOne E { header := {name := "P", typeArgs := [], @@ -223,8 +227,8 @@ Proof Obligation: outputs := [("y", mty[int])] }, spec := { modifies := [], - preconditions := [("0_lt_x", ⟨eb[((~Int.Lt #0) x)], .Default⟩)], - postconditions := [("ret_y_lt_0", ⟨eb[((~Int.Lt y) #0)], .Default⟩)] }, + preconditions := [("0_lt_x", ⟨eb[((~Int.Lt #0) x)], .Default, #[]⟩)], + postconditions := [("ret_y_lt_0", ⟨eb[((~Int.Lt y) #0)], .Default, #[]⟩)] }, body := [ Statement.set "y" eb[(~Int.Neg x)] ] diff --git a/StrataTest/Languages/Boogie/ProcedureTypeTests.lean b/StrataTest/Languages/Boogie/ProcedureTypeTests.lean index 6c99d3d31..0f2eb4e10 100644 --- a/StrataTest/Languages/Boogie/ProcedureTypeTests.lean +++ b/StrataTest/Languages/Boogie/ProcedureTypeTests.lean @@ -17,12 +17,12 @@ open Procedure Statement Lambda Lambda.LTy.Syntax Lambda.LExpr.SyntaxMono Boogie /-- info: ok: ((procedure P : ((x : int)) → ((y : int))) modifies: [] - preconditions: (0_lt_x, (((~Int.Lt : (arrow int (arrow int bool))) (#0 : int)) (x : int))) - postconditions: (ret_y_lt_0, (((~Int.Lt : (arrow int (arrow int bool))) (y : int)) (#0 : int))) - body: y := (((~Int.Sub : (arrow int (arrow int int))) (#0 : int)) (x : int)) + preconditions: (0_lt_x, (((~Int.Lt : (arrow int (arrow int bool))) #0) (x : int))) + postconditions: (ret_y_lt_0, (((~Int.Lt : (arrow int (arrow int bool))) (y : int)) #0)) + body: y := (((~Int.Sub : (arrow int (arrow int int))) #0) (x : int)) , context: - types: + types: ⏎ aliases: [] state: tyGen: 6 tyPrefix: $__ty exprGen: 0 exprPrefix: $__var subst: []) -/ #guard_msgs in @@ -33,12 +33,13 @@ info: ok: ((procedure P : ((x : int)) → ((y : int))) inputs := [("x", mty[int])], outputs := [("y", mty[int])] }, spec := { modifies := [], - preconditions := [("0_lt_x", ⟨eb[((~Int.Lt #0) x)], .Default⟩)], - postconditions := [("ret_y_lt_0", ⟨eb[((~Int.Lt y) #0)], .Default⟩)] }, + preconditions := [("0_lt_x", ⟨eb[((~Int.Lt #0) x)], .Default, #[]⟩)], + postconditions := [("ret_y_lt_0", ⟨eb[((~Int.Lt y) #0)], .Default, #[]⟩)] }, body := [ Statement.set "y" eb[((~Int.Sub #0) x)] ] } + .empty return format ans /-- @@ -52,7 +53,7 @@ body: g := (((~Int.Add : (arrow int (arrow int int))) (a : int)) (g : int)) #eval do let g : TGenEnv Visibility := { @TGenEnv.default Visibility with context := {types := [[("g", t[int])]] }}; let ans ← - typeCheck { LContext.default (IDMeta:=Visibility) with + typeCheck { @LContext.default ⟨Unit, Visibility⟩ with functions := Boogie.Factory} {@TEnv.default Visibility with genEnv := g} Program.init { header := { name := "P", @@ -63,10 +64,10 @@ body: g := (((~Int.Add : (arrow int (arrow int int))) (a : int)) (g : int)) modifies := [("g")], preconditions := [], postconditions := - [("P.g_eq_a", ⟨eb[g == ((~Int.Add (~old g)) a)], .Default⟩)] }, + [("P.g_eq_a", ⟨eb[g == ((~Int.Add (~old g)) a)], .Default, #[]⟩)] }, body := [Statement.set "g" eb[((~Int.Add a) g)]] - } + } .empty return format ans.fst /-- @@ -80,7 +81,7 @@ body: g := (((~Int.Add : (arrow int (arrow int int))) (a : int)) (g : int)) #eval do let g : TGenEnv Visibility := { @TGenEnv.default Visibility with context := {types := [[("g", t[int])]] }}; let ans ← - typeCheck { LContext.default (IDMeta:=Visibility) with + typeCheck { @LContext.default ⟨Unit, Visibility⟩ with functions := Boogie.Factory} { @TEnv.default Visibility with genEnv := g} Program.init @@ -92,10 +93,10 @@ body: g := (((~Int.Add : (arrow int (arrow int int))) (a : int)) (g : int)) modifies := [("g")], preconditions := [], postconditions := - [("P.g_eq_a", ⟨eb[g == (~old ((~Int.Add a) g))], .Default⟩)] }, + [("P.g_eq_a", ⟨eb[g == (~old ((~Int.Add a) g))], .Default, #[]⟩)] }, body := [Statement.set "g" eb[((~Int.Add a) g)]] - } + } .empty return format ans.fst diff --git a/StrataTest/Languages/Boogie/ProgramTypeTests.lean b/StrataTest/Languages/Boogie/ProgramTypeTests.lean index 6d71a243d..45835123a 100644 --- a/StrataTest/Languages/Boogie/ProgramTypeTests.lean +++ b/StrataTest/Languages/Boogie/ProgramTypeTests.lean @@ -37,7 +37,10 @@ def bad_prog : Program := { decls := [ } ]} -/-- info: error: Cannot unify differently named type constructors bool and int! -/ +/-- +info: error: Impossible to unify (Foo bool bool) with (Foo int bool). +First mismatch: bool with int. +-/ #guard_msgs in #eval do let ans ← typeCheckAndPartialEval Options.default bad_prog return (format ans) @@ -80,7 +83,7 @@ Proof Obligation: --- info: ok: [(type Boogie.Boundedness.Infinite Foo [_, _] type FooAlias a := (Foo int bool) - func fooAliasVal : ∀[α]. () → (FooAlias α); + func fooAliasVal : () → (Foo int bool); func fooVal : () → (Foo int bool); (procedure P : () → ()) modifies: [] @@ -127,6 +130,7 @@ info: ok: [(type Boogie.Boundedness.Infinite Foo [_, _] func Bool.Not : ((x : bool)) → bool; func Str.Length : ((x : string)) → int; func Str.Concat : ((x : string) (y : string)) → string; + func Str.Substr : ((x : string) (i : int) (n : int)) → string; func Str.ToRegEx : ((x : string)) → regex; func Str.InRegEx : ((x : string) (y : regex)) → bool; func Re.All : () → regex; @@ -139,6 +143,7 @@ info: ok: [(type Boogie.Boundedness.Infinite Foo [_, _] func Re.Union : ((x : regex) (y : regex)) → regex; func Re.Inter : ((x : regex) (y : regex)) → regex; func Re.Comp : ((x : regex)) → regex; + func Re.None : () → regex; func old : ∀[a]. ((x : a)) → a; func select : ∀[k, v]. ((m : (Map k v)) (i : k)) → v; func update : ∀[k, v]. ((m : (Map k v)) (i : k) (x : v)) → (Map k v); @@ -273,10 +278,12 @@ info: ok: [(type Boogie.Boundedness.Infinite Foo [_, _] func Bv64.SLe : ((x : bv64) (y : bv64)) → bool; func Bv64.SGt : ((x : bv64) (y : bv64)) → bool; func Bv64.SGe : ((x : bv64) (y : bv64)) → bool; - func fooAliasVal : ∀[α]. () → (FooAlias α); + func fooAliasVal : () → (Foo int bool); func fooVal : () → (Foo int bool); ⏎ ⏎ + Datatypes: + ⏎ Path Conditions: ⏎ ⏎ @@ -311,12 +318,12 @@ def outOfScopeVarProg : Program := { decls := [ body := [ Statement.set "y" eb[((~Bool.Or x) x)], .ite eb[(x == #true)] - { ss := [Statement.init "q" t[int] eb[#0], + [Statement.init "q" t[int] eb[#0], Statement.set "q" eb[#1], - Statement.set "y" eb[#true]] } - { ss := [Statement.init "q" t[int] eb[#0], + Statement.set "y" eb[#true]] + [Statement.init "q" t[int] eb[#0], Statement.set "q" eb[#2], - Statement.set "y" eb[#true]] }, + Statement.set "y" eb[#true]], Statement.assert "y_check" eb[y == #true], Statement.assert "q_check" eb[q == #1] ] @@ -333,5 +340,53 @@ Free Variables: [q] --------------------------------------------------------------------- +def polyFuncProg : Program := { decls := [ + -- function identity(x : a) : a; + .func { name := "identity", + typeArgs := ["a"], + inputs := [("x", .ftvar "a")], + output := .ftvar "a" }, + -- function makePair(x : a, y : b) : Map a b; + .func { name := "makePair", + typeArgs := ["a", "b"], + inputs := [("x", .ftvar "a"), ("y", .ftvar "b")], + output := .tcons "Map" [.ftvar "a", .ftvar "b"] }, + -- procedure Test() + .proc { header := { name := "Test", + typeArgs := [], + inputs := [], + outputs := [] }, + spec := { modifies := [], + preconditions := [], + postconditions := [] }, + body := [ + -- var m : Map int bool; + Statement.init "m" (.forAll [] (.tcons "Map" [.tcons "int" [], .tcons "bool" []])) eb[init_m_0], + -- m := makePair(identity(42), identity(true)); + Statement.set "m" eb[((~makePair (~identity #42)) (~identity #true))] + ] + } +]} + +/-- +info: [Strata.Boogie] Type checking succeeded. + +--- +info: ok: func identity : ∀[$__ty0]. ((x : $__ty0)) → $__ty0; +func makePair : ∀[$__ty1, $__ty2]. ((x : $__ty1) (y : $__ty2)) → (Map $__ty1 $__ty2); +(procedure Test : () → ()) +modifies: [] +preconditions: +postconditions: +body: init (m : (Map int bool)) := (init_m_0 : (Map int bool)) +m := (((~makePair : (arrow int (arrow bool (Map int bool)))) ((~identity : (arrow int int)) #42)) ((~identity : (arrow bool bool)) #true)) +-/ +#guard_msgs in +#eval do + let ans ← typeCheck Options.default polyFuncProg + return (format ans) + +--------------------------------------------------------------------- + end Tests end Boogie diff --git a/StrataTest/Languages/Boogie/SMTEncoderDatatypeTest.lean b/StrataTest/Languages/Boogie/SMTEncoderDatatypeTest.lean new file mode 100644 index 000000000..80f3ba9cb --- /dev/null +++ b/StrataTest/Languages/Boogie/SMTEncoderDatatypeTest.lean @@ -0,0 +1,487 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DL.Lambda.Lambda +import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.LState +import Strata.DL.Lambda.LTy +import Strata.DL.Lambda.TypeFactory +import Strata.DL.SMT.Term +import Strata.DL.SMT.Encoder +import Strata.Languages.Boogie.Env +import Strata.Languages.Boogie.Factory +import Strata.Languages.Boogie.Identifiers +import Strata.Languages.Boogie.Options +import Strata.Languages.Boogie.SMTEncoder +import Strata.Languages.Boogie.Verifier + +/-! +This file contains unit tests for SMT datatype encoding. +-/ + +namespace Boogie + +section DatatypeTests + +open Lambda +open Std + +/-! ## Test Datatypes -/ + +/-- Option α = None | Some α -/ +def optionDatatype : LDatatype Visibility := + { name := "TestOption" + typeArgs := ["α"] + constrs := [ + { name := ⟨"None", .unres⟩, args := [], testerName := "TestOption$isNone" }, + { name := ⟨"Some", .unres⟩, args := [(⟨"TestOption$SomeProj0", .unres⟩, .ftvar "α")], testerName := "TestOption$isSome" } + ] + constrs_ne := by decide } + +/-- List α = Nil | Cons α (List α) -/ +def listDatatype : LDatatype Visibility := + { name := "TestList" + typeArgs := ["α"] + constrs := [ + { name := ⟨"Nil", .unres⟩, args := [], testerName := "TestList$isNil" }, + { name := ⟨"Cons", .unres⟩, args := [ + (⟨"TestList$ConsProj0", .unres⟩, .ftvar "α"), + (⟨"TestList$ConsProj1", .unres⟩, .tcons "TestList" [.ftvar "α"]) + ], testerName := "TestList$isCons" } + ] + constrs_ne := by decide } + +/-- Tree α = Leaf | Node α (Tree α) (Tree α) -/ +def treeDatatype : LDatatype Visibility := + { name := "TestTree" + typeArgs := ["α"] + constrs := [ + { name := ⟨"Leaf", .unres⟩, args := [], testerName := "TestTree$isLeaf" }, + { name := ⟨"Node", .unres⟩, args := [ + (⟨"TestTree$NodeProj0", .unres⟩, .ftvar "α"), + (⟨"TestTree$NodeProj1", .unres⟩, .tcons "TestTree" [.ftvar "α"]), + (⟨"TestTree$NodeProj2", .unres⟩, .tcons "TestTree" [.ftvar "α"]) + ], testerName := "TestTree$isNode" } + ] + constrs_ne := by decide } +/-- +Convert an expression to full SMT string including datatype declarations. +-/ +def toSMTStringWithDatatypes (e : LExpr BoogieLParams.mono) (datatypes : List (LDatatype Visibility)) : IO String := do + match Env.init.addDatatypes datatypes with + | .error msg => return s!"Error creating environment: {msg}" + | .ok env => + match toSMTTerm env [] e SMT.Context.default with + | .error err => return err.pretty + | .ok (smt, ctx) => + -- Emit the full SMT output including datatype declarations + let b ← IO.mkRef { : IO.FS.Stream.Buffer } + let solver ← Strata.SMT.Solver.bufferWriter b + match (← ((do + -- First emit datatypes + ctx.emitDatatypes + -- Then encode the term + let _ ← (Strata.SMT.Encoder.encodeTerm false smt).run Strata.SMT.EncoderState.init + pure () + ).run solver).toBaseIO) with + | .error e => return s!"Error: {e}" + | .ok _ => + let contents ← b.get + if h: contents.data.IsValidUTF8 then + return String.fromUTF8 contents.data h + else + return "Invalid UTF-8 in output" + +/-! ## Test Cases with Guard Messages -/ + +-- Test 1: Simple datatype (Option) - zero-argument constructor +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +; x +(declare-const f0 (TestOption Int)) +(define-fun t0 () (TestOption Int) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "x") (.some (.tcons "TestOption" [.int]))) + [optionDatatype] + +-- Test 2: Recursive datatype (List) - using List type +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; xs +(declare-const f0 (TestList Int)) +(define-fun t0 () (TestList Int) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "xs") (.some (.tcons "TestList" [.int]))) + [listDatatype] + +-- Test 3: Multiple constructors - Tree with Leaf and Node +/-- +info: (declare-datatype TestTree (par (α) ( + (Leaf) + (Node (TestTree$NodeProj0 α) (TestTree$NodeProj1 (TestTree α)) (TestTree$NodeProj2 (TestTree α)))))) +; tree +(declare-const f0 (TestTree Bool)) +(define-fun t0 () (TestTree Bool) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "tree") (.some (.tcons "TestTree" [.bool]))) + [treeDatatype] + +-- Test 4: Parametric datatype instantiation - List Int +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; intList +(declare-const f0 (TestList Int)) +(define-fun t0 () (TestList Int) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "intList") (.some (.tcons "TestList" [.int]))) + [listDatatype] + +-- Test 5: Parametric datatype instantiation - List Bool (should reuse same datatype) +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; boolList +(declare-const f0 (TestList Bool)) +(define-fun t0 () (TestList Bool) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "boolList") (.some (.tcons "TestList" [.bool]))) + [listDatatype] + +-- Test 6: Multi-field constructor - Tree with 3 fields +/-- +info: (declare-datatype TestTree (par (α) ( + (Leaf) + (Node (TestTree$NodeProj0 α) (TestTree$NodeProj1 (TestTree α)) (TestTree$NodeProj2 (TestTree α)))))) +; intTree +(declare-const f0 (TestTree Int)) +(define-fun t0 () (TestTree Int) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "intTree") (.some (.tcons "TestTree" [.int]))) + [treeDatatype] + +-- Test 7: Nested parametric types - List of Option (should declare both datatypes) +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +(declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; listOfOption +(declare-const f0 (TestList (TestOption Int))) +(define-fun t0 () (TestList (TestOption Int)) f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "listOfOption") (.some (.tcons "TestList" [.tcons "TestOption" [.int]]))) + [listDatatype, optionDatatype] + +/-! ## Constructor Application Tests -/ + +-- Test 8: None constructor (zero-argument) +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +(define-fun t0 () (TestOption Int) (as None (TestOption Int))) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.op () (BoogieIdent.unres "None") (.some (.tcons "TestOption" [.int]))) + [optionDatatype] + +-- Test 9: Some constructor (single-argument) +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +(define-fun t0 () (TestOption Int) (Some 42)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "Some") (.some (.arrow .int (.tcons "TestOption" [.int])))) (.intConst () 42)) + [optionDatatype] + +-- Test 10: Cons constructor (multi-argument) +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +(define-fun t0 () (TestList Int) (as Nil (TestList Int))) +(define-fun t1 () (TestList Int) (Cons 1 t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () + (.app () (.op () (BoogieIdent.unres "Cons") (.some (.arrow .int (.arrow (.tcons "TestList" [.int]) (.tcons "TestList" [.int]))))) + (.intConst () 1)) + (.op () (BoogieIdent.unres "Nil") (.some (.tcons "TestList" [.int])))) + [listDatatype] + +/-! ## Tester Function Tests -/ + +-- Test 11: isNone tester +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +; x +(declare-const f0 (TestOption Int)) +(define-fun t0 () (TestOption Int) f0) +(define-fun t1 () Bool (is-None t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "TestOption$isNone") (.some (.arrow (.tcons "TestOption" [.int]) .bool))) + (.fvar () (BoogieIdent.unres "x") (.some (.tcons "TestOption" [.int])))) + [optionDatatype] + +-- Test 12: isCons tester +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; xs +(declare-const f0 (TestList Int)) +(define-fun t0 () (TestList Int) f0) +(define-fun t1 () Bool (is-Cons t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "TestList$isCons") (.some (.arrow (.tcons "TestList" [.int]) .bool))) + (.fvar () (BoogieIdent.unres "xs") (.some (.tcons "TestList" [.int])))) + [listDatatype] + +/-! ## Destructor Function Tests -/ + +-- Test 13: Some value destructor +/-- +info: (declare-datatype TestOption (par (α) ( + (None) + (Some (TestOption$SomeProj0 α))))) +; x +(declare-const f0 (TestOption Int)) +(define-fun t0 () (TestOption Int) f0) +(define-fun t1 () Int (TestOption$SomeProj0 t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "TestOption$SomeProj0") (.some (.arrow (.tcons "TestOption" [.int]) .int))) + (.fvar () (BoogieIdent.unres "x") (.some (.tcons "TestOption" [.int])))) + [optionDatatype] + +-- Test 14: Cons head destructor +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; xs +(declare-const f0 (TestList Int)) +(define-fun t0 () (TestList Int) f0) +(define-fun t1 () Int (TestList$ConsProj0 t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "TestList$ConsProj0") (.some (.arrow (.tcons "TestList" [.int]) .int))) + (.fvar () (BoogieIdent.unres "xs") (.some (.tcons "TestList" [.int])))) + [listDatatype] + +-- Test 15: Cons tail destructor +/-- +info: (declare-datatype TestList (par (α) ( + (Nil) + (Cons (TestList$ConsProj0 α) (TestList$ConsProj1 (TestList α)))))) +; xs +(declare-const f0 (TestList Int)) +(define-fun t0 () (TestList Int) f0) +(define-fun t1 () (TestList Int) (TestList$ConsProj1 t0)) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.app () (.op () (BoogieIdent.unres "TestList$ConsProj1") (.some (.arrow (.tcons "TestList" [.int]) (.tcons "TestList" [.int])))) + (.fvar () (BoogieIdent.unres "xs") (.some (.tcons "TestList" [.int])))) + [listDatatype] + +/-! ## Complex Dependency Topological Sorting Tests -/ + +-- Test 16: Very complex dependency graph requiring sophisticated topological sorting +-- Dependencies: Alpha -> Beta, Gamma +-- Beta -> Delta, Epsilon +-- Gamma -> Epsilon, Zeta +-- Delta -> Zeta +-- Epsilon -> Zeta +-- Actual topological order: Zeta, Epsilon, Gamma, Delta, Beta, Alpha + +/-- Alpha = AlphaValue Beta Gamma -/ +def alphaDatatype : LDatatype Visibility := + { name := "Alpha" + typeArgs := [] + constrs := [ + { name := ⟨"AlphaValue", .unres⟩, args := [ + (⟨"Alpha$AlphaValueProj0", .unres⟩, .tcons "Beta" []), + (⟨"Alpha$AlphaValueProj1", .unres⟩, .tcons "Gamma" []) + ], testerName := "Alpha$isAlphaValue" } + ] + constrs_ne := by decide } + +/-- Beta = BetaValue Delta Epsilon -/ +def betaDatatype : LDatatype Visibility := + { name := "Beta" + typeArgs := [] + constrs := [ + { name := ⟨"BetaValue", .unres⟩, args := [ + (⟨"Beta$BetaValueProj0", .unres⟩, .tcons "Delta" []), + (⟨"Beta$BetaValueProj1", .unres⟩, .tcons "Epsilon" []) + ], testerName := "Beta$isBetaValue" } + ] + constrs_ne := by decide } + +/-- Gamma = GammaValue Epsilon Zeta -/ +def gammaDatatype : LDatatype Visibility := + { name := "Gamma" + typeArgs := [] + constrs := [ + { name := ⟨"GammaValue", .unres⟩, args := [ + (⟨"Gamma$GammaValueProj0", .unres⟩, .tcons "Epsilon" []), + (⟨"Gamma$GammaValueProj1", .unres⟩, .tcons "Zeta" []) + ], testerName := "Gamma$isGammaValue" } + ] + constrs_ne := by decide } + +/-- Delta = DeltaValue Zeta -/ +def deltaDatatype : LDatatype Visibility := + { name := "Delta" + typeArgs := [] + constrs := [ + { name := ⟨"DeltaValue", .unres⟩, args := [(⟨"Delta$DeltaValueProj0", .unres⟩, .tcons "Zeta" [])], testerName := "Delta$isDeltaValue" } + ] + constrs_ne := by decide } + +/-- Epsilon = EpsilonValue Zeta -/ +def epsilonDatatype : LDatatype Visibility := + { name := "Epsilon" + typeArgs := [] + constrs := [ + { name := ⟨"EpsilonValue", .unres⟩, args := [(⟨"Epsilon$EpsilonValueProj0", .unres⟩, .tcons "Zeta" [])], testerName := "Epsilon$isEpsilonValue" } + ] + constrs_ne := by decide } + +/-- Zeta = ZetaValue int -/ +def zetaDatatype : LDatatype Visibility := + { name := "Zeta" + typeArgs := [] + constrs := [ + { name := ⟨"ZetaValue", .unres⟩, args := [(⟨"Zeta$ZetaValueProj0", .unres⟩, .int)], testerName := "Zeta$isZetaValue" } + ] + constrs_ne := by decide } + +/-- +info: (declare-datatype Zeta ( + (ZetaValue (Zeta$ZetaValueProj0 Int)))) +(declare-datatype Epsilon ( + (EpsilonValue (Epsilon$EpsilonValueProj0 Zeta)))) +(declare-datatype Gamma ( + (GammaValue (Gamma$GammaValueProj0 Epsilon) (Gamma$GammaValueProj1 Zeta)))) +(declare-datatype Delta ( + (DeltaValue (Delta$DeltaValueProj0 Zeta)))) +(declare-datatype Beta ( + (BetaValue (Beta$BetaValueProj0 Delta) (Beta$BetaValueProj1 Epsilon)))) +(declare-datatype Alpha ( + (AlphaValue (Alpha$AlphaValueProj0 Beta) (Alpha$AlphaValueProj1 Gamma)))) +; alphaVar +(declare-const f0 Alpha) +(define-fun t0 () Alpha f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "alphaVar") (.some (.tcons "Alpha" []))) + [alphaDatatype, betaDatatype, gammaDatatype, deltaDatatype, epsilonDatatype, zetaDatatype] + +-- Test 17: Diamond dependency pattern +-- Dependencies: Diamond -> Left, Right +-- Left -> Root +-- Right -> Root +-- Actual topological order: Root, Right, Left, Diamond (or Root, Left, Right, Diamond) + +/-- Root = RootValue int -/ +def rootDatatype : LDatatype Visibility := + { name := "Root" + typeArgs := [] + constrs := [ + { name := ⟨"RootValue", .unres⟩, args := [(⟨"Root$RootValueProj0", .unres⟩, .int)], testerName := "Root$isRootValue" } + ] + constrs_ne := by decide } + +/-- Left = LeftValue Root -/ +def leftDatatype : LDatatype Visibility := + { name := "Left" + typeArgs := [] + constrs := [ + { name := ⟨"LeftValue", .unres⟩, args := [(⟨"Left$LeftValueProj0", .unres⟩, .tcons "Root" [])], testerName := "Left$isLeftValue" } + ] + constrs_ne := by decide } + +/-- Right = RightValue Root -/ +def rightDatatype : LDatatype Visibility := + { name := "Right" + typeArgs := [] + constrs := [ + { name := ⟨"RightValue", .unres⟩, args := [(⟨"Right$RightValueProj0", .unres⟩, .tcons "Root" [])], testerName := "Right$isRightValue" } + ] + constrs_ne := by decide } + +/-- Diamond = DiamondValue Left Right -/ +def diamondDatatype : LDatatype Visibility := + { name := "Diamond" + typeArgs := [] + constrs := [ + { name := ⟨"DiamondValue", .unres⟩, args := [ + (⟨"Diamond$DiamondValueProj0", .unres⟩, .tcons "Left" []), + (⟨"Diamond$DiamondValueProj1", .unres⟩, .tcons "Right" []) + ], testerName := "Diamond$isDiamondValue" } + ] + constrs_ne := by decide } + +/-- +info: (declare-datatype Root ( + (RootValue (Root$RootValueProj0 Int)))) +(declare-datatype Right ( + (RightValue (Right$RightValueProj0 Root)))) +(declare-datatype Left ( + (LeftValue (Left$LeftValueProj0 Root)))) +(declare-datatype Diamond ( + (DiamondValue (Diamond$DiamondValueProj0 Left) (Diamond$DiamondValueProj1 Right)))) +; diamondVar +(declare-const f0 Diamond) +(define-fun t0 () Diamond f0) +-/ +#guard_msgs in +#eval format <$> toSMTStringWithDatatypes + (.fvar () (BoogieIdent.unres "diamondVar") (.some (.tcons "Diamond" []))) + [diamondDatatype, leftDatatype, rightDatatype, rootDatatype] + +end DatatypeTests + +end Boogie diff --git a/StrataTest/Languages/Boogie/StatementEvalTests.lean b/StrataTest/Languages/Boogie/StatementEvalTests.lean index a121ccca6..77ccc63b5 100644 --- a/StrataTest/Languages/Boogie/StatementEvalTests.lean +++ b/StrataTest/Languages/Boogie/StatementEvalTests.lean @@ -31,6 +31,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -40,7 +42,7 @@ Deferred Proof Obligations: Label: x_eq_18 Assumptions: Proof Obligation: -(#true : bool) +#true -/ #guard_msgs in #eval (evalOne ∅ ∅ [.init "x" t[int] eb[#0], @@ -65,6 +67,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -77,11 +81,12 @@ Proof Obligation: (_yinit == #12) -/ #guard_msgs in -#eval (evalOne (Env.init.pushScope [("y", (mty[int], eb[_yinit]))]) - ∅ - [.init "x" t[int] eb[#0], - .set "x" eb[y], - .assert "x_eq_12" eb[x == #12]]) |>.snd |> format +#eval (evalOne + ((Env.init (empty_factory := true)).pushScope [("y", (mty[int], eb[_yinit]))]) + ∅ + [.init "x" t[int] eb[#0], + .set "x" eb[y], + .assert "x_eq_12" eb[x == #12]]) |>.snd |> format /-- info: Error: @@ -100,6 +105,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -134,6 +141,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -148,25 +157,27 @@ Proof Obligation: Label: m_2_eq_20 Assumptions: Proof Obligation: -(#true : bool) +#true Label: m_1_eq_10 Assumptions: Proof Obligation: -(#true : bool) +#true -/ #guard_msgs in -#eval (evalOne (Env.init.pushScope [("minit", (mty[int → int], eb[(_minit : int → int)]))]) - ∅ - [.init "m" t[int → int] eb[minit], - .init "m0" t[int] eb[(m #0)], - .set "m" eb[λ (if (%0 == #1) then #10 else ((m : int → int) %0))], - .set "m" eb[λ (if (%0 == #2) then #20 else ((m : int → int) %0))], - .assert "m_5_eq_50" eb[(m #5) == #50], - .assert "m_2_eq_20" eb[(m #2) == #20], - .set "m" eb[λ (if (%0 == #3) then #30 else ((m : int → int) %0))], - .assert "m_1_eq_10" eb[(m #1) == #10] - ]) |>.snd |> format +#eval (evalOne + ((Env.init (empty_factory := true)).pushScope + [("minit", (mty[int → int], eb[(_minit : int → int)]))]) + ∅ + [.init "m" t[int → int] eb[minit], + .init "m0" t[int] eb[(m #0)], + .set "m" eb[λ (if (%0 == #1) then #10 else ((m : int → int) %0))], + .set "m" eb[λ (if (%0 == #2) then #20 else ((m : int → int) %0))], + .assert "m_5_eq_50" eb[(m #5) == #50], + .assert "m_2_eq_20" eb[(m #2) == #20], + .set "m" eb[λ (if (%0 == #3) then #30 else ((m : int → int) %0))], + .assert "m_1_eq_10" eb[(m #1) == #10] + ]) |>.snd |> format /-- info: Error: @@ -186,6 +197,8 @@ Factory Functions: +Datatypes: + Path Conditions: @@ -200,24 +213,25 @@ Proof Obligation: Label: m_2_eq_20 Assumptions: Proof Obligation: -(#true : bool) +#true Label: m_1_eq_10 Assumptions: Proof Obligation: -(#true : bool) +#true -/ #guard_msgs in -#eval (evalOne (Env.init.pushScope [("minit", (none, eb[_minit]))]) - ∅ - [.init "m" t[int → int] eb[minit], - .set "m" eb[λ (if (%0 == #1) then #10 else (m %0))], - .set "m" eb[λ (if (%0 == #2) then #20 else (m %0))], - .assert "m_5_eq_50" eb[(m #5) == #50], - .assert "m_2_eq_20" eb[(m #2) == #20], - .set "m" eb[λ (if (%0 == #3) then #30 else (m %0))], - .assert "m_1_eq_10" eb[(m #1) == #10] - ]) |>.snd |> format +#eval (evalOne + ((Env.init (empty_factory := true)).pushScope [("minit", (none, eb[_minit]))]) + ∅ + [.init "m" t[int → int] eb[minit], + .set "m" eb[λ (if (%0 == #1) then #10 else (m %0))], + .set "m" eb[λ (if (%0 == #2) then #20 else (m %0))], + .assert "m_5_eq_50" eb[(m #5) == #50], + .assert "m_2_eq_20" eb[(m #2) == #20], + .set "m" eb[λ (if (%0 == #3) then #30 else (m %0))], + .assert "m_1_eq_10" eb[(m #1) == #10] + ]) |>.snd |> format @@ -225,22 +239,22 @@ private def prog1 : Statements := [ .init "x" t[int] eb[#0], .init "y" t[int] eb[#6], - .block "label_0" { ss := + .block "label_0" [Statement.init "z" t[bool] eb[zinit], Statement.assume "z_false" eb[z == #false], .ite eb[z == #false] - { ss := [Statement.set "x" eb[y]] } + [Statement.set "x" eb[y]] -- The "trivial" assertion, though unreachable, is still verified away by the -- PE because the conclusion of the proof obligation evaluates to `true`. -- However, if the conclusion were anything else (including `false`) and -- the path conditions weren't empty, then this proof obligation would be -- sent on to the SMT solver. - { ss := [Statement.assert "trivial" eb[#true]]}, + [Statement.assert "trivial" eb[#true]], Statement.assert "x_eq_y_label_0" eb[x == y], - ]}, + ], .assert "x_eq_y" eb[x == y] ] @@ -263,9 +277,11 @@ Factory Functions: +Datatypes: + Path Conditions: (z_false, (zinit == #false)) -(, (if (zinit == #false) then (zinit == #false) else (#true : bool))) (, (if (if (zinit == #false) then (#false : bool) else (#true : bool)) then (if (zinit == #false) then (#false : bool) else (#true : bool)) else (#true : bool))) +(, (if (zinit == #false) then (zinit == #false) else #true)) (, (if (if (zinit == #false) then #false else #true) then (if (zinit == #false) then #false else #true) else #true)) Warnings: @@ -281,14 +297,14 @@ Proof Obligation: Label: x_eq_y_label_0 Assumptions: (z_false, (zinit == #false)) -(, (if (zinit == #false) then (zinit == #false) else (#true : bool))) (, (if (if (zinit == #false) then (#false : bool) else (#true : bool)) then (if (zinit == #false) then (#false : bool) else (#true : bool)) else (#true : bool))) +(, (if (zinit == #false) then (zinit == #false) else #true)) (, (if (if (zinit == #false) then #false else #true) then (if (zinit == #false) then #false else #true) else #true)) Proof Obligation: ((if (zinit == #false) then #6 else #0) == #6) Label: x_eq_y Assumptions: (z_false, (zinit == #false)) -(, (if (zinit == #false) then (zinit == #false) else (#true : bool))) (, (if (if (zinit == #false) then (#false : bool) else (#true : bool)) then (if (zinit == #false) then (#false : bool) else (#true : bool)) else (#true : bool))) +(, (if (zinit == #false) then (zinit == #false) else #true)) (, (if (if (zinit == #false) then #false else #true) then (if (zinit == #false) then #false else #true) else #true)) Proof Obligation: ((if (zinit == #false) then #6 else #0) == #6) -/ @@ -308,9 +324,9 @@ private def prog2 : Statements := [ /-- info: init (x : int) := #0 x := #1 -#[] havoc x +havoc x assert [x_eq_1] ($__x0 == #1) -#[] havoc x +havoc x x := #8 -/ #guard_msgs in @@ -333,6 +349,8 @@ Factory Functions: +Datatypes: + Path Conditions: diff --git a/StrataTest/Languages/Boogie/StatementTypeTests.lean b/StrataTest/Languages/Boogie/StatementTypeTests.lean index dd2188fd2..88f441d27 100644 --- a/StrataTest/Languages/Boogie/StatementTypeTests.lean +++ b/StrataTest/Languages/Boogie/StatementTypeTests.lean @@ -30,9 +30,7 @@ init (y : int) := (xinit : int) return format ans.fst -/-- -info: error: Type Checking [init (x : bool) := #true]: Variable x of type bool already in context. --/ +/-- info: error: Variable x of type bool already in context. -/ #guard_msgs in #eval do let ans ← typeCheck LContext.default (TEnv.default.updateContext { types := [[("x", t[bool])]] }) Program.init @@ -60,22 +58,22 @@ subst: [ .init "x" t[int] eb[#0], .init "y" t[int] eb[#6], - .block "label_0" { ss := + .block "label_0" [Statement.init "z" t[bool] eb[zinit], Statement.assume "z_false" eb[z == #false], .ite eb[z == #false] - { ss := [Statement.set "x" eb[y]] } - { ss := [Statement.assert "trivial" eb[#true]]}, + [Statement.set "x" eb[y]] + [Statement.assert "trivial" eb[#true]], Statement.assert "x_eq_y_label_0" eb[x == y], - ]}, + ], .assert "x_eq_y" eb[x == y] ] return format ans.snd -/-- info: error: Cannot unify differently named type constructors bool and int! -/ +/-- info: error: Impossible to unify bool with int. -/ #guard_msgs in #eval do let ans ← typeCheck LContext.default TEnv.default Program.init none [ @@ -85,18 +83,13 @@ subst: ] return format ans -/-- -info: error: Type Checking [init (x : int) := #1]: Variable x of type bool already in context. --/ +/-- info: error: Variable x of type bool already in context. -/ #guard_msgs in #eval do let ans ← typeCheck LContext.default TEnv.default Program.init none [ .init "x" t[bool] eb[#true], - .block "label_0" { - ss := [ - Statement.init "x" t[int] eb[#1] - ] - } + .block "label_0" + [ Statement.init "x" t[int] eb[#1] ] ] return format ans @@ -116,21 +109,17 @@ subst: [($__ty0, int)] [ .init "x" t[int] eb[#0], .ite eb[x == #3] - { ss := [ + [ Statement.init "y" t[∀α. %α] eb[x], Statement.assert "local_y_eq_3" eb[y == #3] - ]} - { - ss := [ - Statement.init "z" t[bool] eb[#true] - ] - } + ] + [ Statement.init "z" t[bool] eb[#true] ] ] return format ans.snd /-- -info: ok: init (x : int) := (#1 : int) -x := (#2 : int) +info: ok: init (x : int) := #1 +x := #2 -/ #guard_msgs in #eval do let ans ← typeCheck LContext.default TEnv.default Program.init none diff --git a/Strata/Languages/C_Simp/Examples/Coprime.lean b/StrataTest/Languages/C_Simp/Examples/Coprime.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/Coprime.lean rename to StrataTest/Languages/C_Simp/Examples/Coprime.lean diff --git a/Strata/Languages/C_Simp/Examples/LinearSearch.lean b/StrataTest/Languages/C_Simp/Examples/LinearSearch.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/LinearSearch.lean rename to StrataTest/Languages/C_Simp/Examples/LinearSearch.lean diff --git a/Strata/Languages/C_Simp/Examples/LoopSimple.lean b/StrataTest/Languages/C_Simp/Examples/LoopSimple.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/LoopSimple.lean rename to StrataTest/Languages/C_Simp/Examples/LoopSimple.lean diff --git a/Strata/Languages/C_Simp/Examples/LoopTrivial.lean b/StrataTest/Languages/C_Simp/Examples/LoopTrivial.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/LoopTrivial.lean rename to StrataTest/Languages/C_Simp/Examples/LoopTrivial.lean diff --git a/Strata/Languages/C_Simp/Examples/Min.lean b/StrataTest/Languages/C_Simp/Examples/Min.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/Min.lean rename to StrataTest/Languages/C_Simp/Examples/Min.lean diff --git a/Strata/Languages/C_Simp/Examples/SimpleTest.lean b/StrataTest/Languages/C_Simp/Examples/SimpleTest.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/SimpleTest.lean rename to StrataTest/Languages/C_Simp/Examples/SimpleTest.lean diff --git a/Strata/Languages/C_Simp/Examples/Trivial.lean b/StrataTest/Languages/C_Simp/Examples/Trivial.lean similarity index 100% rename from Strata/Languages/C_Simp/Examples/Trivial.lean rename to StrataTest/Languages/C_Simp/Examples/Trivial.lean diff --git a/Strata/Languages/Dyn/Examples/Arithmetic.lean b/StrataTest/Languages/Dyn/Examples/Arithmetic.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/Arithmetic.lean rename to StrataTest/Languages/Dyn/Examples/Arithmetic.lean diff --git a/Strata/Languages/Dyn/Examples/BasicTypes.lean b/StrataTest/Languages/Dyn/Examples/BasicTypes.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/BasicTypes.lean rename to StrataTest/Languages/Dyn/Examples/BasicTypes.lean diff --git a/Strata/Languages/Dyn/Examples/ControlFlow.lean b/StrataTest/Languages/Dyn/Examples/ControlFlow.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/ControlFlow.lean rename to StrataTest/Languages/Dyn/Examples/ControlFlow.lean diff --git a/Strata/Languages/Dyn/Examples/FunctionCalls.lean b/StrataTest/Languages/Dyn/Examples/FunctionCalls.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/FunctionCalls.lean rename to StrataTest/Languages/Dyn/Examples/FunctionCalls.lean diff --git a/Strata/Languages/Dyn/Examples/HeapOps.lean b/StrataTest/Languages/Dyn/Examples/HeapOps.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/HeapOps.lean rename to StrataTest/Languages/Dyn/Examples/HeapOps.lean diff --git a/Strata/Languages/Dyn/Examples/ListOperations.lean b/StrataTest/Languages/Dyn/Examples/ListOperations.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/ListOperations.lean rename to StrataTest/Languages/Dyn/Examples/ListOperations.lean diff --git a/Strata/Languages/Dyn/Examples/StringOps.lean b/StrataTest/Languages/Dyn/Examples/StringOps.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/StringOps.lean rename to StrataTest/Languages/Dyn/Examples/StringOps.lean diff --git a/Strata/Languages/Dyn/Examples/Trivial.lean b/StrataTest/Languages/Dyn/Examples/Trivial.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/Trivial.lean rename to StrataTest/Languages/Dyn/Examples/Trivial.lean diff --git a/Strata/Languages/Dyn/Examples/TypeIntrospection.lean b/StrataTest/Languages/Dyn/Examples/TypeIntrospection.lean similarity index 100% rename from Strata/Languages/Dyn/Examples/TypeIntrospection.lean rename to StrataTest/Languages/Dyn/Examples/TypeIntrospection.lean diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean new file mode 100644 index 000000000..3ad972ee0 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean @@ -0,0 +1,30 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +constrained nat = x: int where x >= 0 witness 0 + +composite Option {} +composite Some extends Option { + value: int +} +composite None extends Option +constrained SealedOption = x: Option where x is Some || x is None witness None + +procedure foo() returns (r: nat) { +} +" + +-- Not working yet +-- #eval! testInput "ConstrainedTypes" program processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T1_AssertFalse.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T1_AssertFalse.lean new file mode 100644 index 000000000..8e831c9e1 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T1_AssertFalse.lean @@ -0,0 +1,30 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure foo() { + assert true; + assert false; +// ^^^^^^^^^^^^^ error: assertion does not hold + assert false; +// ^^^^^^^^^^^^^ error: assertion does not hold +} + +procedure bar() { + assume false; + assert false; +} +" + +#eval testInputWithOffset "AssertFalse" program 14 processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean new file mode 100644 index 000000000..04d658343 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -0,0 +1,34 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program: String := r" +procedure nestedImpureStatements(x: int) { + var y := 0; + if (y := y + 1; == { y := y + 1; x }) { + assert x == 1; + assert y == x + 1; + } else { + assert x != 1; + } + assert y == 2; + assert false; +// ^^^^^^^^^^^^^ error: assertion does not hold +} +" + +#guard_msgs (error, drop all) in +#eval! testInputWithOffset "NestedImpureStatements" program 14 processLaurelFile + + +end Laurel diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean new file mode 100644 index 000000000..f0467c36b --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -0,0 +1,86 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure guards(a: int) returns (r: int) +{ + var b := a + 2; + if (b > 2) { + var c := b + 3; + if (c > 3) { + return c + 4; + } + var d := c + 5; + return d + 6; + } + var e := b + 1; + assert e <= 3; + assert e < 3; +// ^^^^^^^^^^^^^ error: assertion does not hold + return e; +} + +procedure dag(a: int) returns (r: int) +{ + var b: int; + + if (a > 0) { + b := 1; + } + assert if (a > 0) { b == 1 } else { true }; + assert if (a > 0) { b == 2 } else { true }; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold + return b; +} +" + +#guard_msgs (error, drop all) in +#eval! testInputWithOffset "ControlFlow" program 14 processLaurelFile + +/- +Translation towards expression form: + +function guards(a: int): int { + var b = a + 2; + if (b > 2) { + var c = b + 3; + if (c > 3) { + c + 4; + } else { + var d = c + 5; + d + 6; + } + } else { + var e = b + 1; + e + } +} + +To translate towards SMT we only need to apply something like WP calculus. + Here's an example of what that looks like: + +function dag(a: int): int { + ( + assume a > 0; + assume b == 1; + b; + ) + OR + ( + assume a <= 0; + assume b == 2; + b; + ) +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean new file mode 100644 index 000000000..e9cb07e93 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean @@ -0,0 +1,70 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure whileWithBreakAndContinue(steps: int, continueSteps: int, exitSteps: int): int { + var counter = 0 + { + while(steps > 0) + invariant counter >= 0 + { + { + if (steps == exitSteps) { + counter = -10; + exit breakBlock; + } + if (steps == continueSteps) { + exit continueBlock; + } + counter = counter + 1; + } continueBlock; + steps = steps - 1; + } + } breakBlock; + counter; +} +" + +-- Not working yet +-- #eval! testInput "LoopJumps" program processLaurelFile + +/- +Translation towards SMT: + +proof whileWithBreakAndContinue_body() { + var steps: int; + var continueSteps: int; + var exitSteps: int; + + var counter = 0; + + label loopStart; + assert counter >= 0; + if (steps > 0) { + if (steps == exitSteps) { + counter = -10; + goto breakLabel; + } + if (steps == continueSteps) { + goto continueLabel; + } + counter = counter + 1; + label continueLabel; + steps = steps - 1; + goto loopStart; + } + label breakLabel; + counter; +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean new file mode 100644 index 000000000..3ba48f00f --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean @@ -0,0 +1,65 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure fooReassign(): int { + var x = 0; + x = x + 1; + assert x == 1; + x = x + 1; + x +} + +procedure fooSingleAssign(): int { + var x = 0 + var x2 = x + 1; + var x3 = x2 + 1; + x3 +} + +procedure fooProof() { + assert fooReassign() == fooSingleAssign(); +} +" + +-- Not working yet +-- #eval! testInput "ProcedureCalls" program processLaurelFile + +/- +Translation towards SMT: + +function fooReassign(): int { + var x0 = 0; + var x1 = x0 + 1; + var x2 = x1 + 1; + x2 +} + +proof fooReassign_body { + var x = 0; + x = x + 1; + assert x == 1; +} + +function fooSingleAssign(): int { + var x = 0; + var x2 = x + 1; + var x3 = x2 + 1; + x3 +} + +proof fooProof_body { + assert fooReassign() == fooSingleAssign(); +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean new file mode 100644 index 000000000..8592576f8 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean @@ -0,0 +1,66 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure hasRequires(x: int): (r: int) + requires assert 1 == 1; x > 2 +{ + assert x > 0; + assert x > 3; +// ^^^^^^^^^^^^^ error: assertion does not hold + x + 1 +} + +procedure caller() { + var x = hasRequires(1); +// ^^^^^^^^^^^^^^ error: precondition does not hold + var y = hasRequires(3); +} +" + +-- Not working yet +-- #eval! testInput "Preconditions" program processLaurelFile + +/- +Translation towards SMT: + +function hasRequires_requires(x: int): boolean { + x > 2 +} + +function hasRequires(x: int): int { + x + 1 +} + +proof hasRequires_requires { + assert 1 == 1; +} + +proof hasRequires_body { + var x: int; + assume hasRequires_requires(); + assert x > 0; // pass + assert x > 3; // fail +} + +proof caller_body { + var hasRequires_arg1 := 1; + assert hasRequires_ensures(hasRequires_arg1); // fail + var x := hasRequires(hasRequires_arg1); + + var hasRequires_arg1_2 := 3; + assert hasRequires_ensures(hasRequires_arg1_2); // pass + var y: int := hasRequires(hasRequires_arg1_2); +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean new file mode 100644 index 000000000..6c72213da --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean @@ -0,0 +1,64 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +/- +A decreases clause CAN be added to a procedure to prove that it terminates. +A procedure with a decreases clause may be called in an erased context. +-/ + +def program := r" +procedure noDecreases(x: int): boolean +procedure caller(x: int) + requires noDecreases(x) +// ^ error: noDecreases can not be called from a pure context, because it is not proven to terminate + +procedure noCyclicCalls() + decreases [] +{ + leaf(); +} + +procedure leaf() decreases [1] { } + +procedure mutualRecursionA(x: nat) + decreases [x, 1] +{ + mutualRecursionB(x); +} + +procedure mutualRecursionB(x: nat) + decreases [x, 0] +{ + if x != 0 { mutualRecursionA(x-1); } +} +" + +-- Not working yet +-- #eval! testInput "Decreases" program processLaurelFile + +/- +Translation towards SMT: + +proof foo_body { + var x: nat; + assert decreases([x, 1], [x, 0]); +} + +proof bar_body { + var x: nat; + if (x != 0) { + assert decreases([x, 0], [x - 1, 1]); + } +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean new file mode 100644 index 000000000..570845a65 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -0,0 +1,70 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +procedure opaqueBody(x: int): (r: int) +// the presence of the ensures make the body opaque. we can consider more explicit syntax. + ensures assert 1 == 1; r >= 0 +{ + Math.abs(x) +} + +procedure transparantBody(x: int): int +{ + Math.abs(x) +} + +procedure caller() { + assert transparantBody(-1) == 1; + assert opaqueBody(-1) >= 0 + assert opaqueBody(-3) == opaqueBody(-3); + assert opaqueBody(-1) == 1; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +} +" + +-- Not working yet +-- #eval! testInput "Postconditions" program processLaurelFile + +/- +Translation towards SMT: + +function opaqueBody(x: int): boolean +// ensures axiom +axiom forall x ontrigger opaqueBody(x) :: let r = opaqueBody(x) in r >= 0 + +proof opaqueBody_ensures { + assert 1 == 1; // pass +} + +proof opaqueBody_body { + var x: int; + var r = Math.abs(x); + assert r >= 0; // pass +} + +function transparantBody(x: int): int { + Math.abs(x) +} + +proof caller_body { + assert transparantBody(-1); // pass + + var r_1: int := opaqueBody_ensures(-1); + assert r_1 >= 0; // pass, using axiom + + var r_2: int := opaqueBody_ensures(-1); + assert r_2 == 1; // error +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean new file mode 100644 index 000000000..3dbd87115 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean @@ -0,0 +1,74 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Laurel + +def program := r" +nondet procedure nonDeterministic(x: int): (r: int) + ensures r > 0 +{ + assumed +} + +procedure caller() { + var x = nonDeterministic(1) + assert x > 0; + var y = nonDeterministic(1) + assert x == y; +// ^^^^^^^^^^^^^^ error: assertion does not hold +} + +nondet procedure nonDeterminsticTransparant(x: int): (r: int) +{ + nonDeterministic(x + 1) +} + +procedure nonDeterministicCaller(x: int): int +{ + nonDeterministic(x) +} +" + +-- Not working yet +-- #eval! testInput "Nondeterministic" program processLaurelFile + +/- +When a procedure is non-deterministic, +every invocation might return a different result, even if the inputs are the same. +It's comparable to having an IO monad. + +Translation towards SMT: + +function nonDeterministic_relation(x: int, r: int): boolean +// ensures axiom +axiom forall x, r: int ontrigger nonDeterministic_relation(x, r) :: r > 0 + +proof nonDeterministic_body { + var x: int; + var r := Math.abs(x) + 1 + assert nonDeterministic_relation(x, r); +} + +proof caller_body { + var x: int; + assume nonDeterministic_relation(1, x); + assert x > 0; // pass + + var y: int; + assume nonDeterministic_relation(1, y); + assert x == y; // fail +} + +function nonDeterminsticTransparant_relation(x: int, r: int): boolean { + nonDeterministic_relation(x + 1, r) +} +-/ diff --git a/StrataTest/Languages/Laurel/Examples/Objects/1. ImmutableFields.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/1. ImmutableFields.lr.st new file mode 100644 index 000000000..8358dff90 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/1. ImmutableFields.lr.st @@ -0,0 +1,26 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +composite ImmutableContainer { + val value: int // val indicates immutability of field +} + +procedure valueReader(c: ImmutableContainer): int + { c.value } // no reads clause needed because value is immutable + +/* +Translation towards SMT: + +type Composite; +function ImmutableContainer_value(c: Composite): int + +function valueReader(c: Composite): int { + ImmutableContainer_value(c) +} + +proof valueReader_body { +} +*/ \ No newline at end of file diff --git a/StrataTest/Languages/Laurel/Examples/Objects/2. MutableFields.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/2. MutableFields.lr.st new file mode 100644 index 000000000..d1b328172 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/2. MutableFields.lr.st @@ -0,0 +1,67 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +composite Container { + var value: int // var indicates mutable field +} + +procedure foo(c: Container, d: Container): int + requires c != d +{ + var x = c.value; + d.value = d.value + 1; + assert x == c.value; // pass +} + +procedure caller(c: Container, d: Container) { + var x = foo(c, d); +} + +procedure impureContract(c: Container) + ensures foo(c, c) +// ^ error: a procedure that modifies the heap may not be called in pure context. + +/* +Translation towards SMT: + +type Composite; +type Field; +val value: Field + +function foo(heap_in: Heap, c: Composite, d: Composite) returns (r: int, out_heap: Heap) { + var heap = heap_in; + var x = read(heap, c, value); + heap = update(heap, d, value, read(heap, d, value)); + heap_out = heap; +} + +proof foo_body { + var heap_in; + var Heap; + var c: Composite; + var d: Composite; + var r: int; + var out_heap: Heap; + + var heap = heap_in; + var x = read(heap, c, value); + heap = update(heap, d, value, read(heap, d, value)); + assert x == read(heap, c, value); +} + +proof caller { + var heap_in; + var Heap; + var c: Composite; + var d: Composite; + var heap_out: Heap; + + heap = heap_in; + var x: int; + (x, heap) = foo(heap, c, d); + heap_out = heap; +} +*/ \ No newline at end of file diff --git a/StrataTest/Languages/Laurel/Examples/Objects/3. ReadsClauses.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/3. ReadsClauses.lr.st new file mode 100644 index 000000000..e96a919aa --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/3. ReadsClauses.lr.st @@ -0,0 +1,78 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +/* +Reads clauses CAN be placed on a deterministic procedure to generate a reads axiom. +This axioms states that the result of the procedure is the same if all arguments +and all read heap objects are the same +*/ + +composite Container { + var value: int +} + +procedure opaqueProcedure(c: Container): int + reads c + ensures true + +procedure foo(c: Container, d: Container) +{ + var x = opaqueProcedure(c); + d.value = 1; + var y = opaqueProcedure(c); + assert x == y; // proved using reads clause of opaqueProcedure + c.value = 1; + var z = opaqueProcedure(c); + assert x == z; +// ^^ error: could not prove assert +} + +procedure permissionLessReader(c: Container): int + reads {} + { c.value } +// ^^^^^^^ error: enclosing procedure 'permissionLessReader' does not have permission to read 'c.value' + +/* +Translation towards SMT: + +type Composite; +type Field; +val value: Field; + +function opaqueProcedure_ensures(heap: Heap, c: Container, r: int): boolean { + true +} + +axiom opaqueProcedure_reads(heap1: Heap, heap2: Heap, c: Container) { + heap1[c] == heap2[c] ==> varReader(heap1, c) == varReader(heap2, c) +} + +proof foo_body { + var heap: Heap; + var c: Container; + var d: Container; + + var x: int; + assume opaqueProcedure_ensures(heap, c, x); + heap = update(heap, d, value, 1); + var y: int; + assume opaqueBody_ensures(heap, c, y); + assert x == y; // pass + heap = update(heap, c, value, 1); + var z: int; + assume opaqueBody_ensures(heap, c, z); + assert x == z; // fail +} + +proof permissionLessReader_body { + var heap: Heap + var c: Container; + var reads_permissions: Set; + + assert reads_permissions[c]; // fail +} +*/ + diff --git a/StrataTest/Languages/Laurel/Examples/Objects/4. ModifiesClauses.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/4. ModifiesClauses.lr.st new file mode 100644 index 000000000..f72ccfac6 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/4. ModifiesClauses.lr.st @@ -0,0 +1,92 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +/* +A modifies clause CAN be placed on any procedure to generate a modifies axiom. +The modifies clause determines which references the procedure may modify. +This modifies axiom states how the in and out heap of the procedure relate. + +A modifies clause is crucial on opaque procedures, +since otherwise all heap state is lost after calling them. + +*/ +composite Container { + var value: int +} + +procedure modifyContainerOpaque(c: Container) + ensures true // makes this procedure opaque. Maybe we should use explicit syntax + modifies c +{ + modifyContainerTransparant(c); +} + +procedure modifyContainerTransparant(c: Container) +{ + c.value = c.value + 1; +} + +procedure caller(c: Container, d: Container) { + var x = d.value; + modifyContainerOpaque(c); + assert x == d.value; // pass +} + +procedure modifyContainerWithoutPermission(c: Container) + ensures true +{ + c.value = c.value + 1; +// ^ error: enclosing procedure 'modifyContainerWithoutPermission' does not have permission to modify 'c.value' +} + +/* +Possible translation towards SMT: + +type Composite +type Field +val value: Field + +function modifyContainer(heap_in: Heap, c: Composite) returns (heap_out: Heap) { + var heap = update(heap_in, c, value, read(heap_in, c, value)) + heap_out = heap; +} + +axiom modifyContainer_modifies(heap_in: Heap, c: Composite, other: Composite, heap_out: Heap) { + c != other ==> heap_in[other] == heap_out[other] +} + +proof caller_body { + var heap_in: Heap; + var c: Composite; + var d: Composite; + var heap_out: Heap; + + var heap = heap_in; + var x = read(heap, d, value); + heap = modifyContainer(heap_in, c); + assert x = read(heap, d, value); + heap_out = heap; +} + +proof modifyContainer_body { + var heap_in: Heap; + var c: Composite; + var heap_out: Heap; + val modify_permission: Set[Composite]; + + assume c in modify_permission; + assert c in modify_permission; // pass +} + +proof modifyContainerWithoutPermission_body { + var heap_in: Heap; + var c: Composite; + var heap_out: Heap; + val modify_permission: Set[Composite]; + + assert c in modify_permission; // fail +} +*/ \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/Allocation.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st similarity index 59% rename from Strata/Languages/Laurel/Examples/Allocation.lr.st rename to StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st index 61bda2f38..496c6ae7b 100644 --- a/Strata/Languages/Laurel/Examples/Allocation.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st @@ -1,77 +1,81 @@ -/- +/* Copyright Strata Contributors SPDX-License-Identifier: Apache-2.0 OR MIT --/ --- WIP. needs further design +*/ +// WIP. needs further design --- Create immutable composite +// Create immutable composite composite Immutable { val x: int val y: int invariant x + y >= 5 - val construct = procedure() + procedure construct() constructor requires contructing == {this} ensures constructing == {} { - x = 3; -- we can assign to an immutable field, while the target is in the constructing set. + x = 3; // we can assign to an immutable field, while the target is in the constructing set. y = 2; - construct this; -- checks that all fields of 'this' have been assigned + construct this; // checks that all fields of 'this' have been assigned } } -val foo = procedure() { - val immutable = Immutable.construct(); -- constructor instance method can be called as a static. +procedure foo() { + val immutable = Immutable.construct(); // constructor instance method can be called as a static. } --- Create immutable circle +// Create immutable circle composite ImmutableChainOfTwo { - val other: ChainOfTwo -- note the field is immutable + val other: ChainOfTwo // note the field is immutable - invariant other.other == this -- reading other.other is allowed because the field is immutable + invariant other.other == this // reading other.other is allowed because the field is immutable - val construct = constructor() + procedure construct() + constructor requires contructing == {this} ensures constructing == {} { var second = allocate(); assert constructing == {this, second}; - second.other = first; -- we can assign to a mutable field because second is in the constructing set + second.other = first; // we can assign to a mutable field because second is in the constructing set first.other = second; construct first; construct second; } - -- only used privately - val allocate = constructor() + // only used privately + procedure allocate() + constructor ensures constructing = {this} { - -- empty body + // empty body } } -val foo2 = procedure() { +procedure foo2() { val immutable = ImmutableChainOfTwo.construct(); val same = immutable.other.other; assert immutable =&= same; } --- Helper constructor +// Helper constructor composite UsesHelperConstructor { val x: int val y: int - val setXhelper = constructor() + procedure setXhelper() + constructor requires constructing == {this} ensures constructing == {this} && assigned(this.x) { this.x = 3; } - val construct = constructor() + procedure construct() + constructor requires contructing == {this} ensures constructing == {} { diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st new file mode 100644 index 000000000..77598f74a --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st @@ -0,0 +1,49 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +/* +WIP +*/ +composite Immutable { + val x: int + val y: int + var z: int + + invariant x + y == 6 + + procedure construct(): Immutable + // fields of Immutable are considered mutable inside this procedure + // and invariants of Immutable are not visible + // can only call procedures that are also constructing Immutable + constructs Immutable + modifies this + { + this.x = 3; + assignToY(); + // implicit: assert modifiesOf(construct()).forall(x -> x.invariant()); + } + + procedure assignToY() + constructs Immutable + { + this.y = 3; + } +} + +procedure foo() { + var c = new Immutable.construct(); + var temp = c.x; + c.z = 1; + assert c.x + c.y == 6; // pass + assert temp == c.x; // pass +} + +procedure pureCompositeAllocator(): boolean { + // can be called in a determinstic context + var i: Immutable = Immutable.construct(); + var j: Immutable = Immutable.construct(); + assert i =&= j; // error: refernce equality is not available on deterministic types +} \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/TypeTests.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st similarity index 52% rename from Strata/Languages/Laurel/Examples/TypeTests.lr.st rename to StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st index c3ce5f9dd..8aead7caa 100644 --- a/Strata/Languages/Laurel/Examples/TypeTests.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st @@ -1,8 +1,12 @@ -/- +/* Copyright Strata Contributors SPDX-License-Identifier: Apache-2.0 OR MIT --/ +*/ + +/* +WIP +*/ composite Base { var x: int } @@ -15,12 +19,12 @@ composite Extended2 extends Base { var z: int } -val typeTests = procedure(e: Extended1) { - var b: Base = e as Base; -- even upcasts are not implicit, but they pass statically +procedure typeTests(e: Extended1) { + var b: Base = e as Base; // even upcasts are not implicit, but they pass statically var e2 = e as Extended2; --- ^^ error: could not prove 'e' is of type 'Extended2' +// ^^ error: could not prove 'e' is of type 'Extended2' if (e is Extended2) { - -- unreachable, but that's OK - var e2pass = e as Extended2; -- no error + // unreachable, but that's OK + var e2pass = e as Extended2; // no error } } \ No newline at end of file diff --git a/Strata/Languages/Laurel/Examples/InstanceCallables.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st similarity index 74% rename from Strata/Languages/Laurel/Examples/InstanceCallables.lr.st rename to StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st index 293e1281b..d2269525d 100644 --- a/Strata/Languages/Laurel/Examples/InstanceCallables.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st @@ -1,8 +1,8 @@ -/- +/* Copyright Strata Contributors SPDX-License-Identifier: Apache-2.0 OR MIT --/ +*/ composite Base { procedure foo(): int ensures result > 3 @@ -12,7 +12,7 @@ composite Base { composite Extender1 extends Base { procedure foo(): int ensures result > 4 --- ^^^^^^^ error: could not prove ensures clause guarantees that of extended method 'Base.foo' +// ^^^^^^^ error: could not prove ensures clause guarantees that of extended method 'Base.foo' { abstract } } @@ -21,11 +21,11 @@ composite Extender2 extends Base { procedure foo(): int ensures result > 2 { - this.value + 2 -- 'this' is an implicit variable inside instance callables + this.value + 2 // 'this' is an implicit variable inside instance callables } } val foo = procedure(b: Base) { var x = b.foo(); - assert x > 3; -- pass + assert x > 3; // pass } \ No newline at end of file diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/8. TerminationInheritance.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/8. TerminationInheritance.lr.st new file mode 100644 index 000000000..0a31449f4 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/8. TerminationInheritance.lr.st @@ -0,0 +1,21 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ +trait Base { + predicate foo() +} + +trait Extender extends Base { + // Commenting this method in or out should not change the result of termination checking + // predicate foo() +} + +datatype AnotherExtender extends Base = AnotherExtender(e: Extender) { + + predicate foo() + { + e.foo() + } +} \ No newline at end of file diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st new file mode 100644 index 000000000..17cad41de --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st @@ -0,0 +1,120 @@ +/* + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +*/ +// Work in progress + +/* +Dafny example: + +method hasClosure() returns (r: int) + ensures r == 13 +{ + var x: int := 1; + x := x + 2; + var f: (int) -> int := (y: int) => assert x == 3; y + x + 4; + x := x + 5; // update is lost. + return f(6); +} + +class Wrapper { + var x: int +} + +method hasClosureAndWrapper(wrapper: Wrapper) returns (r: int) + modifies wrapper + ensures r == 15 +{ + wrapper.x := 3; + var f: (int) ~> int := (y: int) reads wrapper => y + wrapper.x + 4; + wrapper.x := 5; + r := f(6); +} +*/ + +/* + +Java example: + +public void myMethod() { + final String prefix = "Hello"; + int count = 0; // effectively final (not modified after initialization) + + class LocalGreeter { + void greet(String name) { + System.out.println(prefix + " " + name); // OK: accesses local variable + // count++; // ERROR: would need to be effectively final + } + } + + LocalGreeter greeter = new LocalGreeter(); + greeter.greet("World"); +} +*/ + +/* +C# example: + +public Func CreateCounter() { + int count = 0; // local variable + return () => count++; // lambda captures 'count' +} + +// Usage: +var counter1 = CreateCounter(); +Console.WriteLine(counter1()); // 0 +Console.WriteLine(counter1()); // 1 +Console.WriteLine(counter1()); // 2 + +var counter2 = CreateCounter(); // Independent copy +Console.WriteLine(counter2()); // 0 +*/ + +/* +What Dafny does: +- The closure refers to variables with their values at the point where the closure is defined. +- The body is transparant. +- The heap is an implicit argument to the closure, so it can change. + +I think all of the above is good, and we can use it for all three cases. +In the Java example, we can create a separate closure for each method of the type closure. + +In the C# example, preprocessing should create a separate class that holds the on-heap variable, +so in affect there no longer are any variables captured by a closure. + +*/ + +// Option A: first class procedures +procedure hasClosure() returns (r: int) + ensures r == 7 +{ + var x = 3; + var aClosure: procedure() returns (r: int) := closure { + r = x + 4; + } + x = 100; + aClosure(); +} + + +// Option B: type closures +composite ATrait { + procedure foo() returns (r: int) ensures r > 0 { + abstract + } +} + +procedure hasClosure() returns (r: int) + ensures r == 7 +{ + var x = 3; + var aClosure := closure extends ATrait { + procedure foo() returns (r: int) + { + r = x + 4; + } + } + x = 100; + aClosure.foo(); +} diff --git a/StrataTest/Languages/Laurel/TestExamples.lean b/StrataTest/Languages/Laurel/TestExamples.lean new file mode 100644 index 000000000..473eacb03 --- /dev/null +++ b/StrataTest/Languages/Laurel/TestExamples.lean @@ -0,0 +1,36 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import Strata.DDM.Elab +import Strata.DDM.BuiltinDialects.Init +import Strata.Util.IO +import Strata.Languages.Laurel.Grammar.LaurelGrammar +import Strata.Languages.Laurel.Grammar.ConcreteToAbstractTreeTranslator +import Strata.Languages.Laurel.LaurelToBoogieTranslator + +open StrataTest.Util +open Strata +open Strata.Elab (parseStrataProgramFromDialect) +open Lean.Parser (InputContext) + +namespace Laurel + +def processLaurelFile (input : InputContext) : IO (Array Diagnostic) := do + let dialects := Strata.Elab.LoadedDialects.ofDialects! #[initDialect, Laurel] + let strataProgram ← parseStrataProgramFromDialect dialects Laurel.name input + + -- Convert to Laurel.Program using parseProgram (handles unwrapping the program operation) + let laurelProgram ← match Laurel.TransM.run input (Laurel.parseProgram strataProgram) with + | .ok program => pure program + | .error errMsg => throw (IO.userError s!"Translation error: {errMsg}") + + -- Verify the program + let diagnostics ← Laurel.verifyToDiagnostics "z3" laurelProgram + + pure diagnostics + +end Laurel diff --git a/StrataTest/Languages/Python/.gitignore b/StrataTest/Languages/Python/.gitignore new file mode 100644 index 000000000..95038a65d --- /dev/null +++ b/StrataTest/Languages/Python/.gitignore @@ -0,0 +1 @@ +*.python.st.ion \ No newline at end of file diff --git a/StrataTest/Languages/Python/README.md b/StrataTest/Languages/Python/README.md new file mode 100644 index 000000000..6ceb8853f --- /dev/null +++ b/StrataTest/Languages/Python/README.md @@ -0,0 +1,18 @@ +# Usage + +## Generate Dialect file: +``` +cd Tools/Python +python -m strata.gen dialect test_results/dialects +``` + +## Generate Ion files per source program: +``` +cd Tools/Python +python -m strata.gen py_to_strata ../../StrataTest/Languages/Python/test.py ../../StrataTest/Languages/Python/test.python.st.ion +``` + +## Run analysis: +``` +lake exe strata pyAnalyze --include Tools/Python/test_results/dialects StrataTest/Languages/Python/test.python.st.ion +``` diff --git a/StrataTest/Languages/Python/expected/README.md b/StrataTest/Languages/Python/expected/README.md new file mode 100644 index 000000000..cf5be1a89 --- /dev/null +++ b/StrataTest/Languages/Python/expected/README.md @@ -0,0 +1,79 @@ + +# How to read expected outputs +`StrataTest/Languages/Python/expected/test_precondition_verification.expected` looks like this: + +``` +assert_name_is_foo: verified + +assert_opt_name_none_or_str: verified + +assert_opt_name_none_or_bar: verified + +ensures_maybe_except_none: verified + +test_helper_procedure_assert_name_is_foo_3: verified + +test_helper_procedure_assert_opt_name_none_or_str_4: verified + +test_helper_procedure_assert_opt_name_none_or_bar_5: verified + +test_helper_procedure_assert_name_is_foo_11: verified + +test_helper_procedure_assert_opt_name_none_or_str_12: verified + +test_helper_procedure_assert_opt_name_none_or_bar_13: verified + +test_helper_procedure_assert_name_is_foo_19: failed +CEx: + +test_helper_procedure_assert_opt_name_none_or_str_20: verified + +test_helper_procedure_assert_opt_name_none_or_bar_21: verified + +test_helper_procedure_assert_name_is_foo_27: verified + +test_helper_procedure_assert_opt_name_none_or_str_28: verified + +test_helper_procedure_assert_opt_name_none_or_bar_29: unknown + +``` + +This can be read as: + +``` +assert_name_is_foo: verified + +assert_opt_name_none_or_str: verified + +assert_opt_name_none_or_bar: verified + +ensures_maybe_except_none: verified +``` + +These come from checking that the assertions/ensures in `test_helper_procedure` hold. +``` +procedure test_helper_procedure(req_name : string, opt_name : StrOrNone) returns (maybe_except: ExceptOrNone) +spec { + requires [req_name_is_foo]: req_name == "foo"; + requires [req_opt_name_none_or_str]: (if (StrOrNone_tag(opt_name) != SN_NONE_TAG) then (StrOrNone_tag(opt_name) == SN_STR_TAG) else true); + requires [req_opt_name_none_or_bar]: (if (StrOrNone_tag(opt_name) == SN_STR_TAG) then (StrOrNone_str_val(opt_name) == "bar") else true); + ensures [ensures_maybe_except_none]: (ExceptOrNone_tag(maybe_except) == EN_NONE_TAG); +} +{ + assert [assert_name_is_foo]: req_name == "foo"; + assert [assert_opt_name_none_or_str]: (if (StrOrNone_tag(opt_name) != SN_NONE_TAG) then (StrOrNone_tag(opt_name) == SN_STR_TAG) else true); + assert [assert_opt_name_none_or_bar]: (if (StrOrNone_tag(opt_name) == SN_STR_TAG) then (StrOrNone_str_val(opt_name) == "bar") else true); + assume [assume_maybe_except_none]: (ExceptOrNone_tag(maybe_except) == EN_NONE_TAG); +}; +``` + +Each of the following triples: +``` +test_helper_procedure_assert_name_is_foo_3: verified + +test_helper_procedure_assert_opt_name_none_or_str_5: verified + +test_helper_procedure_assert_opt_name_none_or_bar_5: verified +``` + +Comes from checking the assertions in the inlined calls of `test_helper_procedure`. The first two triples succeed, the third has a failure because `"Foo" != "foo"` and the final has an `unknown` (that should ideally be a failure) because `"Bar" != "bar"`. \ No newline at end of file diff --git a/StrataTest/Languages/Python/expected/test_datetime.expected b/StrataTest/Languages/Python/expected/test_datetime.expected new file mode 100644 index 000000000..032651103 --- /dev/null +++ b/StrataTest/Languages/Python/expected/test_datetime.expected @@ -0,0 +1,23 @@ + +datetime_now_ensures_0: verified + +datetime_utcnow_ensures_0: verified + +ensures_str_strp_reverse: verified + +assert_name_is_foo: verified + +assert_opt_name_none_or_str: verified + +assert_opt_name_none_or_bar: verified + +ensures_maybe_except_none: verified + +py_assertion: unknown + +py_assertion: verified + +py_assertion: verified + +py_assertion: failed +CEx: diff --git a/StrataTest/Languages/Python/expected/test_function_def_calls.expected b/StrataTest/Languages/Python/expected/test_function_def_calls.expected new file mode 100644 index 000000000..929ca1a80 --- /dev/null +++ b/StrataTest/Languages/Python/expected/test_function_def_calls.expected @@ -0,0 +1,33 @@ + +datetime_now_ensures_0: verified + +datetime_utcnow_ensures_0: verified + +ensures_str_strp_reverse: verified + +assert_name_is_foo: verified + +assert_opt_name_none_or_str: verified + +assert_opt_name_none_or_bar: verified + +ensures_maybe_except_none: verified + +test_helper_procedure_assert_name_is_foo_3: failed +CEx: + +test_helper_procedure_assert_opt_name_none_or_str_4: verified + +test_helper_procedure_assert_opt_name_none_or_bar_5: verified + +(Origin_test_helper_procedure_Requires)req_name_is_foo: verified + +(Origin_test_helper_procedure_Requires)req_opt_name_none_or_str: verified + +(Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar: verified + +(Origin_test_helper_procedure_Requires)req_name_is_foo: verified + +(Origin_test_helper_procedure_Requires)req_opt_name_none_or_str: verified + +(Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar: verified diff --git a/StrataTest/Languages/Python/expected/test_precondition_verification.expected b/StrataTest/Languages/Python/expected/test_precondition_verification.expected new file mode 100644 index 000000000..f62d653e1 --- /dev/null +++ b/StrataTest/Languages/Python/expected/test_precondition_verification.expected @@ -0,0 +1,39 @@ + +datetime_now_ensures_0: verified + +datetime_utcnow_ensures_0: verified + +ensures_str_strp_reverse: verified + +assert_name_is_foo: verified + +assert_opt_name_none_or_str: verified + +assert_opt_name_none_or_bar: verified + +ensures_maybe_except_none: verified + +test_helper_procedure_assert_name_is_foo_3: verified + +test_helper_procedure_assert_opt_name_none_or_str_4: verified + +test_helper_procedure_assert_opt_name_none_or_bar_5: verified + +test_helper_procedure_assert_name_is_foo_11: verified + +test_helper_procedure_assert_opt_name_none_or_str_12: verified + +test_helper_procedure_assert_opt_name_none_or_bar_13: verified + +test_helper_procedure_assert_name_is_foo_19: failed +CEx: + +test_helper_procedure_assert_opt_name_none_or_str_20: verified + +test_helper_procedure_assert_opt_name_none_or_bar_21: verified + +test_helper_procedure_assert_name_is_foo_27: verified + +test_helper_procedure_assert_opt_name_none_or_str_28: verified + +test_helper_procedure_assert_opt_name_none_or_bar_29: unknown diff --git a/StrataTest/Languages/Python/run_py_analyze.sh b/StrataTest/Languages/Python/run_py_analyze.sh new file mode 100755 index 000000000..d15bac8b6 --- /dev/null +++ b/StrataTest/Languages/Python/run_py_analyze.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +failed=0 + +for test_file in tests/test_*.py; do + if [ -f "$test_file" ]; then + base_name=$(basename "$test_file" .py) + ion_file="tests/${base_name}.python.st.ion" + expected_file="expected/${base_name}.expected" + + if [ -f "$expected_file" ]; then + (cd ../../../Tools/Python && python -m strata.gen py_to_strata "../../StrataTest/Languages/Python/$test_file" "../../StrataTest/Languages/Python/$ion_file") + + output=$(cd ../../.. && lake exe strata pyAnalyze --include Tools/Python/test_results/dialects "StrataTest/Languages/Python/${ion_file}" 0) + + if ! echo "$output" | diff -q "$expected_file" - > /dev/null; then + echo "ERROR: Analysis output for $base_name does not match expected result" + echo "$output" | diff "$expected_file" - + failed=1 + fi + fi + fi +done + +exit $failed diff --git a/StrataTest/Languages/Python/test_helper.py b/StrataTest/Languages/Python/test_helper.py new file mode 100644 index 000000000..4972ff83d --- /dev/null +++ b/StrataTest/Languages/Python/test_helper.py @@ -0,0 +1,63 @@ +"""Test helper functions for Strata Python language testing.""" + +from typing import Dict, Any + +def procedure(req_name: str, opt_name: str | None) -> None: + """Test procedure with required and optional parameters. + + Args: + req_name: Required name parameter, must be "foo" + opt_name: Optional name parameter, must be None or "bar" + """ + assert req_name == "foo" + assert opt_name is None or opt_name == "bar" + +def create_client(client_type: str, client_config: str) -> Any: + """Create a test client with specified type and configuration. + + Args: + client_type: Type of client, must be 'foo' or 'bar' + client_config: Configuration string for the client + + Returns: + Dictionary containing client type and configuration + """ + assert client_type in ['foo', 'bar'] + return {'client_type': client_type, 'client_config': client_config} + +def upload(client: Any, folder: str, key: str, payload: Any, encryption_type: str | None = None, encryption_key_id: str | None = None) -> Dict[str, Any]: + """Upload payload to specified folder with optional encryption. + + Args: + client: Client object for upload + folder: Target folder name (3-63 chars, lowercase, specific format rules) + key: Upload key identifier + payload: Data to upload + encryption_type: Optional encryption method + encryption_key_id: Optional encryption key ID (requires encryption_type) + + Returns: + Dictionary with upload status + """ + assert len(folder) >= 3 and len(folder) <= 63 + assert folder.replace('-', '').replace('.', '').islower() + assert not folder.startswith('-') and not folder.startswith('.') + assert not folder.startswith('xn--') + assert not folder.endswith('-alias') + if encryption_key_id is not None: + assert encryption_type is not None + return {'status': 'success'} + +def invoke(client: Any, model_id: str, input_data: str) -> str: + """Invoke model with input data using specified client. + + Args: + client: Client object (config cannot be 'config-c') + model_id: Identifier for the model to invoke + input_data: Input data for model processing + + Returns: + Model response string + """ + assert client['client_config'] != 'config-c' + return 'model response' \ No newline at end of file diff --git a/StrataTest/Languages/Python/tests/missing_required_param.py b/StrataTest/Languages/Python/tests/missing_required_param.py new file mode 100644 index 000000000..2964f9495 --- /dev/null +++ b/StrataTest/Languages/Python/tests/missing_required_param.py @@ -0,0 +1,38 @@ +from typing import Dict, Any +import test_helper + +print("=== Test 1: Create valid foo client ===") +storage_client = test_helper.create_client('foo', 'config-a') +print("✓ Successfully created storage client\n") + +folder_name: str = "test-folder" +key: str = "test-encryption.txt" +payload: bytes = b"sample encrypted content" + +print("=== Test 2: encryption_key_id without encryption_type parameter ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=storage_client, + folder=folder_name, + key=key, + content=payload, + encryption_key_id='key-12345678-1234-1234-1234-123456789012' + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {e}\n") +except Exception as e: + print(f"✗ Caught unexpected exception: {type(e).__name__}: {e}\n") + +print("=== Test 3: encryption_type='AES256' without encryption_key_id (valid) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=storage_client, + folder=folder_name, + key=key + "-aes256", + content=payload, + encryption_type='AES256' + ) + print("✓ Successfully called upload with valid encryption configuration") +except Exception as e: + print(f"✗ Unexpected exception: {type(e).__name__}: {e}") diff --git a/StrataTest/Languages/Python/tests/test_datetime.py b/StrataTest/Languages/Python/tests/test_datetime.py new file mode 100644 index 000000000..4a82e3862 --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_datetime.py @@ -0,0 +1,30 @@ +from datetime import datetime, date, timedelta + +# def my_f(start: datetime, end: datetime): +# assert start <= end + +# def my_f_str(start: str, end : str): +# format_string : str = "%Y-%m-%d" +# start_dt : datetime = datetime.strptime(start, format_string) +# end_dt : datetime = datetime.strptime(end, format_string) +# assert start_dt <= end_dt + +now : datetime = datetime.now() +end : datetime = datetime.date(now) +delta : timedelta = timedelta(days=7) +start : datetime = end - delta + +# my_f(start, end) + +# my_f_str(str(start), str(end)) + +assert start <= end + +# These require mbqi / autoconfig +start_dt : datetime = datetime.strptime(str(start), "%Y-%m-%d") +assert start_dt == start +end_dt : datetime = datetime.strptime(str(end), "%Y-%m-%d") +assert end_dt == end + +# This is unknown +assert start_dt <= end_dt diff --git a/StrataTest/Languages/Python/tests/test_foo_client_folder.py b/StrataTest/Languages/Python/tests/test_foo_client_folder.py new file mode 100644 index 000000000..c19ee691e --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_foo_client_folder.py @@ -0,0 +1,101 @@ +from typing import Dict, Any +import test_helper + +print("=== Test 1: Create valid foo client ===") +foo_client = test_helper.create_client('foo', 'config-a') +print("✓ Successfully created foo client\n") + +payload: str = "sample contents for test.txt" +key: str = "test.txt" + +short_folder_name: str = "ab" +long_folder_name: str = "a" * 64 +invalid_chars_folder: str = "MyFolderName" +invalid_pattern_folder: str = "-invalid-folder" +invalid_prefix_folder: str = "xn--invalid-folder" +invalid_suffix_folder: str = "invalid-folder-alias" +valid_folder_name: str = "test-folder" + +print("=== Test 2: folder name too short (< 3 chars) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=short_folder_name, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 3: folder name too long (> 63 chars) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=long_folder_name, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 4: folder name contains uppercase (invalid) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=invalid_chars_folder, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 5: folder name starts with hyphen (invalid) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=invalid_pattern_folder, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 6: folder name starts with 'xn--' (invalid prefix) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=invalid_prefix_folder, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 7: folder name ends with '-alias' (invalid suffix) ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=invalid_suffix_folder, + key=key, + content=payload + ) + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {str(e)[:80]}...\n") + +print("=== Test 8: Valid folder name following all rules ===") +try: + result: Dict[str, Any] = test_helper.upload( + client=foo_client, + folder=valid_folder_name, + key=key, + content=payload + ) + print("✓ Successfully called upload_object with valid folder name") +except Exception as e: + print(f"✗ Unexpected exception: {type(e).__name__}: {e}") diff --git a/StrataTest/Languages/Python/tests/test_function_def_calls.py b/StrataTest/Languages/Python/tests/test_function_def_calls.py new file mode 100644 index 000000000..31276d736 --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_function_def_calls.py @@ -0,0 +1,12 @@ +import test_helper + +# Test function defs + +def my_f(s: str): + test_helper.procedure(s) + +def main(): + my_f("foo") + +if __name__ == "__main__": + main() diff --git a/StrataTest/Languages/Python/tests/test_invalid_client_type.py b/StrataTest/Languages/Python/tests/test_invalid_client_type.py new file mode 100644 index 000000000..64b3a0d2f --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_invalid_client_type.py @@ -0,0 +1,15 @@ +from typing import List +import test_helper + +print("=== Test 1: Valid service name 'foo' ===") +foo_client = test_helper.create_client('foo', 'config-a') +print("✓ Successfully created foo client\n") + +print("=== Test 2: Invalid service name 'Foo' ===") +try: + invalid_client = test_helper.create_client('Foo', 'config-a') + print("✗ ERROR: Should have raised AssertionError") +except AssertionError as e: + print(f"✓ Caught expected AssertionError: {e}") +except Exception as e: + print(f"✗ Caught unexpected exception: {type(e).__name__}: {e}") diff --git a/StrataTest/Languages/Python/tests/test_precondition_verification.py b/StrataTest/Languages/Python/tests/test_precondition_verification.py new file mode 100644 index 000000000..f6c4df14b --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_precondition_verification.py @@ -0,0 +1,20 @@ +import test_helper + + +def main(): + # Test minimal precondition verification + + # Should succeed + test_helper.procedure("foo") + + # Should succeed + test_helper.procedure("foo", opt_name = "bar") + + # Should error + test_helper.procedure("Foo") + + # Should error + test_helper.procedure("foo", opt_name = "Bar") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/StrataTest/Languages/Python/tests/test_unsupported_config.py b/StrataTest/Languages/Python/tests/test_unsupported_config.py new file mode 100644 index 000000000..b39eff7ea --- /dev/null +++ b/StrataTest/Languages/Python/tests/test_unsupported_config.py @@ -0,0 +1,25 @@ +import test_helper +import json + +def main(): + # Using config-c which doesn't support AI service + bar_client = test_helper.create_client('bar', 'config-c') + + try: + response: str = test_helper.invoke( + client=bar_client, + arg_str='bar', + input_data=json.dumps({ + 'inputText': 'Hello, world!', + 'config': { + 'myInt': 50, + 'myFloat': 0.7 + } + }) + ) + print("Success:", response) + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main() diff --git a/Strata/Transform/Examples.lean b/StrataTest/Transform/CallElim.lean similarity index 78% rename from Strata/Transform/Examples.lean rename to StrataTest/Transform/CallElim.lean index e72963420..8fe5d5cdb 100644 --- a/Strata/Transform/Examples.lean +++ b/StrataTest/Transform/CallElim.lean @@ -4,16 +4,19 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -import Strata.Transform.CallElim -import Strata.Transform.DetToNondet -import Strata.Languages.Boogie.StatementSemantics +import Strata.DDM.Integration.Lean +import Strata.DDM.Util.Format +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Boogie.DDMTransform.Translate import Strata.Languages.Boogie.ProgramType import Strata.Languages.Boogie.ProgramWF -import Strata.DL.Lambda.IntBoolFactory +import Strata.Languages.Boogie.StatementSemantics +import Strata.Transform.BoogieTransform +import Strata.Transform.CallElim -/-! # Program Transformation Examples -/ open Boogie +open Boogie.Transform open CallElim open Strata @@ -174,7 +177,7 @@ procedure h() returns () spec { }; #end -def translate (t : Strata.Program) : Boogie.Program := (TransM.run (translateProgram t)).fst +def translate (t : Strata.Program) : Boogie.Program := (TransM.run Inhabited.default (translateProgram t)).fst def env := (Lambda.LContext.default.addFactoryFunctions Boogie.Factory) @@ -192,7 +195,7 @@ def tests : List (Boogie.Program × Boogie.Program) := [ def callElim (p : Boogie.Program) : Boogie.Program := - match (runCallElim p callElim') with + match (run p callElim') with | .ok res => res | .error e => panic! e @@ -202,34 +205,7 @@ info: true #guard_msgs in #eval tests.all (λ (test, ans) ↦ (toString (callElim test).eraseTypes) == (toString ans.eraseTypes)) ---#eval callElim tests[1].fst ---#eval tests[1].snd +-- #eval callElim tests[0]!.fst +-- #eval tests[0]!.snd end CallElimExamples - -/-! ## Deterministic-to-Nondeterministic Examples -/ -section NondetExamples - -open Imperative - -def NondetTest1 : Stmt Expression (Cmd Expression) := - .ite (Boogie.true) {ss := - [.cmd $ .havoc "x" ] - } {ss := - [.cmd $ .havoc "y" ] - } - -def NondetTest1Ans : NondetStmt Expression (Cmd Expression) := - .choice - (.seq (.cmd (.assume "true_cond" Boogie.true)) (.seq (.cmd $ .havoc "x") (.assume "skip" Imperative.HasBool.tt))) - (.seq (.cmd (.assume "false_cond" Boogie.false)) (.seq (.cmd $ .havoc "y") (.assume "skip" Imperative.HasBool.tt))) - - --- #eval toString $ Std.format (StmtToNondetStmt NondetTest1) --- #eval toString $ Std.format NondetTest1Ans - -/-- info: true -/ -#guard_msgs in -#eval (toString $ Std.format (StmtToNondetStmt NondetTest1)) == (toString $ Std.format NondetTest1Ans) - -end NondetExamples diff --git a/StrataTest/Transform/DetToNondet.lean b/StrataTest/Transform/DetToNondet.lean new file mode 100644 index 000000000..4c95af7fb --- /dev/null +++ b/StrataTest/Transform/DetToNondet.lean @@ -0,0 +1,36 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Transform.DetToNondet +import Strata.Languages.Boogie.StatementSemantics +import Strata.Languages.Boogie.ProgramType +import Strata.Languages.Boogie.ProgramWF +import Strata.DL.Lambda.IntBoolFactory + +open Boogie + +/-! ## Deterministic-to-Nondeterministic Examples -/ +section NondetExamples + +open Imperative + +def NondetTest1 : Stmt Expression (Cmd Expression) := + .ite (Boogie.true) [.cmd $ .havoc "x" ] [.cmd $ .havoc "y" ] + +def NondetTest1Ans : NondetStmt Expression (Cmd Expression) := + .choice + (.seq (.cmd (.assume "true_cond" Boogie.true)) (.seq (.cmd $ .havoc "x") (.assume "skip" Imperative.HasBool.tt))) + (.seq (.cmd (.assume "false_cond" Boogie.false)) (.seq (.cmd $ .havoc "y") (.assume "skip" Imperative.HasBool.tt))) + + +-- #eval toString $ Std.format (StmtToNondetStmt NondetTest1) +-- #eval toString $ Std.format NondetTest1Ans + +/-- info: true -/ +#guard_msgs in +#eval (toString $ Std.format (StmtToNondetStmt NondetTest1)) == (toString $ Std.format NondetTest1Ans) + +end NondetExamples diff --git a/StrataTest/Transform/ProcedureInlining.lean b/StrataTest/Transform/ProcedureInlining.lean new file mode 100644 index 000000000..fd90951eb --- /dev/null +++ b/StrataTest/Transform/ProcedureInlining.lean @@ -0,0 +1,343 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.DDM.Integration.Lean +import Strata.DDM.Util.Format +import Strata.Languages.Boogie.Boogie +import Strata.Languages.Boogie.DDMTransform.Translate +import Strata.Languages.Boogie.StatementSemantics +import Strata.Languages.Boogie.ProgramType +import Strata.Languages.Boogie.ProgramWF +import Strata.Transform.BoogieTransform +import Strata.Transform.ProcedureInlining + +open Boogie +open Boogie.Transform +open ProcedureInlining +open Strata +open Std + +/-! ## Procedure Inlining Examples -/ + +section ProcedureInliningExamples + + +-- Alpha equivalence of procedures for testing! + + +structure IdMap where + vars: (Map String String × Map String String) + labels: Map String String + +private def IdMap.updateVars (map:IdMap) (newmap: List (String × String)) + : Except Format IdMap := do + let newvars ← newmap.foldlM (fun (m1, m2) ((oldid,newid):String × String) => + match Map.find? m1 oldid, Map.find? m2 newid with + | .some x, _ => .error (f!"Has duplicated definition of var " ++ oldid ++ + "(previously mapped to " ++ x ++ ")") + | _, .some y => .error (f!"Has duplicated definition of var " ++ newid ++ + "(previously mapped to " ++ y ++ ")") + | .none, .none => return (m1.insert oldid newid, m2.insert newid oldid)) + map.vars + return { map with vars := newvars } + +private def IdMap.updateLabel (map:IdMap) (frlbl:String) (tolbl:String) + : Except Format IdMap := do + match Map.find? map.labels frlbl with + | .none => + return { map with labels := Map.insert map.labels frlbl tolbl } + | .some x => + if x == tolbl then return map + else .error ("Label " ++ frlbl ++ " is already mapped to " ++ x ++ + " but tried to map to " ++ tolbl) + +private def IdMap.lblMapsTo (map:IdMap) (fr:String) (to:String): Bool := + match Map.find? map.labels fr with + | .none => false + | .some x => x == to + + +private def substExpr (e1:Expression.Expr) (map:Map String String) (isReverse: Bool) := + map.foldl + (fun (e:Expression.Expr) ((i1,i2):String × String) => + -- old_id has visibility of temp because the new local variables were + -- created by BoogieGenM. + -- new_expr has visibility of unres because that is the default setting + -- from DDM parsed program, and the substituted program is supposed to be + -- equivalent to the answer program translated from DDM + -- These must be reversed when checking e2 -> e1 + let old_vis := if not isReverse then Visibility.temp else Visibility.unres + let new_vis := if not isReverse then Visibility.unres else Visibility.temp + let old_id:Expression.Ident := { name := i1, metadata := old_vis } + + let new_expr:Expression.Expr := .fvar () + { name := i2, metadata := new_vis } .none + e.substFvar old_id new_expr) + e1 + +private def alphaEquivExprs (e1 e2: Expression.Expr) (map:IdMap) + : Bool := + (substExpr e1 (map.vars.fst) false).eraseTypes == e2.eraseTypes && + (substExpr e2 (map.vars.snd) true).eraseTypes == e1.eraseTypes + +private def alphaEquivExprsOpt (e1 e2: Option Expression.Expr) (map:IdMap) + : Except Format Bool := + match e1,e2 with + | .some e1, .some e2 => + return alphaEquivExprs e1 e2 map + | .none, .none => + return .true + | _, _ => + .error ".some and .none mismatch" + +private def alphaEquivIdents (e1 e2: Expression.Ident) (map:IdMap) + : Bool := + (-- Case 1: e1 is created from inliner, e2 was from DDM + (e1.metadata == Visibility.temp && e2.metadata == Visibility.unres) || + -- Caes 2: both e1 and e2 are from DDM + (e1.metadata == e2.metadata)) && + (match Map.find? map.vars.fst e1.name, Map.find? map.vars.snd e2.name with + | .some n', .some m' => n' == e2.name && m' == e1.name + | .none, .none => e1.name == e2.name + | _, _ => false ) + + +mutual + +partial def alphaEquivBlock (b1 b2: Boogie.Block) (map:IdMap) + : Except Format IdMap := do + if b1.length ≠ b2.length then + .error "Block lengths do not match" + else + (b1.zip b2).foldlM + (fun (map:IdMap) (st1,st2) => do + let newmap ← alphaEquivStatement st1 st2 map + return newmap) + map + +partial def alphaEquivStatement (s1 s2: Boogie.Statement) (map:IdMap) + : Except Format IdMap := do + let mk_err (s:Format): Except Format IdMap := + .error (f!"{s}\ns1:{s1}\ns2:{s2}\nmap:{map.vars}") + + match (s1,s2) with + | (.block lbl1 b1 _, .block lbl2 b2 _) => + -- Since 'goto lbl' can appear before 'lbl' is defined, update the label + -- map here + let map ← IdMap.updateLabel map lbl1 lbl2 + alphaEquivBlock b1 b2 map + + | (.ite cond1 thenb1 elseb1 _, .ite cond2 thenb2 elseb2 _) => do + if alphaEquivExprs cond1 cond2 map then + let map' <- alphaEquivBlock thenb1 thenb2 map + let map'' <- alphaEquivBlock elseb1 elseb2 map' + return map'' + else + .error "if conditions do not match" + + | (.loop g1 m1 i1 b1 _, .loop g2 m2 i2 b2 _) => + if ¬ alphaEquivExprs g1 g2 map then + .error "guard does not match" + else if ¬ (← alphaEquivExprsOpt m1 m2 map) then + .error "measure does not match" + else if ¬ (← alphaEquivExprsOpt i1 i2 map) then + .error "invariant does not match" + else alphaEquivBlock b1 b2 map + + | (.goto lbl1 _, .goto lbl2 _) => + IdMap.updateLabel map lbl1 lbl2 + + | (.cmd c1, .cmd c2) => + match (c1, c2) with + | (.call lhs1 procName1 args1 _, .call lhs2 procName2 args2 _) => + if procName1 ≠ procName2 then + .error "Procedure name does not match" + else if lhs1.length ≠ lhs2.length then + .error "Call LHS length does not match" + else if ¬ (lhs1.zip lhs2).all + (fun (lhs1,lhs2) => alphaEquivIdents lhs1 lhs2 map) then + .error "Call LHS name does not map" + else if (args1.length ≠ args2.length) then + .error "Call args length does not match" + else if ¬ (args1.zip args2).all (fun (arg1,arg2) => + ¬ alphaEquivExprs arg1 arg2 map) then + .error "Call args do not map" + else + return map + | (.cmd (.init n1 _ _e1 _), .cmd (.init n2 _ _e2 _)) => + -- Omit e1 and e2 check because init may use undeclared free vars + -- The updateVars below must be the only place that updates the + -- variable name mapping. + IdMap.updateVars map [(n1.name,n2.name)] + | (.cmd (.set n1 e1 _), .cmd (.set n2 e2 _)) => + if ¬ alphaEquivExprs e1 e2 map then + mk_err f!"RHS of sets do not match \ + \n(subst of e1: {repr (substExpr e1 map.vars.fst false)})\n(e2: {repr e2}) + \n(subst of e2: {repr (substExpr e2 map.vars.snd true)})\n(e1: {repr e1})" + else if ¬ alphaEquivIdents n1 n2 map then + mk_err "LHS of sets do not match" + else + return map + | (.cmd (.havoc n1 _), .cmd (.havoc n2 _)) => + if ¬ alphaEquivIdents n1 n2 map then + mk_err "LHS of havocs do not match" + else + return map + | (.cmd (.assert _ e1 _), .cmd (.assert _ e2 _)) => + if ¬ alphaEquivExprs e1 e2 map then + mk_err "Expressions of asserts do not match" + else + return map + | (.cmd (.assume _ e1 _), .cmd (.assume _ e2 _)) => + if ¬ alphaEquivExprs e1 e2 map then + mk_err "Expressions of assumes do not match" + else + return map + | (_,_) => + mk_err "Commands do not match" + + | (_,_) => mk_err "Statements do not match" + +end + +private def alphaEquiv (p1 p2:Boogie.Procedure):Except Format Bool := do + if p1.body.length ≠ p2.body.length then + dbg_trace f!"p1: {p1}" + dbg_trace f!"p2: {p2}" + .error (s!"# statements do not match: inlined fn one has {p1.body.length}" + ++ s!" whereas the answer has {p2.body.length}") + else + let newmap:IdMap := IdMap.mk ([], []) [] + let stmts := (p1.body.zip p2.body) + let m ← List.foldlM (fun (map:IdMap) (s1,s2) => + alphaEquivStatement s1 s2 map) + newmap stmts + -- The corresponding outputs should be pairwise α-equivalent + return ((p1.header.outputs.zip p2.header.outputs).map (fun ((x, _), (y, _)) => alphaEquivIdents x y m)).all id + + + +def translate (t : Strata.Program) : Boogie.Program := + (TransM.run Inhabited.default (translateProgram t)).fst + +def runInlineCall (p : Boogie.Program) : Boogie.Program := + match (runProgram inlineCallStmt p .emp) with + | ⟨.ok res, _⟩ => res + | ⟨.error e, _⟩ => panic! e + +def checkInlining (prog : Boogie.Program) (progAns : Boogie.Program) + : Except Format Bool := do + let prog' := runInlineCall prog + let pp' := prog'.decls.zip progAns.decls + pp'.allM (fun (p,p') => do + match p,p' with + | .proc p _, .proc p' _ => + match alphaEquiv p p' with + | .ok _ => return .true + | .error msg => + dbg_trace s!"{toString prog'}" + dbg_trace s!"{toString progAns}" + .error msg + | _, _ => .error "?") + + + +def Test1 := +#strata +program Boogie; +procedure f(x : bool) returns (y : bool) { + havoc x; + y := !x; +}; + +procedure h() returns () { + var b_in : bool; + var b_out : bool; + call b_out := f(b_in); +}; +#end + +def Test1Ans := +#strata +program Boogie; +procedure f(x : bool) returns (y : bool) { + havoc x; + y := !x; +}; + +procedure h() returns () { + var b_in : bool; + var b_out : bool; + inlined: { + var tmp_arg_0 : bool := b_in; + var tmp_arg_1 : bool; + havoc tmp_arg_0; + tmp_arg_1 := !tmp_arg_0; + b_out := tmp_arg_1; + } +}; + +#end + +/-- info: ok: true -/ +#guard_msgs in +#eval checkInlining (translate Test1) (translate Test1Ans) + +def Test2 := +#strata +program Boogie; +procedure f(x : bool) returns (y : bool) { + if (x) { + goto end; + } else { y := false; + } + end: {} +}; + +procedure h() returns () { + var b_in : bool; + var b_out : bool; + call b_out := f(b_in); + end: {} +}; +#end + +def Test2Ans := +#strata +program Boogie; +procedure f(x : bool) returns (y : bool) { + if (x) { + goto end; + } else { y := false; + } + end: {} +}; + +procedure h() returns () { + var b_in : bool; + var b_out : bool; + inlined: { + var f_x : bool := b_in; + var f_y : bool; + if (f_x) { + goto f_end; + } else { + f_y := false; + } + f_end: {} + b_out := f_y; + } + end: {} +}; + +#end + +/-- info: ok: true -/ +#guard_msgs in +#eval checkInlining (translate Test2) (translate Test2Ans) + + +end ProcedureInliningExamples diff --git a/StrataTest/Util/IO.lean b/StrataTest/Util/IO.lean new file mode 100644 index 000000000..480acd8c0 --- /dev/null +++ b/StrataTest/Util/IO.lean @@ -0,0 +1,50 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Util.IO + +-- Test that readInputSource can read from a regular file +def testReadFile : IO Unit := do + IO.FS.withTempFile fun _handle tempPath => do + -- Write test content to the temporary file + IO.FS.writeFile tempPath "Hello from file" + + -- Read it back using our utility + let content ← Strata.Util.readInputSource tempPath.toString + + -- Verify content + if content != "Hello from file" then + throw (IO.Error.userError "File read failed") + +/-- +info: File read test passed +-/ +#guard_msgs in +#eval do + testReadFile + IO.println "File read test passed" + +-- Test that readBinInputSource can read from a regular file +def testReadBinFile : IO Unit := do + IO.FS.withTempFile fun _handle tempPath => do + -- Write test content to the temporary file + let testData := "Binary test data".toUTF8 + IO.FS.writeBinFile tempPath testData + + -- Read it back using our utility + let content ← Strata.Util.readBinInputSource tempPath.toString + + -- Verify content + if content != testData then + throw (IO.Error.userError "Binary file read failed") + +/-- +info: Binary file read test passed +-/ +#guard_msgs in +#eval do + testReadBinFile + IO.println "Binary file read test passed" diff --git a/StrataTest/Util/TestDiagnostics.lean b/StrataTest/Util/TestDiagnostics.lean new file mode 100644 index 000000000..312cfe54a --- /dev/null +++ b/StrataTest/Util/TestDiagnostics.lean @@ -0,0 +1,135 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Languages.Boogie.Verifier +import Lean.Elab.Command + +open Strata +open String +open Lean Elab +namespace StrataTest.Util + +/-- A diagnostic expectation parsed from source comments -/ +structure DiagnosticExpectation where + line : Nat + colStart : Nat + colEnd : Nat + level : String + message : String + deriving Repr, BEq + +/-- Parse diagnostic expectations from source file comments. + Format: `-- ^^^^^^ error: message` on the line after the problematic code -/ +def parseDiagnosticExpectations (content : String) : List DiagnosticExpectation := Id.run do + let lines := content.splitOn "\n" + let mut expectations := [] + + for i in [0:lines.length] do + let line := lines[i]! + -- Check if this is a comment line with diagnostic expectation + if line.trimLeft.startsWith "//" then + let trimmed := line.trimLeft.drop 2 -- Remove "//" + -- Find the caret sequence + let caretStart := trimmed.find (· == '^') + let mut currentCaret := caretStart + while not (Pos.Raw.atEnd trimmed currentCaret) && (Pos.Raw.get trimmed currentCaret) == '^' do + currentCaret := Pos.Raw.next trimmed currentCaret + + -- Get the message part after carets + let afterCarets := trimmed.drop currentCaret.byteIdx |>.trim + if afterCarets.length > 0 then + -- Parse level and message + match afterCarets.splitOn ":" with + | level :: messageParts => + let level := level.trim + let message := (": ".intercalate messageParts).trim + + -- Calculate column positions (carets are relative to line start including comment spacing) + let commentPrefix := (line.takeWhile (fun c => c == ' ' || c == '\t')).length + "//".length + let caretColStart := commentPrefix + caretStart.byteIdx + let caretColEnd := commentPrefix + currentCaret.byteIdx + + -- The diagnostic is on the previous line + if i > 0 then + expectations := expectations.append [{ + line := i, -- 1-indexed line number (the line before the comment) + colStart := caretColStart, + colEnd := caretColEnd, + level := level, + message := message + }] + | [] => pure () + + expectations + +/-- Check if one string contains another as a substring -/ +def stringContains (haystack : String) (needle : String) : Bool := + needle.isEmpty || (haystack.splitOn needle).length > 1 + +/-- Check if a Diagnostic matches a DiagnosticExpectation -/ +def matchesDiagnostic (diag : Diagnostic) (exp : DiagnosticExpectation) : Bool := + diag.start.line == exp.line && + diag.start.column == exp.colStart && + diag.ending.line == exp.line && + diag.ending.column == exp.colEnd && + stringContains diag.message exp.message + +/-- Test input with line offset - adds imaginary newlines to the start of the input -/ +def testInputWithOffset (filename: String) (input : String) (lineOffset : Nat) + (process : Lean.Parser.InputContext -> IO (Array Diagnostic)) : IO Unit := do + + -- Add imaginary newlines to the start of the input so the reported line numbers match the Lean source file + let offsetInput := String.join (List.replicate lineOffset "\n") ++ input + let inputContext := Parser.stringInputContext filename offsetInput + + -- Parse diagnostic expectations from comments + let expectations := parseDiagnosticExpectations offsetInput + let expectedErrors := expectations.filter (fun e => e.level == "error") + + -- Get actual diagnostics from the language-specific processor + let diagnostics <- process inputContext + + -- Check if all expected errors are matched + let mut allMatched := true + let mut unmatchedExpectations := [] + + for exp in expectedErrors do + let matched := diagnostics.any (fun diag => matchesDiagnostic diag exp) + if !matched then + allMatched := false + unmatchedExpectations := unmatchedExpectations.append [exp] + + let mut unmatchedDiagnostics := [] + for diag in diagnostics do + let matched := expectedErrors.any (fun exp => matchesDiagnostic diag exp) + if !matched then + allMatched := false + unmatchedDiagnostics := unmatchedDiagnostics.append [diag] + + -- Report results + if allMatched && diagnostics.size == expectedErrors.length then + IO.println s!"✓ Test passed: All {expectedErrors.length} error(s) matched" + for exp in expectedErrors do + IO.println s!" - Line {exp.line}, Col {exp.colStart}-{exp.colEnd}: {exp.message}" + else + IO.println s!"✗ Test failed: Mismatched diagnostics" + IO.println s!"\nExpected {expectedErrors.length} error(s), got {diagnostics.size} diagnostic(s)" + + if unmatchedExpectations.length > 0 then + IO.println s!"\nUnmatched expected diagnostics:" + for exp in unmatchedExpectations do + IO.println s!" - Line {exp.line}, Col {exp.colStart}-{exp.colEnd}: {exp.message}" + + if unmatchedDiagnostics.length > 0 then + IO.println s!"\nUnexpected diagnostics:" + for diag in unmatchedDiagnostics do + IO.println s!" - Line {diag.start.line}, Col {diag.start.column}-{diag.ending.column}: {diag.message}" + throw (IO.userError "Test failed") + +def testInput (filename: String) (input : String) (process : Lean.Parser.InputContext -> IO (Array Diagnostic)) : IO Unit := + testInputWithOffset filename input 0 process + +end StrataTest.Util diff --git a/StrataToCBMC.lean b/StrataToCBMC.lean index f11687008..d1abed99f 100644 --- a/StrataToCBMC.lean +++ b/StrataToCBMC.lean @@ -9,6 +9,7 @@ import Strata.Backends.CBMC.StrataToCBMC import Strata.Backends.CBMC.BoogieToCBMC import Strata.Languages.Boogie.Verifier import Strata.Languages.C_Simp.Verify +import Strata.Util.IO import Std.Internal.Parsec open Strata @@ -18,8 +19,8 @@ open Strata def main (args : List String) : IO Unit := do match args with | [file] => do - let text ← IO.FS.readFile file - let inputCtx := Lean.Parser.mkInputContext text file + let text ← Strata.Util.readInputSource file + let inputCtx := Lean.Parser.mkInputContext text (Strata.Util.displayName file) let dctx := Elab.LoadedDialects.builtin let dctx := dctx.addDialect! Boogie let dctx := dctx.addDialect! C_Simp @@ -33,7 +34,7 @@ def main (args : List String) : IO Unit := do let csimp_prog := C_Simp.get_program pgm IO.println (CSimp.testSymbols csimp_prog.funcs.head!) else if file.endsWith ".boogie.st" then - let boogie_prog := (Boogie.getProgram pgm).fst + let boogie_prog := (Boogie.getProgram pgm inputCtx).fst match boogie_prog.decls.head! with | .proc f => IO.println (Boogie.testSymbols f) | _ => IO.println "Error: expected boogie procedure" diff --git a/StrataVerify.lean b/StrataVerify.lean index aa3b98f7a..02eea5907 100644 --- a/StrataVerify.lean +++ b/StrataVerify.lean @@ -7,6 +7,7 @@ -- Executable for verifying a Strata program from a file. import Strata.Languages.Boogie.Verifier import Strata.Languages.C_Simp.Verify +import Strata.Util.IO import Std.Internal.Parsec open Strata @@ -45,8 +46,8 @@ def usageMessage : Std.Format := Options:{Std.Format.line}\ {Std.Format.line} \ --verbose Print extra information during analysis.{Std.Format.line} \ - --check Process up until SMT generation, but don't solve.{Std.Format.line} \ - --type-check Exit after semantic dialect's type inference/checking.{Std.Format.line} \ + --check Process up until SMT generation, but don't solve.{Std.Format.line} \ + --type-check Exit after semantic dialect's type inference/checking.{Std.Format.line} \ --parse-only Exit after DDM parsing and type checking.{Std.Format.line} \ --stop-on-first-error Exit after the first verification error.{Std.Format.line} \ --solver-timeout Set the solver time limit per proof goal." @@ -55,8 +56,8 @@ def main (args : List String) : IO UInt32 := do let parseResult := parseOptions args match parseResult with | .ok (opts, file) => do - let text ← IO.FS.readFile file - let inputCtx := Lean.Parser.mkInputContext text file + let text ← Strata.Util.readInputSource file + let inputCtx := Lean.Parser.mkInputContext text (Strata.Util.displayName file) let dctx := Elab.LoadedDialects.builtin let dctx := dctx.addDialect! Boogie let dctx := dctx.addDialect! C_Simp @@ -71,22 +72,26 @@ def main (args : List String) : IO UInt32 := do C_Simp.typeCheck pgm opts else -- Boogie. - typeCheck pgm opts + typeCheck inputCtx pgm opts match ans with | .error e => - println! f!"Type checking error: {e}" + println! f!"{e}" return 1 | .ok _ => println! f!"Program typechecked." return 0 else -- !typeCheckOnly - let vcResults ← - if file.endsWith ".csimp.st" then - C_Simp.verify "z3" pgm opts - else - verify "z3" pgm opts + let vcResults ← try + if file.endsWith ".csimp.st" then + C_Simp.verify "z3" pgm opts + else + verify "z3" pgm inputCtx opts + catch e => + println! f!"{e}" + return (1 : UInt32) for vcResult in vcResults do - println! f!"{vcResult.obligation.label}: {vcResult.result}" + let posStr := Imperative.MetaData.formatFileRangeD vcResult.obligation.metadata + println! f!"{posStr} [{vcResult.obligation.label}]: {vcResult.result}" let success := vcResults.all isSuccessVCResult if success && !opts.checkOnly then println! f!"Proved all {vcResults.size} goals." diff --git a/Tools/BoogieToStrata/IntegrationTests/BoogieToStrataIntegrationTests.cs b/Tools/BoogieToStrata/IntegrationTests/BoogieToStrataIntegrationTests.cs index 569a3c8a8..7629e0293 100644 --- a/Tools/BoogieToStrata/IntegrationTests/BoogieToStrataIntegrationTests.cs +++ b/Tools/BoogieToStrata/IntegrationTests/BoogieToStrataIntegrationTests.cs @@ -113,7 +113,7 @@ public void VerifyTestFile(string fileName, string filePath) { Assert.Equal(0, exitCode); Assert.True(standardOutput.Length > 0, "Expected some output from BoogieToStrata"); Assert.True(errorOutput.Length == 0, "Expected no error output from BoogieToStrata"); - var strataFile = Path.GetTempFileName() + ".boogie.st"; + var strataFile = Path.ChangeExtension(filePath, "boogie.st"); File.WriteAllText(strataFile, standardOutput); var expectFile = Path.ChangeExtension(filePath, "expect"); string? expectString = null; diff --git a/Tools/BoogieToStrata/Tests/Arrays2.expect b/Tools/BoogieToStrata/Tests/Arrays2.expect index 47c874b7e..ab48727fd 100644 --- a/Tools/BoogieToStrata/Tests/Arrays2.expect +++ b/Tools/BoogieToStrata/Tests/Arrays2.expect @@ -1,14 +1,14 @@ Successfully parsed. -P0_ensures_2: verified -P0_ensures_3: verified -P1_ensures_1: verified -P2_ensures_1: unknown -Q0_ensures_2: verified -Q0_ensures_3: verified -Q1_ensures_1: verified -Q2_ensures_2: verified -Q3_ensures_2: verified -Q4_ensures_1: unknown -Skip0_ensures_2: verified -Skip0_ensures_3: verified +Arrays2.boogie.st(22, 2) [P0_ensures_2]: verified +Arrays2.boogie.st(23, 2) [P0_ensures_3]: verified +Arrays2.boogie.st(39, 2) [P1_ensures_1]: verified +Arrays2.boogie.st(55, 2) [P2_ensures_1]: unknown +Arrays2.boogie.st(72, 2) [Q0_ensures_2]: verified +Arrays2.boogie.st(73, 2) [Q0_ensures_3]: verified +Arrays2.boogie.st(89, 2) [Q1_ensures_1]: verified +Arrays2.boogie.st(106, 2) [Q2_ensures_2]: verified +Arrays2.boogie.st(123, 2) [Q3_ensures_2]: verified +Arrays2.boogie.st(139, 2) [Q4_ensures_1]: unknown +Arrays2.boogie.st(156, 2) [Skip0_ensures_2]: verified +Arrays2.boogie.st(157, 2) [Skip0_ensures_3]: verified Finished with 10 goals proved, 2 failed. diff --git a/Tools/BoogieToStrata/Tests/Axioms.expect b/Tools/BoogieToStrata/Tests/Axioms.expect new file mode 100644 index 000000000..c85893983 --- /dev/null +++ b/Tools/BoogieToStrata/Tests/Axioms.expect @@ -0,0 +1,7 @@ +Successfully parsed. +Axioms.boogie.st(25, 4) [assert_0]: verified +Axioms.boogie.st(26, 4) [assert_1]: verified +Axioms.boogie.st(27, 4) [assert_2]: verified +Axioms.boogie.st(37, 4) [assert_3]: unknown +Axioms.boogie.st(48, 4) [assert_4]: verified +Finished with 4 goals proved, 1 failed. diff --git a/Tools/BoogieToStrata/Tests/B.expect b/Tools/BoogieToStrata/Tests/B.expect index 27d12227f..e60a69bad 100644 --- a/Tools/BoogieToStrata/Tests/B.expect +++ b/Tools/BoogieToStrata/Tests/B.expect @@ -1,6 +1,6 @@ Successfully parsed. -assert_0: verified -assert_1: verified -assert_2: verified -assert_3: verified +B.boogie.st(36, 4) [assert_0]: verified +B.boogie.st(62, 4) [assert_1]: verified +B.boogie.st(88, 4) [assert_2]: verified +B.boogie.st(115, 4) [assert_3]: verified Proved all 4 goals. diff --git a/Tools/BoogieToStrata/Tests/BooleanQuantification.expect b/Tools/BoogieToStrata/Tests/BooleanQuantification.expect new file mode 100644 index 000000000..652dbed07 --- /dev/null +++ b/Tools/BoogieToStrata/Tests/BooleanQuantification.expect @@ -0,0 +1,10 @@ +Successfully parsed. +BooleanQuantification.boogie.st(22, 4) [assert_0]: verified +BooleanQuantification.boogie.st(31, 4) [assert_1]: verified +BooleanQuantification.boogie.st(40, 4) [assert_2]: verified +BooleanQuantification.boogie.st(41, 4) [assert_3]: unknown +BooleanQuantification.boogie.st(50, 4) [assert_4]: verified +BooleanQuantification.boogie.st(51, 4) [assert_5]: verified +BooleanQuantification.boogie.st(52, 4) [assert_6]: failed +CEx: +Finished with 5 goals proved, 2 failed. diff --git a/Tools/BoogieToStrata/Tests/BooleanQuantification2.expect b/Tools/BoogieToStrata/Tests/BooleanQuantification2.expect index 6b923eacd..73ef6e795 100644 --- a/Tools/BoogieToStrata/Tests/BooleanQuantification2.expect +++ b/Tools/BoogieToStrata/Tests/BooleanQuantification2.expect @@ -1,6 +1,6 @@ Successfully parsed. -assert_0: verified -assert_1: verified -assert_2: failed +BooleanQuantification2.boogie.st(18, 4) [assert_0]: verified +BooleanQuantification2.boogie.st(19, 4) [assert_1]: verified +BooleanQuantification2.boogie.st(20, 4) [assert_2]: failed CEx: Finished with 2 goals proved, 1 failed. diff --git a/Tools/BoogieToStrata/Tests/Bubble.expect b/Tools/BoogieToStrata/Tests/Bubble.expect index 3663b4da2..8528f4158 100644 --- a/Tools/BoogieToStrata/Tests/Bubble.expect +++ b/Tools/BoogieToStrata/Tests/Bubble.expect @@ -1,16 +1,16 @@ Successfully parsed. -entry_invariant_0: verified -arbitrary_iter_maintain_invariant_0: verified -entry_invariant_0: verified -BubbleSort_ensures_1: verified -BubbleSort_ensures_2: verified -BubbleSort_ensures_3: verified -BubbleSort_ensures_4: verified -entry_invariant_1: verified -arbitrary_iter_maintain_invariant_1: verified -arbitrary_iter_maintain_invariant_0: verified -BubbleSort_ensures_1: verified -BubbleSort_ensures_2: verified -BubbleSort_ensures_3: verified -BubbleSort_ensures_4: verified +Bubble.boogie.st(31, 4) [entry_invariant_0]: verified +Bubble.boogie.st(31, 4) [arbitrary_iter_maintain_invariant_0]: verified +Bubble.boogie.st(43, 4) [entry_invariant_0]: verified +Bubble.boogie.st(20, 2) [BubbleSort_ensures_1]: verified +Bubble.boogie.st(21, 2) [BubbleSort_ensures_2]: verified +Bubble.boogie.st(22, 2) [BubbleSort_ensures_3]: verified +Bubble.boogie.st(23, 2) [BubbleSort_ensures_4]: verified +Bubble.boogie.st(57, 8) [entry_invariant_1]: verified +Bubble.boogie.st(57, 8) [arbitrary_iter_maintain_invariant_1]: verified +Bubble.boogie.st(43, 4) [arbitrary_iter_maintain_invariant_0]: verified +Bubble.boogie.st(20, 2) [BubbleSort_ensures_1]: verified +Bubble.boogie.st(21, 2) [BubbleSort_ensures_2]: verified +Bubble.boogie.st(22, 2) [BubbleSort_ensures_3]: verified +Bubble.boogie.st(23, 2) [BubbleSort_ensures_4]: verified Proved all 14 goals. diff --git a/Tools/BoogieToStrata/Tests/DivMod.expect b/Tools/BoogieToStrata/Tests/DivMod.expect index 04f19daa0..116c53a1f 100644 --- a/Tools/BoogieToStrata/Tests/DivMod.expect +++ b/Tools/BoogieToStrata/Tests/DivMod.expect @@ -1,8 +1,8 @@ Successfully parsed. -T_from_E_ensures_1: verified -T_from_E_ensures_2: verified -T_from_E_ensures_3: verified -E_from_T_ensures_1: verified -E_from_T_ensures_2: verified -E_from_T_ensures_3: verified +DivMod.boogie.st(25, 2) [T_from_E_ensures_1]: verified +DivMod.boogie.st(26, 2) [T_from_E_ensures_2]: verified +DivMod.boogie.st(27, 2) [T_from_E_ensures_3]: verified +DivMod.boogie.st(45, 2) [E_from_T_ensures_1]: verified +DivMod.boogie.st(46, 2) [E_from_T_ensures_2]: verified +DivMod.boogie.st(47, 2) [E_from_T_ensures_3]: verified Proved all 6 goals. diff --git a/Tools/BoogieToStrata/Tests/Gauss.expect b/Tools/BoogieToStrata/Tests/Gauss.expect index 43ff6758e..4adc0ed90 100644 --- a/Tools/BoogieToStrata/Tests/Gauss.expect +++ b/Tools/BoogieToStrata/Tests/Gauss.expect @@ -1,5 +1,5 @@ Successfully parsed. -entry_invariant_0: verified -arbitrary_iter_maintain_invariant_0: verified -sum_ensures_1: verified +Gauss.boogie.st(18, 4) [entry_invariant_0]: verified +Gauss.boogie.st(18, 4) [arbitrary_iter_maintain_invariant_0]: verified +Gauss.boogie.st(11, 2) [sum_ensures_1]: verified Proved all 3 goals. diff --git a/Tools/BoogieToStrata/Tests/IfThenElse1.expect b/Tools/BoogieToStrata/Tests/IfThenElse1.expect index ed28e8a05..ffd73cc19 100644 --- a/Tools/BoogieToStrata/Tests/IfThenElse1.expect +++ b/Tools/BoogieToStrata/Tests/IfThenElse1.expect @@ -1,10 +1,10 @@ Successfully parsed. -assert_0: verified -assert_1: verified -assert_2: verified -assert_3: verified -assert_4: failed +IfThenElse1.boogie.st(18, 4) [assert_0]: verified +IfThenElse1.boogie.st(19, 4) [assert_1]: verified +IfThenElse1.boogie.st(34, 4) [assert_2]: verified +IfThenElse1.boogie.st(36, 4) [assert_3]: verified +IfThenElse1.boogie.st(48, 4) [assert_4]: failed CEx: (init_x_9, 1) (init_b_8, false) (init_y_10, 0) -assert_5: failed +IfThenElse1.boogie.st(60, 4) [assert_5]: failed CEx: (init_x_12, 1) (init_y_13, 0) Finished with 4 goals proved, 2 failed. diff --git a/Tools/BoogieToStrata/Tests/Implies.expect b/Tools/BoogieToStrata/Tests/Implies.expect index 0d28229bc..64f7cc72e 100644 --- a/Tools/BoogieToStrata/Tests/Implies.expect +++ b/Tools/BoogieToStrata/Tests/Implies.expect @@ -1,14 +1,14 @@ Successfully parsed. -assert_0: verified -assert_1: unknown -assert_2: verified -assert_3: unknown -assert_4: unknown -assert_5: unknown -assert_6: unknown -assert_7: unknown -assert_8: unknown -assert_9: unknown -assert_10: unknown -assert_11: unknown +Implies.boogie.st(24, 4) [assert_0]: verified +Implies.boogie.st(25, 4) [assert_1]: unknown +Implies.boogie.st(26, 4) [assert_2]: verified +Implies.boogie.st(27, 4) [assert_3]: unknown +Implies.boogie.st(36, 4) [assert_4]: unknown +Implies.boogie.st(37, 4) [assert_5]: unknown +Implies.boogie.st(46, 4) [assert_6]: unknown +Implies.boogie.st(47, 4) [assert_7]: unknown +Implies.boogie.st(56, 4) [assert_8]: unknown +Implies.boogie.st(57, 4) [assert_9]: unknown +Implies.boogie.st(66, 4) [assert_10]: unknown +Implies.boogie.st(67, 4) [assert_11]: unknown Finished with 2 goals proved, 10 failed. diff --git a/Tools/BoogieToStrata/Tests/Lambda.expect b/Tools/BoogieToStrata/Tests/Lambda.expect new file mode 100644 index 000000000..d1e56d29d --- /dev/null +++ b/Tools/BoogieToStrata/Tests/Lambda.expect @@ -0,0 +1,14 @@ +Successfully parsed. +Lambda.boogie.st(39, 2) [P_ensures_0]: verified +Lambda.boogie.st(52, 4) [assert_0]: verified +Lambda.boogie.st(61, 4) [assert_1]: verified +Lambda.boogie.st(74, 4) [assert_2]: verified +Lambda.boogie.st(75, 4) [assert_3]: verified +Lambda.boogie.st(76, 4) [assert_4]: verified +Lambda.boogie.st(89, 4) [assert_5]: unknown +Lambda.boogie.st(90, 4) [assert_6]: unknown +Lambda.boogie.st(103, 4) [assert_7]: verified +Lambda.boogie.st(104, 4) [assert_8]: verified +Lambda.boogie.st(106, 4) [assert_9]: verified +Lambda.boogie.st(107, 4) [assert_10]: verified +Finished with 10 goals proved, 2 failed. diff --git a/Tools/BoogieToStrata/Tests/McCarthy-91.expect b/Tools/BoogieToStrata/Tests/McCarthy-91.expect index 34d26f15c..1dddfed42 100644 --- a/Tools/BoogieToStrata/Tests/McCarthy-91.expect +++ b/Tools/BoogieToStrata/Tests/McCarthy-91.expect @@ -1,4 +1,4 @@ Successfully parsed. -F_ensures_0: verified -F_ensures_1: verified +McCarthy-91.boogie.st(10, 2) [F_ensures_0]: verified +McCarthy-91.boogie.st(11, 2) [F_ensures_1]: verified Proved all 2 goals. diff --git a/Tools/BoogieToStrata/Tests/Quantifiers.expect b/Tools/BoogieToStrata/Tests/Quantifiers.expect new file mode 100644 index 000000000..0951eac2d --- /dev/null +++ b/Tools/BoogieToStrata/Tests/Quantifiers.expect @@ -0,0 +1,16 @@ +Successfully parsed. +Quantifiers.boogie.st(39, 4) [assert_0]: verified +Quantifiers.boogie.st(51, 4) [assert_1]: unknown +Quantifiers.boogie.st(64, 4) [assert_2]: verified +Quantifiers.boogie.st(76, 4) [assert_3]: unknown +Quantifiers.boogie.st(89, 4) [assert_4]: verified +Quantifiers.boogie.st(99, 4) [assert_5]: unknown +Quantifiers.boogie.st(110, 4) [assert_6]: unknown +Quantifiers.boogie.st(123, 4) [assert_7]: verified +Quantifiers.boogie.st(134, 4) [assert_8]: verified +Quantifiers.boogie.st(146, 4) [assert_9]: verified +Quantifiers.boogie.st(158, 4) [assert_10]: verified +Quantifiers.boogie.st(170, 4) [assert_11]: verified +Quantifiers.boogie.st(183, 4) [assert_12]: verified +Quantifiers.boogie.st(195, 4) [assert_13]: verified +Finished with 10 goals proved, 4 failed. diff --git a/Tools/BoogieToStrata/Tests/TypeSynonyms2.expect b/Tools/BoogieToStrata/Tests/TypeSynonyms2.expect index aeb649fcc..72d195cc1 100644 --- a/Tools/BoogieToStrata/Tests/TypeSynonyms2.expect +++ b/Tools/BoogieToStrata/Tests/TypeSynonyms2.expect @@ -1,3 +1,3 @@ Successfully parsed. -assert_0: verified +TypeSynonyms2.boogie.st(27, 4) [assert_0]: verified Proved all 1 goals. diff --git a/Tools/BoogieToStrata/Tests/Unique.expect b/Tools/BoogieToStrata/Tests/Unique.expect index 16a2a1253..191b159bc 100644 --- a/Tools/BoogieToStrata/Tests/Unique.expect +++ b/Tools/BoogieToStrata/Tests/Unique.expect @@ -1,8 +1,8 @@ Successfully parsed. -assert_0: failed +Unique.boogie.st(28, 4) [assert_0]: failed CEx: -assert_1: verified -assert_2: failed +Unique.boogie.st(29, 4) [assert_1]: verified +Unique.boogie.st(38, 4) [assert_2]: failed CEx: -assert_3: verified +Unique.boogie.st(39, 4) [assert_3]: verified Finished with 2 goals proved, 2 failed. diff --git a/Tools/BoogieToStrata/Tests/Where.expect b/Tools/BoogieToStrata/Tests/Where.expect index a077b7727..e94125428 100644 --- a/Tools/BoogieToStrata/Tests/Where.expect +++ b/Tools/BoogieToStrata/Tests/Where.expect @@ -1,19 +1,19 @@ Successfully parsed. -assert_0: verified -assert_1: verified -assert_2: failed +Where.boogie.st(16, 4) [assert_0]: verified +Where.boogie.st(17, 4) [assert_1]: verified +Where.boogie.st(18, 4) [assert_2]: failed CEx: (init_y_1, 5) (init_x_0, 0) -assert_3: verified -assert_4: verified -assert_5: failed +Where.boogie.st(34, 4) [assert_3]: verified +Where.boogie.st(37, 4) [assert_4]: verified +Where.boogie.st(38, 4) [assert_5]: failed CEx: ($__x1, 6) ($__y0, 5) (init_x_2, 0) (init_y_3, 0) -assert_6: failed +Where.boogie.st(55, 4) [assert_6]: failed CEx: ($__x3, 1) ($__y2, 0) (init_x_4, 0) (init_y_5, 0) -assert_7: verified -assert_8: failed +Where.boogie.st(73, 4) [assert_7]: verified +Where.boogie.st(74, 4) [assert_8]: failed CEx: ($__y5, 0) (init_x_6, 0) (init_y_7, 0) ($__x4, 0) -assert_9: verified -assert_10: verified -assert_11: failed +Where.boogie.st(91, 4) [assert_9]: verified +Where.boogie.st(96, 4) [assert_10]: verified +Where.boogie.st(97, 4) [assert_11]: failed CEx: ($__x9, 0) (init_x_8, 0) (init_y_9, 0) ($__x6, 0) ($__y7, 0) ($__y8, 0) Finished with 7 goals proved, 5 failed. diff --git a/Tools/BoogieToStrata/Tests/bv9.expect b/Tools/BoogieToStrata/Tests/bv9.expect index aeb649fcc..88bd951ef 100644 --- a/Tools/BoogieToStrata/Tests/bv9.expect +++ b/Tools/BoogieToStrata/Tests/bv9.expect @@ -1,3 +1,3 @@ Successfully parsed. -assert_0: verified +bv9.boogie.st(22, 4) [assert_0]: verified Proved all 1 goals. diff --git a/Tools/Python/README.md b/Tools/Python/README.md index eb340a288..5cb9379a7 100644 --- a/Tools/Python/README.md +++ b/Tools/Python/README.md @@ -1,14 +1,15 @@ # Strata Python Bindings -This directory contains a Python package for strata along with a directory -for generating a Python Strata DDM dialect and generating Strata programs. +This directory contains a Python package for strata along with a module +`strata.gen` for generating Strata dialects and programs from Python. It can be installed by running `pip install .` from the root directory. ## Generating the DDM dialect. -The `dialect` command can generate a Strata dialect by analyzing the Python AST -package implementation. This dialect is generated automatically, but may +The `dialect` command can generate either Python dialect. Strata dialect by +analyzing the Python AST package implementation. This dialect is generated +automatically and thus may change between Python versions if the AST package implementation changes. Strata dialect should be placed into a directory so that it can be read along @@ -16,27 +17,29 @@ with other dialects. To generate the dialect in the directory `dialect_dir`, run the following command: ``` -python -m strata.gen dialect dialect_dir +python -m strata.gen dialect *dir* ``` -The dialect can be worked with using the Strata CLI tools: +`*dir*` should point to the directory to store the dialect in. + +The dialect can be checked with using the Strata CLI tools: ``` -strata check "dialect_dir/Python.dialect.st.ion" +strata check "*dir*/Python.dialect.st.ion" ``` ## Parsing Python into Strata. -The `parse` subcommand will translate a Python file into a Strata file. +The `py_to_strata` subcommand will translate a Python file into a Strata file. As an example, we should using strata.gen to translate `strata/base.py` into Strata below: ``` -python -m strata.gen parse strata/base.py base.python.st.ion +python -m strata.gen py_to_strata strata/base.py base.py.st.ion ``` This can be checked using the Strata CLI tools: ``` -strata check --include dialect_dir sample.python.st.ion +strata check --include dialect_dir base.py.st.ion ``` \ No newline at end of file diff --git a/Tools/Python/pyproject.toml b/Tools/Python/pyproject.toml index 0d7148b76..d62c6fc00 100644 --- a/Tools/Python/pyproject.toml +++ b/Tools/Python/pyproject.toml @@ -2,7 +2,7 @@ name = "strata" version = "0.0.1" description = 'Python support for Strata.' -requires-python = ">= 3.13" +requires-python = ">= 3.14" dependencies = [ "amazon.ion" ] diff --git a/Tools/Python/scripts/run_cpython_tests.sh b/Tools/Python/scripts/run_cpython_tests.sh index c67460266..50c9c4744 100755 --- a/Tools/Python/scripts/run_cpython_tests.sh +++ b/Tools/Python/scripts/run_cpython_tests.sh @@ -9,20 +9,20 @@ elif [ ! -f "scripts/gen_dialect.sh" ]; then exit 1 fi -rm -rf cpython-3.13 -git clone https://github.com/python/cpython.git --branch 3.13 --depth 1 cpython-3.13 +rm -rf cpython-3.14 +git clone https://github.com/python/cpython.git --branch 3.14 --depth 1 cpython-3.14 ./scripts/gen_dialect.sh -expected_failures="cpython-3.13/Lib/test/tokenizedata/bad_coding.py" -expected_failures="${expected_failures};cpython-3.13/Lib/test/tokenizedata/bad_coding2.py" -expected_failures="${expected_failures};cpython-3.13/Lib/test/tokenizedata/badsyntax_3131.py" -expected_failures="${expected_failures};cpython-3.13/Lib/test/tokenizedata/badsyntax_pep3120.py" +expected_failures="cpython-3.14/Lib/test/tokenizedata/bad_coding.py" +expected_failures="${expected_failures};cpython-3.14/Lib/test/tokenizedata/bad_coding2.py" +expected_failures="${expected_failures};cpython-3.14/Lib/test/tokenizedata/badsyntax_3131.py" +expected_failures="${expected_failures};cpython-3.14/Lib/test/tokenizedata/badsyntax_pep3120.py" echo "Generating report in report.xt" | tee report.txt count=1 -for i in `find cpython-3.13/Lib/test -name "*.py"`; do +for i in `find cpython-3.14/Lib/test -name "*.py"`; do if [[ "$expected_failures" == *"$i"* ]]; then should_fail=1 echo "$count : $i (expecting failure)" | tee -a report.txt diff --git a/Tools/Python/scripts/run_test.sh b/Tools/Python/scripts/run_test.sh index 60857bffd..f236d5ee7 100755 --- a/Tools/Python/scripts/run_test.sh +++ b/Tools/Python/scripts/run_test.sh @@ -20,7 +20,7 @@ input_dir=`dirname "$input_path"` filename=`basename "$input_path"` mkdir -p $test_dir/$input_dir -python3 -m strata.gen parse $input_path "$test_dir/$input_dir/$filename.st.ion" +python3 -m strata.gen py_to_strata $input_path "$test_dir/$input_dir/$filename.st.ion" $strata toIon --include "$test_dir/dialects" "$test_dir/$input_dir/$filename.st.ion" "$test_dir/$input_dir/$filename.st.ion2" diff --git a/Tools/Python/strata/__init__.py b/Tools/Python/strata/__init__.py deleted file mode 100644 index 0ef471a43..000000000 --- a/Tools/Python/strata/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright Strata Contributors -# -# SPDX-License-Identifier: Apache-2.0 OR MIT - -from strata.base import * diff --git a/Tools/Python/strata/base.py b/Tools/Python/strata/base.py index 1dcf588cf..704e0e9f0 100644 --- a/Tools/Python/strata/base.py +++ b/Tools/Python/strata/base.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from decimal import Decimal import typing -from typing import Any +from typing import Any, Iterable, cast import amazon.ion.simpleion as ion @@ -105,6 +105,8 @@ class SyntaxCat: args: list['SyntaxCat'] def __init__(self, name: QualifiedIdent, args: list['SyntaxCat'] | None = None, *, ann = None): + assert isinstance(name, QualifiedIdent) + assert args is None or isinstance(args, list) and all(isinstance(a, SyntaxCat) for a in args) self.ann = ann self.name = name self.args = [] if args is None else args @@ -245,20 +247,43 @@ def __init__(self, ident : QualifiedIdent, *, ann=None): def to_ion(self): return ion_sexp(ion_symbol("fn"), ann_to_ion(self.ann), self.ident.to_ion()) +class OperationArgs: + _decls : tuple[ArgDecl, ...] + _arg_indices : dict[str, int] + _args : tuple['Arg', ...] + + def __init__(self, decls : tuple[ArgDecl, ...], arg_indices : dict[str, int], *args : 'Arg'): + assert len(args) == len(decls) + self._decls = decls + self._arg_indices = arg_indices + self._args = args + + def __getitem__[T:'Arg'](self, field : str|int) -> T: + if type(field) is int: + return cast(T, self._args[field]) + if type(field) is str: + return cast(T, self._args[self._arg_indices[field]]) + raise ValueError(f'Expected int or str, got {type(field)}') + + def items(self) -> Iterable[tuple[str, 'Arg']]: + return ((d.name, a) for (d, a) in zip(self._decls, self._args)) + + def __len__(self) -> int: + return len(self._args) + class Operation: ann : Any decl : 'OpDecl' - args : dict[str, 'Arg'] + args : OperationArgs def __init__(self, decl : 'OpDecl', args : list['Arg']|None = None, *, ann = None): self.ann = ann self.decl = decl if args is None: - args = [] - assert len(decl.args) == len(args) - self.args = {} - for i in range(len(decl.args)): - self.args[decl.args[i].name] = args[i] + assert len(decl.args) == 0 + self.args = OperationArgs(decl.args, decl.arg_name_map) + else: + self.args = OperationArgs(decl.args, decl.arg_name_map, *args) def __str__(self) -> str: t = ', '.join(f'{n}={str(v)}' for (n,v) in self.args.items()) @@ -268,7 +293,7 @@ def to_ion(self) -> object: return ion_sexp( self.decl.ident.to_ion(), ann_to_ion(self.ann), - *(arg_to_ion(a) for a in self.args.values()) + *(arg_to_ion(a) for a in self.args) ) @dataclass @@ -286,21 +311,34 @@ class NumLit: value: int def __init__(self, value: int, *, ann = None): - assert isinstance(value, int) + # This is to avoid bool values sneaking in + assert type(value) is int assert value >= 0 self.ann = ann self.value = value @dataclass class DecimalLit: - ann : Any value: Decimal + ann : Any def __init__(self, value: Decimal, *, ann = None): - assert isinstance(value, int) + assert type(value) is int assert value >= 0 + self.value = value self.ann = ann + +@dataclass +class BytesLit: + ann : Any + value: bytes + + def __init__(self, value: bytes, *, ann = None): self.value = value + self.ann = ann + + def __str__(self): + return f'BytesLit({repr(self.value)})' @dataclass class StrLit: @@ -315,13 +353,16 @@ def __str__(self): return f'StrLit({repr(self.value)})' @dataclass -class OptionArg: +class OptionArg[T : 'Arg']: + value: T|None ann : Any - value: 'Arg|None' - def __init__(self, value: 'Arg|None', *, ann = None): - self.ann = ann + def __init__(self, value: T|None, *, ann = None): self.value = value + self.ann = ann + + def __bool__(self) -> bool: + return self.value is not None def __str__(self): if self.value is None: @@ -330,28 +371,36 @@ def __str__(self): return f'Some({self.value})' @dataclass -class Seq: +class Seq[T : 'Arg']: + values: tuple[T, ...] ann : Any - values: list['Arg'] - def __init__(self, values: list['Arg'], *, ann = None): - self.ann = ann + def __init__(self, values: tuple[T, ...], *, ann = None): + for v in values: + assert not isinstance(v, OpDecl), f'Unexpected value {type(v)}' self.values = values + self.ann = ann + + def __getitem__(self, index: int): + return self.values[index] + + def __len__(self): + return len(self.values) def __str__(self) -> str: return f"Seq([{', '.join(str(a) for a in self.values)}])" @dataclass -class CommaSepList: - ann : Any +class CommaSepBy: values: list['Arg'] + ann : Any def __init__(self, values: list['Arg'], *, ann = None): - self.ann = ann self.values = values + self.ann = ann type Arg = SyntaxCat | Operation | TypeExpr | Expr | Ident \ - | NumLit | DecimalLit | StrLit | OptionArg | Seq | CommaSepList + | BytesLit | NumLit | DecimalLit | StrLit | OptionArg['Arg'] | Seq['Arg'] | CommaSepBy strlitSym = ion_symbol("strlit") numSym = ion_symbol("num") @@ -393,7 +442,9 @@ def arg_to_ion(a : Arg) -> object: val = ion.IonPyText(val) else: val = ion.IonPyText(a.value) - return ion_sexp(ion_symbol("strlit"), ann_to_ion(a.ann), val) + return ion_sexp(strlitSym, ann_to_ion(a.ann), val) + elif isinstance(a, BytesLit): + return ion_sexp(ion_symbol("bytes"), ann_to_ion(a.ann), a.value) elif isinstance(a, OptionArg): if a.value is None: return ion_sexp(optionSym, ann_to_ion(a.ann)) @@ -402,26 +453,9 @@ def arg_to_ion(a : Arg) -> object: elif isinstance(a, Seq): return ion_sexp(ion_symbol("seq"), ann_to_ion(a.ann), *(arg_to_ion(e) for e in a.values)) else: - assert isinstance(a, CommaSepList), f'Expected {type(a)} to be a CommaSepList.' + assert isinstance(a, CommaSepBy), f'Expected {type(a)} to be a CommaSepBy.' return ion_sexp(ion_symbol("commaSepList"), ann_to_ion(a.ann), *(arg_to_ion(e) for e in a.values)) -class Program: - programSym = ion.SymbolToken(u'program', None, None) - - def __init__(self, dialect: str): - self.dialect = dialect - self.commands = [] - - def add(self, command): - assert command is not None - self.commands.append(command) - - def to_ion(self): - return [ - ion_sexp(self.programSym, self.dialect), - *(cmd.to_ion() for cmd in self.commands) - ] - def metadata_arg_to_ion(value): if value is None: return "none" @@ -484,7 +518,13 @@ def to_ion(self): @dataclass class SyntaxDefIndent(SyntaxDefAtomBase): indent: int - args : list['SyntaxDefAtom'] + args : tuple['SyntaxDefAtom', ...] + + def __init__(self, indent: int, args: tuple['SyntaxDefAtom', ...]): + self.indent = indent + self.args = args + for a in args: + assert not isinstance(a, SyntaxDefIndent) def to_ion(self): return ion_sexp(ion_symbol("indent"), self.indent, *(syntaxdef_atom_to_ion(a) for a in self.args)) @@ -495,6 +535,7 @@ def syntaxdef_atom_to_ion(atom : SyntaxDefAtom) -> object: if isinstance(atom, str): return atom else: + assert isinstance(atom, SyntaxDefAtomBase) return atom.to_ion() @dataclass @@ -508,9 +549,12 @@ def to_ion(self): "prec": self.prec } +reserved = { "category", "fn", "import", "metadata", "op", "type" } + class SynCatDecl: syncat = ion.SymbolToken(u'syncat', None, None) def __init__(self, dialect : str, name : str, args: list[str]|None = None): + assert name not in reserved, f'{name} is a reserved word.' self.dialect = dialect self.name = name self.ident = QualifiedIdent(dialect, name) @@ -534,7 +578,13 @@ class ArgDecl: kind : SyntaxCat|TypeExpr metadata: Metadata - def __init__(self, name: str, kind : SyntaxCat|TypeExpr, metadata: Metadata|None = None): + def __init__(self, name: str, kind : SyntaxCat|TypeExpr|SynCatDecl, metadata: Metadata|None = None): + assert name not in reserved, f'{name} is a reserved word.' + if isinstance(kind, SynCatDecl): + assert len(kind.argNames) == 0, f'Missing arguments to syntax category' + kind = kind() + assert isinstance(kind, SyntaxCat) or isinstance(kind, TypeExpr), f'Unexpected kind {type(kind)}' + self.name = name self.kind = kind self.metadata = [] if metadata is None else metadata @@ -548,33 +598,124 @@ def to_ion(self): flds["metadata"] = metadata_to_ion(self.metadata) return flds +maxPrec = 1024 + +class SyntaxArg: + """Argument in syntax expression.""" + name : str + prec : int + + def __init__(self, name : str): + (f, s, e) = name.partition(':') + prec = maxPrec + if len(s) == 0: + prec = 0 + else: + prec = int(e) + self.name = f + self.prec = prec + + def resolve(self, args : dict[str, int]) -> SyntaxDefAtom: + level = args.get(self.name, None) + if level is None: + raise ValueError(f'Unknown argument {self.name}') + return SyntaxDefIdent(level, prec=self.prec) + +class Indent: + prec : int + value : Template|SyntaxArg + + def __init__(self, prec : int, value : Template|SyntaxArg): + assert type(prec) is int and prec > 0 + self.prec = prec + self.value = value + +from string.templatelib import Interpolation, Template + +def resolve_template(args : dict[str, int], t : Template) -> list[SyntaxDefAtom|str]: + atoms = [] + for a in t: + if isinstance(a, Interpolation): + value = a.value + if isinstance(value, str): + atoms.append(value) + elif isinstance(value, SyntaxArg): + atoms.append(value.resolve(args)) + else: + assert isinstance(value, Indent) + contents = value.value + if isinstance(contents, SyntaxArg): + iatoms = (contents.resolve(args),) + else: + assert isinstance(contents, Template) + iatoms = tuple(resolve_template(args, contents)) + atoms.append(SyntaxDefIndent(value.prec, iatoms)) + else: + assert isinstance(a, str) + atoms.append(a) + return atoms + +def resolve_syntax(args : dict[str, int], v : str|Template|SyntaxArg|Indent) -> list[SyntaxDefAtom]: + if isinstance(v, str): + return [v] + elif isinstance(v, Template): + return resolve_template(args, v) + elif isinstance(v, Indent): + contents = v.value + if isinstance(contents, SyntaxArg): + atoms = (contents.resolve(args),) + else: + assert isinstance(contents, Template) + atoms = tuple(resolve_template(args, contents)) + return [SyntaxDefIndent(v.prec, atoms)] + else: + assert isinstance(v, SyntaxArg) + return [v.resolve(args)] + class OpDecl: opSym = ion.SymbolToken(u'op', None, None) - result : QualifiedIdent + dialect : str + name : str + ident : QualifiedIdent + arg_name_map : dict[str, int] + args : tuple[ArgDecl, ...] + result : SyntaxCat + metadata : Metadata + syntax : SyntaxDef|None def __init__(self, dialect: str, name: str, - args: list[ArgDecl], + args: tuple[ArgDecl, ...], result : SyntaxCat, *, syntax : SyntaxDef|None = None, metadata : Metadata|None = None): - assert all( isinstance(a, ArgDecl) for a in args) + assert all(isinstance(a, ArgDecl) for a in args) + assert isinstance(result, SyntaxCat) + assert len(result.args) == 0 + assert name not in reserved, f'{name} is a reserved word.' + arg_dict : dict[str, int] = {} + for i, a in enumerate(args): + assert a.name not in arg_dict + arg_dict[a.name] = i + self.dialect = dialect self.name = name self.ident = QualifiedIdent(dialect, name) + self.arg_name_map = arg_dict self.args = args - assert isinstance(result, SyntaxCat) - assert len(result.args) == 0 - self.result = result.name - self.metadata = [] if metadata is None else metadata + self.result = result self.syntax = syntax + self.metadata = [] if metadata is None else metadata def __call__(self, *args, ann=None): assert len(args) == len(self.args), f"{self.ident} given {len(args)} argument(s) when {len(self.args)} expected ({args})" return Operation(self, list(args), ann=ann) + def __str__(self) -> str: + return str(self.ident) + def to_ion(self): flds = { "type": self.opSym, @@ -582,7 +723,7 @@ def to_ion(self): } if len(self.args) > 0: flds["args"] = [ a.to_ion() for a in self.args ] - flds["result"] = self.result.to_ion() + flds["result"] = self.result.name.to_ion() if self.syntax is not None: flds["syntax"] = self.syntax.to_ion() if len(self.metadata) > 0: @@ -592,6 +733,7 @@ def to_ion(self): class TypeDecl: typeSymbol = ion.SymbolToken(u'type', None, None) def __init__(self, name, argNames): + assert name not in reserved, f'{name} is a reserved word.' self.name = name self.argNames = argNames @@ -602,8 +744,16 @@ def to_ion(self): "argNames": self.argNames } +_dialectSym = ion.SymbolToken(u'dialect', None, None) + class Dialect: - dialectSym = ion.SymbolToken(u'dialect', None, None) + """ + A Strata dialect + """ + + name : str + imports : list[str] + decls : list[SynCatDecl | OpDecl] def __init__(self, name: str): self.name = name @@ -618,14 +768,40 @@ def add_syncat(self, name : str, args: list[str]|None = None) -> SynCatDecl: self.add(decl) return decl - def add_op(self, name : str, args: list[ArgDecl], result : SyntaxCat, *, - syntax : SyntaxDef|None = None, + def add_op(self, name : str, *args: ArgDecl|SyntaxCat, + syntax : str|Template|SyntaxArg|Indent|None|list[SyntaxDefAtom] = None, + prec : int|None = None, metadata : Metadata|None = None) -> OpDecl: - decl = OpDecl(self.name, name, args, result, syntax=syntax, metadata=metadata) + assert name not in reserved, f'{name} is a reserved word.' + assert len(args) > 0 + result = args[-1] + assert isinstance(result, SyntaxCat), f'{name} result must be a SyntaxCat' + assert len(result.args) == 0 + args = args[:-1] + assert all((isinstance(a, ArgDecl) for a in args)), f'{name} args must be a ArgDecl' + rargs : tuple[ArgDecl, ...] = args # type: ignore + + arg_dict = {} + for i, a in enumerate(rargs): + assert a.name not in arg_dict + arg_dict[a.name] = i + + if syntax is None: + assert prec is None + syntaxd = None + else: + if prec is None: + prec = maxPrec + if isinstance(syntax, list): + syntax_atoms = syntax + else: + syntax_atoms = resolve_syntax(arg_dict, syntax) + syntaxd = SyntaxDef(syntax_atoms, prec) + decl = OpDecl(self.name, name, rargs, result, syntax=syntaxd, metadata=metadata) self.add(decl) return decl - def add(self, decl): + def add(self, decl : SynCatDecl | OpDecl): assert decl is not None if isinstance(decl, SynCatDecl): assert (decl.dialect == self.name) @@ -634,28 +810,51 @@ def add(self, decl): self.__dict__[decl.name] = decl elif isinstance(decl, OpDecl): assert (decl.dialect == self.name) - assert (decl.name not in self.__dict__) + assert (decl.name not in self.__dict__), f'{decl.name} already added.' self.__dict__[decl.name] = decl self.decls.append(decl) def to_ion(self): - r : list[object] = [(self.dialectSym, self.name)] + r : list[object] = [(_dialectSym, self.name)] for i in self.imports: r.append({"type": "import", "name": i}) for d in self.decls: r.append(d.to_ion()) return r +_programSym = ion.SymbolToken(u'program', None, None) + +class Program: + dialect : Dialect + command : list[Operation] + + def __init__(self, dialect: Dialect): + self.dialect = dialect + self.commands = [] + + def add(self, command : Operation): + assert type(command) is Operation + self.commands.append(command) + + def to_ion(self): + return [ + ion_sexp(_programSym, self.dialect.name), + *(cmd.to_ion() for cmd in self.commands) + ] + + # FIXME: See if we can find way to keep this in sync with Lean implementation. # Perhaps we can have Lean implementation export the dialect as a Ion file and # ship it with Python library so we can read it in. Init : typing.Any = Dialect('Init') Init.add_syncat('Command') Init.add_syncat('Expr') +Init.add_syncat('ByteArray') +Init.add_syncat('Ident') Init.add_syncat('Num') Init.add_syncat('Str') Init.add_syncat('Type') -Init.add_syncat('CommaSepList', ['x']) +Init.add_syncat('CommaSepBy', ['x']) Init.add_syncat('Option', ['x']) Init.add_syncat('Seq', ['x']) diff --git a/Tools/Python/strata/gen.py b/Tools/Python/strata/gen.py index ea99b6498..6916f4b52 100755 --- a/Tools/Python/strata/gen.py +++ b/Tools/Python/strata/gen.py @@ -1,51 +1,91 @@ # Copyright Strata Contributors # # SPDX-License-Identifier: Apache-2.0 OR MIT +#!/usr/bin/env python3 """ Command line script for exporting Python dialect and program to files. """ -#!/usr/bin/env python3 -import argparse -import os import amazon.ion.simpleion as ion -from strata import python as stratap +import argparse +from pathlib import Path +from strata.base import Program +import strata.pythonast as pythonast import sys -def gen_dialect_imp(args): - if not os.path.isdir(args.output_dir): - print(f"Directory {args.output_dir} does not exist.", file=sys.stderr) - exit(1) - output = f"{args.output_dir}/Python.dialect.st.ion" - with open(output, 'wb') as w: - ion.dump(stratap.Python.to_ion(), w, binary=True) - print(f"Wrote Python dialect to {output}") - -def parse_python_imp(args): - path = args.python - with open(path, 'rb') as r: - try: - (_, p) = stratap.parse_module(r.read(), path) - except SyntaxError as e: - print(f"Error parsing {path}:\n {e}", file=sys.stderr) - exit(1) +def write_dialect(dir : Path): + dialect = pythonast.PythonAST + + if dir.exists(): + if not dir.is_dir(): + print(f"{dir} is not a directory.", file=sys.stderr) + sys.exit(1) + else: + dir.mkdir(parents=True) + output = dir / f"{dialect.name}.dialect.st.ion" + with output.open('wb') as w: + ion.dump(dialect.to_ion(), w, binary=True) + print(f"Wrote {dialect.name} dialect to {output}") + +def parse_ast(path : Path) -> Program: + try: + (_, p) = pythonast.parse_module(path.read_bytes(), path) + except SyntaxError as e: + print(f"Error parsing {path}:\n {e}", file=sys.stderr) + sys.exit(1) + return p + +def py_to_strata_imp(args): + path = Path(args.python) + p = parse_ast(path) with open(args.output, 'wb') as w: ion.dump(p.to_ion(), w, binary=True) +def check_ast_imp(args): + path = Path(args.dir) + + if path.is_dir(): + files = path.glob('**/*.py') + else: + files = [path] + + success = 0 + total = 0 + for p in files: + total += 1 + try: + _ = pythonast.parse_module(p.read_bytes(), p) + except SyntaxError as e: + print(f'{p} {type(e).__name__}: {e}') + total -= 1 + continue + except Exception as e: + print(f'{p} {type(e).__name__}: {e}') + continue + success += 1 + print(f'Analyzed {success} of {total} files.') + def main(): parser = argparse.ArgumentParser( prog='strata_python', description='Strata interface to Python parser') subparsers = parser.add_subparsers(help="subcommand help") - gen_dialect_command = subparsers.add_parser('dialect', help='Create Strata dialect.') - gen_dialect_command.add_argument('output_dir', help='Directory to write Strata dialect to.') - gen_dialect_command.set_defaults(func=gen_dialect_imp) + write_python_dialect_command = subparsers.add_parser('dialect', help='Write Python Strata dialect to directory.') + write_python_dialect_command.add_argument('output_dir', help='Directory to write Strata dialect to.') + write_python_dialect_command.set_defaults( + func=lambda args: + write_dialect(Path(args.output_dir))) + + py_to_strata_command = subparsers.add_parser('py_to_strata', help='Parse a Python file') + py_to_strata_command.add_argument('python', help='Path of file to read.') + py_to_strata_command.add_argument('output', help='Path to write Strata') + py_to_strata_command.set_defaults(func=py_to_strata_imp) - parse_command = subparsers.add_parser('parse', help='Parse a Python file') - parse_command.add_argument('python', help='Path ') - parse_command.add_argument('output', help='Path to write Strata') - parse_command.set_defaults(func=parse_python_imp) + checkast_command = subparsers.add_parser('check_ast', help='Check AST parser doesn\'t crash on Python files.') + checkast_command.add_argument('dir', help='Directory with Python files to analyze.') + checkast_command.add_argument('-f', '--features', action='store_true', help='Print out features used in SSA.') + checkast_command.set_defaults(func=check_ast_imp) args = parser.parse_args() if hasattr(args, 'func'): @@ -54,4 +94,4 @@ def main(): parser.print_help() if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/Tools/Python/strata/python.py b/Tools/Python/strata/pythonast.py similarity index 69% rename from Tools/Python/strata/python.py rename to Tools/Python/strata/pythonast.py index 6f975579d..990409558 100644 --- a/Tools/Python/strata/python.py +++ b/Tools/Python/strata/pythonast.py @@ -10,8 +10,8 @@ from os import PathLike import typing import types -import strata -from .base import ArgDecl, FileMapping, Init, SourceRange, SyntaxCat +import strata.base as strata +from .base import ArgDecl, FileMapping, Init, SourceRange, SyntaxCat, reserved @dataclass class OpArg: @@ -26,21 +26,23 @@ def __init__(self, decl : strata.OpDecl, args : list[OpArg]): self.decl = decl self.args = args -Python : typing.Any = strata.Dialect('Python') -Python.add_import("Init") -Python.add_syncat("int") -Python.add_op("IntPos", [ArgDecl("v", Init.Num())], Python.int()) -Python.add_op("IntNeg", [ArgDecl("v", Init.Num())], Python.int()) -Python.add_syncat("constant") -Python.add_op("ConTrue", [], Python.constant()) -Python.add_op("ConFalse", [], Python.constant()) -Python.add_op("ConPos", [ArgDecl("v", Init.Num())], Python.constant()) -Python.add_op("ConNeg", [ArgDecl("v", Init.Num())], Python.constant()) -Python.add_op("ConString", [ArgDecl("v", Init.Str())], Python.constant()) +PythonAST : typing.Any = strata.Dialect('Python') +PythonAST.add_import("Init") +PythonAST.add_syncat("int") +PythonAST.add_op("IntPos", ArgDecl("v", Init.Num()), PythonAST.int()) +PythonAST.add_op("IntNeg", ArgDecl("v", Init.Num()), PythonAST.int()) +PythonAST.add_syncat("constant") +PythonAST.add_op("ConTrue", PythonAST.constant()) +PythonAST.add_op("ConFalse", PythonAST.constant()) +PythonAST.add_op("ConPos", ArgDecl("v", Init.Num()), PythonAST.constant()) +PythonAST.add_op("ConNeg", ArgDecl("v", Init.Num()), PythonAST.constant()) +PythonAST.add_op("ConString", ArgDecl("v", Init.Str()), PythonAST.constant()) # JHx: FIXME: Support floating point literals -Python.add_op("ConFloat", [ArgDecl("v", Init.Str())], Python.constant()) -Python.add_op("ConNone", [], Python.constant()) -Python.add_op("ConEllipsis", [], Python.constant()) +PythonAST.add_op("ConFloat", ArgDecl("v", Init.Str()), PythonAST.constant()) +PythonAST.add_op("ConComplex", ArgDecl("real", Init.Str()), ArgDecl("imag", Init.Str()), PythonAST.constant()) +PythonAST.add_op("ConNone", PythonAST.constant()) +PythonAST.add_op("ConEllipsis", PythonAST.constant()) +PythonAST.add_op("ConBytes", ArgDecl("v", Init.ByteArray()), PythonAST.constant()) # Map python AST types to the syntax cat Python_catmap : dict[type, SyntaxCat] = {} @@ -50,12 +52,12 @@ def __init__(self, decl : strata.OpDecl, args : list[OpArg]): if c is ast.mod: decl = Init.Command else: - decl = Python.add_syncat(name) + decl = PythonAST.add_syncat(name) Python_catmap[c] = decl() -Python.add_syncat("opt_expr") -some_expr = Python.add_op("some_expr", [ArgDecl("x", Python.expr())], Python.opt_expr()) -missing_expr = Python.add_op("missing_expr", [], Python.opt_expr()) +PythonAST.add_syncat("opt_expr") +some_expr = PythonAST.add_op("some_expr", ArgDecl("x", PythonAST.expr()), PythonAST.opt_expr()) +missing_expr = PythonAST.add_op("missing_expr", PythonAST.opt_expr()) op_renamings = { 'op': 'mk_op', @@ -67,15 +69,16 @@ def __init__(self, decl : strata.OpDecl, args : list[OpArg]): def translate_op(name : str, op : type, category : SyntaxCat): def as_atom_type(tp) -> SyntaxCat: if tp is int: - return Python.int() + return PythonAST.int() elif tp is str: return Init.Str() elif tp is object: - return Python.constant() + return PythonAST.constant() else: return Python_catmap[tp] - used_names = { "category", "op", "type", "fn", "metadata" } + used_names = set(reserved) + op_args : list[OpArg]= [] op_argDecls : list[ArgDecl] = [] @@ -83,7 +86,7 @@ def as_atom_type(tp) -> SyntaxCat: field_types : dict[str, object] = op._field_types for (f, tp) in field_types.items(): ddm_name : str = op_renamings.get(f, f) - assert ddm_name not in used_names, f"{f} in {used_names}" + assert ddm_name not in used_names, f'{ddm_name} is used.' used_names.add(ddm_name) if op is ast.arguments and f == 'kw_defaults': assert isinstance(tp, types.GenericAlias) @@ -92,7 +95,7 @@ def as_atom_type(tp) -> SyntaxCat: args = typing.get_args(tp) assert len(args) == 1 assert args[0] is ast.expr - cat = Init.Seq(Python.opt_expr()) + cat = Init.Seq(PythonAST.opt_expr()) elif op is ast.Dict and f == 'keys': assert isinstance(tp, types.GenericAlias) origin = typing.get_origin(tp) @@ -100,7 +103,7 @@ def as_atom_type(tp) -> SyntaxCat: args = typing.get_args(tp) assert len(args) == 1 assert args[0] is ast.expr - cat = Init.Seq(Python.opt_expr()) + cat = Init.Seq(PythonAST.opt_expr()) elif isinstance(tp, types.UnionType): args = typing.get_args(tp) assert len(args) == 2 @@ -121,7 +124,7 @@ def as_atom_type(tp) -> SyntaxCat: except AttributeError: op_args = [] op_argDecls = [] - decl = Python.add_op(name, op_argDecls, category) + decl = PythonAST.add_op(name, *op_argDecls, category) Python_opmap[op] = Op(decl, op_args) # Add all operators to Python dialect and op_map. @@ -157,46 +160,48 @@ def ast_to_arg(mapping : FileMapping, v : object, cat : SyntaxCat) -> strata.Arg return strata.OptionArg(None) else: return strata.OptionArg(ast_to_arg(mapping, v, cat.args[0])) - case Python.int.ident: + case PythonAST.int.ident: assert isinstance(v, int) if v >= 0: - return Python.IntPos(strata.NumLit(v)) + return PythonAST.IntPos(strata.NumLit(v)) else: - return Python.IntNeg(strata.NumLit(-v)) + return PythonAST.IntNeg(strata.NumLit(-v)) case Init.Str.ident: assert isinstance(v, str) return strata.StrLit(v) - case Python.constant.ident: + case PythonAST.constant.ident: if isinstance(v, bool): if v: - return Python.ConTrue() + return PythonAST.ConTrue() else: - return Python.ConFalse() + return PythonAST.ConFalse() elif isinstance(v, int): if v >= 0: - return Python.ConPos(strata.NumLit(v)) + return PythonAST.ConPos(strata.NumLit(v)) else: - return Python.ConNeg(strata.NumLit(-v)) + return PythonAST.ConNeg(strata.NumLit(-v)) elif isinstance(v, str): - return Python.ConString(strata.StrLit(v)) + return PythonAST.ConString(strata.StrLit(v)) elif v is None: - return Python.ConNone() + return PythonAST.ConNone() elif isinstance(v, float): - return Python.ConFloat(strata.StrLit(str(v))) + return PythonAST.ConFloat(strata.StrLit(str(v))) elif isinstance(v, types.EllipsisType): - return Python.ConEllipsis() + return PythonAST.ConEllipsis() elif isinstance(v, bytes): - return Python.ConEllipsis() # FIXME + return PythonAST.ConBytes(strata.BytesLit(v)) elif isinstance(v, complex): - return Python.ConEllipsis() # FIXME + r = strata.StrLit(str(v.real)) + i = strata.StrLit(str(v.imag)) + return PythonAST.ConComplex(r, i) else: raise ValueError(f"Unsupported constant type {type(v)}") - case Python.opt_expr.ident: + case PythonAST.opt_expr.ident: if v is None: - return Python.missing_expr() + return PythonAST.missing_expr() else: assert isinstance(v, ast.expr) - return Python.some_expr(ast_to_arg(mapping, v, Python.expr())) + return PythonAST.some_expr(ast_to_arg(mapping, v, PythonAST.expr())) case Init.Option.ident: if v is None: return strata.OptionArg(None) @@ -205,7 +210,7 @@ def ast_to_arg(mapping : FileMapping, v : object, cat : SyntaxCat) -> strata.Arg case Init.Seq.ident: assert isinstance(v, list) arg_cat = cat.args[0] - return strata.Seq([ ast_to_arg(mapping, e, arg_cat) for e in v]) + return strata.Seq(tuple(ast_to_arg(mapping, e, arg_cat) for e in v)) case ident: assert v is not None, f'None passed to {ident}' return ast_to_op(mapping, v) @@ -230,6 +235,6 @@ def parse_module(source : bytes, filename : str | PathLike = "") -> tup a = ast.parse(source, mode='exec', filename=filename) assert isinstance(a, ast.Module) - p = strata.Program(Python.name) + p = strata.Program(PythonAST) p.add(ast_to_op(m, a)) - return (m, p) \ No newline at end of file + return (m, p) diff --git a/Tools/Python/test_results/dialects/Python.dialect.st.ion b/Tools/Python/test_results/dialects/Python.dialect.st.ion new file mode 100644 index 000000000..7f0f7d798 Binary files /dev/null and b/Tools/Python/test_results/dialects/Python.dialect.st.ion differ diff --git a/docs/ddm/README.md b/docs/ddm/README.md deleted file mode 100644 index 10e48290e..000000000 --- a/docs/ddm/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# DDM User Manual - -Strata dialects are defined in their own domain-specific language that -can be embededed in Lean or used externally. - -This Verso package provides HTML documentation of the Strata dialect -definition language. The documentation can be generated by the command - -``` -lake exe docs -``` - -The output will be written to `_out/single-page`. Links in Verso -documentation do not work if the file is opened in the browser directly. -Instead, we recommend launching a local web server to view the -documentation. -If Python is available, then this can be done with the command - -``` -python -m http.server 1080 -d _out/html-single -``` - -This will print out a URL that can be opened in a browser to view the documentation. diff --git a/docs/ddm/generate.sh b/docs/ddm/generate.sh deleted file mode 100755 index f458bbe3d..000000000 --- a/docs/ddm/generate.sh +++ /dev/null @@ -1,3 +0,0 @@ -set -ex - -lake exe docs --with-html-single --output _out/html diff --git a/docs/ddm/lakefile.toml b/docs/ddm/lakefile.toml deleted file mode 100644 index ed849e325..000000000 --- a/docs/ddm/lakefile.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "StrataDoc" -defaultTargets = ["docs"] - -[[require]] -name = "verso" -git = "https://github.com/leanprover/verso" -rev = "nightly-testing" - -[[lean_lib]] -name = "StrataDoc" - -[[lean_exe]] -name = "docs" -root = "StrataDocMain" \ No newline at end of file diff --git a/docs/ddm/lean-toolchain b/docs/ddm/lean-toolchain deleted file mode 100644 index 27770b571..000000000 --- a/docs/ddm/lean-toolchain +++ /dev/null @@ -1 +0,0 @@ -leanprover/lean4:v4.23.0-rc2 \ No newline at end of file diff --git a/docs/ddm/.gitignore b/docs/verso/.gitignore similarity index 100% rename from docs/ddm/.gitignore rename to docs/verso/.gitignore diff --git a/docs/ddm/StrataDoc.lean b/docs/verso/DDMDoc.lean similarity index 99% rename from docs/ddm/StrataDoc.lean rename to docs/verso/DDMDoc.lean index 376842f66..d271d823f 100644 --- a/docs/ddm/StrataDoc.lean +++ b/docs/verso/DDMDoc.lean @@ -18,7 +18,7 @@ set_option pp.rawOnError true #doc (Manual) "The Strata DDM Manual" => %%% authors := ["Joe Hendrix"] -shortTitle := "Strata" +shortTitle := "Strata DDM" %%% The Strata Dialect Definition Mechanism (DDM) is a set of tools for defining @@ -88,7 +88,7 @@ fn true_lit : Bool => "true"; fn false_lit : Bool => "false"; // Introduce basic Boolean operations. -fn not_expr (tp : Type, a : tp) : tp => "-" a; +fn not_expr (a : Bool) : Bool => "-" a; fn and (a : Bool, b : Bool) : Bool => @[prec(10), leftassoc] a " && " b; fn or (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " || " b; fn imp (a : Bool, b : Bool) : Bool => @[prec(8), leftassoc] a " ==> " b; diff --git a/docs/ddm/StrataDocMain.lean b/docs/verso/DDMDocMain.lean similarity index 76% rename from docs/ddm/StrataDocMain.lean rename to docs/verso/DDMDocMain.lean index 5d5786f7b..2c2ed861f 100644 --- a/docs/ddm/StrataDocMain.lean +++ b/docs/verso/DDMDocMain.lean @@ -6,7 +6,7 @@ -import StrataDoc +import DDMDoc open Verso.Genre.Manual (Config manualMain) def config : Config where @@ -15,4 +15,4 @@ def config : Config where emitHtmlMulti := false htmlDepth := 2 -def main := manualMain (%doc StrataDoc) (config := config) +def main := manualMain (%doc DDMDoc) (config := config) diff --git a/docs/verso/LangDefDoc.lean b/docs/verso/LangDefDoc.lean new file mode 100644 index 000000000..ceadf0b66 --- /dev/null +++ b/docs/verso/LangDefDoc.lean @@ -0,0 +1,327 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import VersoManual + +import Strata.DL.Imperative.Cmd +import Strata.DL.Imperative.CmdSemantics +import Strata.DL.Imperative.Stmt +import Strata.DL.Imperative.StmtSemanticsSmallStep +import Strata.DL.Imperative.NondetStmt +import Strata.DL.Imperative.NondetStmtSemantics +import Strata.DL.Imperative.MetaData +import Strata.DL.Lambda.LExpr +import Strata.DL.Lambda.Semantics +import Strata.DL.Lambda.LExprTypeSpec + +open Lambda +open Imperative + +-- This gets access to most of the manual genre +open Verso.Genre Manual + +-- This gets access to Lean code that's in code blocks, elaborated in +-- the same process and environment as Verso +open Verso.Genre.Manual.InlineLean + +set_option pp.rawOnError true +set_option verso.docstring.allowMissing false + +#doc (Manual) "The Strata Language Definition" => +%%% +shortTitle := "The Strata Language" +%%% + +# 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](https://github.com/strata-org/Strata/tree/main/docs/verso/DDMDoc.lean), +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](strata-hourglass.png) + +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](https://b3-lang.org/), 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. + +# 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](https://github.com/strata-org/Strata/blob/main/Strata/DL/Lambda/LExprTypeSpec.lean) +of a polymorphic Hindley-Milner type system and an +[implementation](https://github.com/strata-org/Strata/blob/main/Strata/DL/Lambda/LTyUnify.lean) +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). + +## Syntax + +The syntax of lambda expressions is provided by the {name LExpr}`LExpr` type. + +{docstring Lambda.LExpr} + +Identifiers in lambda expressions, using the {name Identifier}`Identifier` type, +can be annotated with metadata. + +{docstring Lambda.Identifier} + +Specific constructors exist for constants of various scalar types, including +booleans, bit vectors, integers, reals, and strings. + +{docstring Lambda.LConst} + +The {name LExpr}`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. + +{docstring Lambda.LExprParams} + +{docstring Lambda.LExprParamsT} + +## Type System + +Although {name LExpr}`LExpr` can be parameterized by an arbitrary type system, +Strata currently implements one, based on the types {name LMonoTy}`LMonoTy` and +{name LTy}`LTy`. + +The first, {name LMonoTy}`LMonoTy`, represents monomorphic types. It's a +separate type because some contexts allow only monomorphic types. + +{docstring Lambda.LMonoTy} + +Type variables in {name LMonoTy}`LMonoTy` use the {name TyIdentifier}`TyIdentifier` type. + +{docstring Lambda.TyIdentifier} + +The {name LTy}`LTy` type allows monomorphic types to be wrapped in universal type +quantifiers that bind these type variables, creating polymorphic types. + +{docstring Lambda.LTy} + +An expression {name LExpr}`LExpr` parameterized by {name LTy}`LTy` is +well-typed according to the {name LExpr.HasType}`HasType` relation. +This relation depends on two types of context. + +The first of these, {name LContext}`LContext`, contains information that does +not change throughout the type checking process. This includes information about +built-in functions, using the {name Factory}`Factory` type, and built-in types, +using the {name TypeFactory}`TypeFactory` type. Built-in functions optionally +include concrete evaluation functions, which can be used in the operational +semantics described below. + +{docstring Lambda.LContext} + +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. + +{docstring Lambda.TContext} + +Given these two pieces of context, the {name LExpr.HasType}`HasType` relation +describes the valid type of each expression form. + +{docstring Lambda.LExpr.HasType} + +## Operational Semantics + +The semantics of the {name LExpr}`LExpr` type are specified in a standard way +using the small-step inductive relation {name Lambda.Step}`Lambda.Step`. +This relation is parameterized by a `Factory`, which describes built-in +functions via an optional body and/or evaluation function. + +{docstring Lambda.Step} + +Typically we will want to talk about arbitrarily long sequences of steps, such +as from an initial expression to a value. The +{name Lambda.StepStar}`Lambda.StepStar` relation describes the reflexive, +transitive closure of the {name Lambda.Step}`Lambda.Step` relation. + +{docstring Lambda.StepStar} + +# 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](https://www.cs.cornell.edu/~kozen/Papers/kat.pdf), 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. + +## Command Syntax + +The core built-in set of commands includes variable initializations, +deterministic assignments, non-deterministic assignments ("havoc"), assertions, +and assumptions. + +{docstring Imperative.Cmd} + +## 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 {name InitState}`InitState` relation describes how a +variable obtains its initial value. + +{docstring Imperative.InitState} + +The {name UpdateState}`UpdateState` relation then describes how a variable's +value can change. + +{docstring Imperative.UpdateState} + +Given these two state relations, the semantics of each command is specified in +a standard way. + +{docstring Imperative.EvalCmd} + +## 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. + +{docstring Imperative.Stmt} + +{docstring Imperative.Block} + +## Structured Deterministic Statement Operational Semantics + +The semantics of the {name Stmt}`Stmt` type is defined in terms of +*configurations*, represented by the {name Imperative.Config}`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. + +{docstring Imperative.Config} + +The {name StepStmt}`StepStmt` type describes how each type of statement +transforms configurations. Like with the other components of Strata, the rules +follow standard conventions. + +{docstring Imperative.StepStmt} + +Like with `Lambda`, we typically want to talk about arbitrarily long sequences +of steps. The {name StepStmtStar}`Imperative.StepStmtStar` relation describes +the reflexive, transitive closure of the {name StepStmt}`Imperative.StepStmt` +relation. + +{docstring Imperative.StepStmtStar} + +# 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. + +{docstring Imperative.MetaDataElem.Field} + +A value can take the form of an expression or an arbitrary string. + +{docstring Imperative.MetaDataElem.Value} + +A metadata element pairs a field with a value. + +{docstring Imperative.MetaDataElem} + +And, finally, the metadata attached to an AST node consists of an array of +metadata elements. + +{docstring Imperative.MetaData} diff --git a/docs/verso/LangDefDocMain.lean b/docs/verso/LangDefDocMain.lean new file mode 100644 index 000000000..9423ab6d1 --- /dev/null +++ b/docs/verso/LangDefDocMain.lean @@ -0,0 +1,18 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + + + +import LangDefDoc +open Verso.Genre.Manual (Config manualMain) + +def config : Config where + emitTeX := false + emitHtmlSingle := true + emitHtmlMulti := false + htmlDepth := 2 + +def main := manualMain (%doc LangDefDoc) (config := config) diff --git a/docs/verso/README.md b/docs/verso/README.md new file mode 100644 index 000000000..05c34a620 --- /dev/null +++ b/docs/verso/README.md @@ -0,0 +1,37 @@ +# Strata documents created in Verso + +This Verso package provides documentation of the core Strata language +and the Dialect Definition Mechanism (DDM). The documentation can be +generated by the command + +``` +./generate.sh +``` + +The output will be written to `_out/{document name}`. Links in Verso +documentation do not work if the file is opened in the browser directly. +Instead, we recommend launching a local web server to view the +documentation. If Python is available, then this can be done with the +command + +``` +python3 -m http.server 1080 -d _out/langdef/html-single +``` + +or + +``` +python3 -m http.server 1080 -d _out/ddm/html-single +``` + +This will print out a URL that can be opened in a browser to view the documentation. + +# Strata Language Definition + +TODO + +# DDM User Manual + +New Strata dialects are defined in their own domain-specific language +that can be embededed in Lean or imported from external files. This +document provides a guide to using that DSL. diff --git a/docs/verso/generate.sh b/docs/verso/generate.sh new file mode 100755 index 000000000..92a4ba4d9 --- /dev/null +++ b/docs/verso/generate.sh @@ -0,0 +1,5 @@ +set -ex + +lake exe ddm --with-html-single --output _out/ddm +lake exe langdef --with-html-single --output _out/langdef +cp strata-hourglass.png _out/langdef/html-single/ diff --git a/docs/ddm/lake-manifest.json b/docs/verso/lake-manifest.json similarity index 54% rename from docs/ddm/lake-manifest.json rename to docs/verso/lake-manifest.json index edaee8c6e..2af892281 100644 --- a/docs/ddm/lake-manifest.json +++ b/docs/verso/lake-manifest.json @@ -5,17 +5,34 @@ "type": "git", "subDir": null, "scope": "", - "rev": "590eac5d96f04c5a75214b3b501afe389c333720", + "rev": "65d9578b16437bcd2631eb2b4c191e3498a68c6b", "name": "verso", "manifestFile": "lake-manifest.json", - "inputRev": "nightly-testing", + "inputRev": "v4.26.0", "inherited": false, "configFile": "lakefile.lean"}, + {"type": "path", + "scope": "", + "name": "Strata", + "manifestFile": "lake-manifest.json", + "inherited": false, + "dir": "../..", + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/plausible", + "type": "git", + "subDir": null, + "scope": "", + "rev": "74835c84b38e4070b8240a063c6417c767e551ae", + "name": "plausible", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}, {"url": "https://github.com/acmepjz/md4lean", "type": "git", "subDir": null, "scope": "", - "rev": "aaee7fa4a1a158bd814d76f642df8a1d19db9f49", + "rev": "38ac5945d744903ffcc473ce1030223991b11cf6", "name": "MD4Lean", "manifestFile": "lake-manifest.json", "inputRev": "main", @@ -25,7 +42,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "dd7c477cb8b1898c3ace7bf66a47462eef7ac52c", + "rev": "eb77622e97e942ba2cfe02f60637705fc2d9481b", "name": "subverso", "manifestFile": "lake-manifest.json", "inputRev": "main", diff --git a/docs/verso/lakefile.toml b/docs/verso/lakefile.toml new file mode 100644 index 000000000..c91b6d2ab --- /dev/null +++ b/docs/verso/lakefile.toml @@ -0,0 +1,25 @@ +name = "StrataDoc" +defaultTargets = ["ddm", "langdef"] + +[[require]] +name = "Strata" +path = "../.." + +[[require]] +name = "verso" +git = "https://github.com/leanprover/verso" +rev = "v4.26.0" + +[[lean_lib]] +name = "DDMDoc" + +[[lean_lib]] +name = "LangDefDoc" + +[[lean_exe]] +name = "ddm" +root = "DDMDocMain" + +[[lean_exe]] +name = "langdef" +root = "LangDefDocMain" diff --git a/docs/verso/lean-toolchain b/docs/verso/lean-toolchain new file mode 100644 index 000000000..e59446d59 --- /dev/null +++ b/docs/verso/lean-toolchain @@ -0,0 +1 @@ +leanprover/lean4:v4.26.0 diff --git a/docs/verso/strata-hourglass.png b/docs/verso/strata-hourglass.png new file mode 100644 index 000000000..44d7261ac Binary files /dev/null and b/docs/verso/strata-hourglass.png differ diff --git a/lake-manifest.json b/lake-manifest.json index 2dfed81d1..6b7912b6d 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -1,5 +1,15 @@ {"version": "1.1.0", "packagesDir": ".lake/packages", - "packages": [], + "packages": + [{"url": "https://github.com/leanprover-community/plausible.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "b949552f6ca8e223f424b3e3b33f74185bbf1179", + "name": "plausible", + "manifestFile": "lake-manifest.json", + "inputRev": "b949552f6ca8e223f424b3e3b33f74185bbf1179", + "inherited": false, + "configFile": "lakefile.toml"}], "name": "Strata", "lakeDir": ".lake"} diff --git a/lakefile.toml b/lakefile.toml index 83e2a9f2f..f70d9e7dc 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -1,8 +1,16 @@ name = "Strata" version = "0.1.0" -defaultTargets = ["Strata", "StrataMain", "StrataVerify", "StrataToCBMC"] +defaultTargets = ["Strata", "strata", "StrataMain", "StrataVerify", "StrataToCBMC", "BoogieToGoto"] testDriver = "StrataTest" +[leanOptions] +experimental.module = true + +[[require]] +name = "plausible" +git = "https://github.com/leanprover-community/plausible.git" +rev = "b949552f6ca8e223f424b3e3b33f74185bbf1179" + [[lean_lib]] name = "Strata" @@ -25,4 +33,4 @@ name = "StrataVerify" name = "StrataToCBMC" [[lean_exe]] -name = "BoogieToGoto" \ No newline at end of file +name = "BoogieToGoto" diff --git a/lean-toolchain b/lean-toolchain index 099e86941..e59446d59 100644 --- a/lean-toolchain +++ b/lean-toolchain @@ -1 +1 @@ -v4.24.0 \ No newline at end of file +leanprover/lean4:v4.26.0