char/fjson

friendly JSON implementation in TypeScript

git clone https://git.t4t.associates/char/fjson

Charlotte Somrestructurebb03820

main
1.5 KiB58 linesraw
1/** UTF-16 code-unit offsets into the parsed source. */
2export interface Span {
3  readonly start: number;
4  readonly end: number;
5}
6
7/** malformed input | bad Unicode escape | nesting exceeds limit */
8export type FjsonErrorKind = "syntax" | "unicode" | "nesting";
9
10function location(name: string, source: string, offset: number): string {
11  const prefix = source.slice(0, Math.max(0, Math.min(offset, source.length)));
12  let line = 1;
13  let column = 1;
14  let previousCr = false;
15  for (const ch of prefix) {
16    if (ch === "\r") {
17      line += 1;
18      column = 1;
19    } else if (ch === "\n") {
20      if (!previousCr) line += 1;
21      column = 1;
22    } else {
23      column += 1;
24    }
25    previousCr = ch === "\r";
26  }
27  return `${name}:${line}:${column}`;
28}
29
30/** Represents a failure to parse an fJSON document */
31export class FjsonError extends SyntaxError {
32  readonly kind: FjsonErrorKind;
33  readonly span: Span;
34  readonly previous?: Span;
35
36  constructor(
37    kind: FjsonErrorKind,
38    message: string,
39    span: Span,
40    previous?: Span,
41  ) {
42    super(message);
43    this.kind = kind;
44    this.span = span;
45    if (previous !== undefined) this.previous = previous;
46  }
47
48  /** Render against the same source that was parsed. */
49  render(name: string, source: string): string {
50    let message = `${location(name, source, this.span.start)}: ${this.message}`;
51    if (this.previous !== undefined) {
52      message += `\n${
53        location(name, source, this.previous.start)
54      }: First defined here`;
55    }
56    return message;
57  }
58}