Support for the FreeMonoTengwar font and ConScript encoding
[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 text = text.trim();
16 var result;
17 var state = production.apply(null, [function (_result) {
18 result = _result;
19 return expectEof(function (error) {
20 return errorHandler(error, text);
21 });
22 }].concat(Array.prototype.slice.call(arguments, 1)));
23 // drive the state machine
24 Array.prototype.forEach.call(text, function (letter, i) {
25 state = state(letter);
26 });
27 // break break break
28 while (!result) {
29 state = state(""); // EOF
30 }
31 return result;
32 };
33 }
34
35 function expectEof(errback) {
36 return function (character) {
37 if (character !== "") {
38 errback("Unexpected " + JSON.stringify(character));
39 }
40 return function noop() {
41 return noop;
42 };
43 };
44 }
45
46 exports.makeExpect = makeExpect;
47 function makeExpect(expected) {
48 return function (callback) {
49 return function (character) {
50 if (character === expected) {
51 return callback(character);
52 } else {
53 return callback()(character);
54 }
55 };
56 };
57 }
58
59 exports.makeParseSome = makeParseSome;
60 function makeParseSome(parseOne) {
61 var parseSome = function (callback) {
62 return parseOne(function (one) {
63 if (one != null) {
64 return parseRemaining(callback, [one]);
65 } else {
66 return callback([]);
67 }
68 });
69 };
70 var parseRemaining = makeParseAny(parseOne);
71 return parseSome;
72 }
73
74 exports.makeParseAny = makeParseAny;
75 function makeParseAny(parseOne) {
76 return function parseRemaining(callback, any) {
77 any = any || [];
78 return parseOne(function (one) {
79 if (one != null) {
80 return parseRemaining(callback, any.concat([one]));
81 } else {
82 return callback(any);
83 }
84 });
85 };
86 }
87
88 exports.makeDelimitedParser = makeDelimitedParser;
89 function makeDelimitedParser(parsePrevious, parseDelimiter) {
90 return function parseSelf(callback, options, terms) {
91 terms = terms || [];
92 return parsePrevious(function (term) {
93 if (!term.length) {
94 return callback(terms);
95 } else {
96 terms = terms.concat([term]);
97 return parseDelimiter(function (delimiter) {
98 if (delimiter) {
99 return parseSelf(callback, options, terms);
100 } else {
101 return callback(terms);
102 }
103 });
104 }
105 }, options);
106 }
107 }
108
109 // used by parsers to determine whether the cursor is on a word break
110 exports.isBreak = isBreak;
111 function isBreak(character) {
112 return character === " " || character === "\n" || character === "";
113 }
114
115 exports.isFinal = isFinal;
116 function isFinal(character) {
117 return isBreak(character) || punctuation[character];
118 }
119
120 // used by multiple modes
121 exports.countPrimes = countPrimes;
122 function countPrimes(callback, primes, rewind) {
123 primes = primes || 0;
124 rewind = rewind || function (state) {
125 return state;
126 };
127 return function (character) {
128 if (character === "'") {
129 return countPrimes(callback, primes + 1, function (state) {
130 return rewind(state)("'");
131 });
132 } else {
133 return callback(primes, rewind)(character);
134 }
135 };
136 }
137