# Souffle-LintQ Python Fact Extractor

`ast_to_facts.py` parses a Qiskit Python script with the standard-library
`ast` module and emits tab-separated `.facts` files matching the EDB schema
in the Souffle-LintQ proposal:

| File                 | Columns                                          |
|----------------------|--------------------------------------------------|
| `Stmt.facts`         | id, line, func                                   |
| `CFGEdge.facts`      | from_stmt, to_stmt                               |
| `Assign.facts`       | stmt, dest_var, src_var                          |
| `CircuitAlloc.facts` | stmt, var_name, num_qubits, num_clbits           |
| `GateOp.facts`       | stmt, circuit_var, gate_name, qubit_idx          |
| `MeasureOp.facts`    | stmt, circuit_var, qubit_idx, clbit_idx          |
| `CircuitCall.facts`  | stmt, method, target_var, arg_var                |

## Usage

    python3 ast_to_facts.py <target.py> [--out <facts_dir>]

## Verify

    # 1. extract facts
    python3 ast_to_facts.py sample_circuit.py --out facts

    # 2. run the Souffle analyses (needs `souffle` >= 2.x on PATH)
    #    NOTE: use the facts dir you just wrote (extractor/facts), and create
    #    `out/` first — Souffle 2.5 aborts if -D out does not exist.
    mkdir -p out
    souffle -F facts -D out ../engine/lintq.dl

    # or simply, from the project root:
    #   bash run.sh

    # 3. unit tests (end-to-end tests auto-skip if souffle is absent)
    python3 -m unittest test_extractor -v

`sample_circuit.py` deliberately contains the three anti-patterns the
analyses detect: a double measure, an op-after-measure, and a discarded
`compose` return value.
