@nachoggodino/cello 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BYLAWS.md +446 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +12 -0
- package/README.md +211 -0
- package/dist/cli/cli.d.ts +16 -0
- package/dist/cli/cli.js +360 -0
- package/dist/cli/serve.d.ts +15 -0
- package/dist/cli/serve.js +226 -0
- package/dist/evaluator/evaluate.d.ts +2 -0
- package/dist/evaluator/evaluate.js +129 -0
- package/dist/evaluator/formula.d.ts +13 -0
- package/dist/evaluator/formula.js +141 -0
- package/dist/formatter/format.d.ts +1 -0
- package/dist/formatter/format.js +112 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/parser/parse.d.ts +2 -0
- package/dist/parser/parse.js +552 -0
- package/dist/renderer/render.d.ts +2 -0
- package/dist/renderer/render.js +295 -0
- package/dist/serializer/serialize.d.ts +2 -0
- package/dist/serializer/serialize.js +104 -0
- package/dist/shared/types.d.ts +88 -0
- package/dist/shared/types.js +1 -0
- package/dist/shared/utils.d.ts +16 -0
- package/dist/shared/utils.js +142 -0
- package/dist/validator/validate.d.ts +8 -0
- package/dist/validator/validate.js +10 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/docs/ARCHITECTURE.md +43 -0
- package/docs/CLI.md +58 -0
- package/docs/COMPLIANCE.md +82 -0
- package/docs/ERROR_MODEL.md +25 -0
- package/docs/FORMULA_SUPPORT.md +33 -0
- package/docs/SPEC.md +723 -0
- package/docs/SYNTAX_HIGHLIGHTING.md +91 -0
- package/examples/advanced_kpi.cel +42 -0
- package/examples/basic.cel +8 -0
- package/examples/feature_showcase.cel +37 -0
- package/package.json +96 -0
- package/syntaxes/cel.language-configuration.json +31 -0
- package/syntaxes/cel.tmLanguage.json +250 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
# Cello (.cel) — Specification v1.0
|
|
2
|
+
|
|
3
|
+
> Markdown, but for spreadsheets. Plain-text tabular data with formulas, designed for humans and AI.
|
|
4
|
+
|
|
5
|
+
Cello is a plain-text format for tabular data with formulas. It is human-readable, git-friendly, LLM-friendly, and renders to HTML with multiple sheets, named columns, evaluated formulas, and rich formatting. It is not a replacement for Excel — it is to Excel what Markdown is to Word.
|
|
6
|
+
|
|
7
|
+
The reference implementation is the GPLv3 npm package `@nachoggodino/cello`. Formula evaluation uses HyperFormula under its GPLv3 option.
|
|
8
|
+
|
|
9
|
+
> Implementation note: this document is the target spec. For exact current behavior status, see `docs/COMPLIANCE.md`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Philosophy](#1-philosophy)
|
|
16
|
+
2. [File Structure & Sheets](#2-file-structure--sheets)
|
|
17
|
+
3. [Sheet Input Formats](#3-sheet-input-formats)
|
|
18
|
+
4. [Rows](#4-rows)
|
|
19
|
+
5. [Columns](#5-columns)
|
|
20
|
+
6. [Column Header Rows](#6-column-header-rows)
|
|
21
|
+
7. [Row-Level Formatting](#7-row-level-formatting)
|
|
22
|
+
8. [Data Types](#8-data-types)
|
|
23
|
+
9. [Formulas](#9-formulas)
|
|
24
|
+
10. [Merges](#10-merges)
|
|
25
|
+
11. [Inline Text Formatting](#11-inline-text-formatting)
|
|
26
|
+
12. [Cell Modifiers](#12-cell-modifiers)
|
|
27
|
+
13. [Comments](#13-comments)
|
|
28
|
+
14. [Error Handling](#14-error-handling)
|
|
29
|
+
15. [Reserved Tokens](#15-reserved-tokens)
|
|
30
|
+
16. [Full Example](#16-full-example)
|
|
31
|
+
17. [Implementation Guide](#17-implementation-guide)
|
|
32
|
+
18. [Use Cases](#18-use-cases)
|
|
33
|
+
19. [LLM Integration](#19-llm-integration)
|
|
34
|
+
20. [Out of Scope v1.0](#20-out-of-scope-v10)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. Philosophy
|
|
39
|
+
|
|
40
|
+
**The core insight:** LLMs are bad at arithmetic but good at knowing which formulas to use. Cello lets a LLM describe calculations without performing them — the format evaluates them correctly every time.
|
|
41
|
+
|
|
42
|
+
**Design principles:**
|
|
43
|
+
|
|
44
|
+
- **Fail gracefully** — never a total render failure. Unknown syntax degrades to plain text.
|
|
45
|
+
- **Single-pass parseable** — every construct is resolvable line by line with state. No lookahead needed.
|
|
46
|
+
- **LLM-friendly by design** — syntax is minimal, consistent, and easy to generate from a system prompt.
|
|
47
|
+
- **Human-readable** — a `.cel` file opened in any text editor is immediately understandable.
|
|
48
|
+
- **Git-friendly** — plain text, line-oriented diffs, no binary noise.
|
|
49
|
+
- **Format-agnostic data ingestion** — data sheets accept multiple input formats; analysis sheets use Cello syntax.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. File Structure & Sheets
|
|
54
|
+
|
|
55
|
+
A `.cel` file contains one or more **sheets**. Each sheet is declared with `@sheet`:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
@sheet SheetName [format]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `SheetName` is case-sensitive.
|
|
62
|
+
- `[format]` is optional — if omitted, the sheet uses native Cello syntax.
|
|
63
|
+
- An optional external source line can be provided immediately after the sheet declaration:
|
|
64
|
+
- `-> /ruta/al/archivo.ext`
|
|
65
|
+
- The declared sheet format still controls parsing.
|
|
66
|
+
- Everything between two `@sheet` declarations belongs to the first.
|
|
67
|
+
- A file with no `@sheet` is treated as a single anonymous sheet in native Cello format.
|
|
68
|
+
|
|
69
|
+
### Rendering
|
|
70
|
+
|
|
71
|
+
Sheets render as **horizontal tabs** at the top of the HTML output. The first sheet is active by default. If tabs overflow a single line, they scroll horizontally.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. Sheet Input Formats
|
|
76
|
+
|
|
77
|
+
The `[format]` modifier on `@sheet` defines how the sheet content is parsed. All formats produce the same internal AST — the rest of the pipeline is identical.
|
|
78
|
+
|
|
79
|
+
### 3.1 Native Cello (default)
|
|
80
|
+
|
|
81
|
+
No format modifier needed. Full Cello syntax with formulas, modifiers, merges, etc.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
@sheet Resumen
|
|
85
|
+
|
|
86
|
+
@header | Métrica | Valor[€][2d] |
|
|
87
|
+
| Total ventas | =SUM(Datos!importe) |
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3.2 Delimited formats
|
|
91
|
+
|
|
92
|
+
Any single character can be used as a delimiter:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
@sheet Ventas [,] ← comma-separated (CSV)
|
|
96
|
+
@sheet Ventas [,noheader] ← CSV without header row
|
|
97
|
+
@sheet Exports [\t] ← tab-separated (TSV)
|
|
98
|
+
@sheet EuroData [;] ← semicolon-separated (Excel European)
|
|
99
|
+
@sheet Custom [|] ← pipe-separated
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Named aliases are available as shorthand:
|
|
103
|
+
|
|
104
|
+
| Alias | Equivalent | Notes |
|
|
105
|
+
|-----------|------------|-------|
|
|
106
|
+
| `[csv]` | `[,]` | RFC 4180 compliant |
|
|
107
|
+
| `[tsv]` | `[\t]` | |
|
|
108
|
+
| `[excel]` | `[;]` | European Excel default |
|
|
109
|
+
|
|
110
|
+
The `noheader` flag is available for all delimited formats:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
@sheet Datos [csv:noheader] ← columns assigned letters A, B, C...
|
|
114
|
+
@sheet Datos [\t:noheader]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
All delimited formats use a single generic parser with a delimiter parameter. There is no performance difference between them.
|
|
118
|
+
|
|
119
|
+
### 3.5 External sheet source
|
|
120
|
+
|
|
121
|
+
You can load a sheet from an external file while keeping the same format declaration:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
@sheet Ventas [csv]
|
|
125
|
+
-> ./exports/ventas.csv
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Rules:
|
|
129
|
+
- `-> path` must appear before any row content in that sheet.
|
|
130
|
+
- Relative paths are resolved from the parser `baseDir` (or process cwd when omitted).
|
|
131
|
+
- If loading fails, parsing continues with a warning diagnostic.
|
|
132
|
+
|
|
133
|
+
### 3.3 Markdown tables
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
@sheet Datos [markdown]
|
|
137
|
+
|
|
138
|
+
| nombre | edad | ciudad |
|
|
139
|
+
|--------|------|--------|
|
|
140
|
+
| Ana | 25 | Madrid |
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The separator row (`|---|---|`) is ignored. First row is treated as headers. This allows copy-pasting existing Markdown tables directly into a `.cel` file.
|
|
144
|
+
|
|
145
|
+
### 3.4 JSON
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
@sheet Datos [json]
|
|
149
|
+
|
|
150
|
+
[
|
|
151
|
+
{"nombre": "Ana", "edad": 25, "ciudad": "Madrid"},
|
|
152
|
+
{"nombre": "Luis", "edad": 32, "ciudad": "Barcelona"}
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- Only flat arrays of objects are supported in v1.0.
|
|
157
|
+
- First object's keys become column headers.
|
|
158
|
+
- For complex nested JSON, flatten externally first and paste the result.
|
|
159
|
+
- JSONPath selection is planned for v1.1: `[json:$.items]`
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 4. Rows
|
|
164
|
+
|
|
165
|
+
- Every row is a line containing `|` delimiters.
|
|
166
|
+
- Rows are **auto-numbered** starting at 1, top to bottom, per sheet.
|
|
167
|
+
- A **blank line** (no `|`) does not consume a row number and is not rendered.
|
|
168
|
+
- Leading and trailing `|` are optional but recommended for readability.
|
|
169
|
+
- Multiple consecutive spaces inside a cell **collapse to one** on render — use spaces freely to align columns in plain text.
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
| Manzanas | 1.20 | 50 | ← renders as "Manzanas", "1.20", "50"
|
|
173
|
+
| Peras | 0.90 | 30 |
|
|
174
|
+
|
|
175
|
+
| TOTAL | ... | ... | ← row 3 (blank line is ignored)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 5. Columns
|
|
181
|
+
|
|
182
|
+
- Columns are **auto-assigned a letter** (A, B, C... Z, AA, AB...) left to right.
|
|
183
|
+
- Column letters are scoped per sheet and reset at each `@sheet`.
|
|
184
|
+
- Both letter references and named references are always valid simultaneously.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. Column Header Rows
|
|
189
|
+
|
|
190
|
+
A line that starts with `@header` defines **column names** for all rows below until the next header row:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
@header | Producto | Precio | Cantidad | Total |
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- Column names apply from the next data row downward.
|
|
197
|
+
- A second header row redefines names from that point on.
|
|
198
|
+
- Named references in formulas resolve against the active column header.
|
|
199
|
+
- Header rows render as `<th>` elements by default.
|
|
200
|
+
- Modifiers `[]` on a column header **apply to all cells in that column**.
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
@header | Producto | Precio[€][2d] | Stock[0d][bg:#fff9c4] | Activo |
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`[hidden]` is currently parsed into column metadata (`column.hidden`) and can be used by tooling; current renderer does not hide header/cells yet.
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
@header | Producto | Precio[hidden] | Total |
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 7. Row-Level Formatting
|
|
215
|
+
|
|
216
|
+
Modifier blocks placed **before the first `|`** apply to every cell in that row. Arbitrary text before the first pipe is not part of the public Cello format.
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
| Manzanas | 1.20 | 50 | =Precio*Cantidad |
|
|
220
|
+
| Peras | 0.90 | 30 | =Precio*Cantidad |
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- Row modifiers are parsed and preserved in AST/serialization.
|
|
224
|
+
- Row-name formula references (for example `Sheet!row_name.Column`) are not supported.
|
|
225
|
+
- Rows are referenceable by number.
|
|
226
|
+
- Only modifier blocks should appear before the first pipe.
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
[bold][bg:#f5f5f5] | TOTAL | < | < | =SUM(Total) |
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 8. Data Types
|
|
235
|
+
|
|
236
|
+
Types are inferred automatically:
|
|
237
|
+
|
|
238
|
+
| Type | Rule | Example |
|
|
239
|
+
|---------|-------------------------------|---------------|
|
|
240
|
+
| Number | Parseable as numeric | `42`, `3.14` |
|
|
241
|
+
| Date | Matches `YYYY-MM-DD` | `2024-01-15` |
|
|
242
|
+
| Boolean | Literal `TRUE` or `FALSE` | `TRUE` |
|
|
243
|
+
| Text | Anything else | `hello`, `—` |
|
|
244
|
+
|
|
245
|
+
Force text type with double quotes:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
| "123" | ← text, not number
|
|
249
|
+
| "TRUE" | ← text, not boolean
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 9. Formulas
|
|
255
|
+
|
|
256
|
+
Any cell value starting with `=` is a formula. Evaluation is delegated to HyperFormula, with Cello-side translation for supported named column references.
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
| =Precio*Cantidad |
|
|
260
|
+
| =SUM(Total) |
|
|
261
|
+
| =B2*C2 |
|
|
262
|
+
| =IF(Margen>0.2,"✓","✗") |
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Both **named references** and **coordinate references** are valid and can be mixed freely.
|
|
266
|
+
|
|
267
|
+
### 9.1 Column range references
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
=Precio ← current row's Precio cell, current sheet
|
|
271
|
+
=SUM(Precio) ← Precio rows before current formula row, current sheet
|
|
272
|
+
=SUM(Precio[*]) ← full Precio data column, current sheet
|
|
273
|
+
=SUM(Precio[2:5]) ← rows 2–5 of Precio column
|
|
274
|
+
=AVG(Margen) ← Margen rows before current formula row
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Same-sheet named column references are context-sensitive:
|
|
278
|
+
|
|
279
|
+
- In scalar context, `Precio` resolves to the current row cell in that column.
|
|
280
|
+
- In aggregate/range context on the same sheet, `Precio` resolves from the first data row up to the row before the formula row. This prevents footer totals like `=SUM(Precio)` from including themselves.
|
|
281
|
+
- `Precio[*]` forces the full data span of the column, including rows below the formula row.
|
|
282
|
+
|
|
283
|
+
### 9.2 Cross-sheet references
|
|
284
|
+
|
|
285
|
+
Use `!` as the separator:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
=SUM(Ventas!Total)
|
|
289
|
+
=SUM(Ventas!Total[*])
|
|
290
|
+
=Ventas!B4
|
|
291
|
+
=COUNTIF(Datos!edad,25)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`!!` is an alias for the first sheet name in the workbook:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
=SUM(!!amount)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 9.3 Formula evaluation order
|
|
301
|
+
|
|
302
|
+
Dependencies are resolved automatically via topological sort (HyperFormula). Circular references are detected and reported as `#CIRCULAR!`.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 10. Merges
|
|
307
|
+
|
|
308
|
+
### 10.1 Horizontal merge
|
|
309
|
+
|
|
310
|
+
A cell containing only `<` merges with the previous cell (extends rightward):
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
| ## Informe Q1 | < | < | valor | ← spans 3 columns
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 10.2 Vertical merge
|
|
317
|
+
|
|
318
|
+
A cell containing only `^` merges with the cell directly above (extends downward):
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
| Grupo A | val1 |
|
|
322
|
+
| ^ | val2 | ← "Grupo A" spans 2 rows
|
|
323
|
+
| ^ | val3 | ← spans 3 rows
|
|
324
|
+
| Grupo B | val4 |
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 10.3 Parser resolution
|
|
328
|
+
|
|
329
|
+
Merge tokens are resolved during the single-pass parse using the previously built AST rows. No lookahead required — `<` looks left in the current row, `^` looks up in the previous row at the same column index.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 11. Inline Text Formatting
|
|
334
|
+
|
|
335
|
+
Markdown-style inline formatting is supported inside cell content:
|
|
336
|
+
|
|
337
|
+
| Syntax | Result |
|
|
338
|
+
|---------------|---------------------|
|
|
339
|
+
| `*texto*` | **bold** |
|
|
340
|
+
| `_texto_` | *italic* |
|
|
341
|
+
| `~~texto~~` | ~~strikethrough~~ |
|
|
342
|
+
| `# texto` | Heading style (`cello-h2`) |
|
|
343
|
+
| `## texto` | Heading style (`cello-h1`) |
|
|
344
|
+
|
|
345
|
+
`#` and `##` apply to the entire cell and cannot be combined with other inline formatting in the same cell.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 12. Cell Modifiers `[]`
|
|
350
|
+
|
|
351
|
+
Modifiers control formatting and rendering. They appear after a value with no space:
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
celda[bold][bg:red]
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Modifier scope and inheritance:**
|
|
358
|
+
|
|
359
|
+
| Where applied | Scope |
|
|
360
|
+
|---------------|-------|
|
|
361
|
+
| Column header `@header | Col[mod] |` | All cells in that column |
|
|
362
|
+
| Row name `ref[mod]` | All cells in that row |
|
|
363
|
+
| Individual cell `value[mod]` | That cell only |
|
|
364
|
+
|
|
365
|
+
Individual cell modifiers **override** column and row modifiers on conflict. Row modifiers override column modifiers.
|
|
366
|
+
|
|
367
|
+
### 12.1 Column default formulas
|
|
368
|
+
|
|
369
|
+
| Modifier | Meaning |
|
|
370
|
+
|----------|---------|
|
|
371
|
+
| `@defaults | ... |` | Fill empty cells in each matching column with the formula |
|
|
372
|
+
|
|
373
|
+
`default` is a column-only behavior declared in a non-rendered `@defaults` row below the active header. Header, row, and cell-level default modifiers are ignored. The formula may include the leading `=` or omit it. Explicit row values and formulas always win over the column default.
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
@header | Qty | Price | Total[€][2d] |
|
|
377
|
+
@defaults | | | =Qty*Price |
|
|
378
|
+
| 2 | 3 | ← Total becomes =Qty*Price and renders as €6.00
|
|
379
|
+
| 4 | 5 | 99 ← explicit Total value is preserved
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`@defaults` rows update column metadata only. They do not render and do not consume row numbers.
|
|
383
|
+
|
|
384
|
+
### 12.2 Numeric format
|
|
385
|
+
|
|
386
|
+
| Modifier | Meaning |
|
|
387
|
+
|----------|---------|
|
|
388
|
+
| `[€]` | Prefix with € symbol |
|
|
389
|
+
| `[$]` | Prefix with $ symbol |
|
|
390
|
+
| `[£]` | Prefix with £ symbol |
|
|
391
|
+
| `[%]` | Percentage format |
|
|
392
|
+
| `[Nd]` | N decimal places (e.g. `[2d]`, `[0d]`) |
|
|
393
|
+
|
|
394
|
+
These modifiers are parsed, preserved in AST, and applied by the renderer to numeric cells. When used on a column header, they apply to every numeric cell in that column. Row and cell modifiers follow the normal precedence rules, so a row or cell can override column decimal/currency choices. Percent display multiplies numeric values by 100 before appending `%`.
|
|
395
|
+
|
|
396
|
+
### 12.3 Color
|
|
397
|
+
|
|
398
|
+
| Syntax | Meaning |
|
|
399
|
+
|----------------|---------|
|
|
400
|
+
| `[#rrggbb]` | Text color (hex) |
|
|
401
|
+
| `[bg:#rrggbb]` | Background color (hex) |
|
|
402
|
+
| `[colorname]` | Text color (CSS named color) |
|
|
403
|
+
| `[bg:colorname]` | Background color (CSS named color) |
|
|
404
|
+
| `[#bg:#fg]` | Both colors shorthand: background:text |
|
|
405
|
+
|
|
406
|
+
Named colors are standard CSS color names (`red`, `blue`, `green`, `orange`, `gold`, etc.).
|
|
407
|
+
|
|
408
|
+
### 12.4 Style
|
|
409
|
+
|
|
410
|
+
| Modifier | Meaning |
|
|
411
|
+
|------------|---------|
|
|
412
|
+
| `[bold]` | Bold text |
|
|
413
|
+
| `[italic]` | Italic text |
|
|
414
|
+
| `[hidden]` | Parsed metadata flag (render-time hiding not implemented yet) |
|
|
415
|
+
|
|
416
|
+
### 12.5 Tone presets
|
|
417
|
+
|
|
418
|
+
| Modifier | Meaning |
|
|
419
|
+
|----------|---------|
|
|
420
|
+
| `[tone:ok]` | Positive/success emphasis |
|
|
421
|
+
| `[tone:warn]` | Warning/caution emphasis |
|
|
422
|
+
| `[tone:error]` | Error/failure emphasis |
|
|
423
|
+
| `[tone:info]` | Informational emphasis |
|
|
424
|
+
| `[tone:muted]` | Secondary/de-emphasized emphasis |
|
|
425
|
+
| `[tone:accent]` | Primary highlight emphasis |
|
|
426
|
+
|
|
427
|
+
Tone presets are rendered as CSS classes (`cello-tone-*`) rather than inline colors so host applications can override them with custom CSS. The built-in renderer defines default foreground/background pairs through CSS variables on `.cello-workbook`.
|
|
428
|
+
|
|
429
|
+
### 12.6 Combined example
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
@header | Producto | Precio[€][2d] | Margen[%][1d][bg:#e8f5e9] |
|
|
433
|
+
|
|
434
|
+
[bold][bg:#f0f0f0] | ## TOTAL | < | =SUM(Precio) | =SUM(Margen) |
|
|
435
|
+
|
|
436
|
+
| valor crítico[bg:red][#fff] | < | dato |
|
|
437
|
+
| estado[tone:accent] | normal[tone:ok] | atención[tone:warn] |
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 13. Comments
|
|
443
|
+
|
|
444
|
+
Supported **outside rows only**, using `//`. Not supported inside cell values.
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
// Annual sales report — generated by agent on 2024-01-15
|
|
448
|
+
|
|
449
|
+
@sheet Datos [csv]
|
|
450
|
+
// Raw data from CRM export
|
|
451
|
+
nombre,edad,importe
|
|
452
|
+
Ana,25,120
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## 14. Error Handling
|
|
458
|
+
|
|
459
|
+
Primary non-strict contract: parsing/evaluation tries to continue and diagnostics accumulate on `workbook.diagnostics`.
|
|
460
|
+
|
|
461
|
+
| Stage | Behavior |
|
|
462
|
+
|-------|----------|
|
|
463
|
+
| Parse | Non-row native lines -> warning diagnostics and skipped |
|
|
464
|
+
| Parse (json sheet) | Invalid JSON -> warning + single fallback text row |
|
|
465
|
+
| Evaluate | Missing HyperFormula -> warning, formulas left unresolved |
|
|
466
|
+
| Evaluate | Engine/runtime failure -> error diagnostic; throw only in strict evaluate mode |
|
|
467
|
+
| Formula parse error in engine | `computed` falls back to original formula text |
|
|
468
|
+
|
|
469
|
+
**Strict mode:** `strict: true` propagates parse/evaluate throws; warnings alone do not throw.
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
render(celContent, { strict: true }) // throws when parse/evaluate throw
|
|
473
|
+
render(celContent) // returns HTML, diagnostics on AST/eval path
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## 15. Reserved Tokens
|
|
479
|
+
|
|
480
|
+
| Token | Meaning |
|
|
481
|
+
|------------|---------|
|
|
482
|
+
| `@sheet` | Sheet declaration |
|
|
483
|
+
| `=` | Formula prefix (start of cell value) |
|
|
484
|
+
| `<` | Horizontal merge continuation |
|
|
485
|
+
| `^` | Vertical merge continuation |
|
|
486
|
+
| `@header` | Column header row marker |
|
|
487
|
+
| `//` | Comment (outside rows only) |
|
|
488
|
+
| `"..."` | Force text type |
|
|
489
|
+
| `!` | Cross-sheet reference separator |
|
|
490
|
+
| `->` | External sheet source line |
|
|
491
|
+
| `[n:m]` | Row range in column references |
|
|
492
|
+
| `[...]` | Modifier block |
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 16. Full Example
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
// Q1 Sales Report — generated 2024-01-15
|
|
500
|
+
|
|
501
|
+
@sheet Datos [csv]
|
|
502
|
+
nombre,edad,ciudad,importe
|
|
503
|
+
Ana,25,Madrid,120
|
|
504
|
+
Luis,32,Barcelona,340
|
|
505
|
+
Sara,25,Madrid,89
|
|
506
|
+
Pedro,32,Valencia,210
|
|
507
|
+
Marta,25,Barcelona,95
|
|
508
|
+
|
|
509
|
+
@sheet Por_Edad
|
|
510
|
+
|
|
511
|
+
// KPIs grouped by age
|
|
512
|
+
@header | edad[0d] | total[€][2d] | contador[0d] | ticket_medio[€][2d] |
|
|
513
|
+
| 25 | =SUMIF(Datos!edad,25,Datos!importe) | =COUNTIF(Datos!edad,25) | =total/contador |
|
|
514
|
+
| 32 | =SUMIF(Datos!edad,32,Datos!importe) | =COUNTIF(Datos!edad,32) | =total/contador |
|
|
515
|
+
|
|
516
|
+
[bold][bg:#f0f0f0] | ## TOTAL | =SUM(total) | =SUM(contador) | =SUM(total)/SUM(contador) |
|
|
517
|
+
|
|
518
|
+
@sheet Resumen
|
|
519
|
+
|
|
520
|
+
@header | Métrica | Valor |
|
|
521
|
+
| Total revenue | =SUM(Por_Edad!total) |
|
|
522
|
+
| Total clientes | =SUM(Por_Edad!contador) |
|
|
523
|
+
| Ticket medio | =AVG(Por_Edad!ticket_medio) |
|
|
524
|
+
| Fecha análisis | 2024-01-15 |
|
|
525
|
+
| Generado por | "agente-ventas-v2" |
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## 17. Implementation Guide
|
|
531
|
+
|
|
532
|
+
### 17.1 Library architecture
|
|
533
|
+
|
|
534
|
+
The reference implementation is a TypeScript/JavaScript npm package called `@nachoggodino/cello`. It exposes five core functions:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
parse(text: string, options?): AST
|
|
538
|
+
evaluate(ast: AST, options?): Promise<AST>
|
|
539
|
+
validate(text: string, options?): Promise<{ valid: boolean, diagnostics: Diagnostic[] }>
|
|
540
|
+
render(input: string | AST, options?: { strict?, title?, baseDir?, evaluate?, format?: "document" | "fragment" }): Promise<string>
|
|
541
|
+
serialize(ast: AST): string
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
There are no built-in AST mutation helpers in current public API.
|
|
545
|
+
|
|
546
|
+
### 17.2 Parser design
|
|
547
|
+
|
|
548
|
+
The parser processes the file in a **single pass**, line by line. It maintains these state variables:
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
let currentSheet = null
|
|
552
|
+
let currentHeaders = []
|
|
553
|
+
let previousRowByColumn = new Map()
|
|
554
|
+
let jsonBufferBySheet = new Map()
|
|
555
|
+
let consumedDelimitedHeaderBySheet = new Set()
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
For each line, the parser checks in order:
|
|
559
|
+
1. Is it a comment (`//`)? → skip
|
|
560
|
+
2. Is it a `@sheet` declaration? → open new sheet, reset state
|
|
561
|
+
3. Is it a header row (`@header | ... |`)? → update `currentHeaders`
|
|
562
|
+
4. Is it a defaults row (`@defaults | ... |`)? → update active column defaults
|
|
563
|
+
5. Is it a data row (`|`)? → parse as Cello row
|
|
564
|
+
6. Is it a blank line? → ignore (does not consume row number)
|
|
565
|
+
7. Otherwise → handle by active sheet format rules (native/delimited/markdown/json)
|
|
566
|
+
|
|
567
|
+
Merge tokens are resolved immediately during row parsing:
|
|
568
|
+
- `<` → extend the previous cell's `colspan` in the current row
|
|
569
|
+
- `^` → extend the cell above's `rowspan` in `lastRow` at the same column index
|
|
570
|
+
|
|
571
|
+
### 17.3 Formula evaluation
|
|
572
|
+
|
|
573
|
+
HyperFormula is used as the formula engine when the workbook contains formulas. Formula-free workbooks skip engine loading. The package is licensed as GPLv3 and configures HyperFormula with `licenseKey: "gpl-v3"`. The integration flow:
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
AST (with formula strings)
|
|
577
|
+
↓
|
|
578
|
+
Translate named refs to coordinates
|
|
579
|
+
"=SUM(Precio)" → "=SUM(B2:B9)" // same-sheet footer total excludes current row
|
|
580
|
+
"=SUM(Precio[*])" → "=SUM(B2:B10)"
|
|
581
|
+
"=Ventas!Total" → sheet cross-reference
|
|
582
|
+
↓
|
|
583
|
+
Feed all sheets to HyperFormula
|
|
584
|
+
↓
|
|
585
|
+
HyperFormula resolves dependency graph (topological sort)
|
|
586
|
+
↓
|
|
587
|
+
Retrieve computed values cell by cell
|
|
588
|
+
↓
|
|
589
|
+
AST with computed values alongside original formulas
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The name-to-coordinate translation is the only custom logic needed. HyperFormula handles dependency resolution, error propagation, and all function implementations.
|
|
593
|
+
|
|
594
|
+
### 17.4 Renderer
|
|
595
|
+
|
|
596
|
+
The renderer evaluates by default, unless `evaluate: false` is passed. It walks the selected AST and produces HTML in one of two shapes:
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
<style> /* inline CSS */ </style>
|
|
600
|
+
<div class="cello-workbook">
|
|
601
|
+
<div class="cello-tabs">
|
|
602
|
+
<button class="tab active">Datos</button>
|
|
603
|
+
<button class="tab">Por_Edad</button>
|
|
604
|
+
<button class="tab">Resumen</button>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="cello-sheet active"> ... </div>
|
|
607
|
+
<div class="cello-sheet hidden"> ... </div>
|
|
608
|
+
<div class="cello-sheet hidden"> ... </div>
|
|
609
|
+
</div>
|
|
610
|
+
<script> /* inline tab switching JS */ </script>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
`format: "document"` is the default and returns a self-contained HTML document (`<!doctype html>`) with `html`, `head`, and `body` wrappers. `format: "fragment"` returns only the inline CSS, workbook container, and inline JS so the output can be embedded inside an existing page. No external dependencies are required in either format.
|
|
614
|
+
|
|
615
|
+
Rendered tables include spreadsheet coordinate chrome: a synthetic top row displays column letters (`A`, `B`, `C`...), and a synthetic first column displays semantic row numbers. This chrome is presentation-only; it is not part of the AST or serialized `.cel` text. Header rows use the same row numbering as formulas, so a header at the top of a sheet is row `1` and the first value row below it is row `2`.
|
|
616
|
+
|
|
617
|
+
### 17.5 Serializer
|
|
618
|
+
|
|
619
|
+
The serializer converts an AST back to valid `.cel` text. This enables round-trip editing: parse → mutate → serialize → parse again.
|
|
620
|
+
|
|
621
|
+
```
|
|
622
|
+
AST → serializer → .cel text
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Rules:
|
|
626
|
+
- Emits normalized Cello row text (`| ... |`) rather than preserving original spacing/alignment
|
|
627
|
+
- Reconstructs header rows from column metadata
|
|
628
|
+
- Outputs row modifiers before data rows where they exist in the AST
|
|
629
|
+
- Preserves modifier order from parsed AST (`modifiers` array order)
|
|
630
|
+
|
|
631
|
+
### 17.6 Ecosystem components
|
|
632
|
+
|
|
633
|
+
| Component | Description | Priority |
|
|
634
|
+
|-----------|-------------|----------|
|
|
635
|
+
| `@nachoggodino/cello` (npm) | GPLv3 core library: parse, evaluate, validate, render, serialize | v1 |
|
|
636
|
+
| `cello` CLI | CLI tool: `cello render file.cel > out.html`; `cello serve file.cel` for live previews | v1 |
|
|
637
|
+
| `cello-playground` | Web playground: split-view editor + live preview | v1 |
|
|
638
|
+
| `cello-python` | Python port of parser + renderer | v2 |
|
|
639
|
+
| `cello-vscode` | VSCode extension with live preview | v2 |
|
|
640
|
+
| `cello-react` | React component wrapper | v2 |
|
|
641
|
+
|
|
642
|
+
### 17.7 Format converters
|
|
643
|
+
|
|
644
|
+
Converters transform external formats into `.cel` text. They are separate utilities, not part of the core library.
|
|
645
|
+
|
|
646
|
+
| Converter | Input | Output | Notes |
|
|
647
|
+
|-----------|-------|--------|-------|
|
|
648
|
+
| `fromCSV(csv, sheetName?)` | CSV string | `@sheet [csv]` block | Trivial |
|
|
649
|
+
| `fromXLSX(buffer)` | Excel binary | Multi-sheet `.cel` | Via SheetJS |
|
|
650
|
+
| `fromMarkdown(md)` | Markdown tables | `@sheet [markdown]` block | |
|
|
651
|
+
| `fromJSON(json, path?)` | JSON array | `@sheet [json]` block | Flat arrays only |
|
|
652
|
+
| `toCSV(cel, sheet)` | `.cel` text | CSV string | Per-sheet export |
|
|
653
|
+
| `toXLSX(cel)` | `.cel` text | Excel binary | Via SheetJS |
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## 18. Use Cases
|
|
658
|
+
|
|
659
|
+
### 18.1 Primary pattern: Data sheet + KPI sheets
|
|
660
|
+
|
|
661
|
+
The dominant pattern for LLM-generated analysis:
|
|
662
|
+
|
|
663
|
+
```
|
|
664
|
+
@sheet RawData [csv] ← data, any size, LLM never touches this
|
|
665
|
+
...
|
|
666
|
+
|
|
667
|
+
@sheet KPIs ← LLM generates this from schema only
|
|
668
|
+
...formulas referencing RawData...
|
|
669
|
+
|
|
670
|
+
@sheet Summary ← LLM generates executive summary sheet
|
|
671
|
+
...
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
The LLM reads only the first N rows to understand the schema, then generates the analysis sheets with formulas. A tool handles the full data conversion.
|
|
675
|
+
|
|
676
|
+
### 18.2 Use case examples
|
|
677
|
+
|
|
678
|
+
**Sales analysis**
|
|
679
|
+
```
|
|
680
|
+
"Here's our monthly sales CSV. Group by region, show total and average ticket."
|
|
681
|
+
→ LLM generates KPI sheet with SUMIF/COUNTIF/AVGIF per region
|
|
682
|
+
→ Cello evaluates correctly
|
|
683
|
+
→ Human reads clean tabular render
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Budget vs actuals**
|
|
687
|
+
```
|
|
688
|
+
"Here are my actual expenses and my budget plan."
|
|
689
|
+
→ Sheet 1: actuals [csv], Sheet 2: budget [csv]
|
|
690
|
+
→ LLM generates Sheet 3 with variance formulas and color coding
|
|
691
|
+
→ Over-budget cells highlighted in red automatically
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Financial due diligence**
|
|
695
|
+
```
|
|
696
|
+
"Here are 3 years of P&L data."
|
|
697
|
+
→ One sheet per year [csv]
|
|
698
|
+
→ LLM generates ratios sheet: CAGR, margins, YoY growth
|
|
699
|
+
→ All calculated by Cello, not inferred by LLM
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Data reconciliation**
|
|
703
|
+
```
|
|
704
|
+
"These two CSVs should match. Find discrepancies."
|
|
705
|
+
→ Sheet 1 and Sheet 2 with source data
|
|
706
|
+
→ LLM generates Sheet 3 with VLOOKUP cross-sheet comparisons
|
|
707
|
+
→ Mismatches surface automatically
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**Projection modeling**
|
|
711
|
+
```
|
|
712
|
+
"If I grow 10% monthly from €1000, show me 12 months."
|
|
713
|
+
→ LLM generates single sheet with growth formula per row
|
|
714
|
+
→ No arithmetic by the LLM — just the formula pattern
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**LLM reading back evaluated results**
|
|
718
|
+
```
|
|
719
|
+
// Two-step agentic flow:
|
|
720
|
+
Step 1: LLM generates .cel with formulas
|
|
721
|
+
Step 2: cello_evaluate() resolves all formulas to values
|
|
722
|
+
Step 3: cello_to_markdown_table() converts result sheet to plain text
|
|
723
|
+
Step 4: LLM reads plain numbers and generates written ana
|