# All files / src/parser context.js

100% Statements 30/30
88.46% Branches 23/26
100% Functions 12/12
100% Lines 30/30

    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

    29x

    104x
    104x
    104x

    8x
    8x

    1x

    2x

    1x

    1x

    1544x
    1544x
    1x

    1543x

    5009x

    4568x
    2x

    4566x
    3383x
    2574x
    2574x

    809x

    1183x
    1128x
    1128x

    55x

    2374x

    2904x
    2904x

    438x

     /**
     * 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: ${Array.isArray(value) ? value.join("|") : value}`,
          "Unexpected value"
        );
      }

      unexpected(token?: string) {
        return this.syntaxError(
          `Expected: ${
            Array.isArray(token) ? token.join(" | ") : JSON.stringify(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(node: NodeType, Type: string): NodeType {
        const token = this.token || this.stream.last() || {};
        return {
          ...node,
          Type,
          range: node.range.concat(token.end),
        };
      }

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