GEML block types, illustrated · page 3 of 5 · spec 1.0 · 中文

GEML Blocks Illustrated · table and view

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.

Board

18 rules, each with its source and status

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.

Specified 16 GEP draft 1 Observed 1
RuleSourceStatus
table
1Two 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.§6Specified
2delim= 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.3Specified
3A 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.3Specified
4compute= (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.3Specified
5summary= (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.3Specified
6A [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.§6Specified
7Excluded 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.§6Specified
8External 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.4Specified
9Rendering: 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 htmlObserved
10Coordinates: #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-0011GEP draft
11Borrowing: 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.1Specified
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)
12view has no body and a required src=, with §6's three targets; a body alongside src= is an error.§6.1Specified
13Selection: 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.1Specified
14Derivation 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.1Specified
15Evaluation 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.1Specified
16One 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.1Specified
17A 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.1Specified
18A 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.1Specified
Bodies

A visual grid and a data body, parsed to one model

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.

Pipe grid · csv body · computed columns · printf · summary row SpecifiedThe attribute object is split with \ line continuation (§4); the backslashes are load-bearing — drop one and the whole block becomes a paragraph.
GEML · t1-table.geml
=== 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]].
--to htmlok: no diagnostics
figure.table-figure #budget › table.geml-table
PlanMonthsRate
Basic130
Pro230
figcaption · Annual cost. Right alignment comes from -----: in the separator row; numeric cells carry data-sort. figure.table-figure #fy-report › the view's output, not the table's
SegmentQ1Q2FYShare
Cloud124.5131.2255.751.1%
Hardware88.184.6172.734.5%
Services45.247.893.018.6%
Total257.8263.6521.4 
Blue cells are 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.
printf
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.
Arithmetic
§6's expressions have only + - * / ( ) 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.
Summary
Aggregates are the only construct that crosses rows, and there are five. A column reference in a summary expression must be wrapped in an aggregate; a bare column name has no value in the summary row.
Coordinates

Rows, cells, columns and the summary cell each have an address

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.

Six reads against #fy above GEP draft
geml get
$ 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
Rules
WrittenNames
#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 header-less table uses letter columns 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.
Read, write
Reading lands first. Writing is allowed only where a byte in the source corresponds to the unit: computed columns and src=-fed tables refuse; so does a format=csv cell that contains the delimiter, since changing one byte would corrupt its neighbours.
Why no confusion
[ 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.
src=

Data from elsewhere, and who is allowed to borrow it

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.

A file into a table, a relation borrowed by a view Specified
GEML · from t1-table.geml
=== 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
the same thing spelled as a table1 error
=== 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`
Behaviour todayok: no diagnostics
#ext · file contents loaded; column names checked at render time
SegmentQ1Q2
Cloud124.5131.2
Hardware88.184.6
#borrowed · the source's computed FY is visible; Half is added on top
SegmentQ1Q2FYHalf
Cloud124.5131.2255.7127.85
Hardware88.184.6172.786.35
Services45.247.893.046.50
The source's summary row did not come along: a relation is borrowed, not a report.
Three targets
§6: a data file (csv/tsv, relative path or http(s)), #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.
Hash
Only the 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.
Who borrows
§6 splits by what 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.
Failures

Twelve ways to fail, in one run

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".

t2-table-fail.geml Specified
GEML
=== 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"}
===
geml check8 error 4 warning
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)
error
Src with a body, a delimiter that is not one character, an unknown column, an unknown summary column, a bare summary column, a malformed formula, an unresolvable source, and a table whose 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.
warning
delim in the wrong place, an unknown format falling back to a visual grid, a non-numeric cell counted as 0, division by zero. The first two still read; the last two are facts about the data rather than defects in the document. §6 says of division by zero that it "is a warning and the document stays conforming — but it is never silent".
Fallback
A bad delim falls back to the natural , and the table still reads; a bad format parses as a pipe grid. Degradation always keeps the content.
view

The view draft: select, derive and aggregate from another relation

Specified 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.

Two views: filter, sort, project; and group, aggregate Specified
GEML · v1-view.geml
=== 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)"}
===
Output today
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
What the draft intends to rendernot implemented
#open · where → order → limit → select
AreaDays
Turbopack12
Draft Mode3
#by-area · by → aggregate
AreaOpen
Draft Mode2
Turbopack1
The evaluation order, the draft's load-bearing sentence:
src→compute (per row)→where→compute (aggregates)→by / aggregate→order→limit→select→summary
Why a block
The draft's test: a view's output is another relation that the next view can consume (filtering groups is "another view", not HAVING). Attributes on table could not do that, and would stop a table being "facts".
Two passes
A formula that reads only its own row runs before where, so 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.
Expression
Not a second language: §6's arithmetic plus comparisons, 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.
What it sees
The source's tuples plus computed columns; the source's summary row does not cross. To reach a source's total, address it: A.geml#fy[summary]["FY"]. Shadowing a source column is legal with a warning.
Evidence

Probe files and measured output

All run with the repository's current build, node geml-parser/dist/geml.js. rows.csv sits beside the probes.

ProbeCoverscheck resultWhere on this page
t1-table.geml · rows.csvpipe grid, csv body, delim, compute, printf, summary, src file, borrowing, caption as link text, six coordinates0 diagnosticsBodies, Coordinates, src=
t2-table-fail.gemlsrc 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 table8 error 4 warningTwelve failures
v1-view.gemltwo view blocks in the draft syntax2 warning unknown-block-typeThe view draft