All files / parser index.js

93.75% Statements 30/32
100% Branches 2/2
100% Functions 3/3
93.75% Lines 30/32
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              1x 1x 1x 1x   1x   1x 1x 1x 1x                                   8x   8x       210x     109x     8x     8x 8x       223x   223x     223x 223x           40x                 8x 8x                 8x 8x 8x           8x   8x         8x         8x    
/**
 * Syntax Analysis
 *
 * The parser below creates the "bare" Abstract Syntax Tree.
 */
 
// @flow
import invariant from 'invariant';
import Syntax, { tokens } from 'walt-syntax';
import moo from 'moo';
import curry from 'curry';
// $FlowFixMe
import coreGrammar from './grammar/grammar.ne';
// $FlowFixMe
import defaultArgsGrammar from '../syntax-sugar/default-arguments.ne';
import { Parser, Grammar } from 'nearley';
import helpers from './grammar/helpers';
import nodes from './grammar/nodes';
import type { NodeType } from '../flow/types';
 
type GrammarType = {
  Lexer: any,
  ParserRules: Object[],
  ParserStart: string,
};
type MakeGrammar = () => GrammarType;
 
/**
 * Returns a custom lexer. This wrapper API is necessary to ignore comments
 * in all of the subsequent compiler phases, unfortunately.
 *
 * TODO: Maybe consider adding comment nodes back to the AST. IIRC this causes
 *       lots of ambiguous grammar for whatever reason.
 */
function makeLexer() {
  const mooLexer = moo.compile(tokens);
 
  return {
    current: null,
    lines: [],
    get line() {
      return mooLexer.line;
    },
    get col() {
      return mooLexer.col;
    },
    save() {
      return mooLexer.save();
    },
    reset(chunk, info) {
      this.lines = chunk.split('\n');
      return mooLexer.reset(chunk, info);
    },
    next() {
      // It's a cruel and unusual punishment to implement comments with nearly
      let token = mooLexer.next();
      // Drop all comment tokens found
      while (token && token.type === 'comment') {
        token = mooLexer.next();
      }
      this.current = token;
      return this.current;
    },
    formatError(token) {
      return mooLexer.formatError(token);
    },
    has(name) {
      return mooLexer.has(name);
    },
  };
}
 
export default curry(function parse(
  extraGrammar: MakeGrammar[],
  source: string
): NodeType {
  const grammarList = [coreGrammar, defaultArgsGrammar, ...extraGrammar];
  const context = {
    lexer: makeLexer(),
    nodes,
    helpers,
    Syntax,
  };
 
  // All Grammar plugins are factories resulting in an object which must contain
  // a "ParserRules" array which will be added to the base grammar.
  const grammar = grammarList.slice(1).reduce((acc: any, value: Function) => {
    const extra = value.call(context);
    return {
      ...acc,
      ParserRules: acc.ParserRules.concat(extra.ParserRules),
    };
  }, grammarList[0].call(context));
 
  const parser = new Parser(Grammar.fromCompiled(grammar));
 
  parser.feed(source);
 
  // This is a safeguard against ambiguous syntax that may be generated by blending
  // multiple different grammars together. If there is more than one was to parse
  // something then we did something wrong and we hard exit the compiler pipeline.
  invariant(
    parser.results.length === 1,
    `PANIC - Ambiguous Syntax! Number of productions (${parser.results.length})`
  );
 
  return parser.results[0];
});