elixcee 0.10.1

Emulate and execute Excel VBA macros at high speed — without Microsoft Excel
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
# elixcee

**English** | [日本語](README_ja.md) | [中文](README_zh.md)

Run, test, and diagnose Excel VBA macros without requiring Microsoft Excel. elixcee is a Rust-powered headless VBA runtime for Linux, macOS, and Windows, with static checks, property-based workbook testing, and structured diagnostics for VBA and workbook-operation failures.

The core engine is written in **Rust**; Python bindings are provided via **pyo3 + maturin**.

## Name

**elixcee** = **Excel** + **elixir** + **C**

An *elixir* that cures your Excel dependency — running at C-level speed via Rust.

---

## Comparison with similar tools

| Feature | **elixcee** | xlwings | LibreOffice UNO | openpyxl | xlcalculator |
|---------|:-----------:|:-------:|:---------------:|:--------:|:------------:|
| Runs VBA macros | Yes | Yes | Yes (subset) | No | No |
| Requires Excel | No | Yes | No | No | No |
| Requires LibreOffice | No | No | Yes | No | No |
| Evaluates formulas | Yes | Yes | Yes | No | Yes |
| macOS/Linux/Windows | Yes | partial | Yes | Yes | Yes |
| Simple Python API | Yes | Yes | No | Yes | Yes |
| Read .xlsx | Yes | Yes | Yes | Yes | Yes |
| Read .ods | Yes | Yes | Yes | No | No |
| Write .xlsx | Yes | Yes | Yes | Yes | No |
| Write .ods | Yes | Yes | Yes | No | No |
| Execution speed | Rust (native) | COM/IPC (slow) | IPC (slow) | — | Python |

**Notes:**
- **xlwings** requires Excel for Mac on macOS (via AppleScript) and Excel on Windows (via COM). Linux support requires a running Excel instance or a cloud bridge.
- **LibreOffice UNO** has a slow startup (≥ 1 s process launch) and a complex API. It runs VBA via LibreOffice's own interpreter, which may not match Excel's behavior exactly.
- **openpyxl** reads cached formula values from .xlsx files but does not re-evaluate formulas at runtime.
- **xlcalculator** re-evaluates Excel formulas in Python but has no VBA support.
- elixcee's VBA interpreter covers the subset of VBA used in typical data-processing macros (loops, conditionals, cell read/write, string/math functions, multi-sheet access). Most Excel UI operations such as charting and formatting are unsupported or no-ops. `MsgBox` is handled specially: depending on the mode, it's printed to stdout, collected in JSON output, or raised as an error.

---

## Installation

```bash
pip install elixcee
```

Development build (from source):

```bash
python3 -m venv .venv && source .venv/bin/activate
maturin develop
```

---

## CLI (Windows / Linux / macOS)

Pre-built binaries are available on the [Releases](https://github.com/kent-tokyo/elixcee/releases) page — no Python required.

| Download | Platform |
|---|---|
| [elixcee-x86_64-windows.exe](https://github.com/kent-tokyo/elixcee/releases/latest/download/elixcee-x86_64-windows.exe) | Windows x64 |
| [elixcee-x86_64-linux](https://github.com/kent-tokyo/elixcee/releases/latest/download/elixcee-x86_64-linux) | Linux x64 |
| [elixcee-aarch64-macos](https://github.com/kent-tokyo/elixcee/releases/latest/download/elixcee-aarch64-macos) | macOS Apple Silicon |

### Usage

```
elixcee <vba_file>... <MacroName> [OPTIONS]

Arguments:
  <vba_file>...  One or more VBA source files (.vbs / .bas / .txt). With
                 more than one, use Module.Sub to disambiguate same-named
                 Subs/Functions across modules.
  <MacroName>    Name of the Sub to execute (last argument)

Options:
  --file <path>    Load cell data from spreadsheet (.xlsx / .xlsm / .ods)
  --sheet <name>   Active sheet name (default: first sheet in --file)
  --output <path>  Save result cells to spreadsheet (.xlsx / .ods)
  --json           Emit a single JSON object (result or error) instead of plain text
```

### Examples

Run a VBA file and print results to stdout:

```bat
elixcee macro.vbs ProcessData
```

Load data from an Excel file, run a macro, and save the output:

```bat
elixcee macro.vbs ProcessData --file input.xlsx --output result.xlsx
```

Output format — one line per non-empty cell, tab-separated address and value:

```
A1    Hello
B1    42
A2    3.14
```

`MsgBox` calls are printed to stdout.

### Multiple files (multi-module projects)

Pass more than one source file to run a project spanning several modules.
Sub/Function names are shared project-wide — use `Module.Sub` to pick a
specific one if the bare name exists in more than one module (module names
come from `Attribute VB_Name` if present, else the filename):

```bat
elixcee Helpers.bas Main.bas Main.ProcessData
```

There's no project manifest yet (see [docs/agent-contract.md](docs/agent-contract.md)
for exactly what is/isn't supported, including how cross-module name
collisions are handled).

### JSON output (for scripts / AI agents)

Add `--json` for a single machine-readable JSON object instead of plain text:

```bat
elixcee macro.vbs ProcessData --json
```

```json
{"schema_version":1,"ok":true,"entrypoint":"ProcessData","duration_ms":0.42,"cells":[{"sheet":"sheet1","address":"A1","value":42}],"messages":[]}
```

Full contract — error codes, exit codes, `messages` semantics: [docs/agent-contract.md](docs/agent-contract.md).

### Static analysis without running the macro

`elixcee check` inspects one or more `.bas` files without executing them: parse errors, whether the entrypoint macro exists, undefined Sub/Function calls anywhere in the body, and interactive `MsgBox` calls. Every positional argument is a file; the entrypoint (if any) is always `--entry`, never positional — so `elixcee check *.bas` checks every module in a project without asserting any particular entrypoint.

```bat
elixcee check macro.vbs --entry ProcessData --json
```

```json
{"schema_version":1,"ok":true,"diagnostics":[]}
```

### Workbook snapshot

`elixcee snapshot` reads a `.xlsx`/`.xlsm`/`.ods` file directly — no VBA
execution — and prints every sheet's non-empty cells as Markdown by default,
or JSON with `--json`:

```bat
elixcee snapshot Book1.xlsx --json
```

```json
{"schema_version":1,"ok":true,"file":"Book1.xlsx","sheets":[{"name":"Sheet1","sheet_id":"1","stable_id":"sheet1","cells":[{"address":"A1","value":42}]}]}
```

`stable_id` is derived from the file's own `sheetId` when available (else a
positional fallback) — it is **not** VBA's `CodeName` property. See
[docs/agent-contract.md](docs/agent-contract.md) for the full rationale.

### Property-based workbook testing

`elixcee test-workbook` reruns a macro against a starting workbook many
times with generated boundary-value inputs (blank, `0`, `1`, `-1`, near
overflow, empty/short/long strings), checking every run for panics,
runtime errors, timeouts, and Excel error values — each case starts from a
completely fresh workbook state:

```toml
# fixture.toml
name = "order calculation"
workbook = "orders.xlsx"
vba_files = ["Main.bas"]
macro = "Main.Process"
cases = 100
seed = 42

[[inputs]]
range = "Input!B2:B10"
strategy = "boundary_numeric"

[[assertions]]
range = "Result!A1:F100"
rule = "no_excel_errors"
```

```bat
elixcee test-workbook fixture.toml --json
```

A failing case reports its seed and case index so it can be reproduced
exactly: `elixcee test-workbook fixture.toml --seed 42 --case 17`. Full
schema, strategies, and assertion rules: [docs/agent-contract.md](docs/agent-contract.md).

### Excel operation diagnostics

`elixcee diagnose` runs a macro once and explains *why* Excel would reject
it — a missing worksheet, a missing workbook, an out-of-bounds array
index, a Copy/Paste shape mismatch, a write to a protected sheet, or a
Copy/Paste that conflicts with a merged-cell layout — with evidence,
instead of a bare error string:

```bat
elixcee diagnose Main.bas --file report.xlsx --json Main.Run
```

```json
{
  "schema_version": 1,
  "ok": false,
  "message": "Sheet 'Sales2025' not found",
  "location": {"file": "Main.bas", "line": 2, "column": 5},
  "root_causes": [
    {
      "code": "WORKSHEET_NOT_FOUND",
      "certainty": "definite",
      "expression": "Worksheets(\"Sales2025\")",
      "requested": "Sales2025",
      "available": ["input", "sales2026", "summary"],
      "suggested": "sales2026",
      "suggestions": ["did you mean 'sales2026'?"]
    }
  ],
  "messages": []
}
```

`Range("A1:C10").Copy` followed by `Range("E1:F10").PasteSpecial` reports
both the shape mismatch and where each statement is:

```json
{
  "code": "PASTE_SHAPE_MISMATCH",
  "source_addr": "A1:C10", "source_rows": 10, "source_cols": 3,
  "dest_addr": "E1:F10", "dest_rows": 10, "dest_cols": 2,
  "copy_location": {"file": "Main.bas", "line": 2, "column": 5},
  "suggestions": [
    "resize the destination to E1:G10",
    "or specify only the top-left cell E1"
  ]
}
```

Writing to a `.Protect`ed sheet reports which sheet and how to fix it:

```json
{
  "code": "SHEET_PROTECTED",
  "sheet": "sheet1",
  "suggestions": ["unprotect the sheet first: Worksheets(\"sheet1\").Unprotect"]
}
```

Pasting `A1:C10` into `E1:G10` when the destination's first row is merged
(`E1:G1`) but the source's isn't reports the layout conflict and both
locations:

```json
{
  "code": "PASTE_MERGE_LAYOUT_MISMATCH",
  "source_addr": "A1:C10", "dest_addr": "E1:G10",
  "conflicts": ["E1:G1"],
  "copy_location": {"file": "Main.bas", "line": 2, "column": 5},
  "suggestions": [
    "unmerge E1:G1 before pasting",
    "or make the source and destination merge layouts identical"
  ]
}
```

Full classification rules and JSON schema: [docs/agent-contract.md](docs/agent-contract.md).

### Diagnosing across generated inputs

`elixcee diagnose-workbook` combines the two features above: it reruns a
macro across `test-workbook`'s generated cases and classifies whichever
failures it finds, instead of only reporting a bare error string. It's most
useful for input-dependent failures like array-bounds errors, where only
some drawn values trigger the bug — a single `diagnose` call already finds
structural issues (shape mismatches, merged-cell conflicts, sheet
protection) in one shot, since those don't depend on the input at all:

```bat
elixcee diagnose-workbook fixture.toml --json
```

```json
{
  "schema_version": 1,
  "ok": false,
  "seed": 42,
  "case_index": 3,
  "inputs": [{"address": "sheet1!B2", "value": 999999999}],
  "failure": {
    "rule": "no_runtime_error",
    "message": "Array 'arr': index 999999999 out of bounds (len=6)"
  },
  "root_causes": [
    {
      "code": "ARRAY_INDEX_OUT_OF_BOUNDS",
      "name": "arr", "index": 999999999, "lower": 0, "upper": 5,
      "suggestions": ["check that 'arr' is large enough for index 999999999 (valid range is 0 To 5)"]
    }
  ]
}
```

Same fixture format and `--seed`/`--case` replay as `test-workbook`, plus
`--cases N` to override the fixture's own case count for one run. Full
schema: [docs/agent-contract.md](docs/agent-contract.md).

### Multi-area ranges

`Range("A1:A10,C1:C10")` — a disjoint, multi-area range — is now
recognized by `.Copy`, but pasting it is diagnose-only: `diagnose`/
`diagnose-workbook` classify why, instead of silently doing nothing:

```json
{
  "code": "MULTI_AREA_TO_SINGLE_AREA_PASTE",
  "source_areas": [
    {"address": "A1:A10", "rows": 10, "columns": 1},
    {"address": "C1:C10", "rows": 10, "columns": 1}
  ],
  "destination_areas": [
    {"address": "E1:F10", "rows": 10, "columns": 2}
  ],
  "suggestions": [
    "paste each source area separately",
    "copy a contiguous rectangular range",
    "use destination areas with matching count and shapes"
  ]
}
```

`Union()`, `Areas`, `Dim rng As Range`/`Set` object variables, and matching-shape
multi-area paste are now implemented — see "VBA object model" below. The 4 classified
codes above still apply to every multi-area shape that doesn't match exactly (different
area counts or shapes, or either side single-area): full scope in
[docs/agent-contract.md](docs/agent-contract.md).

### Hidden row/column evidence

`diagnose`/`diagnose-workbook` now report when a `.Copy`'d range overlaps
hidden rows/columns (read from real XLSX `hidden="1"` metadata) — not an
error, just a new `observations` field, present alongside (or instead of)
`root_causes`:

```json
{
  "code": "RANGE_CONTAINS_HIDDEN_CELLS",
  "certainty": "observed",
  "range": {"sheet": "sheet1", "address": "A1:C100", "rows": 100, "columns": 3},
  "visibility": {
    "hidden_rows": ["11:14", "30:39"],
    "hidden_columns": ["B:B"],
    "total_cells": 300,
    "visible_cells": 172
  },
  "message": "The range contains hidden rows or columns. Excel operations using visible cells only may produce a multi-area range."
}
```

This is what `SpecialCells(xlCellTypeVisible)` (below) builds on — plain Copy/Paste
itself is unaffected (hidden cells still copy/paste exactly as before). XLSX only; ODS
is deferred. Full scope: [docs/agent-contract.md](docs/agent-contract.md).

### VBA object model

```vb
Dim rng As Range
Set rng = Range("A1:B2")
rng.Value = 5                        ' real Set reference semantics — an alias, not a copy

Dim u As Range
Set u = Union(Range("A1"), Range("D1"))
Range("C1").Value = u.Areas.Count    ' 2

Dim ws As Worksheet
Set ws = ActiveSheet
ws.Range("A1").Value = 1

Range("F1").Value = 7 Mod 3          ' 1
Range("F2").Value = 2 ^ 3            ' 8
Range("F3").Value = 7 \ 3            ' 2 (integer division)
If Not (a And b) Then MsgBox "ok"

With Cells(r, c)                     ' any target expression, evaluated once
  .Value = 5
  If .Value > 0 Then .Value = .Value + 1   ' .member works at any nesting depth
End With

Set rng = Range("A1"): Set rng2 = rng: Set rng = Nothing
rng2.Value = 1                       ' aliases survive Set ... = Nothing on the original
rng.Value = 2                        ' raises "Object variable or With block variable not set"

Dim n
n = Null
If IsNull(n + 5) Then MsgBox "Null propagates through +"   ' True

Function DoubleIt(x As Integer) As Integer
  DoubleIt = x * 2
End Function
```

`Set`-assigned `Range`/`Worksheet`/`Workbook` object variables with real reference
semantics — including a genuine unset/`Nothing` state (member access through a never-`Set`
or explicitly-`Nothing` variable raises real VBA's "Object variable or With block variable
not set"; `Set x = Nothing` clears only `x`, not any alias made from it earlier) —
`Union`/`Areas`, `SpecialCells(xlCellTypeVisible)` (built on the hidden row/column evidence
above), matching-shape multi-area Copy/Paste, `Mod`/`\`/`^`, infix `And`/`Or`/`Xor`/`Not`
(real bitwise semantics on non-Boolean operands), a runtime `With` stack (any target
expression — including a computed one like `With Cells(r, c)` — evaluated once, with
`.member` resolving correctly at any nesting depth inside `If`/`For`/`Do`/`Select Case`),
`Variant`'s `Null` (documented VBA propagation through `+`/`&`/comparisons, distinct from
`Empty`), the `:` multi-statement-per-line separator, typed `Function` parameters/return
types, comma-separated multi-declarator `Dim` (`Dim a As Integer, b As Range`), and
single-line `If cond Then stmt [Else stmt]` are all supported.

**Known gaps**: multi-area Paste only executes when both sides are multi-area with
matching `Areas.Count` and per-area shapes — every other combination stays diagnose-only
(see above).

### XLSX.read()/write() — `@elixcee/xlsx` (npm, prepared but not yet published)

A synchronous, WebAssembly-backed `XLSX.read(bytes)` — no `await init()` required — is
implemented in the `@elixcee/xlsx` npm package (see
[docs/xlsx-architecture.md](docs/xlsx-architecture.md) for the compatibility initiative
and the sync-bridge design), along with `readFile()`/`readFileSync()` (Node-only; the
browser entry point throws rather than faking a filesystem). They return sheet names,
`!ref`, `!merges`, `!rows`/`!cols` (hidden rows/columns), and per-cell `{t, v, f, w, z}` —
values, formula text, formatted display strings, and date-typed cells, resolved via real
`styles.xml`/number-format parsing. Differential-tested against the real `xlsx@0.18.5`
package: 33/33 MATCH, 0 disclosed (the `src/reader.rs` `xml:space="preserve"` trimming
defect noted in earlier rounds is fixed; see CHANGELOG.md). Works in Node (CJS/ESM) and the
browser: a `"browser"` export condition routes to the inlined-bytes/`initSync` WASM
artifact, verified not just by Node simulating that export condition but by an actual
headless Chrome process loading a real bundle and reading `XLSX.read()`'s result back out
of the page's own DOM (no Safari claim). The browser entry point still assumes bundled
consumption — its shared code has a CJS `require('ssf')`, so it's not literal no-build
`<script type="module">` usage — but a real packed-npm-tarball install (not a relative
import into this repo) and CJS/ESM bundling both round-trip cleanly with no manual asset
copy step required anymore.

`XLSX.write(wb, opts)`/`writeFile()`/`writeFileSync()` — pure JS/XML/ZIP generation, no
Rust writer needed — are implemented too (`bookType: "xlsx"` only), differential-tested
both directions against the real oracle: 36 MATCH + 1 disclosed (`bookType: "ods"`, not
implemented). `package.json`'s `description` was updated to match, but its `version`
(`0.0.0-development`), `private` (`true`), and `publishConfig` (unset) were deliberately
left untouched — no `npm publish` has actually run, and `@elixcee` scope ownership on
npm is unconfirmed from this environment either way (see ROADMAP.md's "Known gaps").

### Build from source

```bash
cargo build --release --bin elixcee
# binary: target/release/elixcee  (or elixcee.exe on Windows)
```

---

## Quick Start

```python
import elixcee

# Run a VBA macro and get all resulting cells
cells = elixcee.run_macro("""
Sub FillSquares()
    For i = 1 To 5
        Cells(i, 1).Value = i * i
    Next i
End Sub
""", "FillSquares")
# cells == {(1,1): 1, (2,1): 4, (3,1): 9, (4,1): 16, (5,1): 25}

# Pre-populate cells from Python, then run a macro
vm = elixcee.Vm()
vm.set_cell(1, 1, 100)
vm.set_cell(2, 1, 200)
vm.run("""
Sub CalcTotal()
    total = Cells(1,1).Value + Cells(2,1).Value
    Cells(3,1).Value = total
End Sub
""", "CalcTotal")
print(vm.get_cell(3, 1))   # 300
print(vm.variables())       # {"total": 300}

# Load cell data from an existing Excel file, then run a macro
vm = elixcee.load_workbook("data.xlsx")
vm.run(vba_code, "ProcessData")
result_cells = vm.cells()   # {(row, col): value, ...}

# Store a worksheet formula on a cell and evaluate it
vm.set_cell_formula(4, 1, "=SUM(A1:A3)")
print(vm.get_cell(4, 1))   # sum of rows 1-3 in column A

# Control MsgBox behavior
vm = elixcee.Vm(on_msgbox="skip")   # silently ignore MsgBox calls (default)
vm = elixcee.Vm(on_msgbox="error")  # raise RuntimeError on MsgBox
```

---

## Python API

| Method | Description |
|---|---|
| `Vm(on_msgbox="skip")` | Create a new VM. `on_msgbox="error"` raises `RuntimeError` on `MsgBox`. |
| `vm.run(vba_code, macro_name)` | Parse and execute the named Sub. |
| `vm.set_cell(row, col, value)` | Write a value into a cell (1-based). |
| `vm.get_cell(row, col)` | Read a cell value. Returns `None` for empty cells. |
| `vm.cells()` | All non-empty cells as `{(row, col): value}`. |
| `vm.variables()` | All VBA variables as `{name: value}`. |
| `vm.set_cell_formula(row, col, formula)` | Store a formula (e.g. `"=SUM(A1:A3)"`) and evaluate it. |
| `vm.set_cell_formula_batch(formulas)` | Set multiple formulas at once: `{(row, col): formula_str}`. |
| `vm.recalculate()` | Re-evaluate all formula cells (useful after manual cell writes). |
| `vm.set_sheet(name)` | Switch the active sheet (creates it if absent). |
| `vm.active_sheet()` | Name of the currently active sheet. |
| `vm.sheet_names()` | List of all sheet names. |
| `vm.get_sheet(name)` | Cells of a named sheet as `{(row, col): value}`. |
| `vm.save_workbook(path)` | Save all sheets to `.xlsx` or `.ods`. |
| `vm.cells_df()` | Return the active sheet as a **pandas DataFrame** (requires pandas). |
| `elixcee.run_macro(vba, name)` | One-shot: run a macro and return `{(row, col): value}`. |
| `elixcee.load_workbook(path)` | Load an `.xlsx` or `.ods` file into a `Vm`. |

---

## Coverage

See **[FUNCTIONS.md](FUNCTIONS.md)** for the complete function and VBA syntax reference, including Excel version for each function.

**Highlights:**
- **Classic (Excel 2003-)**: SUM, VLOOKUP, IF, PMT, FV, PV, NPER, RATE, IPMT, PPMT, NPV, IRR, MIRR, XNPV, XIRR, DGET, DSUM, DAVERAGE, DCOUNT, DCOUNTA, DMAX, DMIN, and 100+ core functions
- **2007–2019**: IFERROR, COUNTIFS/SUMIFS, XOR, IFS, SWITCH, TEXTJOIN, MAXIFS/MINIFS
- **365/2021**: XLOOKUP, XMATCH, FILTER, SORT, UNIQUE, SEQUENCE, LET, LAMBDA, MAP, REDUCE
- **2024/365**: TEXTSPLIT, TEXTBEFORE, TEXTAFTER, VSTACK, HSTACK, TAKE, DROP, CHOOSECOLS, CHOOSEROWS
- **VBA**: For/If/While/With/On Error/Function/`Type...End Type`/Named Ranges/Array of UDT

### Named Ranges

Register a named range in VBA with `Range("A1:B5").Name = "MyData"`, then use the name anywhere a range address is accepted:

```vba
Range("MyData").Value = 0          ' write to all cells in the range
For Each cell In Range("MyData")   ' iterate over cells
    total = total + cell
Next cell
```

Named ranges are stored on `vm.named_ranges` (a `dict[str, str]` mapping lowercase name → address).

### Criteria Syntax (COUNTIF / SUMIF / SUMIFS / etc.)

| Criteria | Example | Meaning |
|---|---|---|
| Number | `10` | Exact numeric match |
| String | `"apple"` | Case-insensitive string match |
| Comparison | `">5"`, `"<=10"`, `"<>"` | Numeric comparison |
| Wildcard | `"a*"`, `"?bc"` | `*` = any chars, `?` = one char |

### Application Object

| Property / Method | Description | Behavior |
|---|---|---|
| `Application.Calculation = xlCalculationManual` | Disable auto-recalculation | **Active** |
| `Application.Calculation = xlCalculationAutomatic` | Enable auto-recalculation + re-evaluate all formula cells | **Active** |
| `Application.ScreenUpdating = False/True` | Suppress screen refresh | **No-op** (no screen) |
| `Application.EnableEvents = False/True` | Disable/enable event triggers | **No-op** (no events) |
| `Application.DisplayAlerts = False/True` | Suppress dialog boxes | **No-op** (no dialogs) |
| `Application.StatusBar = "..."` / `False` | Set/clear status bar text | **No-op** (no UI) |
| `Application.Cursor = xlWait` / `xlDefault` | Change cursor shape | **No-op** (no UI) |
| `Application.CutCopyMode = False` | Cancel clipboard mode | **Active** (clears the modeled clipboard) |

> **No-op** properties are parsed and accepted without error, but have no effect. This allows VBA macro performance patterns (e.g., `Application.ScreenUpdating = False` at the start of a macro) to run unchanged.

---

## Microsoft Excel round-trip validation

elixcee's workbook save path has been validated using five sanitized,
Microsoft Excel-authored `.xlsm` fixtures on Microsoft Excel for Mac.

**Validated scope:**

- open an Excel-authored workbook
- modify cells with elixcee
- save-as and in-place save
- reopen in Microsoft Excel without a repair warning
- preserve formulas, existing cell styles, merged cells, hidden rows/columns,
  VBA project bytes, unknown ZIP parts, and surviving relationships

**Not validated:**

- post-save VBA macro execution
- tables, data validation, conditional formatting, hyperlinks, comments,
  defined names, charts, images, and print settings embedded in regenerated
  worksheet XML

See [`compat/oracle-excel-com/results/0.9.0-A_summary.md`](compat/oracle-excel-com/results/0.9.0-A_summary.md)
for the full results.

---

## Not Yet Supported

See **[FUNCTIONS.md — Not Yet Supported](FUNCTIONS.md#not-yet-supported)** for the full list.

Key gaps by category:
- **Statistical**: NORM.S.DIST, T.INV, F.DIST, CHISQ.DIST, and more
- **Text**: REPT, NUMBERVALUE, PHONETIC
- **Out of scope**: IMAGE (URL image fetch), GROUPBY (pivot aggregation), TRIMRANGE

---

## Status Legend

| Mark | Meaning |
|---|---|
| Done | Implemented and tested |
| TBD | Not yet scheduled |

---

## Development Phases

| Phase | Content | Status |
|---|---|---|
| Phase 1 | Rust project setup + pyo3 Python bindings | Done |
| Phase 2 | VBA parser MVP (Sub/End Sub, assignment, Cells) | Done |
| Phase 3 | Virtual Excel VM (variables, cell storage, interpreter) | Done |
| Phase 3.5 | Excel formula engine (SUM, IF, VLOOKUP, Application.Calculation, etc.) | Done |
| Phase 4 | Control flow (For loop, If/Else, arithmetic expressions) | Done |
| Phase 5 | Python interface (Vm class, run_macro, load_workbook, MsgBox) | Done |
| Phase 6 | Formula function expansion (100+ Excel functions, 118 tests) | Done |
| Phase 7 | Advanced VBA constructs (ElseIf, Exit, For Each, On Error, Function, arrays, While-Wend) | Done |
| Phase 8 | Range API (ClearContents, Offset, Sheets.Cells, WorksheetFunction, multi-sheet) | Done |
| Phase 9 | Multi-sheet support (Sheets HashMap, With Sheets, Python API, load_workbook all sheets) | Done |
| Phase 10 | Worksheet function expansion (math, trig, stats, array/spill, lambda functions) | Done |
| Phase 11 | User-defined types (`Type...End Type`), named ranges, `RANDARRAY`, pandas integration (`cells_df`), type stubs (`.pyi`) | Done |
| Phase D1 | Remove rust_xlsxwriter, hand-written XLSX via zip (dependencies: 5→4) | Done |
| Phase D2 | Remove pest/pest_derive, hand-written recursive descent VBA parser (dependencies: 4→3) | Done |
| Phase D3 | Remove calamine from runtime, hand-written XLSX/ODS reader (dependencies: 3→2) | Done |
| Perf R4 | SUM/AVERAGE/MIN/MAX fast path (skip `Vec<Variant>`), RangeWrite dirty-flag batching | Done |
| CLI | Standalone `elixcee` binary; pyo3 made optional; GitHub Actions release workflow | Done |
| Milestone A | JSON agent contract (`--json`), error classification, MsgBox message log | Done |
| Milestone A.1 | JSON contract hardening (`serde_json`-verified tests, message-log lifecycle, error code docs) | Done |
| Milestone A.5 | Source location tracking — line/column in parse and runtime errors | Done |
| Milestone B1 | `check` subcommand — parse diagnostics, entrypoint check, `MsgBox`/interactive-call detection | Done |
| Milestone B1.1 | `check`: undefined Sub/Function call detection, unsupported-construct (no-op) detection | Done |
| Milestone B2 | Multi-module projects — multiple `.bas` files, `Module.Sub` qualified entrypoints, cross-module collision detection | Done |
| Milestone B3 | Deterministic black-box tests (`tests/blackbox.rs`, declarative `.toml` fixtures) | Done |
| Milestone B4 | `snapshot` subcommand — read a workbook's cells without executing VBA | Done |
| Milestone B5a | `test-workbook` subcommand — property-based testing with generated boundary-value inputs | Done |
| Milestone B6a | `diagnose` subcommand — missing sheet/workbook, array-out-of-bounds root causes | Done |
| Milestone B6b | `diagnose`: Copy/Paste shape mismatch + clipboard state | Done |
| Milestone B6c | `diagnose`: sheet protection (`Protect`/`Unprotect`) | Done |
| Milestone B6c2 | `diagnose`: merged-cell-aware Copy/Paste diagnostics | Done |
| Milestone B6d | `diagnose-workbook` — root-cause diagnosis across generated test cases | Done |
| Milestone B7a | Multi-area `Range`/`Union`/`Areas` foundation for Copy/Paste diagnostics | Done |
| Milestone B7b | Hidden row/column metadata foundation for Copy/Paste diagnostics | Done |
| Phase 3A-1 | `compat/vba-semantics/` value-correctness suite: 208 → 301 cases (6 new categories); fixed single-line-`If` statement dispatch, `Boolean` arithmetic (`True` = -1), `WorksheetFunction` Boolean coercion, `Empty` equality | Done |
| Phase 3A-2 | CI `wasm` job: fresh `wasm-pack` build (Node + web targets) plus a Node/browser-condition smoke test, wired into GitHub Actions | Done |
| 0.5.0 | VBA structural semantics (`:` statement separator, `Variant::Null` with documented propagation, real object-`Nothing` state with alias safety, a runtime `With` target stack) merged with `@elixcee/xlsx` real-consumer/real-browser validation (packed-tarball install, headless-Chrome smoke, bundle-safe WASM loading, `readFile()`); `compat/vba-semantics/` 301 → 386 cases; `elixcee-types` bumped to 0.2.0 for the new public `Variant::Null` enum variant; published to crates.io, PyPI, and GitHub Releases | Done |