All files / json-pack/src/ion ast.ts

90.55% Statements 115/127
81.42% Branches 57/70
95.83% Functions 23/24
91.66% Lines 88/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 1866x                       6x 2584x 2584x   2580x       6x 5323x 5323x   5315x       6x   2887x 2887x 2866x 2123x 1709x 1683x 1672x 1667x 1612x     2829x       6x   2676x 2676x 2676x 2676x 2098x 1686x 1677x 1667x 1662x 1607x     2614x       6x 20624x 20624x   20577x       6x 80197x 4746x             6x   68573x 68573x     68529x       6x   10x 10x     1x       6x   8040x 8040x     8040x   6749x 65565x 6749x         8009x       6x   10711x 10711x     10711x   9683x 9683x 51528x   9683x         8674x       6x       2035x 2035x   2035x 2035x 2035x 2035x 2035x 2035x             6x   6x 119393x 116809x 108769x 108759x   5323x   26187x 20624x     68573x   8676x 8676x 49493x 49493x   8676x          
import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8';
import type {Import} from './Import';
 
export interface AstNode<T> {
  /** Node value as JS value. */
  readonly val: T;
  /** Node representation length. */
  readonly len: number;
  /** Total length of the node. */
  byteLength(): number;
}
 
export class NullAstNode implements AstNode<null> {
  public readonly val = null;
  public readonly len = 1;
  public byteLength(): number {
    return 1;
  }
}
 
export class BoolAstNode implements AstNode<boolean> {
  public readonly len = 1;
  constructor(public readonly val: boolean) {}
  public byteLength(): number {
    return 1;
  }
}
 
export class UintAstNode implements AstNode<number> {
  public readonly len: number;
  constructor(public readonly val: number) {
    if (!val) this.len = 0;
    else if (val <= 0xff) this.len = 1;
    else if (val <= 0xffff) this.len = 2;
    else if (val <= 0xffffff) this.len = 3;
    else if (val <= 0xffffffff) this.len = 4;
    else if (val <= 0xffffffffff) this.len = 5;
    else if (val <= 0xffffffffffff) this.len = 6;
    else this.len = 7;
  }
  public byteLength(): number {
    return 1 + this.len;
  }
}
 
export class NintAstNode implements AstNode<number> {
  public readonly len: number;
  constructor(public readonly val: number) {
    const uint = -val;
    Iif (!uint) this.len = 0;
    else if (uint <= 0xff) this.len = 1;
    else if (uint <= 0xffff) this.len = 2;
    else if (uint <= 0xffffff) this.len = 3;
    else if (uint <= 0xffffffff) this.len = 4;
    else if (uint <= 0xffffffffff) this.len = 5;
    else if (uint <= 0xffffffffffff) this.len = 6;
    else this.len = 7;
  }
  public byteLength(): number {
    return 1 + this.len;
  }
}
 
export class FloatAstNode implements AstNode<number> {
  public readonly len: number = 8;
  constructor(public readonly val: number) {}
  public byteLength(): number {
    return 1 + this.len;
  }
}
 
const vUintLen = (num: number): number => {
  if (num <= 0b1111111) return 1;
  else if (num <= 0b1111111_1111111) return 2;
  else Eif (num <= 0b1111111_1111111_1111111) return 3;
  else if (num <= 0b1111111_1111111_1111111_1111111) return 4;
  else if (num <= 0b1111111_1111111_1111111_1111111_1111111) return 5;
  else return 6;
};
 
export class StrAstNode implements AstNode<string> {
  public readonly len: number;
  constructor(public readonly val: string) {
    this.len = utf8Size(val);
  }
  public byteLength(): number {
    return this.len < 14 ? 1 + this.len : 1 + vUintLen(this.len) + this.len;
  }
}
 
export class BinAstNode implements AstNode<Uint8Array> {
  public readonly len: number;
  constructor(public readonly val: Uint8Array) {
    this.len = val.length;
  }
  public byteLength(): number {
    return this.len < 14 ? 1 + this.len : 1 + vUintLen(this.len) + this.len;
  }
}
 
export class ArrAstNode implements AstNode<AstNode<unknown>[] | null> {
  public readonly len: number;
  constructor(public readonly val: AstNode<unknown>[] | null) {
    Iif (val === null) {
      this.len = 1;
    } else {
      if (!val.length) this.len = 0;
      else {
        let elementLength = 0;
        for (let i = 0; i < val.length; i++) elementLength += val[i].byteLength();
        this.len = elementLength;
      }
    }
  }
  public byteLength(): number {
    return this.len < 14 ? 1 + this.len : 1 + vUintLen(this.len) + this.len;
  }
}
 
export class ObjAstNode implements AstNode<Map<number, AstNode<unknown>> | null> {
  public readonly len: number;
  constructor(public readonly val: Map<number, AstNode<unknown>> | null) {
    Iif (val === null) {
      this.len = 1;
    } else {
      if (!val.size) this.len = 0;
      else {
        let len = 0;
        val.forEach((node, symbolId) => {
          len += vUintLen(symbolId) + node.byteLength();
        });
        this.len = len;
      }
    }
  }
  public byteLength(): number {
    return this.len < 14 ? 1 + this.len : 1 + vUintLen(this.len) + this.len;
  }
}
 
export class AnnotationAstNode implements AstNode<AstNode<unknown>> {
  public readonly len: number;
  public readonly annotationLen: number;
  constructor(
    public readonly val: AstNode<unknown>,
    public readonly annotations: number[],
  ) {
    let len = 0;
    for (let i = 0; i < annotations.length; i++) len += vUintLen(annotations[i]);
    this.annotationLen = len;
    len += vUintLen(len);
    len += val.byteLength();
    this.len = len;
  }
  public byteLength(): number {
    return this.len < 14 ? 1 + this.len : 1 + vUintLen(this.len) + this.len;
  }
}
 
const isSafeInteger = Number.isSafeInteger;
 
export const toAst = (val: unknown, symbols: Import): AstNode<unknown> => {
  if (val === null) return new NullAstNode();
  if (val instanceof Array) return new ArrAstNode(val.map((el) => toAst(el, symbols)));
  if (val instanceof Uint8Array) return new BinAstNode(val);
  switch (typeof val) {
    case 'boolean':
      return new BoolAstNode(val);
    case 'number': {
      if (isSafeInteger(val)) return val >= 0 ? new UintAstNode(val) : new NintAstNode(val);
      else return new FloatAstNode(val);
    }
    case 'string':
      return new StrAstNode(val);
    case 'object': {
      const struct = new Map<number, AstNode<unknown>>();
      for (const key in val) {
        const symbolId = symbols.add(key);
        struct.set(symbolId, toAst((val as any)[key], symbols));
      }
      return new ObjAstNode(struct);
    }
  }
  throw new Error('UNKNOWN_TYPE');
};