Skip to content

Commit 69ea45c

Browse files
Feat/deed lint bun 20261006 (#1195)
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> --> ## Summary <!-- What does this PR do, and why? --> Closes # ## Type of change - [ ] 🐛 Bug fix (non-breaking change that fixes an issue) - [ ] ✨ New feature (non-breaking change that adds functionality) - [ ] 💥 Breaking change (would change existing behaviour) - [ ] 🕳️ Soundness fix (fixes a checker/proof false-negative) - [ ] 📖 Documentation - [ ] 🧹 Refactor / tech debt (behaviour-preserving) - [ ] ⚡ Performance - [ ] 🔧 Build / CI / tooling ## How has this been verified? <!-- Establish ground truth: which tool did you RUN, and what did it report? Don't cite a status doc — cite the command and its output. --> ## Checklist - [ ] My commits are **signed** (`git commit -S`). - [ ] I ran the project's own checks/tests locally and they pass. - [ ] New files carry the correct `SPDX-License-Identifier` (code/config `MPL-2.0`, prose `CC-BY-SA-4.0`); I did not relicense existing files. - [ ] Docs are updated, and no public claim now overstates what the code does. - [ ] I have not introduced a soundness hole (or I have flagged where I might have). ## Notes for reviewers <!-- Anything that needs special attention, follow-up, or context. --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f77fc28 commit 69ea45c

1 file changed

Lines changed: 62 additions & 50 deletions

File tree

‎1-formats/deed/tools/deed_lint.js‎

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -178,64 +178,76 @@ function isDigit(c) {
178178
return c !== "" && c >= "0" && c <= "9";
179179
}
180180
181-
/** value = string / symbol / integer / boolean / uuid5 / quoted / list */
182-
function lexValue(lx) {
183-
const c = lx.peek();
184-
if (c === '"') return lexString(lx);
185-
if (c === "#") {
186-
const two = lx.t.slice(lx.i, lx.i + 3);
187-
if (two.startsWith("#u5")) {
188-
lx.i += 3;
189-
if (lx.peek() !== '"') {
190-
throw new LintError('uuid5 must be followed immediately by a string: #u5"name"', lx.line());
191-
}
192-
lexString(lx);
193-
return ["uuid5", null];
194-
}
195-
if (two.slice(0, 2) === "#t" || two.slice(0, 2) === "#f") {
196-
const nxt = lx.peek(2);
197-
if (nxt && !" \r\n()".includes(nxt)) {
198-
const tok = lx.t.slice(lx.i + 2, lx.i + 24).trim().split(/\s+/)[0].slice(0, 20);
199-
throw new LintError(
200-
`booleans are exactly #t/#f (got ${pyRepr(two + tok)}); true/false/1/0 are parse errors`,
201-
lx.line(),
202-
);
203-
}
204-
lx.i += 2;
205-
return ["boolean", two];
181+
/** #-forms: #u5"name" (uuid5) or #t / #f (boolean); anything else is a parse error. */
182+
function lexHashForm(lx) {
183+
const two = lx.t.slice(lx.i, lx.i + 3);
184+
if (two.startsWith("#u5")) {
185+
lx.i += 3;
186+
if (lx.peek() !== '"') {
187+
throw new LintError('uuid5 must be followed immediately by a string: #u5"name"', lx.line());
206188
}
207-
throw new LintError('unrecognised #-form: only #t, #f, #u5"…" are legal', lx.line());
189+
lexString(lx);
190+
return ["uuid5", null];
208191
}
209-
if (c === "(") {
210-
lx.i += 1;
211-
const items = [];
212-
for (;;) {
213-
lx.skipSep();
214-
if (lx.peek() === ")") {
215-
lx.i += 1;
216-
return ["list", items];
217-
}
218-
items.push(lexValue(lx));
192+
if (two.slice(0, 2) === "#t" || two.slice(0, 2) === "#f") {
193+
const nxt = lx.peek(2);
194+
if (nxt && !" \r\n()".includes(nxt)) {
195+
const tok = lx.t.slice(lx.i + 2, lx.i + 24).trim().split(/\s+/)[0].slice(0, 20);
196+
throw new LintError(
197+
`booleans are exactly #t/#f (got ${pyRepr(two + tok)}); true/false/1/0 are parse errors`,
198+
lx.line(),
199+
);
219200
}
201+
lx.i += 2;
202+
return ["boolean", two];
220203
}
221-
if (c === "'") {
222-
lx.i += 1;
204+
throw new LintError('unrecognised #-form: only #t, #f, #u5"…" are legal', lx.line());
205+
}
206+
207+
/** list = "(" *(sep value) ")" — returns ["list", items]. */
208+
function lexList(lx) {
209+
lx.i += 1;
210+
const items = [];
211+
for (;;) {
223212
lx.skipSep();
224-
const v = lexValue(lx);
225-
if (v[0] !== "symbol" && v[0] !== "list") {
226-
throw new LintError(`only symbols and lists may be quoted, not ${v[0]}`, lx.line());
213+
if (lx.peek() === ")") {
214+
lx.i += 1;
215+
return ["list", items];
227216
}
228-
return ["quoted", v];
217+
items.push(lexValue(lx));
229218
}
230-
if (c === ":") throw new LintError("stray keyword — a keyword may only lead a field", lx.line());
231-
if (isDigit(c) || (c === "-" && isDigit(lx.peek(1)))) {
232-
const v = lexNumber(lx);
233-
const nxt = lx.peek();
234-
if (nxt && (SYMBOL_CONT.test(nxt) || /^\p{L}$/u.test(nxt))) {
235-
throw new LintError("malformed token: number followed by identifier characters", lx.line());
236-
}
237-
return v;
219+
}
220+
221+
/** quoted = "'" value, where only symbols and lists may be quoted. */
222+
function lexQuoted(lx) {
223+
lx.i += 1;
224+
lx.skipSep();
225+
const v = lexValue(lx);
226+
if (v[0] !== "symbol" && v[0] !== "list") {
227+
throw new LintError(`only symbols and lists may be quoted, not ${v[0]}`, lx.line());
228+
}
229+
return ["quoted", v];
230+
}
231+
232+
/** integer token; rejects a number run straight into identifier characters. */
233+
function lexIntegerToken(lx) {
234+
const v = lexNumber(lx);
235+
const nxt = lx.peek();
236+
if (nxt && (SYMBOL_CONT.test(nxt) || /^\p{L}$/u.test(nxt))) {
237+
throw new LintError("malformed token: number followed by identifier characters", lx.line());
238238
}
239+
return v;
240+
}
241+
242+
/** value = string / symbol / integer / boolean / uuid5 / quoted / list */
243+
function lexValue(lx) {
244+
const c = lx.peek();
245+
if (c === '"') return lexString(lx);
246+
if (c === "#") return lexHashForm(lx);
247+
if (c === "(") return lexList(lx);
248+
if (c === "'") return lexQuoted(lx);
249+
if (c === ":") throw new LintError("stray keyword — a keyword may only lead a field", lx.line());
250+
if (isDigit(c) || (c === "-" && isDigit(lx.peek(1)))) return lexIntegerToken(lx);
239251
const v = lexSymbol(lx);
240252
if (v) return v;
241253
throw new LintError(

0 commit comments

Comments
 (0)