# All files / src/parser context.js

100% Statements 39/39
95.45% Branches 21/22
100% Functions 13/13
100% Lines 38/38

    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

    26x

    105x
    105x
    105x

    8x
    8x

    1x

    2x

    1x

    2x

    1886x
    1886x
    1x

    1885x

    6747x

    5920x
    16x

    5904x
    4279x
    3237x
    3237x

    1042x

    1625x
    1563x
    1563x

    62x

    3265x

    3956x
    3956x
    3956x
    3956x
    3x
    3x

    3x

    3956x
    3956x

    3956x

    510x

     /**
     * Context is used to parse tokens into the base AST.
     * Originally the parser was a giant class and the context was the 'this' pointer.
     * Maintaining a monolithic parser is rather difficult so it was broken up into a
     * collection of self-contained parsers for each syntactic construct. The context
     * is passed around between each one to generate the desired tree
     */

    // @flow
    import generateErrorString from "../utils/generate-error";
    import type { TokenStream } from "../utils/token-stream";
    import type { TokenType, NodeType } from "../flow/types";

    export default class Context {
      token: TokenType;
      stream: TokenStream;
      filename: string;
      lines: string[];

      constructor({
        stream,
        token,
        lines,
      }: {
        stream: TokenStream,
        token: TokenType,
        lines: string[],
      }) {
        this.token = token;
        this.stream = stream;
        this.lines = lines;
      }

      syntaxError(msg: string, error: any) {
        const functionId = "unknown";
        return new SyntaxError(
          generateErrorString(
            msg,
            error || "",
            this.token,
            this.filename || "unknown",
            functionId
          )
        );
      }

      unexpectedValue(value: string[] | string) {
        return this.syntaxError(`Expected: ${String(value)}`, "Unexpected value");
      }

      unexpected(token?: string) {
        return this.syntaxError(
          `Expected: ${String(token)}`,
          `Unexpected token ${this.token.type}`
        );
      }

      unknown({ value }: { value: string }) {
        return this.syntaxError("Unknown token", value);
      }

      unsupported() {
        return this.syntaxError("Language feature not supported", this.token.value);
      }

      expect(value: string[] | null, type?: string): TokenType {
        const token = this.token;
        if (!this.eat(value, type)) {
          throw value ? this.unexpectedValue(value) : this.unexpected(type);
        }

        return token;
      }

      next() {
        this.token = this.stream.next();
      }

      eat(value: string[] | null, type?: string): boolean {
        if (this.token == null) {
          return false;
        }

        if (value) {
          if (value.includes(this.token.value)) {
            this.next();
            return true;
          }
          return false;
        }

        if (this.token.type === type) {
          this.next();
          return true;
        }

        return false;
      }

      startNode(token: any = this.token || {}): NodeType {
        return {
          Type: "",
          value: token.value,
          range: [token.start],
          meta: {},
          params: [],
          type: null,
        };
      }

      endNode(base: NodeType, Type: string): NodeType {
        const token = this.token || this.stream.last() || {};
        const range = base.range.concat(token.start);
        const { value } = base;
        const toString = () => {
          const start = range[0];
          const end = range[range.length - 1];

          return start.sourceLine.slice(start.col, end.col);
        };
        const { toString: omit, ...seed } = base;
        const node = {
          toString,
          ...seed,
          Type,
          range,
        };
        return node;
      }

      makeNode(node: any, syntax: string): NodeType {
        return this.endNode(
          {
            ...this.startNode(),
            ...node,
          },
          syntax
        );
      }
    }