All files / src/parser index.js

100% Statements 32/32
100% Branches 2/2
100% Functions 3/3
100% Lines 32/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              27x 27x 27x 27x   27x   27x 27x 27x 27x                                   826x   826x       29602x     15214x     817x     826x 826x       28425x   28425x 72x   28425x 28425x     7x     4130x                 826x 826x                 826x 828x 828x           826x   826x         819x         819x    
/**
 * 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];
});