-
Notifications
You must be signed in to change notification settings - Fork 155
/
index.js
108 lines (98 loc) · 2.87 KB
/
index.js
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
/**
* 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];
});