Add Mode of Beleriand
[tengwarjs.git] / parser.js
1
2 var punctuation = require("./punctuation");
3
4 // builds a string parser from a streaming character parser
5 exports.makeParser = makeParser;
6 function makeParser(production, errorHandler) {
7 var errorHandler = errorHandler || function (error, text) {
8 throw new Error(error + " while parsing " + JSON.stringify(text));
9 };
10 return function (text /*, ...args*/) {
11 // the parser is a monadic state machine.
12 // each state is represented by a function that accepts
13 // a character. parse functions accept a callback (for forwarding the
14 // result) and return a state.
15 var result;
16 var state = production.apply(null, [function (_result) {
17 result = _result;
18 return expectEof(function (error) {
19 return errorHandler(error, text);
20 });
21 }].concat(Array.prototype.slice.call(arguments, 1)));
22 // drive the state machine
23 Array.prototype.forEach.call(text, function (letter, i) {
24 state = state(letter);
25 });
26 // break break break
27 while (!result) {
28 state = state(""); // EOF
29 }
30 return result;
31 };
32 }
33
34 function expectEof(errback) {
35 return function (character) {
36 if (character !== "") {
37 errback("Unexpected " + JSON.stringify(character));
38 }
39 return function noop() {
40 return noop;
41 };
42 };
43 }
44
45 exports.makeExpect = makeExpect;
46 function makeExpect(expected) {
47 return function (callback) {
48 return function (character) {
49 if (character === expected) {
50 return callback(character);
51 } else {
52 return callback()(character);
53 }
54 };
55 };
56 }
57
58 exports.makeParseSome = makeParseSome;
59 function makeParseSome(expected) {
60 var parseSome = function (callback) {
61 return function (character) {
62 if (character === expected) {
63 return parseRemaining(callback);
64 } else {
65 return callback()(character);
66 }
67 };
68 };
69 var parseRemaining = makeParseAny(expected);
70 return parseSome;
71 }
72
73 exports.makeParseAny = makeParseAny;
74 function makeParseAny(expected) {
75 return function parseRemaining(callback) {
76 return function (character) {
77 if (character === expected) {
78 return parseRemaining(callback);
79 } else {
80 return callback(expected)(character);
81 }
82 };
83 };
84 }
85
86 exports.makeDelimitedParser = makeDelimitedParser;
87 function makeDelimitedParser(parsePrevious, parseDelimiter) {
88 return function parseSelf(callback, options, terms) {
89 terms = terms || [];
90 return parsePrevious(function (term) {
91 if (!term.length) {
92 return callback(terms);
93 } else {
94 terms = terms.concat([term]);
95 return parseDelimiter(function (delimiter) {
96 if (delimiter) {
97 return parseSelf(callback, options, terms);
98 } else {
99 return callback(terms);
100 }
101 });
102 }
103 }, options);
104 }
105 }
106
107 // used by parsers to determine whether the cursor is on a word break
108 exports.isBreak = isBreak;
109 function isBreak(character) {
110 return character === " " || character === "\n" || character === "";
111 }
112
113 exports.isFinal = isFinal;
114 function isFinal(character) {
115 return isBreak(character) || punctuation[character];
116 }
117
118 // used by multiple modes
119 exports.countPrimes = countPrimes;
120 function countPrimes(callback, primes) {
121 primes = primes || 0;
122 return function (character) {
123 if (character === "'") {
124 return countPrimes(callback, primes + 1);
125 } else {
126 return callback(primes)(character);
127 }
128 };
129 }
130