@8bitscript/compiler 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/LICENSE +21 -0
- package/index.mjs +91 -0
- package/package.json +29 -0
- package/src/ast/index.mjs +90 -0
- package/src/checker/index.mjs +294 -0
- package/src/diagnostics/index.mjs +179 -0
- package/src/fold/facts.mjs +209 -0
- package/src/fold/index.mjs +412 -0
- package/src/intellisense/index.mjs +558 -0
- package/src/ir/index.mjs +1422 -0
- package/src/lexer/index.mjs +383 -0
- package/src/linker/hazards.mjs +121 -0
- package/src/linker/index.mjs +1075 -0
- package/src/parser/index.mjs +795 -0
- package/src/resolver/index.mjs +534 -0
- package/src/templates/index.mjs +278 -0
- package/src/types/index.mjs +116 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// The compile-time folds: `#frames(...)`, the duration builtin, `#system()`,
|
|
2
|
+
// the machine a build is for, and `#fact(...)`, one fact about it (see
|
|
3
|
+
// facts.mjs for the keys and where the values come from).
|
|
4
|
+
//
|
|
5
|
+
// `#frames(x, unit)` — x an integer or decimal literal, `unit` the word
|
|
6
|
+
// saying what x is measured in — folds to a plain IntegerLiteral holding
|
|
7
|
+
// however many frames — waitFrame() calls — that much time is at the
|
|
8
|
+
// project's configured `frameRate` (8bs.config.ts, default 60; see
|
|
9
|
+
// packages/backend-6502's FRAME_SYNC and packages/cli/src/web-runtime.mjs,
|
|
10
|
+
// which both pace waitFrame() at that same rate). `#frames(0.5, seconds)`
|
|
11
|
+
// is 30 at the default rate, 25 at a configured 50.
|
|
12
|
+
//
|
|
13
|
+
// The `#` is the language's one spelling for "8bitscript evaluates this;
|
|
14
|
+
// the target never sees it" (see the lexer's TokenKind.CompileTime): a
|
|
15
|
+
// plain `name(...)` always runs on the machine. So nothing is reserved —
|
|
16
|
+
// `#frames` is a different token from any `frames` a program declares —
|
|
17
|
+
// and a `#name` the compiler doesn't know, or a `#frames` that isn't
|
|
18
|
+
// called, is a diagnostic here.
|
|
19
|
+
//
|
|
20
|
+
// The builtin is named for what comes *out* — a program stores the result
|
|
21
|
+
// in a frame counter, so the call reads as the frame count it is — and the
|
|
22
|
+
// unit word names what went *in*. The unit is required: `#frames(30)` would
|
|
23
|
+
// either mean thirty frames (pointless) or silently guess a unit, and
|
|
24
|
+
// either way the reader is left doing the conversion in their head, which
|
|
25
|
+
// is the one thing this builtin exists to prevent.
|
|
26
|
+
//
|
|
27
|
+
// Two tables, two directions of growth. DURATION_UNITS is what x can be
|
|
28
|
+
// measured in (`seconds`, so far); DURATION_CLOCKS is what a duration can
|
|
29
|
+
// be counted out in — one entry per builtin function, `frames` so far. A
|
|
30
|
+
// future input unit is one more unit entry; a future output clock is one
|
|
31
|
+
// more clock entry, which is also one more builtin name.
|
|
32
|
+
//
|
|
33
|
+
// The unit word is *contextual*, not reserved either: it is only ever
|
|
34
|
+
// looked up by spelling in the second-argument slot of a clock call, a slot
|
|
35
|
+
// that cannot hold a variable (that is the INVALID_DURATION_ARGUMENT shape
|
|
36
|
+
// rule), so `let seconds: uint` elsewhere in the program is an ordinary
|
|
37
|
+
// declaration and never collides.
|
|
38
|
+
//
|
|
39
|
+
// Runs between parse() and check(), so the existing
|
|
40
|
+
// literal-fits-the-declared-width rule (VALUE_OUT_OF_RANGE) fires on the
|
|
41
|
+
// *folded* value for free — `#frames(100, seconds)` in a `utinyint` gets
|
|
42
|
+
// that diagnostic with no separate rule needed here. Deliberately narrow,
|
|
43
|
+
// the same way the checker's literal-width rule is: this is not general
|
|
44
|
+
// constant folding (`let x: u8 = 200 + 100` still isn't folded anywhere in
|
|
45
|
+
// the compiler).
|
|
46
|
+
//
|
|
47
|
+
// Every arithmetic step below is exact BigInt division — never a `Number`
|
|
48
|
+
// or `parseFloat` intermediate — so a duration's real-world length is never
|
|
49
|
+
// silently perturbed by floating-point rounding.
|
|
50
|
+
import { Codes, diagnostic } from '../diagnostics/index.mjs';
|
|
51
|
+
import { NodeType, walk } from '../ast/index.mjs';
|
|
52
|
+
import { FACTS, factPlaceholder } from './facts.mjs';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The units a duration literal can be written in, keyed by the bare word a
|
|
56
|
+
* program writes as the second argument: `#frames(0.5, seconds)`. Each entry
|
|
57
|
+
* turns the literal (an exact rational `numerator/denominator`, both
|
|
58
|
+
* BigInt) into an exact rational number of seconds, the common currency
|
|
59
|
+
* every clock below is defined against.
|
|
60
|
+
*
|
|
61
|
+
* Adding a unit means adding an entry here and writing it a hover in
|
|
62
|
+
* intellisense/index.mjs. Nothing is reserved: the word is only recognised
|
|
63
|
+
* in that one argument slot.
|
|
64
|
+
*/
|
|
65
|
+
export const DURATION_UNITS = new Map([
|
|
66
|
+
['seconds', {
|
|
67
|
+
toSeconds: (numerator, denominator) => ({ numerator, denominator }),
|
|
68
|
+
}],
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The clocks a duration can be counted out in, keyed by the compile-time
|
|
73
|
+
* function name a program calls: `#frames(...)` (without the `#`). Each entry turns an exact
|
|
74
|
+
* rational number of seconds into an exact rational number of that clock's
|
|
75
|
+
* ticks — still a fraction, so the one rounding step (and its
|
|
76
|
+
* ZERO_DURATION / INEXACT_DURATION diagnostics) happens in foldClockCall()
|
|
77
|
+
* identically for every clock.
|
|
78
|
+
*
|
|
79
|
+
* `tick` names one tick for diagnostics; `describe(options)` names the rate
|
|
80
|
+
* the fold happened at.
|
|
81
|
+
*
|
|
82
|
+
* Adding a clock means adding an entry here and writing it a hover in
|
|
83
|
+
* intellisense/index.mjs. Nothing is reserved: `#name` is its own token.
|
|
84
|
+
*/
|
|
85
|
+
export const DURATION_CLOCKS = new Map([
|
|
86
|
+
['frames', {
|
|
87
|
+
// Logical frames: waitFrame() calls, `frameRate` of them a second.
|
|
88
|
+
ticks: (numerator, denominator, { frameRate }) => ({
|
|
89
|
+
numerator: numerator * BigInt(frameRate),
|
|
90
|
+
denominator,
|
|
91
|
+
}),
|
|
92
|
+
tick: 'frame',
|
|
93
|
+
describe: ({ frameRate }) => `this project's frameRate (${frameRate})`,
|
|
94
|
+
}],
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The machines `#system()` can name, keyed by the target name `8bs build
|
|
99
|
+
* --target` accepts, each with the number `#system()` folds to on that
|
|
100
|
+
* machine. `@8bitscript/system`'s `System` namespace lists the same names
|
|
101
|
+
* with the same numbers (its test checks them against this map), so
|
|
102
|
+
* `#system() == System.C64` compares two compile-time numbers. The numbers
|
|
103
|
+
* are arbitrary and stable: a machine keeps its number when others are
|
|
104
|
+
* added, so a file that records one stays readable.
|
|
105
|
+
*/
|
|
106
|
+
export const SYSTEMS = new Map([
|
|
107
|
+
['web', 0],
|
|
108
|
+
['vic20', 1],
|
|
109
|
+
['c64', 2],
|
|
110
|
+
['pet', 3],
|
|
111
|
+
['c128', 4],
|
|
112
|
+
['atari8', 5],
|
|
113
|
+
['nes', 6],
|
|
114
|
+
['cx16', 7],
|
|
115
|
+
['mega65', 8],
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
/** The `#name` a compile-time call names, or null for anything else. */
|
|
119
|
+
function compileTimeCallName(n) {
|
|
120
|
+
if (n.type !== NodeType.CallExpression || n.callee?.type !== NodeType.Identifier) return null;
|
|
121
|
+
return n.callee.compileTime ? n.callee.name : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const KNOWN_COMPILE_TIME = () => [...[...DURATION_CLOCKS.keys()].map((name) => `#${name}(...)`), '#system()', '#fact(...)'].join(', ');
|
|
125
|
+
const BUILTIN = (name) => DURATION_CLOCKS.has(name) || name === 'system' || name === 'fact';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Round `numerator/denominator` (both BigInt, denominator > 0) to the
|
|
129
|
+
* nearest integer, ties rounding up.
|
|
130
|
+
*
|
|
131
|
+
* @returns {{ value: bigint, exact: boolean }}
|
|
132
|
+
*/
|
|
133
|
+
function roundFraction(numerator, denominator) {
|
|
134
|
+
const quotient = numerator / denominator;
|
|
135
|
+
const remainder = numerator % denominator;
|
|
136
|
+
if (remainder === 0n) return { value: quotient, exact: true };
|
|
137
|
+
return { value: remainder * 2n >= denominator ? quotient + 1n : quotient, exact: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Mutates a folded-away clock CallExpression node into a plain
|
|
141
|
+
// IntegerLiteral in place, preserving its original start/length so a
|
|
142
|
+
// downstream diagnostic (e.g. VALUE_OUT_OF_RANGE) underlines the whole
|
|
143
|
+
// `#frames(...)` call rather than a synthetic span. Deleting `callee`/`args`
|
|
144
|
+
// also means walk()'s own descent — which reads Object.values(root) *after*
|
|
145
|
+
// this visit callback returns — never revisits the argument subtree, so a
|
|
146
|
+
// DecimalLiteral consumed by a valid (or invalidly-shaped) clock call is
|
|
147
|
+
// never separately flagged as misplaced, and a unit word is never handed to
|
|
148
|
+
// the linker to fail to resolve. Every exit from foldClockCall() goes
|
|
149
|
+
// through here for exactly that reason.
|
|
150
|
+
function replaceWithTickCount(n, name, value) {
|
|
151
|
+
delete n.callee;
|
|
152
|
+
delete n.args;
|
|
153
|
+
n.type = NodeType.IntegerLiteral;
|
|
154
|
+
n.value = value;
|
|
155
|
+
n.raw = name === 'system' ? '#system()' : `#${name}(...)`;
|
|
156
|
+
n.radix = 10;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The same, for a fact: a count becomes an IntegerLiteral, a flag a
|
|
160
|
+
// BooleanLiteral, so a `bool` const on the sheet takes a flag and the
|
|
161
|
+
// checker's literal-fits-the-type rule sees the right kind of literal.
|
|
162
|
+
function replaceWithFact(n, key, value) {
|
|
163
|
+
delete n.callee;
|
|
164
|
+
delete n.args;
|
|
165
|
+
if (typeof value === 'boolean') {
|
|
166
|
+
n.type = NodeType.BooleanLiteral;
|
|
167
|
+
n.value = value;
|
|
168
|
+
delete n.radix;
|
|
169
|
+
} else {
|
|
170
|
+
n.type = NodeType.IntegerLiteral;
|
|
171
|
+
n.value = value;
|
|
172
|
+
n.radix = 10;
|
|
173
|
+
}
|
|
174
|
+
n.raw = `#fact(${key})`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const exampleCalls = (name) => {
|
|
178
|
+
if (name === 'system') return '#system()';
|
|
179
|
+
if (name === 'fact') return '#fact(video.columns) or #fact(memory.ram)';
|
|
180
|
+
return `#${name}(1, seconds) or #${name}(0.5, seconds)`;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The dotted key a `#fact(...)` argument spells — `video.columns` is a
|
|
185
|
+
* member expression of plain identifiers — or null for any other shape.
|
|
186
|
+
* The words are contextual, not reserved: they are only read here, in this
|
|
187
|
+
* one slot, so `let video` elsewhere is an ordinary declaration.
|
|
188
|
+
*/
|
|
189
|
+
function factKeyOf(argument) {
|
|
190
|
+
if (!argument) return null;
|
|
191
|
+
if (argument.type === NodeType.Identifier) return argument.compileTime ? null : argument.name;
|
|
192
|
+
if (argument.type === NodeType.MemberExpression && argument.property?.type === NodeType.Identifier) {
|
|
193
|
+
const head = factKeyOf(argument.object);
|
|
194
|
+
return head === null ? null : `${head}.${argument.property.name}`;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* `#fact(key)`: one fact about the machine this build is for, from the
|
|
201
|
+
* sheet the build was handed (FACTS has the keys; the machine packages'
|
|
202
|
+
* catalogs and the CLI's resolveHardware have the values). With no machine
|
|
203
|
+
* in hand — `8bs check`, the editor — it folds to the key's placeholder
|
|
204
|
+
* (0 or false) and is "valid and target-dependent", as `#system()` is. With
|
|
205
|
+
* a machine but no facts it is a diagnostic: a real build has a real sheet,
|
|
206
|
+
* and the fold will not invent one for it.
|
|
207
|
+
*/
|
|
208
|
+
function foldFactCall(n, file, machine, facts, diagnostics) {
|
|
209
|
+
const args = n.args ?? [];
|
|
210
|
+
const key = args.length === 1 ? factKeyOf(args[0]) : null;
|
|
211
|
+
if (key === null || !FACTS.has(key)) {
|
|
212
|
+
diagnostics.push(diagnostic(
|
|
213
|
+
Codes.UNKNOWN_FACT,
|
|
214
|
+
key === null
|
|
215
|
+
? `#fact(...) takes one fact key, written as words — ${exampleCalls('fact')}`
|
|
216
|
+
: `'${key}' is not a fact — the keys are ${[...FACTS.keys()].join(', ')}`,
|
|
217
|
+
file, n.start, n.length,
|
|
218
|
+
));
|
|
219
|
+
replaceWithFact(n, key ?? '?', 0);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (machine === undefined) {
|
|
223
|
+
replaceWithFact(n, key, factPlaceholder(key));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (facts === undefined) {
|
|
227
|
+
diagnostics.push(diagnostic(
|
|
228
|
+
Codes.NO_HARDWARE_FACTS,
|
|
229
|
+
`#fact(${key}) needs this build's hardware facts, and the ${machine} build was given none — a build resolves its hardware first (8bs build does; link() takes 'facts')`,
|
|
230
|
+
file, n.start, n.length,
|
|
231
|
+
));
|
|
232
|
+
replaceWithFact(n, key, factPlaceholder(key));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const value = Object.hasOwn(facts, key) ? facts[key] : factPlaceholder(key);
|
|
236
|
+
replaceWithFact(n, key, value);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* `#system()`: the machine this build is for, as its number in SYSTEMS.
|
|
241
|
+
* Takes no arguments. With no machine in hand — `8bs check` and the editor
|
|
242
|
+
* analyse files, not builds — it folds to 0 and is simply "valid, and
|
|
243
|
+
* target-dependent", the same answer the resolver gives a `.<machine>.8bs`
|
|
244
|
+
* file then: only a build can say which machine, and only a build needs to.
|
|
245
|
+
*/
|
|
246
|
+
function foldSystemCall(n, file, machine, diagnostics) {
|
|
247
|
+
if ((n.args ?? []).length > 0) {
|
|
248
|
+
diagnostics.push(diagnostic(
|
|
249
|
+
Codes.SYSTEM_TAKES_NO_ARGUMENTS,
|
|
250
|
+
'#system() takes no arguments: it is the machine this build is for, and the build already knows which',
|
|
251
|
+
file, n.start, n.length,
|
|
252
|
+
));
|
|
253
|
+
replaceWithTickCount(n, 'system', 0);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (machine === undefined) {
|
|
257
|
+
replaceWithTickCount(n, 'system', 0);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const value = SYSTEMS.get(machine);
|
|
261
|
+
if (value === undefined) {
|
|
262
|
+
diagnostics.push(diagnostic(
|
|
263
|
+
Codes.NOT_ON_THIS_TARGET,
|
|
264
|
+
`#system() has no number for '${machine}' — the machines are ${[...SYSTEMS.keys()].join(', ')}`,
|
|
265
|
+
file, n.start, n.length,
|
|
266
|
+
));
|
|
267
|
+
replaceWithTickCount(n, 'system', 0);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
replaceWithTickCount(n, 'system', value);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function foldClockCall(n, clockName, file, frameRate, diagnostics) {
|
|
274
|
+
const clock = DURATION_CLOCKS.get(clockName);
|
|
275
|
+
const args = n.args ?? [];
|
|
276
|
+
const [argument, unitArgument] = args;
|
|
277
|
+
const isLiteralArgument = argument?.type === NodeType.IntegerLiteral
|
|
278
|
+
|| argument?.type === NodeType.DecimalLiteral;
|
|
279
|
+
const isUnitShape = args.length === 2 && unitArgument?.type === NodeType.Identifier;
|
|
280
|
+
|
|
281
|
+
if (!isLiteralArgument || !isUnitShape) {
|
|
282
|
+
diagnostics.push(diagnostic(
|
|
283
|
+
Codes.INVALID_DURATION_ARGUMENT,
|
|
284
|
+
`#${clockName}(...) takes one integer or decimal literal and the unit it is measured in, `
|
|
285
|
+
+ `e.g. ${exampleCalls(clockName)}`,
|
|
286
|
+
file, n.start, n.length,
|
|
287
|
+
));
|
|
288
|
+
replaceWithTickCount(n, clockName, 0);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const unitName = unitArgument.name;
|
|
293
|
+
const unit = DURATION_UNITS.get(unitName);
|
|
294
|
+
if (!unit) {
|
|
295
|
+
diagnostics.push(diagnostic(
|
|
296
|
+
Codes.UNKNOWN_DURATION_UNIT,
|
|
297
|
+
`'${unitName}' is not a unit #${clockName}(...) can measure — `
|
|
298
|
+
+ `the units are ${[...DURATION_UNITS.keys()].join(', ')}`,
|
|
299
|
+
file, unitArgument.start, unitArgument.length,
|
|
300
|
+
));
|
|
301
|
+
replaceWithTickCount(n, clockName, 0);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const options = { frameRate };
|
|
306
|
+
const seconds = unit.toSeconds(
|
|
307
|
+
BigInt(argument.type === NodeType.IntegerLiteral ? argument.value : argument.numerator),
|
|
308
|
+
BigInt(argument.type === NodeType.IntegerLiteral ? 1 : argument.denominator),
|
|
309
|
+
);
|
|
310
|
+
const ticks = clock.ticks(seconds.numerator, seconds.denominator, options);
|
|
311
|
+
const { value, exact } = roundFraction(ticks.numerator, ticks.denominator);
|
|
312
|
+
const written = `#${clockName}(${argument.raw}, ${unitName})`;
|
|
313
|
+
|
|
314
|
+
if (value === 0n) {
|
|
315
|
+
diagnostics.push(diagnostic(
|
|
316
|
+
Codes.ZERO_DURATION,
|
|
317
|
+
`${written} rounds to 0 ${clock.tick}s at ${clock.describe(options)} — `
|
|
318
|
+
+ `every #${clockName}(...) call must round to at least one ${clock.tick}`,
|
|
319
|
+
file, n.start, n.length,
|
|
320
|
+
));
|
|
321
|
+
} else if (!exact) {
|
|
322
|
+
diagnostics.push(diagnostic(
|
|
323
|
+
Codes.INEXACT_DURATION,
|
|
324
|
+
`${written} is not exact at ${clock.describe(options)} — `
|
|
325
|
+
+ `rounded to ${value} ${clock.tick}${value === 1n ? '' : 's'}`,
|
|
326
|
+
file, n.start, n.length, 'warning',
|
|
327
|
+
));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
replaceWithTickCount(n, clockName, Number(value));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Fold every compile-time call — `#frames(...)` (see DURATION_CLOCKS) and
|
|
335
|
+
* `#system()` (see SYSTEMS) — in `ast` into a plain IntegerLiteral,
|
|
336
|
+
* mutating the tree in place, and flag any decimal literal found outside a
|
|
337
|
+
* valid clock-call argument (the language has no other float syntax), any
|
|
338
|
+
* `#name` the compiler doesn't evaluate, and any `#frames` or `#system`
|
|
339
|
+
* that isn't called.
|
|
340
|
+
*
|
|
341
|
+
* @param {object} ast Program node from the parser.
|
|
342
|
+
* @param {string} file
|
|
343
|
+
* @param {{ frameRate?: number, machine?: string, facts?: object }} [options]
|
|
344
|
+
* `frameRate` is the project's logical frame rate (default 60; already
|
|
345
|
+
* validated positive-integer by the caller — see
|
|
346
|
+
* packages/cli/src/config.mjs's resolveFrameRate). `machine` is the
|
|
347
|
+
* target being built for, or undefined when a file is being checked
|
|
348
|
+
* rather than built (see foldSystemCall). `facts` is the build's merged
|
|
349
|
+
* hardware facts, keyed as facts.mjs's FACTS is, that every `#fact(...)`
|
|
350
|
+
* folds from; required whenever `machine` is given and a fact is read
|
|
351
|
+
* (see foldFactCall).
|
|
352
|
+
* @returns {object[]} diagnostics
|
|
353
|
+
*/
|
|
354
|
+
export function foldCompileTime(ast, file = '<unknown>', { frameRate = 60, machine, facts } = {}) {
|
|
355
|
+
const diagnostics = [];
|
|
356
|
+
if (!ast) return diagnostics;
|
|
357
|
+
|
|
358
|
+
walk(ast, (n) => {
|
|
359
|
+
const name = compileTimeCallName(n);
|
|
360
|
+
if (name) {
|
|
361
|
+
if (name === 'system') {
|
|
362
|
+
foldSystemCall(n, file, machine, diagnostics);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (name === 'fact') {
|
|
366
|
+
foldFactCall(n, file, machine, facts, diagnostics);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (!DURATION_CLOCKS.has(name)) {
|
|
370
|
+
diagnostics.push(diagnostic(
|
|
371
|
+
Codes.UNKNOWN_COMPILE_TIME_FUNCTION,
|
|
372
|
+
`'#${name}' is not a function the compiler evaluates — the compile-time functions are ${KNOWN_COMPILE_TIME()}`,
|
|
373
|
+
file, n.callee.start, n.callee.length,
|
|
374
|
+
));
|
|
375
|
+
replaceWithTickCount(n, name, 0);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
foldClockCall(n, name, file, frameRate, diagnostics);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (n.type === NodeType.Identifier && n.compileTime) {
|
|
382
|
+
// Every called one was consumed above (its callee deleted before walk()
|
|
383
|
+
// descends), so this one is bare: `#frames` with no argument list.
|
|
384
|
+
diagnostics.push(diagnostic(
|
|
385
|
+
Codes.UNKNOWN_COMPILE_TIME_FUNCTION,
|
|
386
|
+
BUILTIN(n.name)
|
|
387
|
+
? `'#${n.name}' is a compile-time function and must be called: ${exampleCalls(n.name)}`
|
|
388
|
+
: `'#${n.name}' is not a function the compiler evaluates — the compile-time functions are ${KNOWN_COMPILE_TIME()}`,
|
|
389
|
+
file, n.start, n.length,
|
|
390
|
+
));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (n.type === NodeType.DecimalLiteral) {
|
|
394
|
+
const raw = n.raw;
|
|
395
|
+
diagnostics.push(diagnostic(
|
|
396
|
+
Codes.MISPLACED_DECIMAL_LITERAL,
|
|
397
|
+
`a decimal literal ('${raw}') is only valid as the first argument to ${KNOWN_COMPILE_TIME()}`,
|
|
398
|
+
file, n.start, n.length,
|
|
399
|
+
));
|
|
400
|
+
// Replaced, like a folded call is, so nothing downstream reports the
|
|
401
|
+
// same mistake a second time in its own words — the language has no
|
|
402
|
+
// float type for lowering to fail on, and it has already been told.
|
|
403
|
+
n.type = NodeType.IntegerLiteral;
|
|
404
|
+
n.value = 0;
|
|
405
|
+
n.radix = 10;
|
|
406
|
+
delete n.numerator;
|
|
407
|
+
delete n.denominator;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
return diagnostics;
|
|
412
|
+
}
|