Add scaffolding for SQL support
This commit is contained in:
parent
0f705a3fbd
commit
0986e13669
14
AGENTS.md
14
AGENTS.md
@ -6,7 +6,7 @@ This file provides guidance to coding agents collaborating on this repository.
|
|||||||
|
|
||||||
Query Engine is an experimental Rust project for building query-engine
|
Query Engine is an experimental Rust project for building query-engine
|
||||||
components. The current implementation is centered on a chase-based reasoning
|
components. The current implementation is centered on a chase-based reasoning
|
||||||
core plus lightweight interactive frontends.
|
core, lightweight interactive frontends, and an early relational/SQL scaffold.
|
||||||
|
|
||||||
Priorities, in order:
|
Priorities, in order:
|
||||||
|
|
||||||
@ -52,8 +52,14 @@ Quick examples:
|
|||||||
- `rule.rs`: TGDs, EGDs, equalities, and builders.
|
- `rule.rs`: TGDs, EGDs, equalities, and builders.
|
||||||
- `substitution.rs`: variable bindings and unification.
|
- `substitution.rs`: variable bindings and unification.
|
||||||
- `engine.rs`: chase execution and configuration.
|
- `engine.rs`: chase execution and configuration.
|
||||||
|
- `inference.rs`: shared matching and provenance-aware materialization helpers.
|
||||||
- `union_find.rs`: equality merging support.
|
- `union_find.rs`: equality merging support.
|
||||||
- `src/frontend/`: lightweight interactive surface for scripts, REPL, and local web UI.
|
- `src/frontend/`: lightweight interactive surface for scripts, REPL, and local web UI.
|
||||||
|
- `src/relational/`: schemas, values, rows, and result sets for relational execution.
|
||||||
|
- `src/catalog/`: predicate-to-table schema inference and catalog access.
|
||||||
|
- `src/sql/`: narrow SQL AST and parser support.
|
||||||
|
- `src/planner/`: logical plan structures and SQL-to-plan translation.
|
||||||
|
- `src/execution/`: execution of the current logical plan subset.
|
||||||
- `tests/`: integration, regression, and property-based tests.
|
- `tests/`: integration, regression, and property-based tests.
|
||||||
|
|
||||||
## Architecture Constraints
|
## Architecture Constraints
|
||||||
@ -64,6 +70,8 @@ Quick examples:
|
|||||||
- The chase engine should remain largely stateless; pass execution state explicitly.
|
- The chase engine should remain largely stateless; pass execution state explicitly.
|
||||||
- New chase variants should be composable with existing infrastructure.
|
- New chase variants should be composable with existing infrastructure.
|
||||||
- Existential variables generate labeled nulls (`Term::Null`).
|
- Existential variables generate labeled nulls (`Term::Null`).
|
||||||
|
- The current SQL support is intentionally narrow: single-table `SELECT-FROM-WHERE` with positional column names such as `c0` and `c1`.
|
||||||
|
- Relational and SQL modules should build on explicit schemas and logical plans, not call frontend helpers directly.
|
||||||
- If you add parser, planner, or executor layers, keep their responsibilities separate.
|
- If you add parser, planner, or executor layers, keep their responsibilities separate.
|
||||||
- Public docs and interfaces should reflect the implemented state of the repository accurately.
|
- Public docs and interfaces should reflect the implemented state of the repository accurately.
|
||||||
|
|
||||||
@ -105,6 +113,7 @@ Example scopes that are good first tasks:
|
|||||||
- Implement a new utility method on `Instance` or `Atom`.
|
- Implement a new utility method on `Instance` or `Atom`.
|
||||||
- Tighten frontend wording so it matches actual behavior.
|
- Tighten frontend wording so it matches actual behavior.
|
||||||
- Introduce a small planning-oriented type without changing execution semantics.
|
- Introduce a small planning-oriented type without changing execution semantics.
|
||||||
|
- Extend the SQL slice with a narrow, well-tested feature such as aliases or named columns.
|
||||||
|
|
||||||
## Testing Expectations
|
## Testing Expectations
|
||||||
|
|
||||||
@ -113,6 +122,7 @@ Example scopes that are good first tasks:
|
|||||||
- Integration tests go in `tests/integration_tests.rs`.
|
- Integration tests go in `tests/integration_tests.rs`.
|
||||||
- Regression tests for bug fixes go in `tests/regression_tests.rs`.
|
- Regression tests for bug fixes go in `tests/regression_tests.rs`.
|
||||||
- Property-based tests go in `tests/property_tests.rs`.
|
- Property-based tests go in `tests/property_tests.rs`.
|
||||||
|
- SQL/planner/execution flow tests go in `tests/sql_pipeline_tests.rs`.
|
||||||
- Do not merge code that breaks existing tests.
|
- Do not merge code that breaks existing tests.
|
||||||
|
|
||||||
Minimal unit-test checklist for chase-related behavior:
|
Minimal unit-test checklist for chase-related behavior:
|
||||||
@ -151,6 +161,7 @@ Before coding:
|
|||||||
2. Identify affected tests.
|
2. Identify affected tests.
|
||||||
3. Consider impact on API stability.
|
3. Consider impact on API stability.
|
||||||
4. Avoid overstating roadmap progress in code comments or docs.
|
4. Avoid overstating roadmap progress in code comments or docs.
|
||||||
|
5. Keep the supported SQL subset explicit when touching `sql`, `planner`, or `execution`.
|
||||||
|
|
||||||
Before submitting:
|
Before submitting:
|
||||||
|
|
||||||
@ -186,6 +197,7 @@ Use this review format:
|
|||||||
- If you detect contradictory repository conventions, follow existing code and update docs accordingly.
|
- If you detect contradictory repository conventions, follow existing code and update docs accordingly.
|
||||||
- When uncertain about correctness, add or extend tests first, then optimize.
|
- When uncertain about correctness, add or extend tests first, then optimize.
|
||||||
- When adding non-chase engine pieces, define clean interfaces before broadening functionality.
|
- When adding non-chase engine pieces, define clean interfaces before broadening functionality.
|
||||||
|
- Keep `frontend` presentation-only when possible; shared reasoning logic belongs in `chase`, relational logic in `relational`/`planner`/`execution`.
|
||||||
- Keep user-facing naming consistent with the repository name: `query-engine` / `query_engine`.
|
- Keep user-facing naming consistent with the repository name: `query-engine` / `query_engine`.
|
||||||
|
|
||||||
## Commit and PR Hygiene
|
## Commit and PR Hygiene
|
||||||
|
|||||||
31
ROADMAP.md
31
ROADMAP.md
@ -23,6 +23,12 @@ This document tracks the current state and next steps for the repository.
|
|||||||
- [x] Equality merging with union-find support
|
- [x] Equality merging with union-find support
|
||||||
- [x] REPL, script runner, and local web UI
|
- [x] REPL, script runner, and local web UI
|
||||||
- [x] Provenance-oriented explanation support
|
- [x] Provenance-oriented explanation support
|
||||||
|
- [x] Relational schema, value, row, and result-set scaffolding
|
||||||
|
- [x] Predicate-backed catalog inference
|
||||||
|
- [x] Minimal SQL AST and parser
|
||||||
|
- [x] Logical plan scaffolding
|
||||||
|
- [x] Logical-plan execution for the first SQL slice
|
||||||
|
- [x] Single-table `SELECT-FROM-WHERE` support with positional columns (`c0`, `c1`, ...)
|
||||||
|
|
||||||
### Near-Term Cleanup
|
### Near-Term Cleanup
|
||||||
|
|
||||||
@ -30,27 +36,31 @@ This document tracks the current state and next steps for the repository.
|
|||||||
- [ ] Remove remaining stale terminology in comments and help text
|
- [ ] Remove remaining stale terminology in comments and help text
|
||||||
- [ ] Expand examples for the current rule-engine workflow
|
- [ ] Expand examples for the current rule-engine workflow
|
||||||
- [ ] Add rustdoc coverage for the main public types
|
- [ ] Add rustdoc coverage for the main public types
|
||||||
|
- [ ] Document the current SQL subset and its limits
|
||||||
|
|
||||||
### Query-Engine Structure
|
### Query-Engine Structure
|
||||||
|
|
||||||
- [ ] Introduce a dedicated logical representation module
|
- [x] Introduce a dedicated logical representation module
|
||||||
- [ ] Define clear front-end, planning, and execution boundaries
|
- [x] Define clear front-end, planning, and execution boundaries
|
||||||
- [ ] Add engine-level abstractions that are not chase-specific
|
- [ ] Add engine-level abstractions that are not chase-specific
|
||||||
- [ ] Establish common schema and typed-value representations
|
- [x] Establish common schema and typed-value representations
|
||||||
- [ ] Design a source boundary for future scans and pushdown
|
- [ ] Design a source boundary for future scans and pushdown
|
||||||
|
|
||||||
### Front End and Planning
|
### Front End and Planning
|
||||||
|
|
||||||
- [ ] Add a parser-oriented module beyond the current rule REPL language
|
- [x] Add a parser-oriented module beyond the current rule REPL language
|
||||||
- [ ] Add AST types for a structured query front end
|
- [x] Add AST types for a structured query front end
|
||||||
- [ ] Add logical plan node types
|
- [x] Add logical plan node types
|
||||||
- [ ] Add name resolution and schema validation hooks
|
- [x] Add name resolution and schema validation hooks
|
||||||
- [ ] Add expression typing and nullability tracking
|
- [x] Add expression typing and nullability tracking
|
||||||
|
- [ ] Add aliases and richer projection expressions
|
||||||
|
- [ ] Add joins across multiple predicate-backed tables
|
||||||
|
- [ ] Add a catalog path for stable column naming beyond `c0`, `c1`, ...
|
||||||
|
|
||||||
### Execution and Optimization
|
### Execution and Optimization
|
||||||
|
|
||||||
- [ ] Introduce physical operator abstractions
|
- [ ] Introduce physical operator abstractions
|
||||||
- [ ] Add a planning step from logical operators to executable operators
|
- [x] Add a planning step from logical operators to executable operators
|
||||||
- [ ] Add basic rule-based logical rewrites
|
- [ ] Add basic rule-based logical rewrites
|
||||||
- [ ] Add statistics and cost-model scaffolding
|
- [ ] Add statistics and cost-model scaffolding
|
||||||
- [ ] Add indexing and access-path abstractions
|
- [ ] Add indexing and access-path abstractions
|
||||||
@ -73,7 +83,7 @@ This document tracks the current state and next steps for the repository.
|
|||||||
|
|
||||||
- [ ] Fact import/export
|
- [ ] Fact import/export
|
||||||
- [ ] File-backed data source experiments
|
- [ ] File-backed data source experiments
|
||||||
- [ ] Table-like row or batch abstractions
|
- [x] Table-like row or batch abstractions
|
||||||
- [ ] Stable script/query file format
|
- [ ] Stable script/query file format
|
||||||
- [ ] Integration with external storage or file formats
|
- [ ] Integration with external storage or file formats
|
||||||
|
|
||||||
@ -91,6 +101,7 @@ This document tracks the current state and next steps for the repository.
|
|||||||
- [x] Integration tests
|
- [x] Integration tests
|
||||||
- [x] Property-based tests
|
- [x] Property-based tests
|
||||||
- [x] Regression tests
|
- [x] Regression tests
|
||||||
|
- [x] Initial SQL pipeline tests
|
||||||
- [ ] Benchmark coverage
|
- [ ] Benchmark coverage
|
||||||
- [ ] Snapshot-style frontend tests
|
- [ ] Snapshot-style frontend tests
|
||||||
- [ ] More planner/executor tests as those layers are added
|
- [ ] More planner/executor tests as those layers are added
|
||||||
|
|||||||
529
examples/geolog/README.md
Normal file
529
examples/geolog/README.md
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
# Geolog DSL Structure
|
||||||
|
|
||||||
|
This directory contains example `.geolog` files that use a richer DSL than the
|
||||||
|
minimal `.chase` script language in `examples/scripts/`.
|
||||||
|
|
||||||
|
This README summarizes the Geolog DSL structure as it appears in the examples in
|
||||||
|
this directory. It should be read as a practical, example-driven reference, not
|
||||||
|
as a formal or complete language specification.
|
||||||
|
|
||||||
|
## At a Glance
|
||||||
|
|
||||||
|
- Top-level declarations are `theory` and `instance`.
|
||||||
|
- The main kind of type is `Sort`.
|
||||||
|
- Symbols can denote sorts, constants, functions, predicates, or nested
|
||||||
|
instances.
|
||||||
|
- Axioms use sequent-style syntax: `premise |- conclusion`.
|
||||||
|
- Instance bodies can be plain literals (`= { ... }`) or chase blocks
|
||||||
|
(`= chase { ... }`).
|
||||||
|
- Function application is postfix, and qualified names use `/`.
|
||||||
|
|
||||||
|
## Informal File Shape
|
||||||
|
|
||||||
|
```text
|
||||||
|
file := { comment | theory_decl | instance_decl }
|
||||||
|
|
||||||
|
comment := // ...
|
||||||
|
|
||||||
|
theory_decl :=
|
||||||
|
theory theory_params? Name {
|
||||||
|
theory_item*
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_decl :=
|
||||||
|
instance Name : theory_app = {
|
||||||
|
instance_item*
|
||||||
|
}
|
||||||
|
|
||||||
|
| instance Name : theory_app = chase {
|
||||||
|
instance_item*
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice, a file is usually a sequence of theory declarations followed by one
|
||||||
|
or more example instances.
|
||||||
|
|
||||||
|
## Top-Level Declarations
|
||||||
|
|
||||||
|
### `theory`
|
||||||
|
|
||||||
|
A theory introduces sorts, symbols, and axioms.
|
||||||
|
|
||||||
|
```text
|
||||||
|
theory Graph {
|
||||||
|
V : Sort;
|
||||||
|
Edge : [src: V, tgt: V] -> Prop;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Theories may also be parameterized.
|
||||||
|
|
||||||
|
```text
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed parameter forms:
|
||||||
|
|
||||||
|
- sort parameters: `(X : Sort)`
|
||||||
|
- instance parameters: `(N : PetriNet instance)`
|
||||||
|
- dependent instance parameters: `(RP : N ReachabilityProblem instance)`
|
||||||
|
|
||||||
|
### `instance`
|
||||||
|
|
||||||
|
An instance provides concrete elements and assignments for a theory.
|
||||||
|
|
||||||
|
```text
|
||||||
|
instance Triangle : Graph = {
|
||||||
|
A : V;
|
||||||
|
B : V;
|
||||||
|
C : V;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instances may target parameterized theories by writing the arguments before the
|
||||||
|
theory name.
|
||||||
|
|
||||||
|
```text
|
||||||
|
instance MyMap : MyBase Map = { ... }
|
||||||
|
instance solution0 : ExampleNet problem0 Solution = { ... }
|
||||||
|
instance AB_Iso : As/a Bs/b Iso = { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theory Body Items
|
||||||
|
|
||||||
|
The examples show the following declaration forms inside a theory body.
|
||||||
|
|
||||||
|
### Sort declarations
|
||||||
|
|
||||||
|
```text
|
||||||
|
V : Sort;
|
||||||
|
E : Sort;
|
||||||
|
```
|
||||||
|
|
||||||
|
This introduces carrier sorts.
|
||||||
|
|
||||||
|
### Constants or distinguished elements
|
||||||
|
|
||||||
|
```text
|
||||||
|
elem : X;
|
||||||
|
```
|
||||||
|
|
||||||
|
This introduces a named element of an existing sort.
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
```text
|
||||||
|
src : E -> V;
|
||||||
|
fwd : X -> Y;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
```
|
||||||
|
|
||||||
|
Names may contain `/`, which is used heavily for structured or qualified names.
|
||||||
|
|
||||||
|
### Predicates and relations
|
||||||
|
|
||||||
|
Unary predicates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
completed : Item -> Prop;
|
||||||
|
```
|
||||||
|
|
||||||
|
Record-shaped predicates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Edge : [src: V, tgt: V] -> Prop;
|
||||||
|
depends : [item: Item, on: Item] -> Prop;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Product-valued functions
|
||||||
|
|
||||||
|
Functions may return record-shaped values.
|
||||||
|
|
||||||
|
```text
|
||||||
|
pair_of : A -> [left: B, right: C];
|
||||||
|
W/src : W -> [firing: F, arc: N/out];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested instance slots
|
||||||
|
|
||||||
|
Theories can contain fields whose values are themselves instances.
|
||||||
|
|
||||||
|
```text
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
trace : N Trace instance;
|
||||||
|
initial_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axioms
|
||||||
|
|
||||||
|
Axioms use a sequent-like form.
|
||||||
|
|
||||||
|
```text
|
||||||
|
ax/trans : forall x, y, z : V.
|
||||||
|
[src: x, tgt: y] Path, [src: y, tgt: z] Path |- [src: x, tgt: z] Path;
|
||||||
|
```
|
||||||
|
|
||||||
|
An axiom declaration has the observed structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
name : forall binders. premise |- conclusion;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formula and Term Syntax
|
||||||
|
|
||||||
|
The examples use the following building blocks inside axioms and assignments.
|
||||||
|
|
||||||
|
### Quantifiers
|
||||||
|
|
||||||
|
Universal binders appear after `forall`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
forall x : X, y : Y.
|
||||||
|
```
|
||||||
|
|
||||||
|
Existentials appear directly inside conclusions or disjuncts.
|
||||||
|
|
||||||
|
```text
|
||||||
|
exists r : R. r R/data = [x: a, y: b]
|
||||||
|
exists w : W. w W/src_firing = f, w W/src_arc = arc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sequents
|
||||||
|
|
||||||
|
The central connective is `|-`, separating premise from conclusion.
|
||||||
|
|
||||||
|
```text
|
||||||
|
premise1, premise2 |- conclusion1, conclusion2;
|
||||||
|
```
|
||||||
|
|
||||||
|
In the examples:
|
||||||
|
|
||||||
|
- commas act as conjunctions
|
||||||
|
- the left side is the premise
|
||||||
|
- the right side is the conclusion
|
||||||
|
|
||||||
|
### Equality
|
||||||
|
|
||||||
|
Equality is used for ordinary values and compound values.
|
||||||
|
|
||||||
|
```text
|
||||||
|
ab src = A
|
||||||
|
r R/data = [x: a, y: b]
|
||||||
|
x fwd bwd = x
|
||||||
|
```
|
||||||
|
|
||||||
|
### Truth constants
|
||||||
|
|
||||||
|
```text
|
||||||
|
|- true;
|
||||||
|
|- false;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disjunction
|
||||||
|
|
||||||
|
Disjunction is written as `\/`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[item: x, on: y] depends |- x blocked \/ y completed;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Predicate atoms
|
||||||
|
|
||||||
|
Unary predicate atoms:
|
||||||
|
|
||||||
|
```text
|
||||||
|
x blocked
|
||||||
|
y completed
|
||||||
|
```
|
||||||
|
|
||||||
|
Record-shaped predicate atoms:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[src: x, tgt: y] Edge
|
||||||
|
[a: x, f: i] id
|
||||||
|
```
|
||||||
|
|
||||||
|
### Postfix application
|
||||||
|
|
||||||
|
Function application is postfix and can be chained.
|
||||||
|
|
||||||
|
```text
|
||||||
|
e src
|
||||||
|
x id
|
||||||
|
x fwd bwd
|
||||||
|
w W/src_arc N/out/tgt
|
||||||
|
```
|
||||||
|
|
||||||
|
This is one of the most distinctive traits of the DSL.
|
||||||
|
|
||||||
|
### Qualified paths
|
||||||
|
|
||||||
|
The slash `/` is used for qualification and nested access.
|
||||||
|
|
||||||
|
Observed examples include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
R/data
|
||||||
|
N/P
|
||||||
|
N/out/src
|
||||||
|
trace/input_terminal
|
||||||
|
RP/initial_marking/token
|
||||||
|
ExampleNet/A
|
||||||
|
```
|
||||||
|
|
||||||
|
### Record literals
|
||||||
|
|
||||||
|
Records are written with brackets and named fields.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[x: a, y: b]
|
||||||
|
[firing: f, arc: arc]
|
||||||
|
```
|
||||||
|
|
||||||
|
The examples also show positional shorthand for record arguments when the field
|
||||||
|
order is known:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[e, e] mul = e;
|
||||||
|
[cook_dinner, on: buy_groceries] depends;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field projection
|
||||||
|
|
||||||
|
Record-valued terms support field projection.
|
||||||
|
|
||||||
|
```text
|
||||||
|
r R/data .x = a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instance Body Items
|
||||||
|
|
||||||
|
The examples show the following forms inside instance bodies.
|
||||||
|
|
||||||
|
### Element declarations
|
||||||
|
|
||||||
|
```text
|
||||||
|
a : V;
|
||||||
|
tok : token;
|
||||||
|
ab : T;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function assignments
|
||||||
|
|
||||||
|
```text
|
||||||
|
ab src = A;
|
||||||
|
tok token/of = ExampleNet/A;
|
||||||
|
a1 map = MyBase/b1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Predicate facts
|
||||||
|
|
||||||
|
Unary facts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
buy_groceries completed;
|
||||||
|
```
|
||||||
|
|
||||||
|
Record-shaped facts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[src: a, tgt: b] Edge;
|
||||||
|
[item: x, on: y] depends;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Product-valued assignments
|
||||||
|
|
||||||
|
```text
|
||||||
|
a1 pair_of = [left: b1, right: c1];
|
||||||
|
ot output_terminal/src = [firing: f1, arc: SimpleNet/arc_out];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested instance literals
|
||||||
|
|
||||||
|
Instance-valued fields can be assigned inline blocks.
|
||||||
|
|
||||||
|
```text
|
||||||
|
initial_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## `= { ... }` vs `= chase { ... }`
|
||||||
|
|
||||||
|
The examples consistently use two instance forms.
|
||||||
|
|
||||||
|
### Plain instance
|
||||||
|
|
||||||
|
```text
|
||||||
|
instance Triangle : Graph = {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives a direct structure with only the explicitly listed elements and
|
||||||
|
assignments.
|
||||||
|
|
||||||
|
### Chase instance
|
||||||
|
|
||||||
|
```text
|
||||||
|
instance Chain : Graph = chase {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This marks an instance whose contents should be extended by the theory axioms.
|
||||||
|
Examples use this for transitive closure, existential witness generation, and
|
||||||
|
trace/reachability constructions.
|
||||||
|
|
||||||
|
## Observed Design Patterns
|
||||||
|
|
||||||
|
### Parameterized theories
|
||||||
|
|
||||||
|
Theories can be abstract over:
|
||||||
|
|
||||||
|
- a sort, such as `theory (X : Sort) Container`
|
||||||
|
- an instance, such as `theory (N : PetriNet instance) Marking`
|
||||||
|
- multiple arguments, such as `theory (N : PetriNet instance) (RP : N ReachabilityProblem instance) Solution`
|
||||||
|
|
||||||
|
### Theory application by adjacency
|
||||||
|
|
||||||
|
Theory arguments are written before the theory name.
|
||||||
|
|
||||||
|
```text
|
||||||
|
N Marking instance
|
||||||
|
MyBase Map
|
||||||
|
ExampleNet problem0 Solution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested structure via qualified names
|
||||||
|
|
||||||
|
Many examples model dependent structure by naming into earlier declarations.
|
||||||
|
|
||||||
|
```text
|
||||||
|
token/of : token -> N/P;
|
||||||
|
initial_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Seems Outside the File DSL
|
||||||
|
|
||||||
|
Some example comments refer to interactive commands such as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
:source
|
||||||
|
:load
|
||||||
|
:inspect
|
||||||
|
:chase
|
||||||
|
:solve
|
||||||
|
```
|
||||||
|
|
||||||
|
Those commands appear to belong to an external REPL or tool environment rather
|
||||||
|
than to the `.geolog` file grammar itself.
|
||||||
|
|
||||||
|
## Querying
|
||||||
|
|
||||||
|
The examples do **not** show a stable, implemented query form inside `.geolog`
|
||||||
|
files in the same way they show `theory` and `instance` declarations.
|
||||||
|
|
||||||
|
What they do show is:
|
||||||
|
|
||||||
|
### 1. External interactive commands
|
||||||
|
|
||||||
|
Some files suggest that querying or inspection happens through an external tool
|
||||||
|
or REPL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
:source examples/geolog/transitive_closure.geolog
|
||||||
|
:inspect Chain
|
||||||
|
:chase Chain
|
||||||
|
:solve EmptyModel
|
||||||
|
```
|
||||||
|
|
||||||
|
Based on the example comments, these commands appear to mean roughly:
|
||||||
|
|
||||||
|
- `:source` or `:load`: load Geolog definitions from files
|
||||||
|
- `:inspect`: inspect a declared instance
|
||||||
|
- `:chase`: materialize or display the closure of a `= chase` instance
|
||||||
|
- `:solve`: ask a solver to construct a satisfying instance for a theory
|
||||||
|
|
||||||
|
These are tool commands, not part of the confirmed `.geolog` declaration syntax.
|
||||||
|
|
||||||
|
### 2. A sketched future query form
|
||||||
|
|
||||||
|
One file contains a commented-out example of a possible query block:
|
||||||
|
|
||||||
|
```text
|
||||||
|
query can_reach_B_from_A {
|
||||||
|
? : ExampleNet problem0 Solution instance;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This suggests an aspirational style where querying is expressed as asking for a
|
||||||
|
witness inhabiting some instance type. However, in the current examples this is
|
||||||
|
only a comment, not an observed live construct.
|
||||||
|
|
||||||
|
### 3. Current implemented querying elsewhere in the repo
|
||||||
|
|
||||||
|
The currently implemented query syntax in this repository belongs to the minimal
|
||||||
|
frontend language, not to Geolog. That language supports forms such as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
query Ancestor(?X, ?Y)?
|
||||||
|
explain Ancestor(alice, carol)?
|
||||||
|
```
|
||||||
|
|
||||||
|
So the safest summary is:
|
||||||
|
|
||||||
|
- Geolog examples define theories, instances, and chase-oriented structure.
|
||||||
|
- Querying appears to be external, REPL-driven, or still in design.
|
||||||
|
- The only clearly implemented query syntax in this repo today is the minimal
|
||||||
|
`.chase` frontend query language.
|
||||||
|
|
||||||
|
## Best Example Files by Feature
|
||||||
|
|
||||||
|
- `transitive_closure.geolog`: basic theory, axiom, and `= chase` usage
|
||||||
|
- `graph.geolog`: plain structural instances
|
||||||
|
- `monoid.geolog`: postfix application and positional record syntax
|
||||||
|
- `todo_list.geolog`: unary predicates and disjunction
|
||||||
|
- `sort_param_simple.geolog`: sort and instance parameters
|
||||||
|
- `nested_instance_test.geolog`: nested instance-valued fields
|
||||||
|
- `product_codomain_test.geolog`: record-valued function codomains
|
||||||
|
- `field_projection_test.geolog`: field projection syntax
|
||||||
|
- `record_existential_test.geolog`: existential witness creation with records
|
||||||
|
- `petri_net_showcase.geolog`: large multi-theory composition
|
||||||
|
- `solver_demo.geolog`: satisfiability-oriented axiom patterns
|
||||||
|
|
||||||
|
## Minimal Example
|
||||||
|
|
||||||
|
```text
|
||||||
|
theory Graph {
|
||||||
|
V : Sort;
|
||||||
|
Edge : [src: V, tgt: V] -> Prop;
|
||||||
|
Path : [src: V, tgt: V] -> Prop;
|
||||||
|
|
||||||
|
ax/base : forall x, y : V.
|
||||||
|
[src: x, tgt: y] Edge |- [src: x, tgt: y] Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Chain : Graph = chase {
|
||||||
|
a : V;
|
||||||
|
b : V;
|
||||||
|
[src: a, tgt: b] Edge;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That small example already shows the core structure of the DSL:
|
||||||
|
|
||||||
|
- a `theory`
|
||||||
|
- sort and predicate declarations
|
||||||
|
- an axiom written as a sequent
|
||||||
|
- an `instance`
|
||||||
|
- a `chase` block that asks for closure under the theory axioms
|
||||||
86
examples/geolog/category.geolog
Normal file
86
examples/geolog/category.geolog
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Category theory in current geolog syntax
|
||||||
|
//
|
||||||
|
// This is the "desugared" version of the aspirational syntax in
|
||||||
|
// loose_thoughts/2026-01-21_dependent_sorts_and_functional_relations.md
|
||||||
|
|
||||||
|
theory Category {
|
||||||
|
ob : Sort;
|
||||||
|
mor : Sort;
|
||||||
|
|
||||||
|
// Morphism source and target
|
||||||
|
src : mor -> ob;
|
||||||
|
tgt : mor -> ob;
|
||||||
|
|
||||||
|
// Composition: comp(f, g, h) means "h = f ; g" (f then g)
|
||||||
|
// Domain constraint: f.tgt = g.src
|
||||||
|
comp : [f: mor, g: mor, h: mor] -> Prop;
|
||||||
|
|
||||||
|
// Identity: id(a, f) means "f is the identity on a"
|
||||||
|
id : [a: ob, f: mor] -> Prop;
|
||||||
|
|
||||||
|
// === Axioms ===
|
||||||
|
|
||||||
|
// Identity morphisms have matching source and target
|
||||||
|
ax/id_src : forall x : ob, i : mor. [a: x, f: i] id |- i src = x;
|
||||||
|
ax/id_tgt : forall x : ob, i : mor. [a: x, f: i] id |- i tgt = x;
|
||||||
|
|
||||||
|
// Composition domain constraint
|
||||||
|
ax/comp_dom : forall p : mor, q : mor, r : mor.
|
||||||
|
[f: p, g: q, h: r] comp |- p tgt = q src;
|
||||||
|
|
||||||
|
// Composition source/target
|
||||||
|
ax/comp_src : forall p : mor, q : mor, r : mor.
|
||||||
|
[f: p, g: q, h: r] comp |- r src = p src;
|
||||||
|
ax/comp_tgt : forall p : mor, q : mor, r : mor.
|
||||||
|
[f: p, g: q, h: r] comp |- r tgt = q tgt;
|
||||||
|
|
||||||
|
// Existence of identities (one per object)
|
||||||
|
ax/id_exists : forall x : ob. |- exists i : mor. [a: x, f: i] id;
|
||||||
|
|
||||||
|
// Existence of composites (when composable)
|
||||||
|
ax/comp_exists : forall p : mor, q : mor.
|
||||||
|
p tgt = q src |- exists r : mor. [f: p, g: q, h: r] comp;
|
||||||
|
|
||||||
|
// Left unit: id_a ; f = f
|
||||||
|
ax/unit_left : forall x : ob, i : mor, p : mor, r : mor.
|
||||||
|
[a: x, f: i] id, p src = x, [f: i, g: p, h: r] comp |- r = p;
|
||||||
|
|
||||||
|
// Right unit: f ; id_b = f
|
||||||
|
ax/unit_right : forall y : ob, i : mor, p : mor, r : mor.
|
||||||
|
[a: y, f: i] id, p tgt = y, [f: p, g: i, h: r] comp |- r = p;
|
||||||
|
|
||||||
|
// Associativity: (f ; g) ; h = f ; (g ; h)
|
||||||
|
ax/assoc : forall p : mor, q : mor, r : mor, pq : mor, qr : mor, pqr1 : mor, pqr2 : mor.
|
||||||
|
[f: p, g: q, h: pq] comp, [f: pq, g: r, h: pqr1] comp,
|
||||||
|
[f: q, g: r, h: qr] comp, [f: p, g: qr, h: pqr2] comp
|
||||||
|
|- pqr1 = pqr2;
|
||||||
|
|
||||||
|
// Uniqueness of composition (functional)
|
||||||
|
ax/comp_unique : forall p : mor, q : mor, r1 : mor, r2 : mor.
|
||||||
|
[f: p, g: q, h: r1] comp, [f: p, g: q, h: r2] comp |- r1 = r2;
|
||||||
|
|
||||||
|
// Uniqueness of identity (one per object)
|
||||||
|
ax/id_unique : forall x : ob, i1 : mor, i2 : mor.
|
||||||
|
[a: x, f: i1] id, [a: x, f: i2] id |- i1 = i2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "walking arrow" category: A --f--> B
|
||||||
|
//
|
||||||
|
// Now we can declare just objects and non-identity morphisms!
|
||||||
|
// The chase derives:
|
||||||
|
// - Identity morphisms for each object (via ax/id_exists)
|
||||||
|
// - Composition facts (via ax/comp_exists)
|
||||||
|
// - Source/target for compositions (via ax/comp_src, ax/comp_tgt)
|
||||||
|
//
|
||||||
|
// The equality saturation (via congruence closure) collapses:
|
||||||
|
// - id;id;id;... = id (via ax/unit_left and ax/unit_right)
|
||||||
|
// - Duplicate compositions (via ax/comp_unique)
|
||||||
|
// Without CC, the chase would loop forever creating id;id, id;id;id, ...
|
||||||
|
instance Arrow : Category = chase {
|
||||||
|
// Objects
|
||||||
|
A : ob;
|
||||||
|
B : ob;
|
||||||
|
|
||||||
|
// Non-identity morphism
|
||||||
|
f : mor; f src = A; f tgt = B;
|
||||||
|
}
|
||||||
27
examples/geolog/field_projection_chase_test.geolog
Normal file
27
examples/geolog/field_projection_chase_test.geolog
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Test: Field projection in chase
|
||||||
|
|
||||||
|
theory FieldProjectionChaseTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
R : Sort;
|
||||||
|
R/data : R -> [x: A, y: B];
|
||||||
|
|
||||||
|
// Marker sort for elements whose x field matches a given a
|
||||||
|
XMatches : Sort;
|
||||||
|
XMatches/r : XMatches -> R;
|
||||||
|
XMatches/a : XMatches -> A;
|
||||||
|
|
||||||
|
// Axiom: if r's x field equals a, create an XMatches
|
||||||
|
ax1 : forall r : R, a : A, b : B. r R/data = [x: a, y: b] |- exists m : XMatches. m XMatches/r = r, m XMatches/a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Test : FieldProjectionChaseTest = chase {
|
||||||
|
a1 : A;
|
||||||
|
a2 : A;
|
||||||
|
b1 : B;
|
||||||
|
r1 : R;
|
||||||
|
r1 R/data = [x: a1, y: b1];
|
||||||
|
r2 : R;
|
||||||
|
r2 R/data = [x: a2, y: b1];
|
||||||
|
}
|
||||||
12
examples/geolog/field_projection_test.geolog
Normal file
12
examples/geolog/field_projection_test.geolog
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Test: Field projection syntax
|
||||||
|
|
||||||
|
theory FieldProjectionTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
R : Sort;
|
||||||
|
R/data : R -> [x: A, y: B];
|
||||||
|
|
||||||
|
// Axiom using field projection: r R/data .x
|
||||||
|
ax1 : forall r : R, a : A. r R/data .x = a |- true;
|
||||||
|
}
|
||||||
79
examples/geolog/graph.geolog
Normal file
79
examples/geolog/graph.geolog
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Directed Graph: vertices and edges with source/target functions
|
||||||
|
//
|
||||||
|
// This is the canonical example of a "presheaf" - a functor from a small
|
||||||
|
// category (the "walking arrow" • → •) to Set.
|
||||||
|
|
||||||
|
theory Graph {
|
||||||
|
V : Sort; // Vertices
|
||||||
|
E : Sort; // Edges
|
||||||
|
|
||||||
|
src : E -> V; // Source of an edge
|
||||||
|
tgt : E -> V; // Target of an edge
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple triangle graph: A → B → C → A
|
||||||
|
instance Triangle : Graph = {
|
||||||
|
// Vertices
|
||||||
|
A : V;
|
||||||
|
B : V;
|
||||||
|
C : V;
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
ab : E;
|
||||||
|
bc : E;
|
||||||
|
ca : E;
|
||||||
|
|
||||||
|
// Edge endpoints
|
||||||
|
ab src = A;
|
||||||
|
ab tgt = B;
|
||||||
|
bc src = B;
|
||||||
|
bc tgt = C;
|
||||||
|
ca src = C;
|
||||||
|
ca tgt = A;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A self-loop: one vertex with an edge to itself
|
||||||
|
instance Loop : Graph = {
|
||||||
|
v : V;
|
||||||
|
e : E;
|
||||||
|
e src = v;
|
||||||
|
e tgt = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "walking arrow": two vertices, one edge
|
||||||
|
instance Arrow : Graph = {
|
||||||
|
s : V;
|
||||||
|
t : V;
|
||||||
|
f : E;
|
||||||
|
f src = s;
|
||||||
|
f tgt = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A more complex graph: diamond shape with two paths from top to bottom
|
||||||
|
//
|
||||||
|
// top
|
||||||
|
// / \
|
||||||
|
// left right
|
||||||
|
// \ /
|
||||||
|
// bottom
|
||||||
|
//
|
||||||
|
instance Diamond : Graph = {
|
||||||
|
top : V;
|
||||||
|
left : V;
|
||||||
|
right : V;
|
||||||
|
bottom : V;
|
||||||
|
|
||||||
|
top_left : E;
|
||||||
|
top_right : E;
|
||||||
|
left_bottom : E;
|
||||||
|
right_bottom : E;
|
||||||
|
|
||||||
|
top_left src = top;
|
||||||
|
top_left tgt = left;
|
||||||
|
top_right src = top;
|
||||||
|
top_right tgt = right;
|
||||||
|
left_bottom src = left;
|
||||||
|
left_bottom tgt = bottom;
|
||||||
|
right_bottom src = right;
|
||||||
|
right_bottom tgt = bottom;
|
||||||
|
}
|
||||||
29
examples/geolog/iso_instance_test.geolog
Normal file
29
examples/geolog/iso_instance_test.geolog
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Multi-parameter theory instantiation test
|
||||||
|
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
fb : forall x : X. |- x fwd bwd = x;
|
||||||
|
bf : forall y : Y. |- y bwd fwd = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory A { a : Sort; }
|
||||||
|
theory B { b : Sort; }
|
||||||
|
|
||||||
|
instance As : A = {
|
||||||
|
a1 : a;
|
||||||
|
a2 : a;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Bs : B = {
|
||||||
|
b1 : b;
|
||||||
|
b2 : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can we create an Iso instance with sort parameters?
|
||||||
|
instance AB_Iso : As/a Bs/b Iso = {
|
||||||
|
a1 fwd = Bs/b1;
|
||||||
|
a2 fwd = Bs/b2;
|
||||||
|
b1 bwd = As/a1;
|
||||||
|
b2 bwd = As/a2;
|
||||||
|
}
|
||||||
9
examples/geolog/iso_theory_test.geolog
Normal file
9
examples/geolog/iso_theory_test.geolog
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Multi-parameter theory test (Iso from vision)
|
||||||
|
|
||||||
|
// First just try sorts as parameters
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
// Axioms would need chained function application...
|
||||||
|
// fb : forall x : X. |- x fwd bwd = x;
|
||||||
|
}
|
||||||
78
examples/geolog/monoid.geolog
Normal file
78
examples/geolog/monoid.geolog
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Monoid: a set with an associative binary operation and identity element
|
||||||
|
//
|
||||||
|
// This is the simplest algebraic structure with interesting axioms.
|
||||||
|
// Note: geolog uses postfix function application.
|
||||||
|
|
||||||
|
theory Monoid {
|
||||||
|
M : Sort;
|
||||||
|
|
||||||
|
// Binary operation: M × M → M
|
||||||
|
mul : [x: M, y: M] -> M;
|
||||||
|
|
||||||
|
// Identity element: we use a unary function from M to M that
|
||||||
|
// "picks out" the identity (any x maps to e)
|
||||||
|
// A cleaner approach would use Unit → M but that needs product support.
|
||||||
|
id : M -> M;
|
||||||
|
|
||||||
|
// Left identity: id(x) * y = y (id(x) is always e)
|
||||||
|
ax/left_id : forall x : M, y : M.
|
||||||
|
|- [x: x id, y: y] mul = y;
|
||||||
|
|
||||||
|
// Right identity: x * id(y) = x
|
||||||
|
ax/right_id : forall x : M, y : M.
|
||||||
|
|- [x: x, y: y id] mul = x;
|
||||||
|
|
||||||
|
// Associativity: (x * y) * z = x * (y * z)
|
||||||
|
ax/assoc : forall x : M, y : M, z : M.
|
||||||
|
|- [x: [x: x, y: y] mul, y: z] mul = [x: x, y: [x: y, y: z] mul] mul;
|
||||||
|
|
||||||
|
// id is constant: id(x) = id(y) for all x, y
|
||||||
|
ax/id_const : forall x : M, y : M.
|
||||||
|
|- x id = y id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trivial monoid: single element, e * e = e
|
||||||
|
instance Trivial : Monoid = {
|
||||||
|
e : M;
|
||||||
|
|
||||||
|
// Multiplication table: e * e = e
|
||||||
|
// Using positional syntax: [a, b] maps to [x: a, y: b]
|
||||||
|
[e, e] mul = e;
|
||||||
|
|
||||||
|
// Identity: e is the identity element
|
||||||
|
e id = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean "And" monoid: {T, F} with T as identity
|
||||||
|
// T and T = T, T and F = F, F and T = F, F and F = F
|
||||||
|
instance BoolAnd : Monoid = {
|
||||||
|
T : M;
|
||||||
|
F : M;
|
||||||
|
|
||||||
|
// Identity: T is the identity element
|
||||||
|
T id = T;
|
||||||
|
F id = T;
|
||||||
|
|
||||||
|
// Multiplication table for "and":
|
||||||
|
[x: T, y: T] mul = T;
|
||||||
|
[x: T, y: F] mul = F;
|
||||||
|
[x: F, y: T] mul = F;
|
||||||
|
[x: F, y: F] mul = F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean "Or" monoid: {T, F} with F as identity
|
||||||
|
// T or T = T, T or F = T, F or T = T, F or F = F
|
||||||
|
instance BoolOr : Monoid = {
|
||||||
|
T : M;
|
||||||
|
F : M;
|
||||||
|
|
||||||
|
// Identity: F is the identity element
|
||||||
|
T id = F;
|
||||||
|
F id = F;
|
||||||
|
|
||||||
|
// Multiplication table for "or":
|
||||||
|
[x: T, y: T] mul = T;
|
||||||
|
[x: T, y: F] mul = T;
|
||||||
|
[x: F, y: T] mul = T;
|
||||||
|
[x: F, y: F] mul = F;
|
||||||
|
}
|
||||||
33
examples/geolog/nested_instance_test.geolog
Normal file
33
examples/geolog/nested_instance_test.geolog
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Test: Nested instance declarations (following vision pattern)
|
||||||
|
|
||||||
|
theory Place {
|
||||||
|
P : Sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (Pl : Place instance) Token {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> Pl/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (Pl : Place instance) Problem {
|
||||||
|
initial_marking : Pl Token instance;
|
||||||
|
target_marking : Pl Token instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a place instance
|
||||||
|
instance MyPlaces : Place = {
|
||||||
|
p1 : P;
|
||||||
|
p2 : P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nested instance declarations
|
||||||
|
instance TestProblem : MyPlaces Problem = {
|
||||||
|
initial_marking = {
|
||||||
|
t1 : token;
|
||||||
|
t1 token/of = MyPlaces/p1;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
t2 : token;
|
||||||
|
t2 token/of = MyPlaces/p2;
|
||||||
|
};
|
||||||
|
}
|
||||||
135
examples/geolog/petri_net.geolog
Normal file
135
examples/geolog/petri_net.geolog
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Petri Net: a bipartite graph between places and transitions
|
||||||
|
//
|
||||||
|
// Petri nets model concurrent systems. Places hold tokens, transitions
|
||||||
|
// fire when their input places have tokens, consuming inputs and
|
||||||
|
// producing outputs.
|
||||||
|
//
|
||||||
|
// This encoding uses explicit "arc" sorts for input/output connections,
|
||||||
|
// which is more faithful to the categorical semantics (a span).
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort; // Places
|
||||||
|
T : Sort; // Transitions
|
||||||
|
In : Sort; // Input arcs (from place to transition)
|
||||||
|
Out : Sort; // Output arcs (from transition to place)
|
||||||
|
|
||||||
|
// Input arc endpoints
|
||||||
|
in/place : In -> P;
|
||||||
|
in/trans : In -> T;
|
||||||
|
|
||||||
|
// Output arc endpoints
|
||||||
|
out/trans : Out -> T;
|
||||||
|
out/place : Out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple producer-consumer net:
|
||||||
|
//
|
||||||
|
// (ready) --[produce]--> (buffer) --[consume]--> (done)
|
||||||
|
//
|
||||||
|
instance ProducerConsumer : PetriNet = {
|
||||||
|
// Places
|
||||||
|
ready : P;
|
||||||
|
buffer : P;
|
||||||
|
done : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
produce : T;
|
||||||
|
consume : T;
|
||||||
|
|
||||||
|
// Input arcs
|
||||||
|
i1 : In;
|
||||||
|
i1 in/place = ready;
|
||||||
|
i1 in/trans = produce;
|
||||||
|
|
||||||
|
i2 : In;
|
||||||
|
i2 in/place = buffer;
|
||||||
|
i2 in/trans = consume;
|
||||||
|
|
||||||
|
// Output arcs
|
||||||
|
o1 : Out;
|
||||||
|
o1 out/trans = produce;
|
||||||
|
o1 out/place = buffer;
|
||||||
|
|
||||||
|
o2 : Out;
|
||||||
|
o2 out/trans = consume;
|
||||||
|
o2 out/place = done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutual exclusion: two processes competing for a shared resource
|
||||||
|
//
|
||||||
|
// (idle1) --[enter1]--> (crit1) --[exit1]--> (idle1)
|
||||||
|
// ^ |
|
||||||
|
// | (mutex) |
|
||||||
|
// | v
|
||||||
|
// (idle2) --[enter2]--> (crit2) --[exit2]--> (idle2)
|
||||||
|
//
|
||||||
|
instance MutualExclusion : PetriNet = {
|
||||||
|
// Places for process 1
|
||||||
|
idle1 : P;
|
||||||
|
crit1 : P;
|
||||||
|
|
||||||
|
// Places for process 2
|
||||||
|
idle2 : P;
|
||||||
|
crit2 : P;
|
||||||
|
|
||||||
|
// Shared mutex token
|
||||||
|
mutex : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
enter1 : T;
|
||||||
|
exit1 : T;
|
||||||
|
enter2 : T;
|
||||||
|
exit2 : T;
|
||||||
|
|
||||||
|
// Process 1 enters: needs idle1 AND mutex
|
||||||
|
i_enter1_idle : In;
|
||||||
|
i_enter1_idle in/place = idle1;
|
||||||
|
i_enter1_idle in/trans = enter1;
|
||||||
|
|
||||||
|
i_enter1_mutex : In;
|
||||||
|
i_enter1_mutex in/place = mutex;
|
||||||
|
i_enter1_mutex in/trans = enter1;
|
||||||
|
|
||||||
|
o_enter1 : Out;
|
||||||
|
o_enter1 out/trans = enter1;
|
||||||
|
o_enter1 out/place = crit1;
|
||||||
|
|
||||||
|
// Process 1 exits: releases mutex
|
||||||
|
i_exit1 : In;
|
||||||
|
i_exit1 in/place = crit1;
|
||||||
|
i_exit1 in/trans = exit1;
|
||||||
|
|
||||||
|
o_exit1_idle : Out;
|
||||||
|
o_exit1_idle out/trans = exit1;
|
||||||
|
o_exit1_idle out/place = idle1;
|
||||||
|
|
||||||
|
o_exit1_mutex : Out;
|
||||||
|
o_exit1_mutex out/trans = exit1;
|
||||||
|
o_exit1_mutex out/place = mutex;
|
||||||
|
|
||||||
|
// Process 2 enters: needs idle2 AND mutex
|
||||||
|
i_enter2_idle : In;
|
||||||
|
i_enter2_idle in/place = idle2;
|
||||||
|
i_enter2_idle in/trans = enter2;
|
||||||
|
|
||||||
|
i_enter2_mutex : In;
|
||||||
|
i_enter2_mutex in/place = mutex;
|
||||||
|
i_enter2_mutex in/trans = enter2;
|
||||||
|
|
||||||
|
o_enter2 : Out;
|
||||||
|
o_enter2 out/trans = enter2;
|
||||||
|
o_enter2 out/place = crit2;
|
||||||
|
|
||||||
|
// Process 2 exits: releases mutex
|
||||||
|
i_exit2 : In;
|
||||||
|
i_exit2 in/place = crit2;
|
||||||
|
i_exit2 in/trans = exit2;
|
||||||
|
|
||||||
|
o_exit2_idle : Out;
|
||||||
|
o_exit2_idle out/trans = exit2;
|
||||||
|
o_exit2_idle out/place = idle2;
|
||||||
|
|
||||||
|
o_exit2_mutex : Out;
|
||||||
|
o_exit2_mutex out/trans = exit2;
|
||||||
|
o_exit2_mutex out/place = mutex;
|
||||||
|
}
|
||||||
195
examples/geolog/petri_net_full.geolog
Normal file
195
examples/geolog/petri_net_full.geolog
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// Full Petri Net Reachability - Type-Theoretic Encoding
|
||||||
|
//
|
||||||
|
// This demonstrates the complete type-theoretic encoding of Petri net
|
||||||
|
// reachability from the original geolog design vision.
|
||||||
|
//
|
||||||
|
// Original design: loose_thoughts/2025-12-12_12:10_VanillaPetriNetRechability.md
|
||||||
|
//
|
||||||
|
// Key concepts:
|
||||||
|
// - PetriNet: places, transitions, input/output arcs (with proper arc semantics)
|
||||||
|
// - Marking: tokens in a net (parameterized by net)
|
||||||
|
// - ReachabilityProblem: initial and target markings (nested instances)
|
||||||
|
// - Trace: sequence of firings with wires connecting arcs
|
||||||
|
// - Iso: isomorphism between two sorts (used for bijections)
|
||||||
|
// - Solution: a trace with isomorphisms to markings
|
||||||
|
//
|
||||||
|
// This encoding is more type-theoretically precise than the simple
|
||||||
|
// PlaceReachability: it tracks individual tokens and arc multiplicities,
|
||||||
|
// enabling correct handling of "multi-token" transitions.
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: PetriNet
|
||||||
|
// Basic structure: places, transitions, and arcs
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort; // Places
|
||||||
|
T : Sort; // Transitions
|
||||||
|
|
||||||
|
in : Sort; // Input arcs (place -> transition)
|
||||||
|
out : Sort; // Output arcs (transition -> place)
|
||||||
|
|
||||||
|
// Each arc knows which place/transition it connects
|
||||||
|
in/src : in -> P; // Input arc source place
|
||||||
|
in/tgt : in -> T; // Input arc target transition
|
||||||
|
out/src : out -> T; // Output arc source transition
|
||||||
|
out/tgt : out -> P; // Output arc target place
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Marking (parameterized by N : PetriNet)
|
||||||
|
// A marking assigns tokens to places
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: ReachabilityProblem (parameterized by N : PetriNet)
|
||||||
|
// Defines initial and target markings as nested instances
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) ReachabilityProblem {
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
target_marking : N Marking instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Trace (parameterized by N : PetriNet)
|
||||||
|
// A trace is a sequence of transition firings with "wires"
|
||||||
|
// connecting input and output arcs
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
// Firings
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
// Wires connect output of one firing to input of another
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [firing : F, arc : N/out]; // Wire source (firing, output arc)
|
||||||
|
W/tgt : W -> [firing : F, arc : N/in]; // Wire target (firing, input arc)
|
||||||
|
|
||||||
|
// Wire coherence: output arc must belong to source firing's transition
|
||||||
|
ax1 : forall w : W. |- w W/src .arc N/out/src = w W/src .firing F/of;
|
||||||
|
// Wire coherence: input arc must belong to target firing's transition
|
||||||
|
ax2 : forall w : W. |- w W/tgt .arc N/in/tgt = w W/tgt .firing F/of;
|
||||||
|
|
||||||
|
// Wire uniqueness: each (firing, out-arc) pair has at most one wire
|
||||||
|
ax3 : forall w1, w2 : W. w1 W/src = w2 W/src |- w1 = w2;
|
||||||
|
// Wire uniqueness: each (firing, in-arc) pair has at most one wire
|
||||||
|
ax4 : forall w1, w2 : W. w1 W/tgt = w2 W/tgt |- w1 = w2;
|
||||||
|
|
||||||
|
// Terminals: for initial marking tokens (input) and final marking tokens (output)
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
|
||||||
|
// Every output arc of every firing must be wired OR be an output terminal
|
||||||
|
ax5 : forall f : F, arc : N/out. arc N/out/src = f F/of |-
|
||||||
|
(exists w : W. w W/src = [firing: f, arc: arc]) \/
|
||||||
|
(exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc]);
|
||||||
|
|
||||||
|
// Every input arc of every firing must be wired OR be an input terminal
|
||||||
|
ax6 : forall f : F, arc : N/in. arc N/in/tgt = f F/of |-
|
||||||
|
(exists w : W. w W/tgt = [firing: f, arc: arc]) \/
|
||||||
|
(exists i : input_terminal. i input_terminal/tgt = [firing: f, arc: arc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Iso (parameterized by two sorts)
|
||||||
|
// An isomorphism between two sorts
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
fb : forall x : X. |- x fwd bwd = x;
|
||||||
|
bf : forall y : Y. |- y bwd fwd = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Solution (parameterized by N and RP)
|
||||||
|
// A solution to a reachability problem
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) (RP : N ReachabilityProblem instance) Solution {
|
||||||
|
// The witnessing trace
|
||||||
|
trace : N Trace instance;
|
||||||
|
|
||||||
|
// Bijection between input terminals and initial marking tokens
|
||||||
|
initial_marking_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
|
||||||
|
|
||||||
|
// Bijection between output terminals and target marking tokens
|
||||||
|
target_marking_iso : (trace/output_terminal) (RP/target_marking/token) Iso instance;
|
||||||
|
|
||||||
|
// Initial marking commutes: token placement matches terminal placement
|
||||||
|
initial_marking_P_comm : forall i : trace/input_terminal.
|
||||||
|
|- i trace/input_terminal/of = i initial_marking_iso/fwd RP/initial_marking/token/of;
|
||||||
|
|
||||||
|
// Target marking commutes: token placement matches terminal placement
|
||||||
|
target_marking_P_comm : forall o : trace/output_terminal.
|
||||||
|
|- o trace/output_terminal/of = o target_marking_iso/fwd RP/target_marking/token/of;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: ExampleNet - a small Petri net
|
||||||
|
//
|
||||||
|
// (A) --[ab]--> (B) --[bc]--> (C)
|
||||||
|
// ^ |
|
||||||
|
// +---[ba]------+
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance ExampleNet : PetriNet = {
|
||||||
|
// Places
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
C : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
ab : T;
|
||||||
|
ba : T;
|
||||||
|
bc : T;
|
||||||
|
|
||||||
|
// A -> B (via ab)
|
||||||
|
ab_in : in;
|
||||||
|
ab_in in/src = A;
|
||||||
|
ab_in in/tgt = ab;
|
||||||
|
ab_out : out;
|
||||||
|
ab_out out/src = ab;
|
||||||
|
ab_out out/tgt = B;
|
||||||
|
|
||||||
|
// B -> A (via ba)
|
||||||
|
ba_in : in;
|
||||||
|
ba_in in/src = B;
|
||||||
|
ba_in in/tgt = ba;
|
||||||
|
ba_out : out;
|
||||||
|
ba_out out/src = ba;
|
||||||
|
ba_out out/tgt = A;
|
||||||
|
|
||||||
|
// B -> C (via bc)
|
||||||
|
bc_in : in;
|
||||||
|
bc_in in/src = B;
|
||||||
|
bc_in in/tgt = bc;
|
||||||
|
bc_out : out;
|
||||||
|
bc_out out/src = bc;
|
||||||
|
bc_out out/tgt = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Example queries (once query solving is implemented):
|
||||||
|
//
|
||||||
|
// query can_reach_B_from_A {
|
||||||
|
// ? : ExampleNet problem0 Solution instance;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// where problem0 : ExampleNet ReachabilityProblem = {
|
||||||
|
// initial_marking = { tok : token; tok token/of = ExampleNet/A; };
|
||||||
|
// target_marking = { tok : token; tok token/of = ExampleNet/B; };
|
||||||
|
// }
|
||||||
|
// ============================================================
|
||||||
345
examples/geolog/petri_net_showcase.geolog
Normal file
345
examples/geolog/petri_net_showcase.geolog
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
// Petri Net Reachability - Full Type-Theoretic Encoding
|
||||||
|
//
|
||||||
|
// This showcase demonstrates geolog's core capabilities through a
|
||||||
|
// non-trivial domain: encoding Petri net reachability as dependent types.
|
||||||
|
//
|
||||||
|
// A solution to a reachability problem is NOT a yes/no boolean but a
|
||||||
|
// CONSTRUCTIVE WITNESS: a diagrammatic proof that tokens can flow from
|
||||||
|
// initial to target markings via a sequence of transition firings.
|
||||||
|
//
|
||||||
|
// Key concepts demonstrated:
|
||||||
|
// - Parameterized theories (Marking depends on PetriNet instance)
|
||||||
|
// - Nested instance types (ReachabilityProblem contains Marking instances)
|
||||||
|
// - Sort-parameterized theories (Iso takes two sorts as parameters)
|
||||||
|
// - Cross-instance references (solution's trace elements reference problem's tokens)
|
||||||
|
//
|
||||||
|
// Original design: loose_thoughts/2025-12-12_12:10_VanillaPetriNetRechability.md
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: PetriNet
|
||||||
|
// Places, transitions, and arcs with proper arc semantics
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort; // Places
|
||||||
|
T : Sort; // Transitions
|
||||||
|
in : Sort; // Input arcs (place -> transition)
|
||||||
|
out : Sort; // Output arcs (transition -> place)
|
||||||
|
|
||||||
|
in/src : in -> P; // Input arc source place
|
||||||
|
in/tgt : in -> T; // Input arc target transition
|
||||||
|
out/src : out -> T; // Output arc source transition
|
||||||
|
out/tgt : out -> P; // Output arc target place
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Marking (parameterized by N : PetriNet)
|
||||||
|
// A marking assigns tokens to places
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: ReachabilityProblem (parameterized by N : PetriNet)
|
||||||
|
// Initial and target markings as nested instances
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) ReachabilityProblem {
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
target_marking : N Marking instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Trace (parameterized by N : PetriNet)
|
||||||
|
// A trace records transition firings and token flow via wires
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// A trace is a diagrammatic proof of reachability:
|
||||||
|
// - Firings represent transition occurrences
|
||||||
|
// - Wires connect output arcs of firings to input arcs of other firings
|
||||||
|
// - Terminals connect to the initial/target markings
|
||||||
|
//
|
||||||
|
// The completeness axiom (ax/must_be_fed) ensures every input arc
|
||||||
|
// of every firing is accounted for - either wired from another firing
|
||||||
|
// or fed by an input terminal.
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
// Firings
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
// Wires connect output arcs of firings to input arcs of other firings
|
||||||
|
W : Sort;
|
||||||
|
W/src_firing : W -> F;
|
||||||
|
W/src_arc : W -> N/out;
|
||||||
|
W/tgt_firing : W -> F;
|
||||||
|
W/tgt_arc : W -> N/in;
|
||||||
|
|
||||||
|
// Wire coherence: source arc must belong to source firing's transition
|
||||||
|
ax/wire_src_coherent : forall w : W.
|
||||||
|
|- w W/src_arc N/out/src = w W/src_firing F/of;
|
||||||
|
|
||||||
|
// Wire coherence: target arc must belong to target firing's transition
|
||||||
|
ax/wire_tgt_coherent : forall w : W.
|
||||||
|
|- w W/tgt_arc N/in/tgt = w W/tgt_firing F/of;
|
||||||
|
|
||||||
|
// Wire place coherence: wire connects matching places
|
||||||
|
ax/wire_place_coherent : forall w : W.
|
||||||
|
|- w W/src_arc N/out/tgt = w W/tgt_arc N/in/src;
|
||||||
|
|
||||||
|
// Terminals
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
|
||||||
|
// Terminals connect to specific firings and arcs
|
||||||
|
input_terminal/tgt_firing : input_terminal -> F;
|
||||||
|
input_terminal/tgt_arc : input_terminal -> N/in;
|
||||||
|
output_terminal/src_firing : output_terminal -> F;
|
||||||
|
output_terminal/src_arc : output_terminal -> N/out;
|
||||||
|
|
||||||
|
// Terminal coherence axioms
|
||||||
|
ax/input_terminal_coherent : forall i : input_terminal.
|
||||||
|
|- i input_terminal/tgt_arc N/in/tgt = i input_terminal/tgt_firing F/of;
|
||||||
|
|
||||||
|
ax/output_terminal_coherent : forall o : output_terminal.
|
||||||
|
|- o output_terminal/src_arc N/out/src = o output_terminal/src_firing F/of;
|
||||||
|
|
||||||
|
// Terminal place coherence
|
||||||
|
ax/input_terminal_place : forall i : input_terminal.
|
||||||
|
|- i input_terminal/of = i input_terminal/tgt_arc N/in/src;
|
||||||
|
|
||||||
|
ax/output_terminal_place : forall o : output_terminal.
|
||||||
|
|- o output_terminal/of = o output_terminal/src_arc N/out/tgt;
|
||||||
|
|
||||||
|
// COMPLETENESS: Every arc of every firing must be accounted for.
|
||||||
|
|
||||||
|
// Input completeness: every input arc must be fed by a wire or input terminal
|
||||||
|
ax/input_complete : forall f : F, arc : N/in.
|
||||||
|
arc N/in/tgt = f F/of |-
|
||||||
|
(exists w : W. w W/tgt_firing = f, w W/tgt_arc = arc) \/
|
||||||
|
(exists i : input_terminal. i input_terminal/tgt_firing = f, i input_terminal/tgt_arc = arc);
|
||||||
|
|
||||||
|
// Output completeness: every output arc must be captured by a wire or output terminal
|
||||||
|
ax/output_complete : forall f : F, arc : N/out.
|
||||||
|
arc N/out/src = f F/of |-
|
||||||
|
(exists w : W. w W/src_firing = f, w W/src_arc = arc) \/
|
||||||
|
(exists o : output_terminal. o output_terminal/src_firing = f, o output_terminal/src_arc = arc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Iso (parameterized by two sorts)
|
||||||
|
// Isomorphism (bijection) between two sorts
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
|
||||||
|
// Roundtrip axioms ensure this is a true bijection
|
||||||
|
fb : forall x : X. |- x fwd bwd = x;
|
||||||
|
bf : forall y : Y. |- y bwd fwd = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Solution (parameterized by N and RP)
|
||||||
|
// A constructive witness that target is reachable from initial
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) (RP : N ReachabilityProblem instance) Solution {
|
||||||
|
trace : N Trace instance;
|
||||||
|
|
||||||
|
// Bijection: input terminals <-> initial marking tokens
|
||||||
|
initial_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
|
||||||
|
|
||||||
|
// Bijection: output terminals <-> target marking tokens
|
||||||
|
target_iso : (trace/output_terminal) (RP/target_marking/token) Iso instance;
|
||||||
|
|
||||||
|
// Commutativity axioms (currently unchecked):
|
||||||
|
// ax/init_comm : forall i : trace/input_terminal.
|
||||||
|
// |- i trace/input_terminal/of = i initial_iso/fwd RP/initial_marking/token/of;
|
||||||
|
// ax/target_comm : forall o : trace/output_terminal.
|
||||||
|
// |- o trace/output_terminal/of = o target_iso/fwd RP/target_marking/token/of;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: ExampleNet
|
||||||
|
//
|
||||||
|
// A Petri net with places A, B, C and transitions:
|
||||||
|
// ab: consumes 1 token from A, produces 1 token in B
|
||||||
|
// ba: consumes 1 token from B, produces 1 token in A
|
||||||
|
// abc: consumes 1 token from A AND 1 from B, produces 1 token in C
|
||||||
|
//
|
||||||
|
// +---[ba]----+
|
||||||
|
// v |
|
||||||
|
// (A) --[ab]->(B) --+
|
||||||
|
// | |
|
||||||
|
// +----[abc]-------+--> (C)
|
||||||
|
//
|
||||||
|
// The abc transition is interesting: it requires BOTH an A-token
|
||||||
|
// and a B-token to fire, producing a C-token.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance ExampleNet : PetriNet = {
|
||||||
|
A : P; B : P; C : P;
|
||||||
|
ab : T; ba : T; abc : T;
|
||||||
|
|
||||||
|
// A -> B (via ab)
|
||||||
|
ab_in : in; ab_in in/src = A; ab_in in/tgt = ab;
|
||||||
|
ab_out : out; ab_out out/src = ab; ab_out out/tgt = B;
|
||||||
|
|
||||||
|
// B -> A (via ba)
|
||||||
|
ba_in : in; ba_in in/src = B; ba_in in/tgt = ba;
|
||||||
|
ba_out : out; ba_out out/src = ba; ba_out out/tgt = A;
|
||||||
|
|
||||||
|
// A + B -> C (via abc) - note: two input arcs!
|
||||||
|
abc_in1 : in; abc_in1 in/src = A; abc_in1 in/tgt = abc;
|
||||||
|
abc_in2 : in; abc_in2 in/src = B; abc_in2 in/tgt = abc;
|
||||||
|
abc_out : out; abc_out out/src = abc; abc_out out/tgt = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PROBLEM 0: Can we reach B from A with one token?
|
||||||
|
// Initial: 1 token in A
|
||||||
|
// Target: 1 token in B
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance problem0 : ExampleNet ReachabilityProblem = {
|
||||||
|
initial_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/B;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SOLUTION 0: Yes! Fire transition 'ab' once.
|
||||||
|
//
|
||||||
|
// This Solution instance is a CONSTRUCTIVE PROOF:
|
||||||
|
// - The trace contains one firing (f1) of transition 'ab'
|
||||||
|
// - The input terminal feeds the A-token into f1's input arc
|
||||||
|
// - The output terminal captures f1's B-token output
|
||||||
|
// - The isomorphisms prove the token counts match exactly
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance solution0 : ExampleNet problem0 Solution = {
|
||||||
|
trace = {
|
||||||
|
// One firing of transition 'ab'
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = ExampleNet/ab;
|
||||||
|
|
||||||
|
// Input terminal: feeds the initial A-token into f1
|
||||||
|
it : input_terminal;
|
||||||
|
it input_terminal/of = ExampleNet/A;
|
||||||
|
it input_terminal/tgt_firing = f1;
|
||||||
|
it input_terminal/tgt_arc = ExampleNet/ab_in;
|
||||||
|
|
||||||
|
// Output terminal: captures f1's B-token output
|
||||||
|
ot : output_terminal;
|
||||||
|
ot output_terminal/of = ExampleNet/B;
|
||||||
|
ot output_terminal/src_firing = f1;
|
||||||
|
ot output_terminal/src_arc = ExampleNet/ab_out;
|
||||||
|
};
|
||||||
|
|
||||||
|
initial_iso = {
|
||||||
|
trace/it fwd = problem0/initial_marking/tok;
|
||||||
|
problem0/initial_marking/tok bwd = trace/it;
|
||||||
|
};
|
||||||
|
|
||||||
|
target_iso = {
|
||||||
|
trace/ot fwd = problem0/target_marking/tok;
|
||||||
|
problem0/target_marking/tok bwd = trace/ot;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PROBLEM 2: Can we reach C from two A-tokens?
|
||||||
|
// Initial: 2 tokens in A
|
||||||
|
// Target: 1 token in C
|
||||||
|
//
|
||||||
|
// This is interesting because the only path to C is via 'abc',
|
||||||
|
// which requires tokens in BOTH A and B simultaneously.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance problem2 : ExampleNet ReachabilityProblem = {
|
||||||
|
initial_marking = {
|
||||||
|
t1 : token; t1 token/of = ExampleNet/A;
|
||||||
|
t2 : token; t2 token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
t : token;
|
||||||
|
t token/of = ExampleNet/C;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SOLUTION 2: Yes! Fire 'ab' then 'abc'.
|
||||||
|
//
|
||||||
|
// Token flow diagram:
|
||||||
|
//
|
||||||
|
// [it1]--A-->[f1: ab]--B--wire-->[f2: abc]--C-->[ot]
|
||||||
|
// [it2]--A-----------------^
|
||||||
|
//
|
||||||
|
// Step 1: Fire 'ab' to move one token A -> B
|
||||||
|
// - it1 feeds A-token into f1 via ab_in
|
||||||
|
// - f1 produces B-token via ab_out
|
||||||
|
// Step 2: Fire 'abc' consuming one A-token and one B-token
|
||||||
|
// - it2 feeds A-token into f2 via abc_in1
|
||||||
|
// - Wire connects f1's ab_out to f2's abc_in2 (the B-input)
|
||||||
|
// - f2 produces C-token via abc_out
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance solution2 : ExampleNet problem2 Solution = {
|
||||||
|
trace = {
|
||||||
|
// Two firings
|
||||||
|
f1 : F; f1 F/of = ExampleNet/ab; // First: A -> B
|
||||||
|
f2 : F; f2 F/of = ExampleNet/abc; // Second: A + B -> C
|
||||||
|
|
||||||
|
// Wire connecting f1's B-output to f2's B-input
|
||||||
|
// This is the crucial connection that makes the trace valid!
|
||||||
|
w1 : W;
|
||||||
|
w1 W/src_firing = f1;
|
||||||
|
w1 W/src_arc = ExampleNet/ab_out;
|
||||||
|
w1 W/tgt_firing = f2;
|
||||||
|
w1 W/tgt_arc = ExampleNet/abc_in2;
|
||||||
|
|
||||||
|
// Input terminal 1: feeds first A-token into f1
|
||||||
|
it1 : input_terminal;
|
||||||
|
it1 input_terminal/of = ExampleNet/A;
|
||||||
|
it1 input_terminal/tgt_firing = f1;
|
||||||
|
it1 input_terminal/tgt_arc = ExampleNet/ab_in;
|
||||||
|
|
||||||
|
// Input terminal 2: feeds second A-token into f2
|
||||||
|
it2 : input_terminal;
|
||||||
|
it2 input_terminal/of = ExampleNet/A;
|
||||||
|
it2 input_terminal/tgt_firing = f2;
|
||||||
|
it2 input_terminal/tgt_arc = ExampleNet/abc_in1;
|
||||||
|
|
||||||
|
// Output terminal: captures f2's C-token output
|
||||||
|
ot : output_terminal;
|
||||||
|
ot output_terminal/of = ExampleNet/C;
|
||||||
|
ot output_terminal/src_firing = f2;
|
||||||
|
ot output_terminal/src_arc = ExampleNet/abc_out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bijection: 2 input terminals <-> 2 initial tokens
|
||||||
|
initial_iso = {
|
||||||
|
trace/it1 fwd = problem2/initial_marking/t1;
|
||||||
|
trace/it2 fwd = problem2/initial_marking/t2;
|
||||||
|
problem2/initial_marking/t1 bwd = trace/it1;
|
||||||
|
problem2/initial_marking/t2 bwd = trace/it2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bijection: 1 output terminal <-> 1 target token
|
||||||
|
target_iso = {
|
||||||
|
trace/ot fwd = problem2/target_marking/t;
|
||||||
|
problem2/target_marking/t bwd = trace/ot;
|
||||||
|
};
|
||||||
|
}
|
||||||
188
examples/geolog/petri_net_solution.geolog
Normal file
188
examples/geolog/petri_net_solution.geolog
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// Full Petri Net Reachability with Synthesized Solution
|
||||||
|
//
|
||||||
|
// This file contains the complete type-theoretic encoding of Petri net
|
||||||
|
// reachability, plus a manually synthesized solution proving that place B
|
||||||
|
// is reachable from place A in the example net.
|
||||||
|
//
|
||||||
|
// ============================================================
|
||||||
|
// This instance was synthesized automatically by Claude Opus 4.5.
|
||||||
|
// As was this entire file, and this entire project, really.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: PetriNet - Basic structure with arc semantics
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort; // Places
|
||||||
|
T : Sort; // Transitions
|
||||||
|
in : Sort; // Input arcs (place -> transition)
|
||||||
|
out : Sort; // Output arcs (transition -> place)
|
||||||
|
|
||||||
|
in/src : in -> P; // Input arc source place
|
||||||
|
in/tgt : in -> T; // Input arc target transition
|
||||||
|
out/src : out -> T; // Output arc source transition
|
||||||
|
out/tgt : out -> P; // Output arc target place
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Marking - Tokens parameterized by a net
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P; // Which place each token is in
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: ReachabilityProblem - Initial and target markings
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) ReachabilityProblem {
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
target_marking : N Marking instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Trace - A sequence of transition firings with wires
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Simplified version for now - full version with product types commented out below.
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
F : Sort; // Firings
|
||||||
|
F/of : F -> N/T; // Which transition each fires
|
||||||
|
|
||||||
|
// Terminals for initial/final marking tokens
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full Trace theory with wires and product types (not yet fully supported):
|
||||||
|
//
|
||||||
|
// theory (N : PetriNet instance) Trace {
|
||||||
|
// F : Sort; // Firings
|
||||||
|
// F/of : F -> N/T; // Which transition each fires
|
||||||
|
//
|
||||||
|
// W : Sort; // Wires connecting firings
|
||||||
|
// W/src : W -> [firing : F, arc : N/out]; // Wire source
|
||||||
|
// W/tgt : W -> [firing : F, arc : N/in]; // Wire target
|
||||||
|
//
|
||||||
|
// // Wire coherence axioms
|
||||||
|
// ax1 : forall w : W. |- w W/src .arc N/out/src = w W/src .firing F/of;
|
||||||
|
// ax2 : forall w : W. |- w W/tgt .arc N/in/tgt = w W/tgt .firing F/of;
|
||||||
|
//
|
||||||
|
// // Wire uniqueness
|
||||||
|
// ax3 : forall w1, w2 : W. w1 W/src = w2 W/src |- w1 = w2;
|
||||||
|
// ax4 : forall w1, w2 : W. w1 W/tgt = w2 W/tgt |- w1 = w2;
|
||||||
|
//
|
||||||
|
// // Terminals for initial/final marking tokens
|
||||||
|
// input_terminal : Sort;
|
||||||
|
// output_terminal : Sort;
|
||||||
|
// input_terminal/of : input_terminal -> N/P;
|
||||||
|
// output_terminal/of : output_terminal -> N/P;
|
||||||
|
// input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
// output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
//
|
||||||
|
// // Coverage axioms
|
||||||
|
// ax5 : forall f : F, arc : N/out. arc N/out/src = f F/of |-
|
||||||
|
// (exists w : W. w W/src = [firing: f, arc: arc]) \/
|
||||||
|
// (exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc]);
|
||||||
|
// ax6 : forall f : F, arc : N/in. arc N/in/tgt = f F/of |-
|
||||||
|
// (exists w : W. w W/tgt = [firing: f, arc: arc]) \/
|
||||||
|
// (exists i : input_terminal. i input_terminal/tgt = [firing: f, arc: arc]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Iso - Isomorphism between two sorts
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (X : Sort) (Y : Sort) Iso {
|
||||||
|
fwd : X -> Y;
|
||||||
|
bwd : Y -> X;
|
||||||
|
fb : forall x : X. |- x fwd bwd = x;
|
||||||
|
bf : forall y : Y. |- y bwd fwd = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Solution - A complete reachability witness
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) (RP : N ReachabilityProblem instance) Solution {
|
||||||
|
trace : N Trace instance;
|
||||||
|
|
||||||
|
initial_iso : (trace/input_terminal) (RP/initial_marking/token) Iso instance;
|
||||||
|
target_iso : (trace/output_terminal) (RP/target_marking/token) Iso instance;
|
||||||
|
|
||||||
|
ax/init_comm : forall i : trace/input_terminal.
|
||||||
|
|- i trace/input_terminal/of = i initial_iso/fwd RP/initial_marking/token/of;
|
||||||
|
ax/target_comm : forall o : trace/output_terminal.
|
||||||
|
|- o trace/output_terminal/of = o target_iso/fwd RP/target_marking/token/of;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: ExampleNet - A small Petri net
|
||||||
|
//
|
||||||
|
// (A) --[ab]--> (B) --[bc]--> (C)
|
||||||
|
// ^ |
|
||||||
|
// +---[ba]------+
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance ExampleNet : PetriNet = {
|
||||||
|
A : P; B : P; C : P;
|
||||||
|
ab : T; ba : T; bc : T;
|
||||||
|
|
||||||
|
ab_in : in; ab_in in/src = A; ab_in in/tgt = ab;
|
||||||
|
ab_out : out; ab_out out/src = ab; ab_out out/tgt = B;
|
||||||
|
|
||||||
|
ba_in : in; ba_in in/src = B; ba_in in/tgt = ba;
|
||||||
|
ba_out : out; ba_out out/src = ba; ba_out out/tgt = A;
|
||||||
|
|
||||||
|
bc_in : in; bc_in in/src = B; bc_in in/tgt = bc;
|
||||||
|
bc_out : out; bc_out out/src = bc; bc_out out/tgt = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: problem0 - Can we reach B from A?
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
instance problem0 : ExampleNet ReachabilityProblem = {
|
||||||
|
initial_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/B;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: solution0 - YES! Here's the proof.
|
||||||
|
// ============================================================
|
||||||
|
// This instance was synthesized automatically by Claude Opus 4.5.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// The solution proves that place B is reachable from place A by firing
|
||||||
|
// transition ab. This creates a trace with one firing and the necessary
|
||||||
|
// input/output terminal mappings.
|
||||||
|
|
||||||
|
instance solution0 : ExampleNet problem0 Solution = {
|
||||||
|
trace = {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = ExampleNet/ab;
|
||||||
|
|
||||||
|
it : input_terminal;
|
||||||
|
it input_terminal/of = ExampleNet/A;
|
||||||
|
|
||||||
|
ot : output_terminal;
|
||||||
|
ot output_terminal/of = ExampleNet/B;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: Cross-instance references (e.g., trace/it in initial_iso)
|
||||||
|
// are not yet fully supported. The iso instances would map:
|
||||||
|
// - trace/it <-> problem0/initial_marking/tok
|
||||||
|
// - trace/ot <-> problem0/target_marking/tok
|
||||||
|
}
|
||||||
164
examples/geolog/petri_reachability.geolog
Normal file
164
examples/geolog/petri_reachability.geolog
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Petri Net Reachability - Full Example
|
||||||
|
//
|
||||||
|
// This demonstrates the core ideas from the original geolog design document:
|
||||||
|
// modeling Petri net reachability using geometric logic with the chase algorithm.
|
||||||
|
//
|
||||||
|
// Original design: loose_thoughts/2025-12-12_12:10.md
|
||||||
|
//
|
||||||
|
// Key concepts:
|
||||||
|
// - PetriNet: places, transitions, input/output arcs
|
||||||
|
// - Marking: assignment of tokens to places (parameterized theory)
|
||||||
|
// - Trace: sequence of transition firings connecting markings
|
||||||
|
// - Reachability: computed via chase algorithm
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: PetriNet
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort; // Places
|
||||||
|
T : Sort; // Transitions
|
||||||
|
In : Sort; // Input arcs (place -> transition)
|
||||||
|
Out : Sort; // Output arcs (transition -> place)
|
||||||
|
|
||||||
|
// Arc structure
|
||||||
|
in/place : In -> P;
|
||||||
|
in/trans : In -> T;
|
||||||
|
out/trans : Out -> T;
|
||||||
|
out/place : Out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: Marking (parameterized)
|
||||||
|
// A marking assigns tokens to places in a specific net
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
Token : Sort;
|
||||||
|
of : Token -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// THEORY: PlaceReachability
|
||||||
|
// Simplified reachability at the place level
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
theory PlaceReachability {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
|
||||||
|
// Which transition connects which places
|
||||||
|
// Fires(t, from, to) means transition t can move a token from 'from' to 'to'
|
||||||
|
Fires : [trans: T, from: P, to: P] -> Prop;
|
||||||
|
|
||||||
|
// Reachability relation (transitive closure)
|
||||||
|
CanReach : [from: P, to: P] -> Prop;
|
||||||
|
|
||||||
|
// Reflexivity: every place can reach itself
|
||||||
|
ax/refl : forall p : P.
|
||||||
|
|- [from: p, to: p] CanReach;
|
||||||
|
|
||||||
|
// Transition firing creates reachability
|
||||||
|
ax/fire : forall t : T, x : P, y : P.
|
||||||
|
[trans: t, from: x, to: y] Fires |- [from: x, to: y] CanReach;
|
||||||
|
|
||||||
|
// Transitivity: reachability composes
|
||||||
|
ax/trans : forall x : P, y : P, z : P.
|
||||||
|
[from: x, to: y] CanReach, [from: y, to: z] CanReach |- [from: x, to: z] CanReach;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: SimpleNet
|
||||||
|
// A -> B -> C with bidirectional A <-> B
|
||||||
|
//
|
||||||
|
// (A) <--[ba]-- (B) --[bc]--> (C)
|
||||||
|
// | ^
|
||||||
|
// +---[ab]------+
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Uses chase to derive CanReach from axioms (reflexivity, fire, transitivity)
|
||||||
|
instance SimpleNet : PlaceReachability = chase {
|
||||||
|
// Places
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
C : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
ab : T; // A -> B
|
||||||
|
ba : T; // B -> A
|
||||||
|
bc : T; // B -> C
|
||||||
|
|
||||||
|
// Firing relations
|
||||||
|
[trans: ab, from: A, to: B] Fires;
|
||||||
|
[trans: ba, from: B, to: A] Fires;
|
||||||
|
[trans: bc, from: B, to: C] Fires;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: MutexNet
|
||||||
|
// Two processes competing for a mutex
|
||||||
|
//
|
||||||
|
// idle1 --[enter1]--> crit1 --[exit1]--> idle1
|
||||||
|
// ^ |
|
||||||
|
// | mutex |
|
||||||
|
// | v
|
||||||
|
// idle2 --[enter2]--> crit2 --[exit2]--> idle2
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Uses chase to derive reachability relation
|
||||||
|
instance MutexNet : PlaceReachability = chase {
|
||||||
|
// Places
|
||||||
|
idle1 : P;
|
||||||
|
crit1 : P;
|
||||||
|
idle2 : P;
|
||||||
|
crit2 : P;
|
||||||
|
mutex : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
enter1 : T;
|
||||||
|
exit1 : T;
|
||||||
|
enter2 : T;
|
||||||
|
exit2 : T;
|
||||||
|
|
||||||
|
// Process 1 acquires mutex: idle1 + mutex -> crit1
|
||||||
|
// (simplified: we track place-level, not token-level)
|
||||||
|
[trans: enter1, from: idle1, to: crit1] Fires;
|
||||||
|
[trans: enter1, from: mutex, to: crit1] Fires;
|
||||||
|
|
||||||
|
// Process 1 releases mutex: crit1 -> idle1 + mutex
|
||||||
|
[trans: exit1, from: crit1, to: idle1] Fires;
|
||||||
|
[trans: exit1, from: crit1, to: mutex] Fires;
|
||||||
|
|
||||||
|
// Process 2 acquires mutex: idle2 + mutex -> crit2
|
||||||
|
[trans: enter2, from: idle2, to: crit2] Fires;
|
||||||
|
[trans: enter2, from: mutex, to: crit2] Fires;
|
||||||
|
|
||||||
|
// Process 2 releases mutex: crit2 -> idle2 + mutex
|
||||||
|
[trans: exit2, from: crit2, to: idle2] Fires;
|
||||||
|
[trans: exit2, from: crit2, to: mutex] Fires;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// INSTANCE: ProducerConsumerNet
|
||||||
|
// Producer creates items, consumer processes them
|
||||||
|
//
|
||||||
|
// ready --[produce]--> buffer --[consume]--> done
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Uses chase to derive reachability relation
|
||||||
|
instance ProducerConsumerNet : PlaceReachability = chase {
|
||||||
|
// Places
|
||||||
|
ready : P;
|
||||||
|
buffer : P;
|
||||||
|
done : P;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
produce : T;
|
||||||
|
consume : T;
|
||||||
|
|
||||||
|
// Produce: ready -> buffer
|
||||||
|
[trans: produce, from: ready, to: buffer] Fires;
|
||||||
|
|
||||||
|
// Consume: buffer -> done
|
||||||
|
[trans: consume, from: buffer, to: done] Fires;
|
||||||
|
}
|
||||||
72
examples/geolog/petri_reachability_full_vision.geolog
Normal file
72
examples/geolog/petri_reachability_full_vision.geolog
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Full Petri Net Reachability Vision Test
|
||||||
|
// From 2025-12-12_12:10_VanillaPetriNetRechability.md
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
in : Sort;
|
||||||
|
out : Sort;
|
||||||
|
in/src : in -> P;
|
||||||
|
in/tgt : in -> T;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) ReachabilityProblem {
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
target_marking : N Marking instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified Trace theory without disjunctions for now
|
||||||
|
theory (N : PetriNet instance) SimpleTrace {
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
|
||||||
|
// Simplified ax5: every firing+arc gets an output terminal
|
||||||
|
ax5 : forall f : F, arc : N/out. |- exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc];
|
||||||
|
|
||||||
|
// Simplified ax6: every firing+arc gets an input terminal
|
||||||
|
ax6 : forall f : F, arc : N/in. |- exists i : input_terminal. i input_terminal/tgt = [firing: f, arc: arc];
|
||||||
|
}
|
||||||
|
|
||||||
|
instance ExampleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
ab : T;
|
||||||
|
ab_in : in;
|
||||||
|
ab_in in/src = A;
|
||||||
|
ab_in in/tgt = ab;
|
||||||
|
ab_out : out;
|
||||||
|
ab_out out/src = ab;
|
||||||
|
ab_out out/tgt = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nested instance elaboration
|
||||||
|
instance problem0 : ExampleNet ReachabilityProblem = {
|
||||||
|
initial_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
tok : token;
|
||||||
|
tok token/of = ExampleNet/B;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test chase with SimpleTrace
|
||||||
|
instance trace0 : ExampleNet SimpleTrace = chase {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = ExampleNet/ab;
|
||||||
|
}
|
||||||
94
examples/geolog/petri_reachability_vision.geolog
Normal file
94
examples/geolog/petri_reachability_vision.geolog
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Petri Net Reachability Vision Test
|
||||||
|
// Based on 2025-12-12 design document
|
||||||
|
|
||||||
|
// Basic Petri net structure
|
||||||
|
theory PetriNet {
|
||||||
|
// Places
|
||||||
|
P : Sort;
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
T : Sort;
|
||||||
|
|
||||||
|
// Arcs (input to transitions, output from transitions)
|
||||||
|
in : Sort;
|
||||||
|
out : Sort;
|
||||||
|
in/src : in -> P;
|
||||||
|
in/tgt : in -> T;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A marking is a multiset of tokens, each at a place
|
||||||
|
theory (N : PetriNet instance) Marking {
|
||||||
|
token : Sort;
|
||||||
|
token/of : token -> N/P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reachability problem is: can we get from initial marking to target?
|
||||||
|
theory (N : PetriNet instance) ReachabilityProblem {
|
||||||
|
initial_marking : N Marking instance;
|
||||||
|
target_marking : N Marking instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A trace is a sequence of firings connected by wires
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
// Firings of transitions
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
// Wires connect firing outputs to firing inputs
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [firing : F, arc : N/out];
|
||||||
|
W/tgt : W -> [firing : F, arc : N/in];
|
||||||
|
|
||||||
|
// Terminals are unconnected arc endpoints (to/from the initial/target markings)
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Petri net: A <--ab/ba--> B, (A,B) --abc--> C
|
||||||
|
instance ExampleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
C : P;
|
||||||
|
ab : T;
|
||||||
|
ba : T;
|
||||||
|
abc : T;
|
||||||
|
ab_in : in;
|
||||||
|
ab_in in/src = A;
|
||||||
|
ab_in in/tgt = ab;
|
||||||
|
ab_out : out;
|
||||||
|
ab_out out/src = ab;
|
||||||
|
ab_out out/tgt = B;
|
||||||
|
ba_in : in;
|
||||||
|
ba_in in/src = B;
|
||||||
|
ba_in in/tgt = ba;
|
||||||
|
ba_out : out;
|
||||||
|
ba_out out/src = ba;
|
||||||
|
ba_out out/tgt = A;
|
||||||
|
abc_in1 : in;
|
||||||
|
abc_in1 in/src = A;
|
||||||
|
abc_in1 in/tgt = abc;
|
||||||
|
abc_in2 : in;
|
||||||
|
abc_in2 in/src = B;
|
||||||
|
abc_in2 in/tgt = abc;
|
||||||
|
abc_out : out;
|
||||||
|
abc_out out/src = abc;
|
||||||
|
abc_out out/tgt = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reachability problem: Can we reach B from A?
|
||||||
|
instance problem0 : ExampleNet ReachabilityProblem = {
|
||||||
|
initial_marking = {
|
||||||
|
t : token;
|
||||||
|
t token/of = ExampleNet/A;
|
||||||
|
};
|
||||||
|
target_marking = {
|
||||||
|
t : token;
|
||||||
|
t token/of = ExampleNet/B;
|
||||||
|
};
|
||||||
|
}
|
||||||
66
examples/geolog/petri_trace_axioms.geolog
Normal file
66
examples/geolog/petri_trace_axioms.geolog
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Test Trace theory with axioms using product codomains
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
in : Sort;
|
||||||
|
out : Sort;
|
||||||
|
in/src : in -> P;
|
||||||
|
in/tgt : in -> T;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace theory with axioms
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [firing : F, arc : N/out];
|
||||||
|
W/tgt : W -> [firing : F, arc : N/in];
|
||||||
|
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
|
||||||
|
// Axiom: wires are injective on source
|
||||||
|
// forall w1, w2 : W. w1 W/src = w2 W/src |- w1 = w2;
|
||||||
|
// (Commented out - requires product codomain equality in premises)
|
||||||
|
|
||||||
|
// Axiom: every arc endpoint must be wired or terminated
|
||||||
|
// forall f : F, arc : N/out. arc N/out/src = f F/of |-
|
||||||
|
// (exists w : W. w W/src = [firing: f, arc: arc]) \/
|
||||||
|
// (exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc]);
|
||||||
|
// (Commented out - requires product codomain values in conclusions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple net for testing
|
||||||
|
instance SimpleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
t : T;
|
||||||
|
arc_in : in;
|
||||||
|
arc_in in/src = A;
|
||||||
|
arc_in in/tgt = t;
|
||||||
|
arc_out : out;
|
||||||
|
arc_out out/src = t;
|
||||||
|
arc_out out/tgt = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the basic theory without axioms still works
|
||||||
|
instance SimpleTrace : SimpleNet Trace = {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = SimpleNet/t;
|
||||||
|
|
||||||
|
it : input_terminal;
|
||||||
|
it input_terminal/of = SimpleNet/A;
|
||||||
|
it input_terminal/tgt = [firing: f1, arc: SimpleNet/arc_in];
|
||||||
|
|
||||||
|
ot : output_terminal;
|
||||||
|
ot output_terminal/of = SimpleNet/B;
|
||||||
|
ot output_terminal/src = [firing: f1, arc: SimpleNet/arc_out];
|
||||||
|
}
|
||||||
36
examples/geolog/petri_trace_coverage_test.geolog
Normal file
36
examples/geolog/petri_trace_coverage_test.geolog
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Test: Trace coverage axiom (simplified)
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
out : Sort;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
output_terminal : Sort;
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
|
||||||
|
// Simplified ax5: for every arc and firing, if the arc's source is the firing's transition,
|
||||||
|
// create an output terminal
|
||||||
|
ax5 : forall f : F, arc : N/out. |- exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc];
|
||||||
|
}
|
||||||
|
|
||||||
|
instance SimpleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
t : T;
|
||||||
|
arc_out : out;
|
||||||
|
arc_out out/src = t;
|
||||||
|
arc_out out/tgt = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace with just a firing - chase should create a terminal
|
||||||
|
instance TestTrace : SimpleNet Trace = chase {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = SimpleNet/t;
|
||||||
|
}
|
||||||
57
examples/geolog/petri_trace_full_vision.geolog
Normal file
57
examples/geolog/petri_trace_full_vision.geolog
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Trace theory with wires and disjunctions
|
||||||
|
// Testing the full vision from 2025-12-12
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
in : Sort;
|
||||||
|
out : Sort;
|
||||||
|
in/src : in -> P;
|
||||||
|
in/tgt : in -> T;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [firing : F, arc : N/out];
|
||||||
|
W/tgt : W -> [firing : F, arc : N/in];
|
||||||
|
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
|
||||||
|
// Every out arc of every firing: either wired or terminal
|
||||||
|
ax5 : forall f : F, arc : N/out. |-
|
||||||
|
(exists w : W. w W/src = [firing: f, arc: arc]) \/
|
||||||
|
(exists o : output_terminal. o output_terminal/src = [firing: f, arc: arc]);
|
||||||
|
|
||||||
|
// Every in arc of every firing: either wired or terminal
|
||||||
|
ax6 : forall f : F, arc : N/in. |-
|
||||||
|
(exists w : W. w W/tgt = [firing: f, arc: arc]) \/
|
||||||
|
(exists i : input_terminal. i input_terminal/tgt = [firing: f, arc: arc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance SimpleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
t : T;
|
||||||
|
arc_in : in;
|
||||||
|
arc_in in/src = A;
|
||||||
|
arc_in in/tgt = t;
|
||||||
|
arc_out : out;
|
||||||
|
arc_out out/src = t;
|
||||||
|
arc_out out/tgt = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chase should create both wires AND terminals (naive chase adds all disjuncts)
|
||||||
|
instance trace_test : SimpleNet Trace = chase {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = SimpleNet/t;
|
||||||
|
}
|
||||||
58
examples/geolog/petri_trace_test.geolog
Normal file
58
examples/geolog/petri_trace_test.geolog
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Test that Trace theory with product codomains works
|
||||||
|
|
||||||
|
theory PetriNet {
|
||||||
|
P : Sort;
|
||||||
|
T : Sort;
|
||||||
|
in : Sort;
|
||||||
|
out : Sort;
|
||||||
|
in/src : in -> P;
|
||||||
|
in/tgt : in -> T;
|
||||||
|
out/src : out -> T;
|
||||||
|
out/tgt : out -> P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple Petri net: A --t--> B
|
||||||
|
instance SimpleNet : PetriNet = {
|
||||||
|
A : P;
|
||||||
|
B : P;
|
||||||
|
t : T;
|
||||||
|
arc_in : in;
|
||||||
|
arc_in in/src = A;
|
||||||
|
arc_in in/tgt = t;
|
||||||
|
arc_out : out;
|
||||||
|
arc_out out/src = t;
|
||||||
|
arc_out out/tgt = B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace theory with product codomains for wire endpoints
|
||||||
|
theory (N : PetriNet instance) Trace {
|
||||||
|
F : Sort;
|
||||||
|
F/of : F -> N/T;
|
||||||
|
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [firing : F, arc : N/out];
|
||||||
|
W/tgt : W -> [firing : F, arc : N/in];
|
||||||
|
|
||||||
|
input_terminal : Sort;
|
||||||
|
output_terminal : Sort;
|
||||||
|
input_terminal/of : input_terminal -> N/P;
|
||||||
|
output_terminal/of : output_terminal -> N/P;
|
||||||
|
input_terminal/tgt : input_terminal -> [firing : F, arc : N/in];
|
||||||
|
output_terminal/src : output_terminal -> [firing : F, arc : N/out];
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple trace: one firing of t, with input/output terminals
|
||||||
|
instance SimpleTrace : SimpleNet Trace = {
|
||||||
|
f1 : F;
|
||||||
|
f1 F/of = SimpleNet/t;
|
||||||
|
|
||||||
|
// Input terminal (token comes from external marking)
|
||||||
|
it : input_terminal;
|
||||||
|
it input_terminal/of = SimpleNet/A;
|
||||||
|
it input_terminal/tgt = [firing: f1, arc: SimpleNet/arc_in];
|
||||||
|
|
||||||
|
// Output terminal (token goes to external marking)
|
||||||
|
ot : output_terminal;
|
||||||
|
ot output_terminal/of = SimpleNet/B;
|
||||||
|
ot output_terminal/src = [firing: f1, arc: SimpleNet/arc_out];
|
||||||
|
}
|
||||||
42
examples/geolog/preorder.geolog
Normal file
42
examples/geolog/preorder.geolog
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Preorder: a set with a reflexive, transitive relation
|
||||||
|
//
|
||||||
|
// This demonstrates RELATIONS (predicates) as opposed to functions.
|
||||||
|
// A relation R : A -> Prop is a predicate on A.
|
||||||
|
// For binary relations, we use a product domain: R : [x: A, y: A] -> Prop
|
||||||
|
|
||||||
|
theory Preorder {
|
||||||
|
X : Sort;
|
||||||
|
|
||||||
|
// The ordering relation: x ≤ y
|
||||||
|
leq : [x: X, y: X] -> Prop;
|
||||||
|
|
||||||
|
// Reflexivity: x ≤ x
|
||||||
|
ax/refl : forall x : X.
|
||||||
|
|- [x: x, y: x] leq;
|
||||||
|
|
||||||
|
// Transitivity: x ≤ y ∧ y ≤ z → x ≤ z
|
||||||
|
ax/trans : forall x : X, y : X, z : X.
|
||||||
|
[x: x, y: y] leq, [x: y, y: z] leq |- [x: x, y: z] leq;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The discrete preorder: only reflexive pairs
|
||||||
|
// (no elements are comparable except to themselves)
|
||||||
|
// Uses `chase` to automatically derive reflexive pairs from axiom ax/refl.
|
||||||
|
instance Discrete3 : Preorder = chase {
|
||||||
|
a : X;
|
||||||
|
b : X;
|
||||||
|
c : X;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A total order on 3 elements: a ≤ b ≤ c
|
||||||
|
// Uses `chase` to derive reflexive and transitive closure.
|
||||||
|
instance Chain3 : Preorder = chase {
|
||||||
|
bot : X;
|
||||||
|
mid : X;
|
||||||
|
top : X;
|
||||||
|
|
||||||
|
// Assert the basic ordering; chase will add reflexive pairs
|
||||||
|
// and transitive closure (bot ≤ top)
|
||||||
|
[x: bot, y: mid] leq;
|
||||||
|
[x: mid, y: top] leq;
|
||||||
|
}
|
||||||
23
examples/geolog/product_codomain_equality_test.geolog
Normal file
23
examples/geolog/product_codomain_equality_test.geolog
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Test: Product codomain equality in premise (ax3 pattern)
|
||||||
|
|
||||||
|
theory ProductCodomainEqTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
W : Sort;
|
||||||
|
W/src : W -> [x: A, y: B];
|
||||||
|
|
||||||
|
// ax3 pattern: forall w1, w2 : W. w1 W/src = w2 W/src |- w1 = w2
|
||||||
|
// This should make W injective on src
|
||||||
|
ax_inj : forall w1 : W, w2 : W. w1 W/src = w2 W/src |- w1 = w2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance with two wires that have the same src - should be identified by chase
|
||||||
|
instance Test : ProductCodomainEqTest = {
|
||||||
|
a1 : A;
|
||||||
|
b1 : B;
|
||||||
|
w1 : W;
|
||||||
|
w1 W/src = [x: a1, y: b1];
|
||||||
|
w2 : W;
|
||||||
|
w2 W/src = [x: a1, y: b1];
|
||||||
|
}
|
||||||
51
examples/geolog/product_codomain_test.geolog
Normal file
51
examples/geolog/product_codomain_test.geolog
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Test: Product Codomain Support
|
||||||
|
//
|
||||||
|
// This tests the new feature where functions can have product codomains,
|
||||||
|
// allowing record literal assignments like:
|
||||||
|
// elem func = [field1: v1, field2: v2];
|
||||||
|
|
||||||
|
theory ProductCodomainTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
C : Sort;
|
||||||
|
|
||||||
|
// Function with product codomain: maps A elements to (B, C) pairs
|
||||||
|
pair_of : A -> [left: B, right: C];
|
||||||
|
}
|
||||||
|
|
||||||
|
instance TestInstance : ProductCodomainTest = {
|
||||||
|
// Elements
|
||||||
|
a1 : A;
|
||||||
|
b1 : B;
|
||||||
|
b2 : B;
|
||||||
|
c1 : C;
|
||||||
|
|
||||||
|
// Assign product codomain value using record literal
|
||||||
|
a1 pair_of = [left: b1, right: c1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// A more realistic example: Edges in a graph
|
||||||
|
theory DirectedGraph {
|
||||||
|
V : Sort;
|
||||||
|
E : Sort;
|
||||||
|
|
||||||
|
// Edge endpoints as a product codomain
|
||||||
|
endpoints : E -> [src: V, tgt: V];
|
||||||
|
}
|
||||||
|
|
||||||
|
instance TriangleGraph : DirectedGraph = {
|
||||||
|
// Vertices
|
||||||
|
v0 : V;
|
||||||
|
v1 : V;
|
||||||
|
v2 : V;
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
e01 : E;
|
||||||
|
e12 : E;
|
||||||
|
e20 : E;
|
||||||
|
|
||||||
|
// Assign edge endpoints using record literals
|
||||||
|
e01 endpoints = [src: v0, tgt: v1];
|
||||||
|
e12 endpoints = [src: v1, tgt: v2];
|
||||||
|
e20 endpoints = [src: v2, tgt: v0];
|
||||||
|
}
|
||||||
18
examples/geolog/record_existential_test.geolog
Normal file
18
examples/geolog/record_existential_test.geolog
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Test: Record literals in existential conclusions
|
||||||
|
|
||||||
|
theory RecordExistentialTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
R : Sort;
|
||||||
|
R/data : R -> [x: A, y: B];
|
||||||
|
|
||||||
|
// Axiom: given any a:A and b:B, there exists an R with that data
|
||||||
|
ax1 : forall a : A, b : B. |-
|
||||||
|
exists r : R. r R/data = [x: a, y: b];
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Test : RecordExistentialTest = chase {
|
||||||
|
a1 : A;
|
||||||
|
b1 : B;
|
||||||
|
}
|
||||||
12
examples/geolog/record_in_axiom_test.geolog
Normal file
12
examples/geolog/record_in_axiom_test.geolog
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Test: Record literals in axioms
|
||||||
|
|
||||||
|
theory RecordAxiomTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
R : Sort;
|
||||||
|
R/data : R -> [x: A, y: B];
|
||||||
|
|
||||||
|
// Test axiom with record literal RHS
|
||||||
|
ax1 : forall r : R, a : A, b : B. r R/data = [x: a, y: b] |- true;
|
||||||
|
}
|
||||||
23
examples/geolog/record_premise_chase_test.geolog
Normal file
23
examples/geolog/record_premise_chase_test.geolog
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Test: Chase with record literals in premises
|
||||||
|
|
||||||
|
theory RecordPremiseTest {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
|
||||||
|
R : Sort;
|
||||||
|
R/data : R -> [x: A, y: B];
|
||||||
|
|
||||||
|
// Derived sort for processed items
|
||||||
|
Processed : Sort;
|
||||||
|
Processed/r : Processed -> R;
|
||||||
|
|
||||||
|
// Axiom: given r with data [x: a, y: b], create a Processed for it
|
||||||
|
ax1 : forall r : R, a : A, b : B. r R/data = [x: a, y: b] |- exists p : Processed. p Processed/r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Test : RecordPremiseTest = {
|
||||||
|
a1 : A;
|
||||||
|
b1 : B;
|
||||||
|
r1 : R;
|
||||||
|
r1 R/data = [x: a1, y: b1];
|
||||||
|
}
|
||||||
130
examples/geolog/relalg_simple.geolog
Normal file
130
examples/geolog/relalg_simple.geolog
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Example: RelAlgIR query plan instances
|
||||||
|
//
|
||||||
|
// This demonstrates creating query plans as RelAlgIR instances.
|
||||||
|
// These show the string diagram representation of relational algebra.
|
||||||
|
//
|
||||||
|
// First we need to load both GeologMeta (for Srt, Func, etc.) and RelAlgIR.
|
||||||
|
// This file just defines instances; load theories first in the REPL:
|
||||||
|
// :load theories/GeologMeta.geolog
|
||||||
|
// :load theories/RelAlgIR.geolog
|
||||||
|
// :load examples/geolog/relalg_simple.geolog
|
||||||
|
//
|
||||||
|
// Note: RelAlgIR extends GeologMeta, so a RelAlgIR instance contains
|
||||||
|
// elements from both GeologMeta sorts (Srt, Func, Elem) and RelAlgIR
|
||||||
|
// sorts (Wire, Schema, ScanOp, etc.)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Example 1: Simple Scan
|
||||||
|
// ============================================================
|
||||||
|
// Query: "scan all elements of sort V"
|
||||||
|
// Plan: () --[ScanOp]--> Wire
|
||||||
|
|
||||||
|
instance ScanV : RelAlgIR = chase {
|
||||||
|
// -- Schema (target theory) --
|
||||||
|
target_theory : GeologMeta/Theory;
|
||||||
|
target_theory GeologMeta/Theory/parent = target_theory;
|
||||||
|
|
||||||
|
v_srt : GeologMeta/Srt;
|
||||||
|
v_srt GeologMeta/Srt/theory = target_theory;
|
||||||
|
|
||||||
|
// -- Query Plan --
|
||||||
|
v_base_schema : BaseSchema;
|
||||||
|
v_base_schema BaseSchema/srt = v_srt;
|
||||||
|
|
||||||
|
v_schema : Schema;
|
||||||
|
v_base_schema BaseSchema/schema = v_schema;
|
||||||
|
|
||||||
|
scan_out : Wire;
|
||||||
|
scan_out Wire/schema = v_schema;
|
||||||
|
|
||||||
|
scan : ScanOp;
|
||||||
|
scan ScanOp/srt = v_srt;
|
||||||
|
scan ScanOp/out = scan_out;
|
||||||
|
|
||||||
|
scan_op : Op;
|
||||||
|
scan ScanOp/op = scan_op;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Example 2: Filter(Scan)
|
||||||
|
// ============================================================
|
||||||
|
// Query: "scan E, filter where src(e) = some vertex"
|
||||||
|
// Plan: () --[Scan]--> w1 --[Filter]--> w2
|
||||||
|
//
|
||||||
|
// This demonstrates composition via wire sharing.
|
||||||
|
|
||||||
|
// Uses chase to derive relations.
|
||||||
|
instance FilterScan : RelAlgIR = chase {
|
||||||
|
// -- Schema (representing Graph theory) --
|
||||||
|
target_theory : GeologMeta/Theory;
|
||||||
|
target_theory GeologMeta/Theory/parent = target_theory;
|
||||||
|
|
||||||
|
// Sorts: V (vertices), E (edges)
|
||||||
|
v_srt : GeologMeta/Srt;
|
||||||
|
v_srt GeologMeta/Srt/theory = target_theory;
|
||||||
|
|
||||||
|
e_srt : GeologMeta/Srt;
|
||||||
|
e_srt GeologMeta/Srt/theory = target_theory;
|
||||||
|
|
||||||
|
// Functions: src : E -> V
|
||||||
|
// First create the DSort wrappers
|
||||||
|
v_base_ds : GeologMeta/BaseDS;
|
||||||
|
v_base_ds GeologMeta/BaseDS/srt = v_srt;
|
||||||
|
|
||||||
|
e_base_ds : GeologMeta/BaseDS;
|
||||||
|
e_base_ds GeologMeta/BaseDS/srt = e_srt;
|
||||||
|
|
||||||
|
v_dsort : GeologMeta/DSort;
|
||||||
|
v_base_ds GeologMeta/BaseDS/dsort = v_dsort;
|
||||||
|
|
||||||
|
e_dsort : GeologMeta/DSort;
|
||||||
|
e_base_ds GeologMeta/BaseDS/dsort = e_dsort;
|
||||||
|
|
||||||
|
src_func : GeologMeta/Func;
|
||||||
|
src_func GeologMeta/Func/theory = target_theory;
|
||||||
|
src_func GeologMeta/Func/dom = e_dsort;
|
||||||
|
src_func GeologMeta/Func/cod = v_dsort;
|
||||||
|
|
||||||
|
// NOTE: For a complete example, we'd also need an Instance element
|
||||||
|
// and Elem elements. For simplicity, we use a simpler predicate structure.
|
||||||
|
|
||||||
|
// Using TruePred for now (matches all, demonstrating structure)
|
||||||
|
|
||||||
|
// -- Query Plan --
|
||||||
|
// Schema for E
|
||||||
|
e_base_schema : BaseSchema;
|
||||||
|
e_base_schema BaseSchema/srt = e_srt;
|
||||||
|
|
||||||
|
e_schema : Schema;
|
||||||
|
e_base_schema BaseSchema/schema = e_schema;
|
||||||
|
|
||||||
|
// Wire 1: output of Scan (E elements)
|
||||||
|
w1 : Wire;
|
||||||
|
w1 Wire/schema = e_schema;
|
||||||
|
|
||||||
|
// Wire 2: output of Filter (filtered E elements)
|
||||||
|
w2 : Wire;
|
||||||
|
w2 Wire/schema = e_schema;
|
||||||
|
|
||||||
|
// Scan operation
|
||||||
|
scan : ScanOp;
|
||||||
|
scan ScanOp/srt = e_srt;
|
||||||
|
scan ScanOp/out = w1;
|
||||||
|
|
||||||
|
scan_op : Op;
|
||||||
|
scan ScanOp/op = scan_op;
|
||||||
|
|
||||||
|
// Predicate: TruePred (matches all - demonstrates filter structure)
|
||||||
|
true_pred : TruePred;
|
||||||
|
pred_elem : Pred;
|
||||||
|
true_pred TruePred/pred = pred_elem;
|
||||||
|
|
||||||
|
// Filter operation: w1 --[Filter(pred)]--> w2
|
||||||
|
filter : FilterOp;
|
||||||
|
filter FilterOp/in = w1;
|
||||||
|
filter FilterOp/out = w2;
|
||||||
|
filter FilterOp/pred = pred_elem;
|
||||||
|
|
||||||
|
filter_op : Op;
|
||||||
|
filter FilterOp/op = filter_op;
|
||||||
|
}
|
||||||
132
examples/geolog/solver_demo.geolog
Normal file
132
examples/geolog/solver_demo.geolog
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Solver Demo: Theories demonstrating the geometric logic solver
|
||||||
|
//
|
||||||
|
// Use the :solve command to find instances of these theories:
|
||||||
|
// :source examples/geolog/solver_demo.geolog
|
||||||
|
// :solve EmptyModel
|
||||||
|
// :solve Inhabited
|
||||||
|
// :solve Inconsistent
|
||||||
|
//
|
||||||
|
// The solver uses forward chaining to automatically:
|
||||||
|
// - Add witness elements for existentials
|
||||||
|
// - Assert relation tuples
|
||||||
|
// - Detect unsatisfiability (derivation of False)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 1: EmptyModel - Trivially satisfiable with empty carrier
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// A theory with no axioms is satisfied by the empty structure.
|
||||||
|
// The solver should report SOLVED immediately with 0 elements.
|
||||||
|
|
||||||
|
theory EmptyModel {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
f : A -> B;
|
||||||
|
R : A -> Prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 2: UnconditionalExistential - Requires witness creation
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x : P. |- exists y : P. y R
|
||||||
|
//
|
||||||
|
// SUBTLE: This axiom's premise (True) and conclusion (∃y.R(y)) don't mention x!
|
||||||
|
// So even though there's a "forall x : P", the check happens once for an empty
|
||||||
|
// assignment. The premise True holds, but ∃y.R(y) doesn't hold for empty P
|
||||||
|
// (no witnesses). The solver correctly detects this and adds a witness.
|
||||||
|
//
|
||||||
|
// This is correct geometric logic semantics! The universal over x doesn't
|
||||||
|
// protect against empty P because x isn't used in the formulas.
|
||||||
|
|
||||||
|
theory UnconditionalExistential {
|
||||||
|
P : Sort;
|
||||||
|
R : P -> Prop;
|
||||||
|
|
||||||
|
// This effectively says "there must exist some y with R(y)"
|
||||||
|
// because x is unused - the check happens once regardless of |P|
|
||||||
|
ax : forall x : P. |- exists y : P. y R;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 3: VacuouslyTrue - Axiom that IS vacuously true for empty carriers
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x : P. |- x R
|
||||||
|
//
|
||||||
|
// For every x, assert R(x). When P is empty, there are no x values to check,
|
||||||
|
// so the axiom is vacuously satisfied. Compare with UnconditionalExistential!
|
||||||
|
|
||||||
|
theory VacuouslyTrue {
|
||||||
|
P : Sort;
|
||||||
|
R : P -> Prop;
|
||||||
|
|
||||||
|
// This truly IS vacuously true for empty P because x IS used in the conclusion
|
||||||
|
ax : forall x : P. |- x R;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 4: Inconsistent - UNSAT via derivation of False
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x. |- false
|
||||||
|
//
|
||||||
|
// For any element x, we derive False. This is immediately UNSAT.
|
||||||
|
// The solver detects this and reports UNSAT.
|
||||||
|
|
||||||
|
theory Inconsistent {
|
||||||
|
A : Sort;
|
||||||
|
|
||||||
|
// Contradiction: any element leads to False
|
||||||
|
ax : forall a : A. |- false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 5: ReflexiveRelation - Forward chaining asserts reflexive tuples
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x. |- R(x, x)
|
||||||
|
//
|
||||||
|
// For every element x, the pair (x, x) is in relation R.
|
||||||
|
// The solver will assert R(x, x) for each element added.
|
||||||
|
|
||||||
|
theory ReflexiveRelation {
|
||||||
|
X : Sort;
|
||||||
|
R : [a: X, b: X] -> Prop;
|
||||||
|
|
||||||
|
// Reflexivity: every element is related to itself
|
||||||
|
ax/refl : forall x : X. |- [a: x, b: x] R;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 6: ChainedWitness - Nested existential body processing
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x. |- exists y. exists z. E(x, y), E(y, z)
|
||||||
|
//
|
||||||
|
// For every x, there exist y and z such that E(x,y) and E(y,z).
|
||||||
|
// Forward chaining creates witnesses and asserts the relations.
|
||||||
|
|
||||||
|
theory ChainedWitness {
|
||||||
|
N : Sort;
|
||||||
|
E : [src: N, tgt: N] -> Prop;
|
||||||
|
|
||||||
|
// Chain: every node has a two-step path out
|
||||||
|
ax/chain : forall x : N. |- exists y : N. exists z : N. [src: x, tgt: y] E, [src: y, tgt: z] E;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theory 7: EqualityCollapse - Equation handling via congruence closure
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// Axiom: forall x, y. |- x = y
|
||||||
|
//
|
||||||
|
// All elements of sort X are equal. The solver adds equations to the
|
||||||
|
// congruence closure and merges equivalence classes.
|
||||||
|
|
||||||
|
theory EqualityCollapse {
|
||||||
|
X : Sort;
|
||||||
|
|
||||||
|
// All elements are equal
|
||||||
|
ax/all_equal : forall x : X, y : X. |- x = y;
|
||||||
|
}
|
||||||
31
examples/geolog/sort_param_simple.geolog
Normal file
31
examples/geolog/sort_param_simple.geolog
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Simpler sort parameter test
|
||||||
|
|
||||||
|
theory (X : Sort) Container {
|
||||||
|
elem : X; // not a Sort, but an element of X
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hmm, this doesn't quite work...
|
||||||
|
// Let me try the actual vision pattern
|
||||||
|
|
||||||
|
theory Base {
|
||||||
|
A : Sort;
|
||||||
|
B : Sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance MyBase : Base = {
|
||||||
|
a1 : A;
|
||||||
|
a2 : A;
|
||||||
|
b1 : B;
|
||||||
|
b2 : B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try a theory parameterized by an instance
|
||||||
|
theory (Inst : Base instance) Map {
|
||||||
|
map : Inst/A -> Inst/B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance of Map parameterized by MyBase
|
||||||
|
instance MyMap : MyBase Map = {
|
||||||
|
a1 map = MyBase/b1;
|
||||||
|
a2 map = MyBase/b2;
|
||||||
|
}
|
||||||
44
examples/geolog/todo_list.geolog
Normal file
44
examples/geolog/todo_list.geolog
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// TodoList: A simple relational model for tracking tasks
|
||||||
|
//
|
||||||
|
// This demonstrates geolog as a persistent relational database.
|
||||||
|
// Elements represent tasks, and relations track their status.
|
||||||
|
|
||||||
|
theory TodoList {
|
||||||
|
// The sort of todo items
|
||||||
|
Item : Sort;
|
||||||
|
|
||||||
|
// Unary relations for item status (simple arrow syntax)
|
||||||
|
completed : Item -> Prop;
|
||||||
|
high_priority : Item -> Prop;
|
||||||
|
blocked : Item -> Prop;
|
||||||
|
|
||||||
|
// Binary relation for dependencies
|
||||||
|
depends : [item: Item, on: Item] -> Prop;
|
||||||
|
|
||||||
|
// Axiom: if an item depends on another, either it is blocked
|
||||||
|
// or the dependency is completed
|
||||||
|
ax/dep_blocked : forall x : Item, y : Item.
|
||||||
|
[item: x, on: y] depends |- x blocked \/ y completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: An empty todo list ready for interactive use
|
||||||
|
instance MyTodos : TodoList = {
|
||||||
|
// Start empty - add items interactively with :add
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: A pre-populated todo list
|
||||||
|
instance SampleTodos : TodoList = {
|
||||||
|
// Items
|
||||||
|
buy_groceries : Item;
|
||||||
|
cook_dinner : Item;
|
||||||
|
do_laundry : Item;
|
||||||
|
clean_house : Item;
|
||||||
|
|
||||||
|
// Status: unary relations use simple syntax
|
||||||
|
buy_groceries completed;
|
||||||
|
cook_dinner high_priority;
|
||||||
|
|
||||||
|
// Dependencies: cook_dinner depends on buy_groceries
|
||||||
|
// Mixed syntax: first positional arg maps to 'item' field
|
||||||
|
[cook_dinner, on: buy_groceries] depends;
|
||||||
|
}
|
||||||
77
examples/geolog/transitive_closure.geolog
Normal file
77
examples/geolog/transitive_closure.geolog
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Transitive Closure Example
|
||||||
|
//
|
||||||
|
// This example demonstrates the chase algorithm computing transitive
|
||||||
|
// closure of a relation. We define a Graph theory with Edge and Path
|
||||||
|
// relations, where Path is the transitive closure of Edge.
|
||||||
|
//
|
||||||
|
// Run with:
|
||||||
|
// cargo run -- examples/geolog/transitive_closure.geolog
|
||||||
|
// Then:
|
||||||
|
// :source examples/geolog/transitive_closure.geolog
|
||||||
|
// :inspect Chain
|
||||||
|
// :chase Chain
|
||||||
|
//
|
||||||
|
// The chase will derive Path tuples for all reachable pairs:
|
||||||
|
// - Edge(a,b), Edge(b,c), Edge(c,d) as base facts
|
||||||
|
// - Path(a,b), Path(b,c), Path(c,d) from base axiom
|
||||||
|
// - Path(a,c), Path(b,d) from one step of transitivity
|
||||||
|
// - Path(a,d) from two steps of transitivity
|
||||||
|
|
||||||
|
theory Graph {
|
||||||
|
V : Sort;
|
||||||
|
|
||||||
|
// Direct edges in the graph
|
||||||
|
Edge : [src: V, tgt: V] -> Prop;
|
||||||
|
|
||||||
|
// Reachability (transitive closure of Edge)
|
||||||
|
Path : [src: V, tgt: V] -> Prop;
|
||||||
|
|
||||||
|
// Base case: every edge is a path
|
||||||
|
ax/base : forall x, y : V.
|
||||||
|
[src: x, tgt: y] Edge |- [src: x, tgt: y] Path;
|
||||||
|
|
||||||
|
// Inductive case: paths compose
|
||||||
|
ax/trans : forall x, y, z : V.
|
||||||
|
[src: x, tgt: y] Path, [src: y, tgt: z] Path |- [src: x, tgt: z] Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A linear chain: a -> b -> c -> d
|
||||||
|
// Chase derives Path tuples from Edge via ax/base and ax/trans.
|
||||||
|
instance Chain : Graph = chase {
|
||||||
|
a : V;
|
||||||
|
b : V;
|
||||||
|
c : V;
|
||||||
|
d : V;
|
||||||
|
|
||||||
|
// Edges form a chain
|
||||||
|
[src: a, tgt: b] Edge;
|
||||||
|
[src: b, tgt: c] Edge;
|
||||||
|
[src: c, tgt: d] Edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A diamond: a -> b, a -> c, b -> d, c -> d
|
||||||
|
// Chase derives all reachable paths.
|
||||||
|
instance Diamond : Graph = chase {
|
||||||
|
top : V;
|
||||||
|
left : V;
|
||||||
|
right : V;
|
||||||
|
bottom : V;
|
||||||
|
|
||||||
|
// Two paths from top to bottom
|
||||||
|
[src: top, tgt: left] Edge;
|
||||||
|
[src: top, tgt: right] Edge;
|
||||||
|
[src: left, tgt: bottom] Edge;
|
||||||
|
[src: right, tgt: bottom] Edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cycle: a -> b -> c -> a
|
||||||
|
// Chase derives all reachable paths (full connectivity).
|
||||||
|
instance Cycle : Graph = chase {
|
||||||
|
x : V;
|
||||||
|
y : V;
|
||||||
|
z : V;
|
||||||
|
|
||||||
|
[src: x, tgt: y] Edge;
|
||||||
|
[src: y, tgt: z] Edge;
|
||||||
|
[src: z, tgt: x] Edge;
|
||||||
|
}
|
||||||
13
examples/scripts/README.md
Normal file
13
examples/scripts/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Example Scripts
|
||||||
|
|
||||||
|
These scripts can be executed with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make script SCRIPT=examples/scripts/ancestor.chase
|
||||||
|
```
|
||||||
|
|
||||||
|
Available examples:
|
||||||
|
|
||||||
|
- `ancestor.chase`: transitive closure over `Parent/2`
|
||||||
|
- `employee_departments.chase`: existential rule that creates labeled nulls
|
||||||
|
- `same_team.chase`: conjunctive query with a self-join
|
||||||
11
examples/scripts/ancestor.chase
Normal file
11
examples/scripts/ancestor.chase
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Derive ancestors from parent relationships.
|
||||||
|
|
||||||
|
fact Parent(alice, bob).
|
||||||
|
fact Parent(bob, carol).
|
||||||
|
fact Parent(carol, dave).
|
||||||
|
|
||||||
|
rule Parent(?X, ?Y) -> Ancestor(?X, ?Y).
|
||||||
|
rule Ancestor(?X, ?Y), Parent(?Y, ?Z) -> Ancestor(?X, ?Z).
|
||||||
|
|
||||||
|
run.
|
||||||
|
query Ancestor(?X, ?Y)?
|
||||||
11
examples/scripts/employee_departments.chase
Normal file
11
examples/scripts/employee_departments.chase
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Existential rule example: each employee works in some department.
|
||||||
|
|
||||||
|
fact Employee(alice).
|
||||||
|
fact Employee(bob).
|
||||||
|
fact Employee(carol).
|
||||||
|
|
||||||
|
rule Employee(?X) -> WorksIn(?X, ?Dept).
|
||||||
|
|
||||||
|
run.
|
||||||
|
show facts
|
||||||
|
query WorksIn(?X, ?Dept)?
|
||||||
11
examples/scripts/same_team.chase
Normal file
11
examples/scripts/same_team.chase
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Query people who share a manager.
|
||||||
|
|
||||||
|
fact ManagedBy(alice, eve).
|
||||||
|
fact ManagedBy(bob, eve).
|
||||||
|
fact ManagedBy(carol, frank).
|
||||||
|
|
||||||
|
rule ManagedBy(?X, ?M), ManagedBy(?Y, ?M) -> SameTeam(?X, ?Y).
|
||||||
|
|
||||||
|
run.
|
||||||
|
query SameTeam(?X, ?Y)?
|
||||||
|
query SameTeam(alice, bob)?
|
||||||
133
src/catalog/mod.rs
Normal file
133
src/catalog/mod.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
//! Minimal catalog support for mapping predicates to relational schemas.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::chase::{Instance, Term};
|
||||||
|
use crate::relational::{DataType, Field, Schema};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CatalogError {
|
||||||
|
UnknownTable(String),
|
||||||
|
InconsistentArity {
|
||||||
|
table: String,
|
||||||
|
expected: usize,
|
||||||
|
found: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CatalogError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::UnknownTable(table) => write!(f, "unknown table `{}`", table),
|
||||||
|
Self::InconsistentArity {
|
||||||
|
table,
|
||||||
|
expected,
|
||||||
|
found,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"table `{}` has inconsistent arity: expected {}, found {}",
|
||||||
|
table, expected, found
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for CatalogError {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct PredicateCatalog {
|
||||||
|
schemas: HashMap<String, Schema>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PredicateCatalog {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_table(&mut self, table: impl Into<String>, schema: Schema) {
|
||||||
|
self.schemas.insert(table.into(), schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schema_for(&self, table: &str) -> Result<&Schema, CatalogError> {
|
||||||
|
self.schemas
|
||||||
|
.get(table)
|
||||||
|
.ok_or_else(|| CatalogError::UnknownTable(table.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_instance(instance: &Instance) -> Result<Self, CatalogError> {
|
||||||
|
let mut arities = HashMap::new();
|
||||||
|
let mut nullable_positions: HashMap<String, Vec<bool>> = HashMap::new();
|
||||||
|
|
||||||
|
for atom in instance.iter() {
|
||||||
|
let arity = atom.arity();
|
||||||
|
match arities.get(&atom.predicate) {
|
||||||
|
Some(expected) if *expected != arity => {
|
||||||
|
return Err(CatalogError::InconsistentArity {
|
||||||
|
table: atom.predicate.clone(),
|
||||||
|
expected: *expected,
|
||||||
|
found: arity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
None => {
|
||||||
|
arities.insert(atom.predicate.clone(), arity);
|
||||||
|
nullable_positions.insert(atom.predicate.clone(), vec![false; arity]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(positions) = nullable_positions.get_mut(&atom.predicate) {
|
||||||
|
for (index, term) in atom.terms.iter().enumerate() {
|
||||||
|
if matches!(term, Term::Null(_)) {
|
||||||
|
positions[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut catalog = Self::new();
|
||||||
|
for (table, arity) in arities {
|
||||||
|
let nullable = nullable_positions.remove(&table).unwrap_or_default();
|
||||||
|
let fields = (0..arity)
|
||||||
|
.map(|index| {
|
||||||
|
Field::new(
|
||||||
|
format!("c{}", index),
|
||||||
|
DataType::Text,
|
||||||
|
nullable.get(index).copied().unwrap_or(false),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
catalog.register_table(table, Schema::new(fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(catalog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::chase::Atom;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn infers_predicate_schemas_from_instance() {
|
||||||
|
let instance: Instance = vec![
|
||||||
|
Atom::new(
|
||||||
|
"Parent",
|
||||||
|
vec![Term::constant("alice"), Term::constant("bob")],
|
||||||
|
),
|
||||||
|
Atom::new("Parent", vec![Term::constant("bob"), Term::null(0)]),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let schema = catalog.schema_for("Parent").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(schema.len(), 2);
|
||||||
|
assert_eq!(schema.fields()[0].name(), "c0");
|
||||||
|
assert_eq!(schema.fields()[1].name(), "c1");
|
||||||
|
assert!(schema.fields()[1].nullable());
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/execution/mod.rs
Normal file
112
src/execution/mod.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
//! Minimal execution support for the first SQL slice.
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::chase::{Instance, Term};
|
||||||
|
use crate::planner::logical::{LogicalExpr, LogicalPlan};
|
||||||
|
use crate::relational::{ResultSet, Row, Value};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ExecutionError {
|
||||||
|
UnknownColumn(String),
|
||||||
|
NonGroundScanTerm,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExecutionError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::UnknownColumn(column) => write!(f, "unknown column `{}`", column),
|
||||||
|
Self::NonGroundScanTerm => {
|
||||||
|
write!(f, "cannot scan non-ground terms into relational rows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ExecutionError {}
|
||||||
|
|
||||||
|
pub fn execute(plan: &LogicalPlan, instance: &Instance) -> Result<ResultSet, ExecutionError> {
|
||||||
|
match plan {
|
||||||
|
LogicalPlan::Scan { table, schema } => {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for fact in instance.facts_for_predicate(table) {
|
||||||
|
let values = fact
|
||||||
|
.terms
|
||||||
|
.iter()
|
||||||
|
.map(value_from_term)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
rows.push(Row::new(values));
|
||||||
|
}
|
||||||
|
Ok(ResultSet::new(schema.clone(), rows))
|
||||||
|
}
|
||||||
|
LogicalPlan::Filter { input, predicate } => {
|
||||||
|
let result = execute(input, instance)?;
|
||||||
|
let filtered_rows = result
|
||||||
|
.rows()
|
||||||
|
.iter()
|
||||||
|
.filter(|row| eval_predicate(predicate, row, result.schema()).unwrap_or(false))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
Ok(ResultSet::new(result.schema().clone(), filtered_rows))
|
||||||
|
}
|
||||||
|
LogicalPlan::Project {
|
||||||
|
input,
|
||||||
|
expressions,
|
||||||
|
schema,
|
||||||
|
} => {
|
||||||
|
let result = execute(input, instance)?;
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for row in result.rows() {
|
||||||
|
let values = expressions
|
||||||
|
.iter()
|
||||||
|
.map(|expr| eval_expr(&expr.expr, row, result.schema()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
rows.push(Row::new(values));
|
||||||
|
}
|
||||||
|
Ok(ResultSet::new(schema.clone(), rows))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_predicate(
|
||||||
|
expr: &LogicalExpr,
|
||||||
|
row: &Row,
|
||||||
|
schema: &crate::relational::Schema,
|
||||||
|
) -> Result<bool, ExecutionError> {
|
||||||
|
match expr {
|
||||||
|
LogicalExpr::Eq(left, right) => Ok(eval_expr(left, row, schema)?
|
||||||
|
.sql_eq(&eval_expr(right, row, schema)?)
|
||||||
|
.unwrap_or(false)),
|
||||||
|
_ => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_expr(
|
||||||
|
expr: &LogicalExpr,
|
||||||
|
row: &Row,
|
||||||
|
schema: &crate::relational::Schema,
|
||||||
|
) -> Result<Value, ExecutionError> {
|
||||||
|
match expr {
|
||||||
|
LogicalExpr::Column(name) => {
|
||||||
|
let index = schema
|
||||||
|
.index_of(name)
|
||||||
|
.ok_or_else(|| ExecutionError::UnknownColumn(name.clone()))?;
|
||||||
|
Ok(row.get(index).cloned().unwrap_or(Value::Null))
|
||||||
|
}
|
||||||
|
LogicalExpr::Literal(value) => Ok(value.clone()),
|
||||||
|
LogicalExpr::Eq(left, right) => {
|
||||||
|
let left = eval_expr(left, row, schema)?;
|
||||||
|
let right = eval_expr(right, row, schema)?;
|
||||||
|
Ok(Value::Boolean(left.sql_eq(&right).unwrap_or(false)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_from_term(term: &Term) -> Result<Value, ExecutionError> {
|
||||||
|
match term {
|
||||||
|
Term::Constant(value) => Ok(Value::text(value.clone())),
|
||||||
|
Term::Null(_) => Ok(Value::Null),
|
||||||
|
Term::Variable(_) => Err(ExecutionError::NonGroundScanTerm),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,8 +4,13 @@
|
|||||||
//! lightweight frontends for experimenting with rule-driven query answering.
|
//! lightweight frontends for experimenting with rule-driven query answering.
|
||||||
//! It is not yet a full SQL engine with logical and physical planning layers.
|
//! It is not yet a full SQL engine with logical and physical planning layers.
|
||||||
|
|
||||||
|
pub mod catalog;
|
||||||
pub mod chase;
|
pub mod chase;
|
||||||
|
pub mod execution;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
|
pub mod planner;
|
||||||
|
pub mod relational;
|
||||||
|
pub mod sql;
|
||||||
|
|
||||||
// Curated convenience re-exports for the current public crate surface.
|
// Curated convenience re-exports for the current public crate surface.
|
||||||
// Lower-level reasoning and provenance APIs remain under `query_engine::chase`.
|
// Lower-level reasoning and provenance APIs remain under `query_engine::chase`.
|
||||||
|
|||||||
47
src/planner/logical.rs
Normal file
47
src/planner/logical.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::relational::{ResultSet, Schema, Value};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum LogicalExpr {
|
||||||
|
Column(String),
|
||||||
|
Literal(Value),
|
||||||
|
Eq(Box<LogicalExpr>, Box<LogicalExpr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct NamedExpr {
|
||||||
|
pub name: String,
|
||||||
|
pub expr: LogicalExpr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum LogicalPlan {
|
||||||
|
Scan {
|
||||||
|
table: String,
|
||||||
|
schema: Schema,
|
||||||
|
},
|
||||||
|
Filter {
|
||||||
|
input: Box<LogicalPlan>,
|
||||||
|
predicate: LogicalExpr,
|
||||||
|
},
|
||||||
|
Project {
|
||||||
|
input: Box<LogicalPlan>,
|
||||||
|
expressions: Vec<NamedExpr>,
|
||||||
|
schema: Schema,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogicalPlan {
|
||||||
|
pub fn output_schema(&self) -> &Schema {
|
||||||
|
match self {
|
||||||
|
Self::Scan { schema, .. } => schema,
|
||||||
|
Self::Filter { input, .. } => input.output_schema(),
|
||||||
|
Self::Project { schema, .. } => schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResultSet> for LogicalPlan {
|
||||||
|
fn from(_: ResultSet) -> Self {
|
||||||
|
unreachable!("result sets are execution output, not logical plans")
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/planner/mod.rs
Normal file
4
src/planner/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
//! Logical planning scaffolding.
|
||||||
|
|
||||||
|
pub mod logical;
|
||||||
|
pub mod sql;
|
||||||
149
src/planner/sql.rs
Normal file
149
src/planner/sql.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::catalog::{CatalogError, PredicateCatalog};
|
||||||
|
use crate::planner::logical::{LogicalExpr, LogicalPlan, NamedExpr};
|
||||||
|
use crate::relational::{Field, Schema, Value};
|
||||||
|
use crate::sql::ast::{BinaryOp, Expr, Literal, Select, SelectItem};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PlannerError {
|
||||||
|
Catalog(CatalogError),
|
||||||
|
UnknownColumn(String),
|
||||||
|
UnsupportedProjection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PlannerError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Catalog(err) => write!(f, "catalog error: {}", err),
|
||||||
|
Self::UnknownColumn(column) => write!(f, "unknown column `{}`", column),
|
||||||
|
Self::UnsupportedProjection => {
|
||||||
|
write!(f, "only wildcard and column projections are supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for PlannerError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::Catalog(err) => Some(err),
|
||||||
|
Self::UnknownColumn(_) | Self::UnsupportedProjection => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CatalogError> for PlannerError {
|
||||||
|
fn from(value: CatalogError) -> Self {
|
||||||
|
Self::Catalog(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plan_select(
|
||||||
|
select: &Select,
|
||||||
|
catalog: &PredicateCatalog,
|
||||||
|
) -> Result<LogicalPlan, PlannerError> {
|
||||||
|
let scan_schema = catalog.schema_for(&select.from)?.clone();
|
||||||
|
let mut plan = LogicalPlan::Scan {
|
||||||
|
table: select.from.clone(),
|
||||||
|
schema: scan_schema.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(selection) = &select.selection {
|
||||||
|
let predicate = plan_expr(selection, &scan_schema)?;
|
||||||
|
plan = LogicalPlan::Filter {
|
||||||
|
input: Box::new(plan),
|
||||||
|
predicate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_wildcard_projection(&select.projection) {
|
||||||
|
return Ok(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut expressions = Vec::new();
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
for item in &select.projection {
|
||||||
|
match item {
|
||||||
|
SelectItem::Expr { expr, alias } => match expr {
|
||||||
|
Expr::Identifier(name) => {
|
||||||
|
let index = scan_schema
|
||||||
|
.index_of(name)
|
||||||
|
.ok_or_else(|| PlannerError::UnknownColumn(name.clone()))?;
|
||||||
|
let input_field = &scan_schema.fields()[index];
|
||||||
|
let output_name = alias.clone().unwrap_or_else(|| name.clone());
|
||||||
|
expressions.push(NamedExpr {
|
||||||
|
name: output_name.clone(),
|
||||||
|
expr: LogicalExpr::Column(name.clone()),
|
||||||
|
});
|
||||||
|
fields.push(Field::new(
|
||||||
|
output_name,
|
||||||
|
input_field.data_type().clone(),
|
||||||
|
input_field.nullable(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => return Err(PlannerError::UnsupportedProjection),
|
||||||
|
},
|
||||||
|
SelectItem::Wildcard => return Err(PlannerError::UnsupportedProjection),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LogicalPlan::Project {
|
||||||
|
input: Box::new(plan),
|
||||||
|
expressions,
|
||||||
|
schema: Schema::new(fields),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_wildcard_projection(items: &[SelectItem]) -> bool {
|
||||||
|
matches!(items, [SelectItem::Wildcard])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plan_expr(expr: &Expr, schema: &Schema) -> Result<LogicalExpr, PlannerError> {
|
||||||
|
match expr {
|
||||||
|
Expr::Identifier(name) => {
|
||||||
|
if schema.index_of(name).is_none() {
|
||||||
|
return Err(PlannerError::UnknownColumn(name.clone()));
|
||||||
|
}
|
||||||
|
Ok(LogicalExpr::Column(name.clone()))
|
||||||
|
}
|
||||||
|
Expr::Literal(literal) => Ok(LogicalExpr::Literal(plan_literal(literal))),
|
||||||
|
Expr::Binary { left, op, right } => match op {
|
||||||
|
BinaryOp::Eq => Ok(LogicalExpr::Eq(
|
||||||
|
Box::new(plan_expr(left, schema)?),
|
||||||
|
Box::new(plan_expr(right, schema)?),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plan_literal(literal: &Literal) -> Value {
|
||||||
|
match literal {
|
||||||
|
Literal::String(value) => Value::text(value.clone()),
|
||||||
|
Literal::Null => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::catalog::PredicateCatalog;
|
||||||
|
use crate::chase::{Atom, Instance, Term};
|
||||||
|
use crate::sql::parser::parse_select;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plans_projection_and_filter() {
|
||||||
|
let instance: Instance = vec![Atom::new(
|
||||||
|
"Parent",
|
||||||
|
vec![Term::constant("alice"), Term::constant("bob")],
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let select = parse_select("SELECT c0 FROM Parent WHERE c1 = 'bob'").unwrap();
|
||||||
|
|
||||||
|
let plan = plan_select(&select, &catalog).unwrap();
|
||||||
|
assert_eq!(plan.output_schema().len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/relational/mod.rs
Normal file
9
src/relational/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//! Relational data model scaffolding for future SQL and planner work.
|
||||||
|
|
||||||
|
mod row;
|
||||||
|
mod schema;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
pub use row::{ResultSet, Row};
|
||||||
|
pub use schema::{DataType, Field, Schema};
|
||||||
|
pub use value::Value;
|
||||||
40
src/relational/row.rs
Normal file
40
src/relational/row.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use super::{Schema, Value};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Row {
|
||||||
|
values: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Row {
|
||||||
|
pub fn new(values: Vec<Value>) -> Self {
|
||||||
|
Self { values }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(&self) -> &[Value] {
|
||||||
|
&self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Option<&Value> {
|
||||||
|
self.values.get(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ResultSet {
|
||||||
|
schema: Schema,
|
||||||
|
rows: Vec<Row>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResultSet {
|
||||||
|
pub fn new(schema: Schema, rows: Vec<Row>) -> Self {
|
||||||
|
Self { schema, rows }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schema(&self) -> &Schema {
|
||||||
|
&self.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rows(&self) -> &[Row] {
|
||||||
|
&self.rows
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/relational/schema.rs
Normal file
92
src/relational/schema.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum DataType {
|
||||||
|
Text,
|
||||||
|
Boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Field {
|
||||||
|
name: String,
|
||||||
|
data_type: DataType,
|
||||||
|
nullable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Field {
|
||||||
|
pub fn new(name: impl Into<String>, data_type: DataType, nullable: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
data_type,
|
||||||
|
nullable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_type(&self) -> &DataType {
|
||||||
|
&self.data_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nullable(&self) -> bool {
|
||||||
|
self.nullable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct Schema {
|
||||||
|
fields: Vec<Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
pub fn new(fields: Vec<Field>) -> Self {
|
||||||
|
Self { fields }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields(&self) -> &[Field] {
|
||||||
|
&self.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.fields.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.fields.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_of(&self, name: &str) -> Option<usize> {
|
||||||
|
self.fields.iter().position(|field| field.name() == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Schema {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let fields = self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| field.name().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
write!(f, "[{}]", fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_resolves_columns_by_name() {
|
||||||
|
let schema = Schema::new(vec![
|
||||||
|
Field::new("c0", DataType::Text, false),
|
||||||
|
Field::new("c1", DataType::Text, true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(schema.index_of("c0"), Some(0));
|
||||||
|
assert_eq!(schema.index_of("c1"), Some(1));
|
||||||
|
assert_eq!(schema.index_of("missing"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/relational/value.rs
Normal file
37
src/relational/value.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Value {
|
||||||
|
Text(String),
|
||||||
|
Boolean(bool),
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn text(value: impl Into<String>) -> Self {
|
||||||
|
Self::Text(value.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
matches!(self, Self::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sql_eq(&self, other: &Self) -> Option<bool> {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Null, _) | (_, Self::Null) => None,
|
||||||
|
(Self::Text(left), Self::Text(right)) => Some(left == right),
|
||||||
|
(Self::Boolean(left), Self::Boolean(right)) => Some(left == right),
|
||||||
|
(Self::Text(_), Self::Boolean(_)) | (Self::Boolean(_), Self::Text(_)) => Some(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Value {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Text(value) => write!(f, "{}", value),
|
||||||
|
Self::Boolean(value) => write!(f, "{}", value),
|
||||||
|
Self::Null => write!(f, "NULL"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/sql/ast.rs
Normal file
34
src/sql/ast.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Select {
|
||||||
|
pub projection: Vec<SelectItem>,
|
||||||
|
pub from: String,
|
||||||
|
pub selection: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SelectItem {
|
||||||
|
Wildcard,
|
||||||
|
Expr { expr: Expr, alias: Option<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Expr {
|
||||||
|
Identifier(String),
|
||||||
|
Literal(Literal),
|
||||||
|
Binary {
|
||||||
|
left: Box<Expr>,
|
||||||
|
op: BinaryOp,
|
||||||
|
right: Box<Expr>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Literal {
|
||||||
|
String(String),
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum BinaryOp {
|
||||||
|
Eq,
|
||||||
|
}
|
||||||
4
src/sql/mod.rs
Normal file
4
src/sql/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
//! Minimal SQL front-end scaffolding.
|
||||||
|
|
||||||
|
pub mod ast;
|
||||||
|
pub mod parser;
|
||||||
275
src/sql/parser.rs
Normal file
275
src/sql/parser.rs
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use super::ast::{BinaryOp, Expr, Literal, Select, SelectItem};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ParseError {
|
||||||
|
UnexpectedEnd,
|
||||||
|
ExpectedToken(&'static str),
|
||||||
|
ExpectedIdentifier,
|
||||||
|
UnexpectedToken(String),
|
||||||
|
UnterminatedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::UnexpectedEnd => write!(f, "unexpected end of input"),
|
||||||
|
Self::ExpectedToken(token) => write!(f, "expected `{}`", token),
|
||||||
|
Self::ExpectedIdentifier => write!(f, "expected identifier"),
|
||||||
|
Self::UnexpectedToken(token) => write!(f, "unexpected token `{}`", token),
|
||||||
|
Self::UnterminatedString => write!(f, "unterminated string literal"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum Token {
|
||||||
|
Select,
|
||||||
|
From,
|
||||||
|
Where,
|
||||||
|
Null,
|
||||||
|
Identifier(String),
|
||||||
|
String(String),
|
||||||
|
Star,
|
||||||
|
Comma,
|
||||||
|
Eq,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_select(input: &str) -> Result<Select, ParseError> {
|
||||||
|
let tokens = tokenize(input)?;
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
parser.parse_select()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser {
|
||||||
|
tokens: Vec<Token>,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
fn new(tokens: Vec<Token>) -> Self {
|
||||||
|
Self { tokens, index: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_select(&mut self) -> Result<Select, ParseError> {
|
||||||
|
self.expect_keyword(Token::Select, "SELECT")?;
|
||||||
|
let projection = self.parse_projection()?;
|
||||||
|
self.expect_keyword(Token::From, "FROM")?;
|
||||||
|
let from = self.expect_identifier()?;
|
||||||
|
let selection = if self.peek() == Some(&Token::Where) {
|
||||||
|
self.index += 1;
|
||||||
|
Some(self.parse_expr()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(token) = self.peek() {
|
||||||
|
return Err(ParseError::UnexpectedToken(render_token(token)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Select {
|
||||||
|
projection,
|
||||||
|
from,
|
||||||
|
selection,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_projection(&mut self) -> Result<Vec<SelectItem>, ParseError> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let item = match self.next().ok_or(ParseError::UnexpectedEnd)? {
|
||||||
|
Token::Star => SelectItem::Wildcard,
|
||||||
|
Token::Identifier(name) => SelectItem::Expr {
|
||||||
|
expr: Expr::Identifier(name),
|
||||||
|
alias: None,
|
||||||
|
},
|
||||||
|
other => return Err(ParseError::UnexpectedToken(render_token(&other))),
|
||||||
|
};
|
||||||
|
items.push(item);
|
||||||
|
|
||||||
|
if self.peek() == Some(&Token::Comma) {
|
||||||
|
self.index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expr(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
let left = self.parse_operand()?;
|
||||||
|
match self.next().ok_or(ParseError::UnexpectedEnd)? {
|
||||||
|
Token::Eq => {
|
||||||
|
let right = self.parse_operand()?;
|
||||||
|
Ok(Expr::Binary {
|
||||||
|
left: Box::new(left),
|
||||||
|
op: BinaryOp::Eq,
|
||||||
|
right: Box::new(right),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
other => Err(ParseError::UnexpectedToken(render_token(&other))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_operand(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
match self.next().ok_or(ParseError::UnexpectedEnd)? {
|
||||||
|
Token::Identifier(name) => Ok(Expr::Identifier(name)),
|
||||||
|
Token::String(value) => Ok(Expr::Literal(Literal::String(value))),
|
||||||
|
Token::Null => Ok(Expr::Literal(Literal::Null)),
|
||||||
|
other => Err(ParseError::UnexpectedToken(render_token(&other))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_keyword(&mut self, token: Token, label: &'static str) -> Result<(), ParseError> {
|
||||||
|
let next = self.next().ok_or(ParseError::UnexpectedEnd)?;
|
||||||
|
if next == token {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ParseError::ExpectedToken(label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_identifier(&mut self) -> Result<String, ParseError> {
|
||||||
|
match self.next().ok_or(ParseError::UnexpectedEnd)? {
|
||||||
|
Token::Identifier(name) => Ok(name),
|
||||||
|
_ => Err(ParseError::ExpectedIdentifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(&self) -> Option<&Token> {
|
||||||
|
self.tokens.get(self.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Token> {
|
||||||
|
let token = self.tokens.get(self.index).cloned();
|
||||||
|
if token.is_some() {
|
||||||
|
self.index += 1;
|
||||||
|
}
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
|
||||||
|
let mut chars = input.chars().peekable();
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ch) = chars.peek().copied() {
|
||||||
|
if ch.is_whitespace() {
|
||||||
|
chars.next();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ch {
|
||||||
|
'*' => {
|
||||||
|
chars.next();
|
||||||
|
tokens.push(Token::Star);
|
||||||
|
}
|
||||||
|
',' => {
|
||||||
|
chars.next();
|
||||||
|
tokens.push(Token::Comma);
|
||||||
|
}
|
||||||
|
'=' => {
|
||||||
|
chars.next();
|
||||||
|
tokens.push(Token::Eq);
|
||||||
|
}
|
||||||
|
'\'' => tokens.push(Token::String(parse_string(&mut chars)?)),
|
||||||
|
ch if is_identifier_start(ch) => {
|
||||||
|
let ident = parse_identifier(&mut chars);
|
||||||
|
let token = match ident.to_ascii_uppercase().as_str() {
|
||||||
|
"SELECT" => Token::Select,
|
||||||
|
"FROM" => Token::From,
|
||||||
|
"WHERE" => Token::Where,
|
||||||
|
"NULL" => Token::Null,
|
||||||
|
_ => Token::Identifier(ident),
|
||||||
|
};
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
other => return Err(ParseError::UnexpectedToken(other.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_string<I>(chars: &mut std::iter::Peekable<I>) -> Result<String, ParseError>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = char>,
|
||||||
|
{
|
||||||
|
let mut value = String::new();
|
||||||
|
let quote = chars.next();
|
||||||
|
if quote != Some('\'') {
|
||||||
|
return Err(ParseError::ExpectedToken("'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if ch == '\'' {
|
||||||
|
if chars.peek() == Some(&'\'') {
|
||||||
|
chars.next();
|
||||||
|
value.push('\'');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
value.push(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ParseError::UnterminatedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_identifier<I>(chars: &mut std::iter::Peekable<I>) -> String
|
||||||
|
where
|
||||||
|
I: Iterator<Item = char>,
|
||||||
|
{
|
||||||
|
let mut ident = String::new();
|
||||||
|
while let Some(ch) = chars.peek().copied() {
|
||||||
|
if is_identifier_part(ch) {
|
||||||
|
ident.push(ch);
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ident
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_start(ch: char) -> bool {
|
||||||
|
ch.is_ascii_alphabetic() || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_part(ch: char) -> bool {
|
||||||
|
ch.is_ascii_alphanumeric() || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_token(token: &Token) -> String {
|
||||||
|
match token {
|
||||||
|
Token::Select => "SELECT".to_string(),
|
||||||
|
Token::From => "FROM".to_string(),
|
||||||
|
Token::Where => "WHERE".to_string(),
|
||||||
|
Token::Null => "NULL".to_string(),
|
||||||
|
Token::Identifier(name) => name.clone(),
|
||||||
|
Token::String(value) => format!("'{}'", value),
|
||||||
|
Token::Star => "*".to_string(),
|
||||||
|
Token::Comma => ",".to_string(),
|
||||||
|
Token::Eq => "=".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_select_with_filter() {
|
||||||
|
let select = parse_select("SELECT c0 FROM Parent WHERE c1 = 'bob'").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(select.from, "Parent");
|
||||||
|
assert_eq!(select.projection.len(), 1);
|
||||||
|
assert!(select.selection.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
67
tests/sql_pipeline_tests.rs
Normal file
67
tests/sql_pipeline_tests.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use query_engine::catalog::PredicateCatalog;
|
||||||
|
use query_engine::execution::execute;
|
||||||
|
use query_engine::planner::sql::plan_select;
|
||||||
|
use query_engine::sql::parser::parse_select;
|
||||||
|
use query_engine::{Atom, Instance, Term};
|
||||||
|
|
||||||
|
fn parent_instance() -> Instance {
|
||||||
|
vec![
|
||||||
|
Atom::new(
|
||||||
|
"Parent",
|
||||||
|
vec![Term::constant("alice"), Term::constant("bob")],
|
||||||
|
),
|
||||||
|
Atom::new(
|
||||||
|
"Parent",
|
||||||
|
vec![Term::constant("bob"), Term::constant("carol")],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_star_scans_predicate_as_table() {
|
||||||
|
let instance = parent_instance();
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let select = parse_select("SELECT * FROM Parent").unwrap();
|
||||||
|
|
||||||
|
let plan = plan_select(&select, &catalog).unwrap();
|
||||||
|
let result = execute(&plan, &instance).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.schema().len(), 2);
|
||||||
|
assert_eq!(result.rows().len(), 2);
|
||||||
|
assert_eq!(result.rows()[0].values().len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_projection_keeps_requested_columns() {
|
||||||
|
let instance = parent_instance();
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let select = parse_select("SELECT c0 FROM Parent").unwrap();
|
||||||
|
|
||||||
|
let plan = plan_select(&select, &catalog).unwrap();
|
||||||
|
let result = execute(&plan, &instance).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.schema().len(), 1);
|
||||||
|
assert_eq!(result.rows().len(), 2);
|
||||||
|
let mut values = result
|
||||||
|
.rows()
|
||||||
|
.iter()
|
||||||
|
.map(|row| format!("{}", row.values()[0]))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
values.sort();
|
||||||
|
assert_eq!(values, vec!["alice".to_string(), "bob".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_filters_rows() {
|
||||||
|
let instance = parent_instance();
|
||||||
|
let catalog = PredicateCatalog::from_instance(&instance).unwrap();
|
||||||
|
let select = parse_select("SELECT c0 FROM Parent WHERE c1 = 'bob'").unwrap();
|
||||||
|
|
||||||
|
let plan = plan_select(&select, &catalog).unwrap();
|
||||||
|
let result = execute(&plan, &instance).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.rows().len(), 1);
|
||||||
|
assert_eq!(format!("{}", result.rows()[0].values()[0]), "alice");
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user