table is the type with the most rules in the specification: two bodies, one model, a closed arithmetic, one summary row. GEP-0012's proposed view moves "select, derive and aggregate from another table" out of it, and intends to take "borrowing another table's rows" away from table. This page first measures every rule table has today, then draws the view draft, marking what is specified, what is draft, and where the two collide.
Specified written into the specification. GEP draft defined by a proposal, not yet in the spec. Observed not specified; what the reference implementation does today. Draft gap something a draft should say and does not. No implementation gap was found on this page.
| Rule | Source | Status | |
|---|---|---|---|
| table | |||
| 1 | Two interchangeable bodies, one model: a visual pipe grid (the default), or a format=csv/tsv data body with header=. The model is a column model; a cell is a string, number or boolean; dates are ISO-8601 text. | §6 | Specified |
| 2 | delim= applies only to a data body and must be exactly one character; a tab is spelled format=tsv. Two characters is a bad-table-delimiter error that falls back to the natural delimiter; with no format it is an ignored-table-delimiter warning. An unknown format is a warning, parsed as a visual grid. | §6 · A.3 | Specified |
| 3 | A table's src= takes one target: a csv/tsv data file (relative path or http(s)). Unresolvable: error; src with a body: error; a src= naming a block — #id or doc.geml#id — is an error that names view (§6.1), because a block's output is something another block derived. The three-target form below belongs to view. Only the src text enters the .gemlhistory hash, never the contents. | §6 · A.3 | Specified |
| 4 | compute= (a view's attribute, §6.1 — on a table it is an unknown-attribute warning and computes nothing): Name = expr entries separated by ;, arithmetic + - * / ( ) and unary minus; columns by header name (single-quoted when it has a space) or letter; an earlier computed column may be referenced, acyclically. An empty or non-numeric cell counts as 0 with a compute-non-numeric-cell warning; division by zero yields no value, displays -, and warns compute-not-a-number. Computed columns are appended after the data columns and never written into the body. | §6 · A.3 | Specified |
| 5 | summary= (a view's attribute, §6.1 — same warning on a table): one foot row of Cell = value entries; a value is a literal label or an aggregate expression; the aggregates sum avg min max count are the only construct that crosses rows; every column reference must be reduced by an aggregate; unspecified columns are blank; an unknown column is an error. | §6 · A.3 | Specified |
| 6 | A [printf] display format binds to the name on the left of a formula and affects display only; it is the last […] group of the left side that contains a %, so a column name may itself be bracketed. | §6 | Specified |
| 7 | Excluded by design: cell and range addressing, relative-row references, conditionals, cross-table remote(), lookup, any embedded program. A table is a document feature, not a spreadsheet engine. | §6 | Specified |
| 8 | External data arrives at render time, so the column names used by compute and by a chart are validated then, not at build time. | §6 · §9.4 | Specified |
| 9 | Rendering: figure.table-figure > table.geml-table; computed cells are td.computed, numeric cells carry data-sort, the summary row sits in tfoot, the caption in figcaption. | --to html | Observed |
| 10 | Coordinates: #fy[2] a row (1-based; the header is not a row), #fy[2]["Q1"] a cell, #fy["Q1"] a column, #fy[summary]["FY"] the summary cell. An integer is a row, a quoted string a column, a bare word a reserved row name. list does not enumerate coordinates. | GEP-0011 | GEP draft |
| 11 | Borrowing: a table whose src= names a block is an error that names view — table source `#fy` names another block's output; use `view`. A block target is someone else's output, and a table that borrows it is no longer "facts". A short-lived table {src=#fy compute=…} form was legal before this landed; writing it now earns that error plus an unknown-attribute warning for compute. | §6 · §6.1 | Specified |
view (§6.1 — registered, so no unknown-block-type warning; GEP-0012's own front matter still reads state: draft, but the specification has absorbed it) | |||
| 12 | view has no body and a required src=, with §6's three targets; a body alongside src= is an error. | §6.1 | Specified |
| 13 | Selection: where= (comparisons, and/or/not, numbers or single-quoted literals), order= (asc/desc, stable), limit=, select= (names only; an = inside it is an error pointing at compute). | §6.1 | Specified |
| 14 | Derivation and aggregation: compute=/summary= keep their grammar and move to view; by= names grouping keys, aggregate="Name = fn(col)" the group columns; by= without aggregate is the distinct set. | §6.1 | Specified |
| 15 | Evaluation order is SQL's logical order: src → per-row compute → where → aggregate compute → by/aggregate → order → limit → select → summary. Compute runs in two passes so where may use a column defined in the same block; select runs late so order may sort on an unshown column; summary runs last, as a report over the rows shown. | §6.1 | Specified |
| 16 | One expression language: a single-quoted run is a column name in column position and a literal in value position. An unknown column in where= is an error; a text-only column compared against a number is an error; a single non-numeric cell simply does not match, with no diagnostic. No regexes, no like, no functions. | §6.1 | Specified |
| 17 | A view sees the source's tuples plus its computed columns, never its summary row; a compute= shadowing a source column is legal with a shadowed-source-column warning. Filtering groups is the next view, not a HAVING. | §6.1 | Specified |
| 18 | A chart reads a view: §7 says data= "may name a view as readily as a table, and for a derived column it must" — charting FY is data=#fy-report y=FY, and aiming it at the base table is chart: column `FY` not found in table. rows=all|summary acts on the view's own summary row; with no summary= on the view, rows=summary is an error and rows=all warns and uses the data rows. (Carried here as a draft gap while GEP-0012 said nothing about charts.) | §7.1 · §6.1 | Specified |
On the left, a pipe table, a csv table, and the view that computes and summarises over it; on the right, the structure --to html actually emits.
\ line continuation (§4); the backslashes are load-bearing — drop one and the whole block becomes a paragraph.=== table {#budget caption="Annual cost"} | Plan | Months | Rate | |-------|-------:|-----:| | Basic | 1 | 30 | | Pro | 2 | 30 | === === table {#fy caption="FY revenue ($M)" format=csv header=1 delim=;} Segment ; Q1 ; Q2 Cloud ; 124.5 ; 131.2 Hardware; 88.1 ; 84.6 Services; 45.2 ; 47.8 === === view {#fy-report src=#fy \ compute="FY [%.1f] = Q1 + Q2; Share [%.1f%%] = FY * 100 / 500" \ summary="Segment = 'Total'; Q1 = sum(Q1); Q2 = sum(Q2); FY = sum(FY)"} === %% the table holds facts; the view derives and summarises See [[#fy]] and [[#budget]].
| Plan | Months | Rate |
|---|---|---|
| Basic | 1 | 30 |
| Pro | 2 | 30 |
-----: in the separator row; numeric cells carry data-sort.
figure.table-figure #fy-report › the view's output, not the table's
| Segment | Q1 | Q2 | FY | Share |
|---|---|---|---|---|
| Cloud | 124.5 | 131.2 | 255.7 | 51.1% |
| Hardware | 88.1 | 84.6 | 172.7 | 34.5% |
| Services | 45.2 | 47.8 | 93.0 | 18.6% |
| Total | 257.8 | 263.6 | 521.4 |
td.computed: not in the body, produced by the model. The summary row sits in tfoot; the unspecified Share column is blank. [[#fy]] reads "FY revenue ($M)", the caption.
FY [%.1f]: the format is the last bracket group on the left that contains a %, so 93 displays as 93.0 while the model keeps data-sort="93". %% is a literal percent sign. There is no date format; a date is ISO text.+ - * / ( ) and unary minus, *// binding tighter, left-associative. A formula may use an earlier computed column (Share uses FY) and must be acyclic. This is the one bit of closed arithmetic §9.1's "no expression language" allows.GEP-0011, implemented on this branch, not yet in the spec. Three token species that cannot be confused: an integer is a row, a quoted string a column, a bare word a reserved row name.
#fy above GEP draft$ geml get t1-table.geml '#fy[2]' second body row; the header is not a row Hardware; 88.1; 84.6; 172.7; 34.5% $ geml get t1-table.geml '#fy[2]["Q1"]' 88.1 $ geml get t1-table.geml '#fy["Q1"]' one column, header excluded 124.5 88.1 45.2 $ geml get t1-table.geml '#fy[summary]["FY"]' 521.4 $ geml get t1-table.geml '#fy[summary]' Total; 257.8; 263.6; 521.4; $ geml get t1-table.geml '#borrowed[1]["Half"]' a column the borrowing table computed itself 127.85
| Written | Names |
|---|---|
| #fy[2] | one body row, 1-based. The header is not a row: the model's rows already exclude it |
| #fy[2]["Q1"] | one cell, the column by its header |
| #fy["Q1"] | one column, in row order, header excluded |
| #fy[summary]["FY"] | a cell in the summary row; [summary] is the only reserved row name and inserting a row does not move it |
| A.geml#fy[summary]["FY"] | the same across documents; ![[…]] may project that one leaf value into a sentence |
A, B: the same column namespace compute= reads. geml list does not enumerate coordinates — a four-hundred-row table would drown it — so the author constructs them.
src=-fed tables refuse; so does a format=csv cell that contains the delimiter, since changing one byte would corrupt its neighbours.[ cannot occur in a legal id (§4's NAME is letters, digits, -, _), so a coordinate is never read as an id and no existing document changes meaning.A table's src= takes a file. Borrowing another block's relation is a view's job — §6 took that target away from table and named the replacement.
=== table {#ext caption="From a file" format=csv src=rows.csv} === === view {#borrowed caption="Borrowed rows" src=#fy-report compute="Half = FY / 2"} === %% rows.csv beside the document: Segment,Q1,Q2 / Cloud,124.5,131.2 / Hardware,88.1,84.6 %% #borrowed gets #fy-report's tuples and its computed FY, then computes its own Half
=== table {#borrowed src=#fy-report compute="Half = FY / 2"} === %% error: table source `#fy-report` names another block's output; use `view` %% plus: warning: unknown attribute `compute` for block type `table`
| Segment | Q1 | Q2 |
|---|---|---|
| Cloud | 124.5 | 131.2 |
| Hardware | 88.1 | 84.6 |
| Segment | Q1 | Q2 | FY | Half |
|---|---|---|---|---|
| Cloud | 124.5 | 131.2 | 255.7 | 127.85 |
| Hardware | 88.1 | 84.6 | 172.7 | 86.35 |
| Services | 45.2 | 47.8 | 93.0 | 46.50 |
#id, doc.geml#id. Local and cross-document targets must resolve and exist at build time; unresolvable is an error, existing-but-not-a-table is an error. Src with a body is an error.src text enters the .gemlhistory hash, never the fetched contents. External data arrives at render time, so compute's and a chart's column names are validated then.src points at: a file stays with table, a block belongs to view, and a table naming a block is an error that names view. The reasoning is in the spec: a block's output is something another block derived, and a table holding facts someone else derived is not holding facts. A short-lived table-with-compute borrowing form existed before this landed; it is gone, and this page used to document it.Eight errors and four warnings, matching A.3's catalogue row for row. The line runs between "the document misspoke" and "the data is dirty".
=== table {#a format=csv src=rows.csv} x,y ← src and a body together === === table {#b format=csv delim=;;} ← two characters a;b === === table {#c delim=;} %% no format: a visual grid has no use for delim | a | b | === === table {#d format=weird} | a | b | === %% compute=/summary= are a view's (§6.1), so every formula case below is a view === view {#e src=#src compute="Z = Q9 + 1"} === === table {#fsrc format=csv} Q1,Q2 1,n/a %% non-numeric counts as 0, with a warning === === view {#f src=#fsrc compute="S = Q1 + Q2"} === === table {#gsrc format=csv} Q1,Q2 1,0 %% division by zero === === view {#g src=#gsrc compute="R = Q1 / Q2" summary="Q1 = sum(Q1); Nope = 1; Q2 = Q2"} === === table {#h src=#n} ← a table's src= may not name a block === === table {#i format=csv src=missing.csv} === === view {#j src=#src compute="bad formula"} ===
error: table has both `src` and an inline body; provide one, not both (line 8) error: `delim=";;"` must be exactly one character (for a tab use `format=tsv`); split on `,` instead (line 11) warning: `delim` applies to a data body (`format=csv`/`tsv`); this body was parsed as a visual grid, so it is ignored (line 15) warning: unknown table format `weird`; parsed as visual grid (line 20) error: compute `Z`: unknown column `Q9` (line 25) warning: column `Q2` row 1 is not a number (`n/a`); counted as 0 (line 29) warning: compute `R` row 1: division by zero; the cell holds no value and shows `-` (line 33) error: summary targets unknown column `Nope` (line 33) error: summary `Q2`: column `Q2` must be reduced by an aggregate (e.g. sum(Q2)) (line 33) error: bad compute formula `bad formula` (want `Name = expr`) (line 41) error: cannot resolve table source `missing.csv` (line 39) error: table source `#n` names another block's output; use `view` (line 37)
src= names a block at all — that one is now answered by naming view rather than by "not a table", so it fires for a note and for a real table alike. All are the document itself misspeaking., and the table still reads; a bad format parses as a pipe grid. Degradation always keeps the content.view draft: select, derive and aggregate from another relationSpecified in §6.1 and registered in the parser, so a view parses as itself rather than earning an unknown-block-type warning. (GEP-0012's own front matter still reads state: draft; the specification absorbed it anyway.) Left: the syntax. Right: what it renders.
=== table {#tickets format=csv} Area,Status,Days Draft Mode,open,3 Turbopack,open,12 Draft Mode,closed,1 === === view {#open src=#tickets where="Status = 'open'" order="Days desc" limit=10 select="Area, Days"} === === view {#by-area src=#tickets by="Area" aggregate="Open = count(Status)"} ===
warning: unknown block type `view`; body kept as raw (line 11) warning: unknown block type `view`; body kept as raw (line 13) 0 error(s), 2 warning(s) exit 0 — a draft document passes check today; it just computes nothing
| Area | Days |
|---|---|
| Turbopack | 12 |
| Draft Mode | 3 |
| Area | Open |
|---|---|
| Draft Mode | 2 |
| Turbopack | 1 |
where="FY > 40" may use an FY defined in the same block; a formula reading an aggregate runs after, or the filter would depend on what it filters. The reward: sum(FY) means the same rows in compute and in summary. A deliberate departure from SQL.and/or/not, numbers and single-quoted literals. A single-quoted run is a column in column position ('Unit Price') and a literal in value position ('open'); there is no third position, because §4 gives attribute values no escape to introduce a second kind of quote.A.geml#fy[summary]["FY"]. Shadowing a source column is legal with a warning.All run with the repository's current build, node geml-parser/dist/geml.js. rows.csv sits beside the probes.
| Probe | Covers | check result | Where on this page |
|---|---|---|---|
| t1-table.geml · rows.csv | pipe grid, csv body, delim, compute, printf, summary, src file, borrowing, caption as link text, six coordinates | 0 diagnostics | Bodies, Coordinates, src= |
| t2-table-fail.geml | src with body, bad delim, delim without format, unknown format, unknown column, non-numeric, division by zero, unknown summary column, bare summary column, malformed formula, unresolvable source, source not a table | 8 error 4 warning | Twelve failures |
| v1-view.geml | two view blocks in the draft syntax | 2 warning unknown-block-type | The view draft |