Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 62 additions & 50 deletions 1-formats/deed/tools/deed_lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,64 +178,76 @@ function isDigit(c) {
return c !== "" && c >= "0" && c <= "9";
}

/** value = string / symbol / integer / boolean / uuid5 / quoted / list */
function lexValue(lx) {
const c = lx.peek();
if (c === '"') return lexString(lx);
if (c === "#") {
const two = lx.t.slice(lx.i, lx.i + 3);
if (two.startsWith("#u5")) {
lx.i += 3;
if (lx.peek() !== '"') {
throw new LintError('uuid5 must be followed immediately by a string: #u5"name"', lx.line());
}
lexString(lx);
return ["uuid5", null];
}
if (two.slice(0, 2) === "#t" || two.slice(0, 2) === "#f") {
const nxt = lx.peek(2);
if (nxt && !" \r\n()".includes(nxt)) {
const tok = lx.t.slice(lx.i + 2, lx.i + 24).trim().split(/\s+/)[0].slice(0, 20);
throw new LintError(
`booleans are exactly #t/#f (got ${pyRepr(two + tok)}); true/false/1/0 are parse errors`,
lx.line(),
);
}
lx.i += 2;
return ["boolean", two];
/** #-forms: #u5"name" (uuid5) or #t / #f (boolean); anything else is a parse error. */
function lexHashForm(lx) {
const two = lx.t.slice(lx.i, lx.i + 3);
if (two.startsWith("#u5")) {
lx.i += 3;
if (lx.peek() !== '"') {
throw new LintError('uuid5 must be followed immediately by a string: #u5"name"', lx.line());
}
throw new LintError('unrecognised #-form: only #t, #f, #u5"…" are legal', lx.line());
lexString(lx);
return ["uuid5", null];
}
if (c === "(") {
lx.i += 1;
const items = [];
for (;;) {
lx.skipSep();
if (lx.peek() === ")") {
lx.i += 1;
return ["list", items];
}
items.push(lexValue(lx));
if (two.slice(0, 2) === "#t" || two.slice(0, 2) === "#f") {
const nxt = lx.peek(2);
if (nxt && !" \r\n()".includes(nxt)) {
const tok = lx.t.slice(lx.i + 2, lx.i + 24).trim().split(/\s+/)[0].slice(0, 20);
throw new LintError(
`booleans are exactly #t/#f (got ${pyRepr(two + tok)}); true/false/1/0 are parse errors`,
lx.line(),
);
}
lx.i += 2;
return ["boolean", two];
}
if (c === "'") {
lx.i += 1;
throw new LintError('unrecognised #-form: only #t, #f, #u5"…" are legal', lx.line());
}

/** list = "(" *(sep value) ")" — returns ["list", items]. */
function lexList(lx) {
lx.i += 1;
const items = [];
for (;;) {
lx.skipSep();
const v = lexValue(lx);
if (v[0] !== "symbol" && v[0] !== "list") {
throw new LintError(`only symbols and lists may be quoted, not ${v[0]}`, lx.line());
if (lx.peek() === ")") {
lx.i += 1;
return ["list", items];
}
return ["quoted", v];
items.push(lexValue(lx));
}
if (c === ":") throw new LintError("stray keyword — a keyword may only lead a field", lx.line());
if (isDigit(c) || (c === "-" && isDigit(lx.peek(1)))) {
const v = lexNumber(lx);
const nxt = lx.peek();
if (nxt && (SYMBOL_CONT.test(nxt) || /^\p{L}$/u.test(nxt))) {
throw new LintError("malformed token: number followed by identifier characters", lx.line());
}
return v;
}

/** quoted = "'" value, where only symbols and lists may be quoted. */
function lexQuoted(lx) {
lx.i += 1;
lx.skipSep();
const v = lexValue(lx);
if (v[0] !== "symbol" && v[0] !== "list") {
throw new LintError(`only symbols and lists may be quoted, not ${v[0]}`, lx.line());
}
return ["quoted", v];
}

/** integer token; rejects a number run straight into identifier characters. */
function lexIntegerToken(lx) {
const v = lexNumber(lx);
const nxt = lx.peek();
if (nxt && (SYMBOL_CONT.test(nxt) || /^\p{L}$/u.test(nxt))) {
throw new LintError("malformed token: number followed by identifier characters", lx.line());
}
return v;
}

/** value = string / symbol / integer / boolean / uuid5 / quoted / list */
function lexValue(lx) {
const c = lx.peek();
if (c === '"') return lexString(lx);
if (c === "#") return lexHashForm(lx);
if (c === "(") return lexList(lx);
if (c === "'") return lexQuoted(lx);
if (c === ":") throw new LintError("stray keyword — a keyword may only lead a field", lx.line());
if (isDigit(c) || (c === "-" && isDigit(lx.peek(1)))) return lexIntegerToken(lx);
const v = lexSymbol(lx);
if (v) return v;
throw new LintError(
Expand Down
Loading