@tinta-lang/engine 0.0.0-stage → 0.1.0-dev.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/LICENSE +21 -0
- package/THIRD_PARTY_NOTICES.txt +13651 -0
- package/dist/analysis.d.ts +8 -0
- package/dist/analysis.js +19 -0
- package/dist/binding.d.ts +15 -0
- package/dist/binding.js +292 -0
- package/dist/chart-presentation.d.ts +11 -0
- package/dist/chart-presentation.js +61 -0
- package/dist/charts.d.ts +3 -0
- package/dist/charts.js +139 -0
- package/dist/codec.d.ts +3 -0
- package/dist/codec.js +52 -0
- package/dist/common.d.ts +24 -0
- package/dist/common.js +128 -0
- package/dist/compiler.d.ts +3 -0
- package/dist/compiler.js +458 -0
- package/dist/expression.d.ts +15 -0
- package/dist/expression.js +236 -0
- package/dist/grid.d.ts +5 -0
- package/dist/grid.js +57 -0
- package/dist/imports.d.ts +4 -0
- package/dist/imports.js +293 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/inspect.d.ts +3 -0
- package/dist/inspect.js +42 -0
- package/dist/program-validator.d.ts +19 -0
- package/dist/program-validator.js +17949 -0
- package/dist/programs.d.ts +3 -0
- package/dist/programs.js +1 -0
- package/dist/registry.d.ts +144 -0
- package/dist/registry.js +200 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.js +32 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +304 -0
- package/package.json +74 -3
- package/README.md +0 -3
package/dist/common.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { registry } from "./registry.js";
|
|
2
|
+
export class Failure extends Error {
|
|
3
|
+
diagnostic;
|
|
4
|
+
diagnostics;
|
|
5
|
+
constructor(input) {
|
|
6
|
+
const diagnostics = Array.isArray(input) ? input : [input];
|
|
7
|
+
super(diagnostics[0].message);
|
|
8
|
+
this.diagnostic = diagnostics[0];
|
|
9
|
+
this.diagnostics = diagnostics;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function fail(code, span, phase = "type") {
|
|
13
|
+
throw new Failure({
|
|
14
|
+
code,
|
|
15
|
+
severity: "error",
|
|
16
|
+
phase,
|
|
17
|
+
message: registry.errors[code],
|
|
18
|
+
span,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function orderedDiagnostics(diagnostics, maximum = 100) {
|
|
22
|
+
const phases = [
|
|
23
|
+
"decode",
|
|
24
|
+
"parse",
|
|
25
|
+
"bind",
|
|
26
|
+
"type",
|
|
27
|
+
"evaluate",
|
|
28
|
+
"assets",
|
|
29
|
+
"export",
|
|
30
|
+
"host",
|
|
31
|
+
];
|
|
32
|
+
const sorted = [...diagnostics].sort((a, b) => Number(a.details?.limitName === "diagnostics") -
|
|
33
|
+
Number(b.details?.limitName === "diagnostics") ||
|
|
34
|
+
phases.indexOf(a.phase) - phases.indexOf(b.phase) ||
|
|
35
|
+
a.span.start.offset - b.span.start.offset ||
|
|
36
|
+
(a.code < b.code ? -1 : a.code > b.code ? 1 : 0));
|
|
37
|
+
if (sorted.length > maximum) {
|
|
38
|
+
const last = sorted[maximum - 1];
|
|
39
|
+
return [
|
|
40
|
+
...sorted.slice(0, maximum - 1),
|
|
41
|
+
{
|
|
42
|
+
code: "E_LIMIT",
|
|
43
|
+
severity: "error",
|
|
44
|
+
phase: last.phase,
|
|
45
|
+
message: "The diagnostic limit was exceeded.",
|
|
46
|
+
span: last.span,
|
|
47
|
+
details: {
|
|
48
|
+
limitName: "diagnostics",
|
|
49
|
+
observed: sorted.length,
|
|
50
|
+
maximum,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
return sorted;
|
|
56
|
+
}
|
|
57
|
+
export function attempt(run, diagnostics = [], maximumDiagnostics = 100) {
|
|
58
|
+
try {
|
|
59
|
+
const value = run();
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
value,
|
|
63
|
+
diagnostics: orderedDiagnostics(diagnostics, maximumDiagnostics),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof Failure)
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
diagnostics: orderedDiagnostics([...diagnostics, ...error.diagnostics], maximumDiagnostics),
|
|
71
|
+
};
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function freeze(value) {
|
|
76
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
77
|
+
for (const child of Object.values(value))
|
|
78
|
+
freeze(child);
|
|
79
|
+
Object.freeze(value);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
export function record() {
|
|
84
|
+
return Object.create(null);
|
|
85
|
+
}
|
|
86
|
+
export class Budget {
|
|
87
|
+
span;
|
|
88
|
+
phase;
|
|
89
|
+
limits = { ...registry.limits };
|
|
90
|
+
operations = 0;
|
|
91
|
+
get used() {
|
|
92
|
+
return this.operations;
|
|
93
|
+
}
|
|
94
|
+
constructor(span, phase, options) {
|
|
95
|
+
this.span = span;
|
|
96
|
+
this.phase = phase;
|
|
97
|
+
for (const [key, value] of Object.entries(options?.limits ?? {})) {
|
|
98
|
+
if (!Object.hasOwn(this.limits, key) ||
|
|
99
|
+
!Number.isSafeInteger(value) ||
|
|
100
|
+
value < 1 ||
|
|
101
|
+
value > this.limits[key])
|
|
102
|
+
fail("E_LIMIT", span, phase);
|
|
103
|
+
this.limits[key] = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
limit(name, observed, span = this.span) {
|
|
107
|
+
const maximum = this.limits[name];
|
|
108
|
+
if (observed > maximum)
|
|
109
|
+
throw new Failure({
|
|
110
|
+
code: "E_LIMIT",
|
|
111
|
+
severity: "error",
|
|
112
|
+
phase: this.phase,
|
|
113
|
+
message: "A resource limit was exceeded.",
|
|
114
|
+
span,
|
|
115
|
+
details: { limitName: name, observed, maximum },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
charge(span = this.span, amount = 1) {
|
|
119
|
+
this.operations += amount;
|
|
120
|
+
this.limit(this.phase === "evaluate" ? "evaluationOperations" : "compileOperations", this.operations, span);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export const label = (name) => name.replaceAll("_", " ");
|
|
124
|
+
export const scalarType = (value) => typeof value === "string"
|
|
125
|
+
? "text"
|
|
126
|
+
: typeof value === "number"
|
|
127
|
+
? "number"
|
|
128
|
+
: "boolean";
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { parse, } from "@tinta-lang/syntax";
|
|
2
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
4
|
+
import { attempt, Budget, fail, freeze, label } from "./common.js";
|
|
5
|
+
import { grid, inDomain, units } from "./grid.js";
|
|
6
|
+
import { bind } from "./binding.js";
|
|
7
|
+
import { registry } from "./registry.js";
|
|
8
|
+
import { programs } from "./programs.js";
|
|
9
|
+
export { programs } from "./programs.js";
|
|
10
|
+
function literal(expr) {
|
|
11
|
+
if (expr.kind === "literal")
|
|
12
|
+
return expr.value;
|
|
13
|
+
if (expr.kind === "unary" &&
|
|
14
|
+
expr.operator !== "not" &&
|
|
15
|
+
expr.operand.kind === "literal" &&
|
|
16
|
+
typeof expr.operand.value === "number")
|
|
17
|
+
return (expr.operator === "-" ? -1 : 1) * expr.operand.value || 0;
|
|
18
|
+
fail("E_PROPERTY_VALUE", expr.span);
|
|
19
|
+
}
|
|
20
|
+
function option(d, key, fallback, node) {
|
|
21
|
+
const expr = d.options[key];
|
|
22
|
+
const value = expr ? literal(expr) : fallback;
|
|
23
|
+
if (typeof value !== typeof fallback)
|
|
24
|
+
fail("E_PROPERTY_VALUE", expr?.span ?? node.span);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
export function compile(input, options) {
|
|
28
|
+
const parsed = parse(input, options);
|
|
29
|
+
if (!parsed.ok)
|
|
30
|
+
return parsed;
|
|
31
|
+
return attempt(() => {
|
|
32
|
+
const { tree, source, bytes } = parsed.value;
|
|
33
|
+
const budget = new Budget(tree.span, "bind", options);
|
|
34
|
+
budget.charge(tree.span, parsed.value.compileOperations);
|
|
35
|
+
const declarations = [];
|
|
36
|
+
const locations = new Map();
|
|
37
|
+
const heading = tree.children.find((n) => n.kind === "heading");
|
|
38
|
+
const plain = (node) => node.kind === "interpolation"
|
|
39
|
+
? ""
|
|
40
|
+
: (node.value ?? node.children.map(plain).join(""));
|
|
41
|
+
let metadata = {
|
|
42
|
+
version: "1.0",
|
|
43
|
+
title: heading ? plain(heading) : "Untitled document",
|
|
44
|
+
locale: "en-US",
|
|
45
|
+
currency: "USD",
|
|
46
|
+
};
|
|
47
|
+
function convert(node) {
|
|
48
|
+
budget.charge(node.span);
|
|
49
|
+
const base = { id: node.id, span: node.span };
|
|
50
|
+
const children = () => node.children.flatMap(convert);
|
|
51
|
+
const a = node.attributes ?? {};
|
|
52
|
+
if (node.directive) {
|
|
53
|
+
const d = node.directive, name = d.binding ?? "";
|
|
54
|
+
const declaration = { ...base, name };
|
|
55
|
+
const staticLiteral = (expression) => {
|
|
56
|
+
const raw = source.slice(expression.span.start.offset, expression.span.end.offset);
|
|
57
|
+
if (expression.kind === "literal")
|
|
58
|
+
return raw === expression.raw;
|
|
59
|
+
if (expression.kind === "unary")
|
|
60
|
+
return ((expression.operator === "+" || expression.operator === "-") &&
|
|
61
|
+
expression.operand.kind === "literal" &&
|
|
62
|
+
typeof expression.operand.value === "number" &&
|
|
63
|
+
raw === expression.operator + expression.operand.raw);
|
|
64
|
+
if (expression.kind === "list")
|
|
65
|
+
return (raw.startsWith("[") &&
|
|
66
|
+
raw.endsWith("]") &&
|
|
67
|
+
expression.items.every(staticLiteral));
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
70
|
+
for (const [key, expression] of Object.entries(d.options)) {
|
|
71
|
+
locations.set(`${node.id}:option:${key}`, expression.span);
|
|
72
|
+
if (d.name === "view" && key === "where")
|
|
73
|
+
continue;
|
|
74
|
+
if (d.name === "chart" && key === "select") {
|
|
75
|
+
if (expression.kind !== "name" ||
|
|
76
|
+
source.slice(expression.span.start.offset, expression.span.end.offset) !== expression.name)
|
|
77
|
+
fail("E_SELECTION_TARGET", expression.span);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (d.name === "chart" &&
|
|
81
|
+
key === "y" &&
|
|
82
|
+
expression.kind !== "list" &&
|
|
83
|
+
!(expression.kind === "literal" &&
|
|
84
|
+
typeof expression.value === "string"))
|
|
85
|
+
continue;
|
|
86
|
+
if (!staticLiteral(expression))
|
|
87
|
+
fail(d.name === "chart" && key === "y"
|
|
88
|
+
? "E_CHART_SERIES"
|
|
89
|
+
: "E_PROPERTY_VALUE", expression.span);
|
|
90
|
+
}
|
|
91
|
+
d.columnSpans?.forEach((span, i) => locations.set(`${node.id}:column:${i}`, span));
|
|
92
|
+
d.rows?.forEach((row, i) => {
|
|
93
|
+
locations.set(`${node.id}:${i}`, row.span);
|
|
94
|
+
row.cellSpans.forEach((span, j) => locations.set(`${node.id}:${i}:cell:${j}`, span));
|
|
95
|
+
});
|
|
96
|
+
const text = (key, fallback) => {
|
|
97
|
+
const value = option(d, key, fallback, node);
|
|
98
|
+
budget.limit("labelCodePoints", [...value].length, d.options[key]?.span ?? node.span);
|
|
99
|
+
return value;
|
|
100
|
+
};
|
|
101
|
+
switch (d.name) {
|
|
102
|
+
case "meta": {
|
|
103
|
+
const version = option(d, "version", "1.0", node);
|
|
104
|
+
if (version !== "1.0")
|
|
105
|
+
fail("E_VERSION", d.options.version.span);
|
|
106
|
+
const locale = option(d, "locale", "en-US", node), currency = option(d, "currency", "USD", node);
|
|
107
|
+
if (!registry.locales.includes(locale) ||
|
|
108
|
+
!registry.currencies.includes(currency))
|
|
109
|
+
fail("E_META_VALUE", node.span);
|
|
110
|
+
metadata = {
|
|
111
|
+
version,
|
|
112
|
+
title: text("title", metadata.title),
|
|
113
|
+
locale: locale,
|
|
114
|
+
currency: currency,
|
|
115
|
+
};
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
case "let":
|
|
119
|
+
case "calc":
|
|
120
|
+
declarations.push({
|
|
121
|
+
...declaration,
|
|
122
|
+
kind: d.name,
|
|
123
|
+
expression: d.expression,
|
|
124
|
+
});
|
|
125
|
+
return [];
|
|
126
|
+
case "data":
|
|
127
|
+
declarations.push({
|
|
128
|
+
...declaration,
|
|
129
|
+
kind: "data",
|
|
130
|
+
table: {
|
|
131
|
+
columns: d.columns,
|
|
132
|
+
rows: d.rows.map((row, i) => ({
|
|
133
|
+
id: `${node.id}:${i}`,
|
|
134
|
+
cells: row.cells.map((value) => typeof value === "number" && value === 0 ? 0 : value),
|
|
135
|
+
})),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return [];
|
|
139
|
+
case "view": {
|
|
140
|
+
if (d.options.descending && !d.options.sort)
|
|
141
|
+
fail("E_PROPERTY_DEPENDENCY", d.options.descending.span);
|
|
142
|
+
declarations.push({
|
|
143
|
+
...declaration,
|
|
144
|
+
kind: "view",
|
|
145
|
+
source: d.source,
|
|
146
|
+
descending: option(d, "descending", false, node),
|
|
147
|
+
...(d.options.where ? { where: d.options.where } : {}),
|
|
148
|
+
...(d.options.sort
|
|
149
|
+
? { sort: option(d, "sort", "", node) }
|
|
150
|
+
: {}),
|
|
151
|
+
});
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
case "slider":
|
|
155
|
+
case "toggle":
|
|
156
|
+
case "choice": {
|
|
157
|
+
const initial = literal(d.expression);
|
|
158
|
+
const controlLabel = text("label", label(name));
|
|
159
|
+
if (!controlLabel.trim())
|
|
160
|
+
fail("E_PROPERTY_VALUE", d.options.label?.span ?? node.span);
|
|
161
|
+
const controlBase = {
|
|
162
|
+
...declaration,
|
|
163
|
+
label: controlLabel,
|
|
164
|
+
};
|
|
165
|
+
let control;
|
|
166
|
+
if (d.name === "slider") {
|
|
167
|
+
const min = option(d, "min", 0, node), max = option(d, "max", 100, node), step = option(d, "step", 1, node);
|
|
168
|
+
const raw = (key, value) => {
|
|
169
|
+
const e = d.options[key];
|
|
170
|
+
return e
|
|
171
|
+
? source.slice(e.span.start.offset, e.span.end.offset)
|
|
172
|
+
: String(value);
|
|
173
|
+
};
|
|
174
|
+
control = {
|
|
175
|
+
...controlBase,
|
|
176
|
+
kind: "slider",
|
|
177
|
+
initial: initial,
|
|
178
|
+
min,
|
|
179
|
+
max,
|
|
180
|
+
step,
|
|
181
|
+
grid: grid(raw("min", min), raw("max", max), raw("step", step), node.span),
|
|
182
|
+
};
|
|
183
|
+
if (units(source.slice(d.expression.span.start.offset, d.expression.span.end.offset)) === undefined)
|
|
184
|
+
fail("E_STATE_DOMAIN", node.span);
|
|
185
|
+
budget.limit("sliderTicks", control.grid.ticks, node.span);
|
|
186
|
+
budget.limit("sliderMagnitude", Math.max(Math.abs(min), Math.abs(max)), node.span);
|
|
187
|
+
const divisor = 10n ** BigInt(6 - budget.limits.sliderFractionalDigits);
|
|
188
|
+
for (const value of [
|
|
189
|
+
control.grid.minUnits,
|
|
190
|
+
control.grid.stepUnits,
|
|
191
|
+
String(units(raw("max", max))),
|
|
192
|
+
String(units(source.slice(d.expression.span.start.offset, d.expression.span.end.offset))),
|
|
193
|
+
]) {
|
|
194
|
+
if (BigInt(value) % divisor !== 0n)
|
|
195
|
+
fail("E_STATE_DOMAIN", node.span);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (d.name === "toggle")
|
|
199
|
+
control = {
|
|
200
|
+
...controlBase,
|
|
201
|
+
kind: "toggle",
|
|
202
|
+
initial: initial,
|
|
203
|
+
};
|
|
204
|
+
else {
|
|
205
|
+
const e = d.options.options;
|
|
206
|
+
if (!e)
|
|
207
|
+
fail("E_PROPERTY_REQUIRED", node.span);
|
|
208
|
+
if (e.kind !== "list" || e.items.length === 0)
|
|
209
|
+
fail("E_PROPERTY_VALUE", e.span);
|
|
210
|
+
budget.limit("choiceOptions", e.items.length, e.span);
|
|
211
|
+
const values = e.items.map((item) => {
|
|
212
|
+
const value = literal(item);
|
|
213
|
+
if (typeof value !== "string" || !value.length)
|
|
214
|
+
fail("E_PROPERTY_VALUE", item.span);
|
|
215
|
+
budget.limit("choiceOptionCodePoints", [...value].length, item.span);
|
|
216
|
+
return value;
|
|
217
|
+
});
|
|
218
|
+
if (new Set(values).size !== values.length)
|
|
219
|
+
fail("E_PROPERTY_VALUE", e.span);
|
|
220
|
+
control = {
|
|
221
|
+
...controlBase,
|
|
222
|
+
kind: "choice",
|
|
223
|
+
initial: initial,
|
|
224
|
+
options: values,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (!inDomain(control, initial))
|
|
228
|
+
fail("E_STATE_DOMAIN", d.expression.span);
|
|
229
|
+
declarations.push(control);
|
|
230
|
+
return [{ ...base, kind: "control", name }];
|
|
231
|
+
}
|
|
232
|
+
case "when":
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
...base,
|
|
236
|
+
kind: "when",
|
|
237
|
+
condition: d.expression,
|
|
238
|
+
children: children(),
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
case "details":
|
|
242
|
+
if (d.summary !== undefined && !d.summary.trim())
|
|
243
|
+
fail("E_PROPERTY_VALUE", node.span);
|
|
244
|
+
budget.limit("labelCodePoints", [...(d.summary ?? "More information")].length, node.span);
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
...base,
|
|
248
|
+
kind: "details",
|
|
249
|
+
summary: d.summary ?? "More information",
|
|
250
|
+
children: children(),
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
case "table":
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
...base,
|
|
257
|
+
kind: "data-table",
|
|
258
|
+
source: d.source,
|
|
259
|
+
title: text("title", label(d.source)),
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
case "chart": {
|
|
263
|
+
const kind = d.chartType;
|
|
264
|
+
if (!d.options.x || (kind !== "histogram" && !d.options.y))
|
|
265
|
+
fail("E_PROPERTY_REQUIRED", node.span);
|
|
266
|
+
if ((kind === "histogram" && (d.options.y || d.options.select)) ||
|
|
267
|
+
(kind !== "histogram" && d.options.bins))
|
|
268
|
+
fail("E_PROPERTY_VALUE", node.span);
|
|
269
|
+
const y = d.options.y;
|
|
270
|
+
let series;
|
|
271
|
+
if (y?.kind === "literal" && typeof y.value === "string")
|
|
272
|
+
series = { kind: "columns", columns: [y.value] };
|
|
273
|
+
else if (y?.kind === "list") {
|
|
274
|
+
const columns = y.items.map((e) => {
|
|
275
|
+
if (e.kind !== "literal" || typeof e.value !== "string")
|
|
276
|
+
fail("E_CHART_SERIES", e.span);
|
|
277
|
+
return e.value;
|
|
278
|
+
});
|
|
279
|
+
if (!columns.length ||
|
|
280
|
+
columns.length > 6 ||
|
|
281
|
+
new Set(columns).size !== columns.length)
|
|
282
|
+
fail("E_CHART_SERIES", y.span);
|
|
283
|
+
series = { kind: "columns", columns };
|
|
284
|
+
}
|
|
285
|
+
else if (y)
|
|
286
|
+
series = { kind: "expression", expression: y };
|
|
287
|
+
if ((kind === "pie" || kind === "donut") &&
|
|
288
|
+
series?.kind === "columns" &&
|
|
289
|
+
series.columns.length !== 1)
|
|
290
|
+
fail("E_CHART_SERIES", y.span);
|
|
291
|
+
const select = d.options.select;
|
|
292
|
+
if (select && select.kind !== "name")
|
|
293
|
+
fail("E_SELECTION_TARGET", select.span);
|
|
294
|
+
const bins = option(d, "bins", 8, node);
|
|
295
|
+
if (!Number.isInteger(bins) || bins < 2 || bins > 30)
|
|
296
|
+
fail("E_PROPERTY_VALUE", node.span);
|
|
297
|
+
if (kind === "histogram")
|
|
298
|
+
budget.limit("histogramBinsMax", bins, node.span);
|
|
299
|
+
const typeLabel = {
|
|
300
|
+
line: "Line chart",
|
|
301
|
+
area: "Area chart",
|
|
302
|
+
bar: "Bar chart",
|
|
303
|
+
scatter: "Scatter plot",
|
|
304
|
+
pie: "Pie chart",
|
|
305
|
+
donut: "Donut chart",
|
|
306
|
+
histogram: "Histogram",
|
|
307
|
+
}[kind];
|
|
308
|
+
const x = option(d, "x", "", node);
|
|
309
|
+
const chart = {
|
|
310
|
+
...base,
|
|
311
|
+
kind,
|
|
312
|
+
source: d.source,
|
|
313
|
+
x,
|
|
314
|
+
title: text("title", `${typeLabel} — ${label(d.source)}`),
|
|
315
|
+
xLabel: text("xLabel", label(x)),
|
|
316
|
+
yLabel: text("yLabel", kind === "histogram"
|
|
317
|
+
? "Frequency"
|
|
318
|
+
: series?.kind === "columns" && series.columns.length === 1
|
|
319
|
+
? label(series.columns[0])
|
|
320
|
+
: "Value"),
|
|
321
|
+
...(series ? { y: series } : {}),
|
|
322
|
+
...(select?.kind === "name" ? { select: select.name } : {}),
|
|
323
|
+
...(kind === "histogram" ? { bins } : {}),
|
|
324
|
+
};
|
|
325
|
+
return [{ ...base, kind: "chart", chart }];
|
|
326
|
+
}
|
|
327
|
+
case "end":
|
|
328
|
+
fail("E_BLOCK_END", node.span);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
switch (node.kind) {
|
|
332
|
+
case "root":
|
|
333
|
+
return children();
|
|
334
|
+
case "definition":
|
|
335
|
+
return [];
|
|
336
|
+
case "text":
|
|
337
|
+
return [{ ...base, kind: "text", value: node.value }];
|
|
338
|
+
case "interpolation":
|
|
339
|
+
return [
|
|
340
|
+
{
|
|
341
|
+
...base,
|
|
342
|
+
kind: "interpolation",
|
|
343
|
+
expression: node.expression,
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
case "html":
|
|
347
|
+
return /^(?:\s*<!--(?:(?!-->)[\s\S])*-->)+\s*$/.test(node.value)
|
|
348
|
+
? []
|
|
349
|
+
: [{ ...base, kind: "raw-html", value: node.value }];
|
|
350
|
+
case "paragraph":
|
|
351
|
+
case "emphasis":
|
|
352
|
+
case "strong":
|
|
353
|
+
case "delete":
|
|
354
|
+
case "blockquote":
|
|
355
|
+
return [{ ...base, kind: node.kind, children: children() }];
|
|
356
|
+
case "heading":
|
|
357
|
+
return [
|
|
358
|
+
{
|
|
359
|
+
...base,
|
|
360
|
+
kind: "heading",
|
|
361
|
+
level: a.level,
|
|
362
|
+
anchor: a.anchor,
|
|
363
|
+
children: children(),
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
case "link":
|
|
367
|
+
case "linkReference":
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
...base,
|
|
371
|
+
kind: "link",
|
|
372
|
+
destination: a.destination,
|
|
373
|
+
...(typeof a.title === "string" ? { title: a.title } : {}),
|
|
374
|
+
children: children(),
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
case "image":
|
|
378
|
+
case "imageReference":
|
|
379
|
+
return [
|
|
380
|
+
{
|
|
381
|
+
...base,
|
|
382
|
+
kind: "image",
|
|
383
|
+
destination: a.destination,
|
|
384
|
+
alt: a.alt,
|
|
385
|
+
...(typeof a.title === "string" ? { title: a.title } : {}),
|
|
386
|
+
},
|
|
387
|
+
];
|
|
388
|
+
case "code":
|
|
389
|
+
case "inlineCode":
|
|
390
|
+
return [
|
|
391
|
+
{
|
|
392
|
+
...base,
|
|
393
|
+
kind: "code",
|
|
394
|
+
block: node.kind === "code",
|
|
395
|
+
value: node.value,
|
|
396
|
+
...(typeof a.language === "string"
|
|
397
|
+
? { language: a.language }
|
|
398
|
+
: {}),
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
case "break":
|
|
402
|
+
return [{ ...base, kind: "break", hard: true }];
|
|
403
|
+
case "thematicBreak":
|
|
404
|
+
return [{ ...base, kind: "thematic-break" }];
|
|
405
|
+
case "list":
|
|
406
|
+
return [
|
|
407
|
+
{
|
|
408
|
+
...base,
|
|
409
|
+
kind: "list",
|
|
410
|
+
ordered: a.ordered,
|
|
411
|
+
start: a.start,
|
|
412
|
+
tight: !a.spread,
|
|
413
|
+
items: node.children.map((item) => ({
|
|
414
|
+
id: item.id,
|
|
415
|
+
span: item.span,
|
|
416
|
+
...(typeof item.attributes?.checked === "boolean"
|
|
417
|
+
? { checked: item.attributes.checked }
|
|
418
|
+
: {}),
|
|
419
|
+
children: item.children.flatMap(convert),
|
|
420
|
+
})),
|
|
421
|
+
},
|
|
422
|
+
];
|
|
423
|
+
case "table":
|
|
424
|
+
return [
|
|
425
|
+
{
|
|
426
|
+
...base,
|
|
427
|
+
kind: "markdown-table",
|
|
428
|
+
align: a.align.map((v) => v ?? "none"),
|
|
429
|
+
header: node.children[0].children.map((cell) => cell.children.flatMap(convert)),
|
|
430
|
+
rows: node.children
|
|
431
|
+
.slice(1)
|
|
432
|
+
.map((row) => row.children.map((cell) => cell.children.flatMap(convert))),
|
|
433
|
+
},
|
|
434
|
+
];
|
|
435
|
+
default:
|
|
436
|
+
fail("E_INTERNAL", node.span);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const flow = tree.children.flatMap(convert);
|
|
440
|
+
budget.limit("labelCodePoints", [...metadata.title].length);
|
|
441
|
+
const ir = {
|
|
442
|
+
abi: "tinta-ir-1",
|
|
443
|
+
sourceVersion: "1.0",
|
|
444
|
+
contractRevision: "1.0-draft.3",
|
|
445
|
+
profile: input.profile,
|
|
446
|
+
source,
|
|
447
|
+
sourceUri: input.uri,
|
|
448
|
+
sourceSha256: bytesToHex(sha256(bytes)),
|
|
449
|
+
metadata,
|
|
450
|
+
declarations,
|
|
451
|
+
flow,
|
|
452
|
+
};
|
|
453
|
+
const bound = bind(ir, budget, locations);
|
|
454
|
+
const program = freeze(ir);
|
|
455
|
+
programs.set(program, bound);
|
|
456
|
+
return program;
|
|
457
|
+
}, parsed.diagnostics, options?.limits?.diagnostics);
|
|
458
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Column, Expr, Metadata, Scalar, ScalarType, TableValue, Value } from "@tinta-lang/types";
|
|
2
|
+
import { Budget } from "./common.js";
|
|
3
|
+
export type Type = ScalarType | "list:never" | "list:text" | "list:number" | "list:boolean" | readonly Column[];
|
|
4
|
+
export type RuntimeValue = Value | TableValue;
|
|
5
|
+
export interface Environment {
|
|
6
|
+
readonly get: (name: string) => RuntimeValue;
|
|
7
|
+
readonly metadata: Metadata;
|
|
8
|
+
readonly budget: Budget;
|
|
9
|
+
readonly row?: ReadonlyMap<string, Scalar>;
|
|
10
|
+
}
|
|
11
|
+
export declare function tableType(type: Type, expr: Expr): readonly Column[];
|
|
12
|
+
export declare function infer(expr: Expr, get: (name: string, expr: Expr) => Type, budget: Budget, columns?: readonly Column[]): Type;
|
|
13
|
+
export declare function numeric(value: number, expr: Expr): number;
|
|
14
|
+
export declare function format(value: Scalar, metadata: Metadata): string;
|
|
15
|
+
export declare function evaluate(expr: Expr, env: Environment): RuntimeValue;
|