# All files / src/parser context.js

100% Statements 36/36
95% Branches 19/20
100% Functions 13/13
100% Lines 35/35

    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

    27x

    225x
    225x
    225x

    8x
    8x

    1x

    2x

    1x

    2x

    5378x
    5378x
    1x

    5377x

    19165x

    16746x
    11595x
    8640x
    8640x

    2955x

    5151x
    4310x
    4310x

    841x

    9479x

    11135x
    11135x
    11135x
    3x
    3x

    3x

    11135x
    11135x

    11135x

    2568x

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