# Compatibility Notes

This document tracks compatibility with Python beancount, including intentional differences.

## Compatibility Goal

**100% compatible** with valid beancount files. Any file that Python beancount accepts should produce identical results in rustledger.

## Compatibility Levels

### Level 1: Syntax (Parser)

The parser must accept exactly the same syntax as Python beancount.

**Status**: Target full compatibility

### Level 2: Semantics (Processing)

Interpolation, booking, and validation must produce identical results.

**Status**: Target full compatibility

### Level 3: Output (Formatting)

Error messages, query results, and printed output may differ in formatting.

**Status**: Allowed differences

### Level 4: Performance

Processing order and internal representation may differ as long as results match.

**Status**: Allowed differences

## Known Differences

### Intentional Differences

| Feature | Python Behavior | Rust Behavior | Rationale |
|---------|-----------------|---------------|-----------|
| Plugin system | Python modules | WASM modules | Language independence |
| Error messages | Python format | Rich formatting (ariadne) | Better UX |
| Query output | Python tables | Various formats | Flexibility |

### Accepted Deviations

| Behavior | Python | Rust | Impact |
|----------|--------|------|--------|
| Floating point display | Python repr | Decimal display | Cosmetic only |
| Error ordering | Undefined | By source location | UX improvement |
| Hash/ID generation | Python hash | Different algorithm | Cosmetic only |

### Known Parse Differences

These are syntax constructs where rustledger's parser behavior differs from Python beancount:

| Syntax | Python | Rust | Planned Fix |
|--------|--------|------|-------------|
| Numbers with trailing decimal (`1.`) | Accepts as 1.0 | Parse error | Under consideration |
| UTF-8 decode errors | May partially parse | Parse error | No (strict UTF-8 is safer) |
| Org-mode style headers (`* Section`) | Accepted | Parse error | Under consideration |

#### Non-English Account Types (Supported)

rustledger supports non-English account type prefixes via options, matching Python beancount:

```beancount
option "name_assets" "Aktiva"
option "name_expenses" "Ausgaben"
option "name_liabilities" "Schulden"
option "name_income" "Einnahmen"
option "name_equity" "Eigenkapital"

2000-01-01 open Aktiva:Bank USD
2000-01-01 open Ausgaben:Food USD
```

The options must be placed at the beginning of the file before account usage.

#### Trailing Decimal Numbers

Some ledger2beancount conversions produce numbers like `1.` (integer with trailing decimal):

```beancount
2024-01-01 * "Test"
  Assets:Test  1. USD   ; Python accepts, Rust rejects
  Assets:Other
```

**Workaround**: Use `1.0` or `1` instead of `1.`

## Plugin Compatibility

### Python Plugins (NOT supported)

rustledger does **not** support Python plugins. Users must:

1. Port plugins to WASM (Rust, Go, AssemblyScript, etc.)
1. Use built-in reimplementations of common plugins

### Built-in Plugin Equivalents

| Python Plugin | rustledger Equivalent |
|---------------|-------------------------|
| `beancount.plugins.implicit_prices` | Built-in Rust |
| `beancount.plugins.check_commodity` | Built-in Rust |
| `beancount.plugins.check_closing` | Built-in Rust |
| `beancount.plugins.coherent_cost` | Built-in Rust |

### Plugin Porting Guide

```python
# Python plugin
def process(entries, options_map):
    new_entries = []
    errors = []
    for entry in entries:
        # Transform entry
        new_entries.append(entry)
    return new_entries, errors
```

```rust
// Rust WASM plugin
#[no_mangle]
pub fn process(input: PluginInput) -> PluginOutput {
    let mut directives = input.directives;
    let mut errors = Vec::new();

    for directive in &mut directives {
        // Transform directive
    }

    PluginOutput { directives, errors }
}
```

## BQL Compatibility

### Fully Supported

- SELECT with columns and expressions
- WHERE filtering
- GROUP BY aggregation
- ORDER BY sorting
- LIMIT
- Standard functions (sum, count, first, last, etc.)
- OPEN ON / CLOSE ON / CLEAR

### Partially Supported

| Feature | Python | Rust | Notes |
|---------|--------|------|-------|
| PIVOT BY | Yes | Planned | Phase 2 |
| FLATTEN | Yes | Planned | Phase 2 |
| Sub-selects | Yes | Planned | Phase 3 |

### Differences

| Behavior | Python | Rust |
|----------|--------|------|
| NULL handling | Python None | Rust Option |
| Regex engine | Python re | Rust regex |

## Error Message Compatibility

Error messages will differ in format but convey the same information:

### Python

```
ledger.beancount:42:
  Assets:Unknown is not defined
```

### Rust (ariadne)

```
error[E1001]: Account "Assets:Unknown" is not open
  --> ledger.beancount:42:3
   |
42 |   Assets:Unknown  100 USD
   |   ^^^^^^^^^^^^^^ account used here
   |
   = help: add an `open` directive before line 42
```

## Testing Compatibility

### Comparison Test Suite

```bash
#!/bin/bash
# Run both implementations and compare output

FILE=$1

# Python output
python -m beancount.scripts.check "$FILE" 2>&1 | \
    grep -E '^(error|warning)' | sort > /tmp/python.txt

# Rust output
rustledger check "$FILE" 2>&1 | \
    grep -E '^(error|warning)' | sort > /tmp/rust.txt

# Compare (ignoring formatting)
diff /tmp/python.txt /tmp/rust.txt
```

### Semantic Comparison

Compare computed values rather than output format:

```python
# Python: extract balances
import beancount.loader
entries, errors, options = beancount.loader.load_file('ledger.beancount')
balances = compute_balances(entries)
print(json.dumps(balances))
```

```bash
# Rust: extract balances
rustledger query ledger.beancount \
    "SELECT account, sum(position) GROUP BY 1" \
    --format json
```

```bash
# Compare JSON outputs
diff <(python extract.py | jq -S .) <(rustledger ... | jq -S .)
```

## Reporting Compatibility Issues

When you find a compatibility issue:

1. **Create minimal reproducer**: Smallest beancount file that shows the difference
1. **Document both behaviors**: Python output vs Rust output
1. **Classify**: Is this a bug or intentional difference?
1. **File issue**: Include reproducer and classification

### Issue Template

````markdown
## Compatibility Issue

**File**: minimal.beancount
```beancount
2024-01-01 open Assets:Cash
2024-01-15 * "Test"
  Assets:Cash  100 USD
  ; <-- specific issue here
````

**Python behavior**:

```
[output]
```

**Rust behavior**:

```
[output]
```

**Expected**: [Python/Rust/Neither] is correct because [reason]

````

## Version Compatibility

### Beancount Versions

| Python Version | Compatibility |
|----------------|---------------|
| v2.x (current) | Full target |
| v3.x (future) | Track changes |

### Syntax Changes

We track the beancount syntax changelog and update accordingly:

| Change | Version | rustledger Status |
|--------|---------|---------------------|
| (none currently) | | |

## Deprecated Syntax Policy

### Philosophy

rustledger follows Python beancount's approach to deprecated syntax:

1. **Accept deprecated syntax** to maintain compatibility with existing files
2. **Emit warnings** (not errors) for deprecated constructs
3. **No firm removal timeline** - deprecated syntax may remain indefinitely
4. **Document alternatives** in warning messages

This ensures users can migrate to rustledger without modifying working ledger files.

### Currently Supported Deprecated Syntax

| Syntax | Status | Alternative | Notes |
|--------|--------|-------------|-------|
| Pipe separator `"payee" \| "narration"` | Deprecated | `"payee" "narration"` | Warn on use |

### Pipe Separator (`|`)

**Deprecated syntax**:
```beancount
2024-01-15 * "Coffee Shop" | "Morning coffee"
  Expenses:Food  5.00 USD
  Assets:Cash
````

**Preferred syntax**:

```beancount
2024-01-15 * "Coffee Shop" "Morning coffee"
  Expenses:Food  5.00 USD
  Assets:Cash
```

**Behavior**: rustledger parses both forms identically. The pipe symbol is simply ignored as a separator between payee and narration strings.

**Warning**: When the pipe syntax is detected, a deprecation warning is emitted:

```
warning: deprecated pipe separator '|' between payee and narration
  --> ledger.beancount:5:23
   |
 5 | 2024-01-15 * "Coffee Shop" | "Morning coffee"
   |                            ^ use space instead
   |
   = help: Remove the pipe symbol: "Coffee Shop" "Morning coffee"
```

### Future Deprecations

When Python beancount deprecates additional syntax:

1. Track the deprecation in beancount changelog
1. Implement support with warning emission
1. Document in this table
1. Never remove support without major version bump

## Migration Guide

### From Python Beancount

1. **Run compatibility check**:

   ```bash
   rustledger compat-check ledger.beancount
   ```

1. **Address plugin dependencies**:

   - List used plugins: `grep "^plugin" ledger.beancount`
   - Find WASM equivalents or port

1. **Update query scripts**:

   - Replace `bean-query` with `rustledger query`
   - Check for BQL feature usage

1. **Run parallel validation**:

   ```bash
   # Verify identical results
   diff <(bean-check ledger.beancount) <(rustledger check ledger.beancount)
   ```

### Gradual Migration

Use rustledger for read-only operations first:

1. **Phase 1**: Use for validation (`rustledger check`)
1. **Phase 2**: Use for queries (`rustledger query`)
1. **Phase 3**: Use for reporting
1. **Phase 4**: Full replacement
