# All files / src/parser context.js

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

    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

    27x

    122x
    122x
    122x

    8x
    8x

    1x

    2x

    1x

    2x

    2569x
    2569x
    1x

    2568x

    9429x

    8449x
    16x

    8433x
    6080x
    4439x
    4439x

    1641x

    2353x
    2264x
    2264x

    89x

    4521x

    5557x
    5557x
    5557x
    9x
    9x

    9x

    5557x
    5557x

    5557x

    690x

     /**
     * 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 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
        );
      }
    }