# All files / src/parser context.js

100% Statements 38/38
90.91% Branches 20/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

    26x

    110x
    110x
    110x

    7x
    7x

    1x

    2x

    1x

    2x

    2041x
    2041x
    1x

    2040x

    7262x

    6411x
    16x

    6395x
    4656x
    3508x
    3508x

    1148x

    1739x
    1672x
    1672x

    67x

    3524x

    4261x
    4261x
    4261x
    3x
    3x

    3x

    4261x
    4261x

    4261x

    562x

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