2 var TengwarAnnatar
= require("./tengwar-annatar");
3 var Notation
= require("./notation");
4 var Parser
= require("./parser");
5 var makeDocumentParser
= require("./document-parser");
6 var normalize
= require("./normalize");
7 var punctuation
= require("./punctuation");
8 var parseNumber
= require("./numbers");
10 exports
.name
= "General Use Mode";
13 exports
.makeOptions
= makeOptions
;
14 function makeOptions(options
) {
15 options
= options
|| defaults
;
17 if (options
.blackSpeech
) {
18 options
.language
= "blackSpeech";
20 if (options
.language
=== "blackSpeech") {
21 options
.language
= "black-speech";
24 font
: options
.font
|| TengwarAnnatar
,
27 doubleNasalsWithTildeBelow
: options
.doubleNasalsWithTildeBelow
,
28 // Any tengwa can be doubled by placing a tilde above, and any tengwa
29 // can be prefixed with the nasal from the same series by putting a
30 // tilde below. Doubled nasals have the special distinction that
31 // either of these rules might apply so the tilde can go either above
33 // false: by default, place a tilde above doubled nasals.
34 // true: place the tilde below doubled nasals.
35 reverseCurls
: options
.reverseCurls
|| options
.language
=== "black-speech",
36 // false: by default, o is forward, u is backward
37 // true: o is backward, u is forward
38 swapDotSlash
: options
.swapDotSlash
,
39 // false: by default, e is a slash, i is a dot
40 // true: e is a dot, i is a slash
41 medialOre
: options
.medialOre
|| options
.language
=== "black-speech",
42 // false: by default, ore only appears in final position
43 // true: ore also appears before consonants, as in the ring inscription
44 language
: options
.language
,
45 // by default, no change
46 // "english": final e implicitly silent
47 // "black speech": sh is calma-extended, gh is ungwe-extended, as in
48 // the ring inscription
49 // not "black-speech": sh is harma, gh is unque
50 noAchLaut
: options
.noAchLaut
,
51 // false: "ch" is interpreted as ach-laut, "cc" as "ch" as in "chew"
52 // true: "ch" is interpreted as "ch" as in chew
54 // false: "is" is silme with I tehta
55 // true: "is" is short carrier with S hook and I tehta
57 // false: "ts" and "dz" are rendered as separate characters
58 // true: "ts" is IPA "c" and "dz" is IPA "dʒ"
59 duodecimal
: options
.duodecimal
60 // false: numbers are decimal by default
61 // true: numbers are duodecimal by default
65 exports
.transcribe
= transcribe
;
66 function transcribe(text
, options
) {
67 options
= makeOptions(options
);
68 var font
= options
.font
;
69 return font
.transcribe(parse(text
, options
), options
);
72 exports
.encode
= encode
;
73 function encode(text
, options
) {
74 options
= makeOptions(options
);
75 return Notation
.encode(parse(text
, options
), options
);
78 var parse
= exports
.parse
= makeDocumentParser(parseNormalWord
, makeOptions
);
80 function parseNormalWord(callback
, options
) {
81 return normalize(parseWord(callback
, options
));
84 function parseWord(callback
, options
) {
85 var font
= options
.font
;
86 var makeColumn
= font
.makeColumn
;
87 return scanWord(function (word
, rewind
) {
88 if (options
.language
=== "english") {
90 return function (character
) {
91 if (Parser
.isBreak(character
)) {
92 return scanWord(function (word
, rewind
) {
97 } else if (word
=== "the'") {
100 makeThePrime(makeColumn
)
102 } else if (word
=== "the''") {
105 makeThePrime(makeColumn
)
108 return rewind(callback([
114 return callback([makeOf(makeColumn
)])(character
);
117 } else if (word
=== "of'") {
118 return scanWord(function (word
, rewind
) {
119 if (word
=== "the") {
121 makeOfPrime(makeColumn
),
124 } else if (word
=== "the'") {
126 makeOfPrime(makeColumn
),
127 makeThePrime(makeColumn
)
129 } else if (word
=== "the''") {
131 makeOfPrime(makeColumn
),
132 makeThePrimePrime(makeColumn
)
135 return rewind(callback([
136 makeOfPrime(makeColumn
)
140 } else if (word
=== "the") {
144 } else if (word
=== "the'") {
146 makeThePrime(makeColumn
)
148 } else if (word
=== "the''") {
150 makeThePrimePrime(makeColumn
)
152 } else if (word
=== "of'the") {
156 } else if (word
=== "of'the'") {
158 makeOfPrime(makeColumn
)
159 ])("t")("h")("e")("'");
160 } else if (word
=== "and") {
164 } else if (word
=== "and'") {
166 makeAndPrime(makeColumn
)
168 } else if (word
=== "and''") {
170 makeAndPrimePrime(makeColumn
)
172 } else if (word
=== "we") {
174 makeColumn("vala", {from
: "w"}),
175 makeColumn("short-carrier", {from
: ""})
176 .addAbove("e", {from
: "e"})
179 } else if (word
=== "we'") { // Unattested, my invention - kriskowal
181 makeColumn("vala", {from
: "w", diphthong
: true})
182 .addBelow("y", {from
: "ē"})
187 return callback(Notation
.decodeWord(book
[word
], makeColumn
), {
191 return callback(parseWordPiecewise(word
, word
.length
, options
), word
);
197 "iant": "yanta;tinco:a,tilde-above",
198 "iaur": "yanta;vala:a;ore",
199 "baranduiniant": "umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",
200 "ioreth": "yanta;romen:o;thule:e",
201 "noldo": "nwalme;lambe:o;ando;short-carrier:o",
202 "noldor": "nwalme;lambe:o;ando;ore:o"
205 // TODO Fix bug where "of", "the", and "and" decompose with following
207 function scanWord(callback
, options
, word
, rewind
) {
209 rewind
= rewind
|| function (state
) {
212 return function (character
) {
213 if (Parser
.isBreak(character
)) {
214 return callback(word
, rewind
)(character
);
216 return scanWord(callback
, options
, word
+ character
, function (state
) {
217 return rewind(state
)(character
);
223 var parseWordPiecewise
= Parser
.makeParser(function (callback
, length
, options
) {
224 return parseWordTail(callback
, length
, options
, []);
227 function parseWordTail(callback
, length
, options
, columns
, previous
) {
228 return parseColumn(function (moreColumns
) {
229 if (!moreColumns
.length
) {
230 return callback(columns
);
232 return parseWordTail(
236 columns
.concat(moreColumns
),
237 moreColumns
[moreColumns
.length
- 1] // previous
240 }, length
, options
, previous
);
243 function makeOf(makeColumn
) {
244 return makeColumn("umbar-extended", {from
: "of"})
248 function makeOfPrime(makeColumn
) {
249 return makeOf(makeColumn
)
250 .addAbove("o", {from
: "o", silent
: true})
251 .varies(); // TODO is this supposed to be u above?
254 function makeOfPrimePrime(makeColumn
) {
255 return makeColumn("formen", {from
: "f"})
256 .addAbove("o", {from
: "o"});
259 function makeThe(makeColumn
) {
260 return makeColumn("ando-extended", {from
: "the"})
264 function makeThePrime(makeColumn
) {
265 return makeThe(makeColumn
).addBelow("i-below", {from
: ""})
269 function makeThePrimePrime(makeColumn
) {
270 return makeColumn("thule", {from
: "th"}).addBelow("i-below", {from
: "e", silent
: true});
273 function makeOfThe(makeColumn
) {
274 return makeColumn("umbar-extended", {from
: "of the"})
275 .addTildeBelow({from
: ""});
278 function makeAnd(makeColumn
) {
279 return makeColumn("ando", {from
: "and"})
280 .addTildeAbove({from
: ""});
283 function makeAndPrime(makeColumn
) {
284 return makeAnd(makeColumn
)
285 .addBelow("i-below", {from
: ""})
289 function makeAndPrimePrime(makeColumn
) {
290 return makeColumn("ando", {from
: "d"})
291 .addTildeAbove("n", {from
: "n"})
292 .addAbove("a", {from
: "a"});
295 function parseColumn(callback
, length
, options
, previous
) {
296 var font
= options
.font
;
297 var makeColumn
= font
.makeColumn
;
299 return parseTehta(function (tehta
, tehtaFrom
) {
300 return parseTengwa(function (column
, tehta
, tehtaFrom
) {
303 if (options
.reverseCurls
) {
304 tehta
= reverseCurls
[tehta
] || tehta
;
306 if (options
.swapDotSlash
) {
307 tehta
= swapDotSlash
[tehta
] || tehta
;
309 if (column
.tengwa
=== "silme" && tehta
&& options
.sHook
) {
311 makeColumn("short-carrier", {from
: ""})
312 .addAbove(tehta
, {from
: tehtaFrom
})
313 .addBelow("s", {from
: "s"})
315 } else if (options
.language
=== "english" && shorterVowels
[tehta
]) {
316 // doubled vowels are composed from individual letters,
319 makeColumn("long-carrier", {from
: shorterVowels
[tehta
]})
320 .addAbove(shorterVowels
[tehta
], {from
: shorterVowels
[tehta
]}),
323 } else if (canAddAboveTengwa(tehta
) && column
.canAddAbove(tehta
)) {
324 column
.addAbove(tehta
, {from
: tehtaFrom
});
325 return parseTengwaAnnotations(function (column
) {
326 return callback([column
]);
327 }, column
, length
, options
);
329 // some tengwar inherently lack space above them
330 // and cannot be reversed to make room.
331 // some long tehtar cannot be placed on top of
333 // put the previous tehta over the appropriate carrier
334 // then follow up with this tengwa.
335 return parseTengwaAnnotations(function (column
) {
336 return callback([makeCarrier(tehta
, tehtaFrom
, options
), column
]);
337 }, column
, length
, options
);
340 return parseTengwaAnnotations(function (column
) {
341 return callback([column
]);
342 }, column
, length
, options
);
345 if (options
.reverseCurls
) {
346 tehta
= reverseCurls
[tehta
] || tehta
;
348 if (options
.swapDotSlash
) {
349 tehta
= swapDotSlash
[tehta
] || tehta
;
351 return parseTengwaAnnotations(function (carrier
) {
352 return callback([carrier
]);
353 }, makeCarrier(tehta
, tehtaFrom
, options
), length
, options
);
355 return function (character
) {
356 if (Parser
.isBreak(character
)) {
358 } else if (/\d/.test(character
)) {
359 return parseNumber(callback
, options
)(character
);
360 } else if (punctuation
[character
]) {
361 return callback([makeColumn(punctuation
[character
], {from
: character
})]);
364 makeColumn("ure", {from
: character
})
366 "Cannot transcribe " +
367 JSON
.stringify(character
) +
368 " in General Use Mode"
374 }, options
, tehta
, tehtaFrom
);
379 function makeCarrier(tehta
, tehtaFrom
, options
) {
380 var font
= options
.font
;
381 var makeColumn
= font
.makeColumn
;
383 return makeColumn("wilya", {from
: "a"})
384 .addAbove("a", {from
: "a"});
385 } else if (shorterVowels
[tehta
]) {
386 return makeColumn("long-carrier", {from
: tehtaFrom
})
387 .addAbove(shorterVowels
[tehta
], {from
: ""});
389 return makeColumn("short-carrier", {from
: tehtaFrom
})
390 .addAbove(tehta
, {from
: ""});
394 function parseTehta(callback
, options
) {
395 return function (character
) {
396 var firstCharacter
= character
;
397 if (character
=== "ë" && options
.language
!== "english") {
400 if (character
=== "") {
402 } else if (lengthenableVowels
.indexOf(character
) !== -1) {
403 return function (nextCharacter
) {
404 if (nextCharacter
=== character
) {
405 return callback(longerVowels
[character
], longerVowels
[character
]);
407 return callback(character
, character
)(nextCharacter
);
410 } else if (nonLengthenableVowels
.indexOf(character
) !== -1) {
411 return callback(character
, character
);
413 return callback()(character
);
418 var lengthenableVowels
= "aeiou";
419 var longerVowels
= {"a": "á", "e": "é", "i": "í", "o": "ó", "u": "ú"};
420 var nonLengthenableVowels
= "aeióú";
421 var tehtarThatCanBeAddedAbove
= "aeiouóú";
422 var vowels
= "aeëiouáéíóú";
423 var shorterVowels
= {"á": "a", "é": "e", "í": "i", "ó": "o", "ú": "u"};
424 var reverseCurls
= {"o": "u", "u": "o", "ó": "ú", "ú": "ó"};
425 var swapDotSlash
= {"i": "e", "e": "i"};
427 function canAddAboveTengwa(tehta
) {
428 return tehtarThatCanBeAddedAbove
.indexOf(tehta
) !== -1;
431 function parseTengwa(callback
, options
, tehta
, tehtaFrom
) {
432 var font
= options
.font
;
433 var makeColumn
= font
.makeColumn
;
434 return function (character
) {
435 if (character
=== "n") {
436 return function (character
) {
437 if (character
=== "n") { // nn
438 if (options
.doubleNasalsWithTildeBelow
) {
440 makeColumn("numen", {from
: "n"})
441 .addTildeBelow({from
: "n"}),
447 makeColumn("numen", {from
: "n"})
448 .addTildeAbove({from
: "n"}),
453 } else if (character
=== "t") { // nt
454 return function (character
) {
455 if (character
=== "h") { // nth
457 makeColumn("thule", {from
: "th"})
458 .addTildeAbove({from
: "n"}),
464 makeColumn("tinco", {from
: "t"})
465 .addTildeAbove({from
: "n"}),
471 } else if (character
=== "d") { // nd
472 return callback(makeColumn("ando", {from
: "d"}).addTildeAbove({from
: "n"}), tehta
, tehtaFrom
);
473 } else if (character
=== "c") { // nc -> ñc
474 return callback(makeColumn("quesse", {from
: "c"}).addTildeAbove({from
: "ñ"}), tehta
, tehtaFrom
);
475 } else if (character
=== "g") { // ng -> ñg
476 return callback(makeColumn("ungwe", {from
: "g"}).addTildeAbove({from
: "ñ"}), tehta
, tehtaFrom
);
477 } else if (character
=== "j") { // nj
478 return callback(makeColumn("anca", {from
: "j"}).addTildeAbove({from
: "n"}), tehta
, tehtaFrom
);
479 } else if (character
=== "f") { // nf -> nv
480 return callback(makeColumn("numen", {from
: "n"}), tehta
, tehtaFrom
)("v");
481 } else if (character
=== "w") { // nw -> ñw
482 return function (character
) {
483 if (character
=== "a") { // nwa
484 return function (character
) { // nwal
485 if (character
=== "l") {
486 return callback(makeColumn("nwalme", {from
: "n"}).addAbove("w", {from
: "w"}), tehta
, tehtaFrom
)("a")(character
);
488 return callback(makeColumn("numen", {from
: "n"}).addAbove("w", {from
: "w"}), tehta
, tehtaFrom
)("a")(character
);
491 } else if (character
=== "nw'") { // nw' prime -> ñw
492 return callback(makeColumn("nwalme", {from
: "ñ"}).addAbove("w", {from
: "w"}), tehta
, tehtaFrom
);
494 return callback(makeColumn("numen", {from
: "n"}).addAbove("w", {from
: "w"}), tehta
, tehtaFrom
)(character
);
498 return callback(makeColumn("numen", {from
: "n"}), tehta
, tehtaFrom
)(character
);
501 } else if (character
=== "m") { // m
502 return function (character
) {
503 if (character
=== "m") { // mm
504 if (options
.doubleNasalsWithTildeBelow
) {
505 return callback(makeColumn("malta", {from
: "m"}).addTildeBelow({from
: "m"}), tehta
, tehtaFrom
);
507 return callback(makeColumn("malta", {from
: "m"}).addTildeAbove({from
: "m"}), tehta
, tehtaFrom
);
509 } else if (character
=== "p") { // mp
510 // mph is simplified to mf using the normalizer (deprecated TODO)
511 return callback(makeColumn("parma", {from
: "p"}).addTildeAbove({from
: "m"}), tehta
, tehtaFrom
);
512 } else if (character
=== "b") { // mb
513 // mbh is simplified to mf using the normalizer (deprecated TODO)
514 return callback(makeColumn("umbar", {from
: "b"}).addTildeAbove({from
: "m"}), tehta
, tehtaFrom
);
515 } else if (character
=== "f") { // mf
516 return callback(makeColumn("formen", {from
: "f"}).addTildeAbove({from
: "m"}), tehta
, tehtaFrom
);
517 } else if (character
=== "v") { // mv
518 return callback(makeColumn("ampa", {from
: "v"}).addTildeAbove({from
: "m"}), tehta
, tehtaFrom
);
520 return callback(makeColumn("malta", {from
: "m"}), tehta
, tehtaFrom
)(character
);
523 } else if (character
=== "ñ") { // ñ
524 return function (character
) {
525 // ññ does not exist to the best of my knowledge
526 // ñw is handled naturally by following w
527 if (character
=== "c") { // ñc
528 return callback(makeColumn("quesse", {from
: "c"}).addTildeAbove({from
: "ñ"}), tehta
, tehtaFrom
);
529 } else if (character
=== "g") { // ñg
530 return callback(makeColumn("ungwe", {from
: "g"}).addTildeAbove({from
: "ñ"}), tehta
, tehtaFrom
);
532 return callback(makeColumn("nwalme", {from
: "ñ"}), tehta
, tehtaFrom
)(character
);
535 } else if (character
=== "t") { // t
536 return function (character
) {
537 if (character
=== "t") { // tt
538 return callback(makeColumn("tinco", {from
: "t"}).addTildeBelow({from
: "t"}), tehta
, tehtaFrom
);
539 } else if (character
=== "h") { // th
540 return callback(makeColumn("thule", {from
: "th"}), tehta
, tehtaFrom
);
541 } else if (character
=== "c") { // tc
542 return function (character
) {
543 if (character
=== "h") { // tch -> tinco calma
544 return callback(makeColumn("tinco", {from
: "t"}), tehta
, tehtaFrom
)("c")("h")("'");
546 return callback(makeColumn("tinco", {from
: "t"}), tehta
, tehtaFrom
)("c")(character
);
549 } else if (character
=== "s" && options
.tsdz
) { // ts
550 return callback(makeColumn("calma", {from
: "ts"}), tehta
, tehtaFrom
);
552 return callback(makeColumn("tinco", {from
: "t"}), tehta
, tehtaFrom
)(character
);
555 } else if (character
=== "p") { // p
556 return function (character
) {
557 // ph is simplified to f by the normalizer (deprecated)
558 if (character
=== "p") { // pp
559 return callback(makeColumn("parma", {from
: "p"}).addTildeBelow({from
: "p"}), tehta
, tehtaFrom
);
560 } else if (character
=== "h") { // ph
561 return callback(makeColumn("formen", {from
: "ph"}), tehta
, tehtaFrom
);
563 return callback(makeColumn("parma", {from
: "p"}), tehta
, tehtaFrom
)(character
);
566 } else if (character
=== "c") { // c
567 return function (character
) {
568 // cw should be handled either by following-w or a subsequent
570 if (character
=== "c") { // ch as in charm
571 return callback(makeColumn("calma", {from
: "cc"}), tehta
, tehtaFrom
);
572 } else if (character
=== "h") { // ch, ach-laut, as in bach
573 return Parser
.countPrimes(function (primes
) {
574 if (options
.noAchLaut
&& !primes
) {
575 return callback(makeColumn("calma", {from
: "ch"}), tehta
, tehtaFrom
); // ch as in charm
577 return callback(makeColumn("hwesta", {from
: "ch"}), tehta
, tehtaFrom
); // ch as in bach
581 return callback(makeColumn("quesse", {from
: "c"}), tehta
, tehtaFrom
)(character
);
584 } else if (character
=== "d") {
585 return function (character
) {
586 if (character
=== "d") { // dd
587 return callback(makeColumn("ando", {from
: "d"}).addTildeBelow({from
: "d"}), tehta
, tehtaFrom
);
588 } else if (character
=== "j") { // dj
589 return callback(makeColumn("anga", {from
: "dj"}), tehta
, tehtaFrom
);
590 } else if (character
=== "z" && options
.tsdz
) { // dz
591 // TODO annotate dz to indicate that options.tsdz affects this cluster
592 return callback(makeColumn("anga", {from
: "dz"}), tehta
, tehtaFrom
);
593 } else if (character
=== "h") { // dh
594 return callback(makeColumn("anto", {from
: "dh"}), tehta
, tehtaFrom
);
596 return callback(makeColumn("ando", {from
: "d"}), tehta
, tehtaFrom
)(character
);
599 } else if (character
=== "b") { // b
600 return function (character
) {
601 // bh is simplified to v by the normalizer (deprecated)
602 if (character
=== "b") { // bb
603 return callback(makeColumn("umbar", {from
: "b"}).addTildeBelow({from
: "b"}), tehta
, tehtaFrom
);
604 } else if (character
=== "bh") { // bh
605 return callback(makeColumn("ampa", {from
: "bh (v)"}), tehta
, tehtaFrom
);
607 return callback(makeColumn("umbar", {from
: "b"}), tehta
, tehtaFrom
)(character
);
610 } else if (character
=== "g") { // g
611 return function (character
) {
612 if (character
=== "g") { // gg
613 return callback(makeColumn("ungwe", {from
: "g"}).addTildeBelow({from
: "g"}), tehta
, tehtaFrom
);
614 } else if (character
=== "h") { // gh
615 if (options
.language
=== "black-speech") {
616 return callback(makeColumn("ungwe-extended", {from
: "gh"}), tehta
, tehtaFrom
);
618 return callback(makeColumn("unque", {from
: "gh"}), tehta
, tehtaFrom
);
621 return callback(makeColumn("ungwe", {from
: "g"}), tehta
, tehtaFrom
)(character
);
624 } else if (character
=== "f") { // f
625 return function (character
) {
626 if (character
=== "f") { // ff
627 return callback(makeColumn("formen", {from
: "f"}).addTildeBelow({from
: "f"}), tehta
, tehtaFrom
);
629 return callback(makeColumn("formen", {from
: "f"}), tehta
, tehtaFrom
)(character
);
632 } else if (character
=== "v") { // v
633 return callback(makeColumn("ampa", {from
: "v"}), tehta
, tehtaFrom
);
634 } else if (character
=== "j") { // j
635 return callback(makeColumn("anca", {from
: "j"}), tehta
, tehtaFrom
);
636 } else if (character
=== "s") { // s
637 return function (character
) {
638 if (character
=== "s") { // ss
639 return Parser
.countPrimes(function (primes
) {
640 var tengwa
= primes
> 0 ? "silme-nuquerna" : "silme";
641 var tengwaFrom
= primes
> 0 ? "s′" : "s";
642 var column
= makeColumn(tengwa
, {from
: tengwaFrom
}).addTildeBelow({from
: "s"});
647 column
.addError("Silme does not have this many alternate forms.");
649 return callback(column
, tehta
, tehtaFrom
);
651 } else if (character
=== "h") { // sh
652 if (options
.language
=== "black-speech") {
653 return callback(makeColumn("calma-extended", {from
: "sh"}), tehta
, tehtaFrom
);
655 return callback(makeColumn("harma", {from
: "sh"}), tehta
, tehtaFrom
);
658 return Parser
.countPrimes(function (primes
) {
659 var tengwa
= primes
> 0 ? "silme-nuquerna" : "silme";
660 var tengwaFrom
= primes
> 0 ? "s′" : "s";
661 var column
= makeColumn(tengwa
, {from
: tengwaFrom
});
666 column
.addError("Silme does not have this many alternate forms.");
668 return callback(column
, tehta
, tehtaFrom
);
672 } else if (character
=== "z") { // z
673 return function (character
) {
674 if (character
=== "z") { // zz
675 return Parser
.countPrimes(function (primes
) {
676 var tengwa
= primes
> 0 ? "esse-nuquerna" : "esse";
677 var column
= makeColumn(tengwa
, {from
: "z"}).addTildeBelow({from
: "z"});
682 column
.addError("Esse does not have this many alternate forms.");
684 return callback(column
, tehta
, tehtaFrom
);
687 return Parser
.countPrimes(function (primes
) {
688 var tengwa
= primes
> 0 ? "esse-nuquerna" : "esse";
689 var column
= makeColumn(tengwa
, {from
: "z"});
694 column
.addError("Silme does not have this many alternate forms.");
696 return callback(column
, tehta
, tehtaFrom
);
700 } else if (character
=== "h") { // h
701 return function (character
) {
702 if (character
=== "w") { // hw
703 return callback(makeColumn("hwesta-sindarinwa", {from
: "hw"}), tehta
, tehtaFrom
);
705 return callback(makeColumn("hyarmen", {from
: "h"}), tehta
, tehtaFrom
)(character
);
708 } else if (character
=== "r") { // r
709 return function (character
) {
710 if (character
=== "r") { // rr
711 return callback(makeColumn("romen", {from
: "r"}).addTildeBelow({from
: "r"}), tehta
, tehtaFrom
);
712 } else if (character
=== "h") { // rh
713 return callback(makeColumn("arda", {from
: "rh"}), tehta
, tehtaFrom
);
715 Parser
.isFinal(character
) || (
717 vowels
.indexOf(character
) === -1
719 ) { // r final (optionally r before consonant)
720 return callback(makeColumn("ore", {from
: "r", final
: true}), tehta
, tehtaFrom
)(character
);
722 return callback(makeColumn("romen", {from
: "r"}), tehta
, tehtaFrom
)(character
);
725 } else if (character
=== "l") {
726 return function (character
) {
727 if (character
=== "l") { // ll
728 return callback(makeColumn("lambe", {from
: "l"}).addTildeBelow({from
: "l"}), tehta
, tehtaFrom
);
729 } else if (character
=== "h") { // lh
730 return callback(makeColumn("alda", {from
: "lh"}), tehta
, tehtaFrom
);
732 return callback(makeColumn("lambe", {from
: "l"}), tehta
, tehtaFrom
)(character
);
735 } else if (character
=== "i") { // i
736 return callback(makeColumn("anna", {from
: "i", diphthong
: true}), tehta
, tehtaFrom
);
737 } else if (character
=== "u") { // u
738 return callback(makeColumn("vala", {from
: "u", diphthong
: true}), tehta
, tehtaFrom
);
739 } else if (character
=== "w") { // w
740 return function (character
) {
741 if (character
=== "h") { // wh
742 return callback(makeColumn("hwesta-sindarinwa", {from
: "wh"}), tehta
, tehtaFrom
);
744 return callback(makeColumn("vala", {from
: "w", dipththong
: true}), tehta
, tehtaFrom
)(character
);
747 } else if (character
=== "e" && (!tehta
|| tehta
=== "a")) { // ae or e after consonants
748 return callback(makeColumn("yanta", {from
: "e", diphthong
: true}), tehta
, tehtaFrom
);
749 } else if (character
=== "ë") { // if "ë" makes it this far, it's a diaresis for english
750 return callback(makeColumn("short-carrier", {from
: ""}).addAbove("e", {from
: "e"}));
751 } else if (character
=== "y") {
752 return Parser
.countPrimes(function (primes
) {
754 return callback(makeColumn("wilya", {from
: ""}).addBelow("y", {from
: "y"}), tehta
, tehtaFrom
);
755 } else if (primes
=== 1) {
756 return callback(makeColumn("long-carrier", {from
: "y"}).addAbove("i", {from
: ""}), tehta
, tehtaFrom
);
758 return callback(makeColumn("ure", {from
: "y"}).addError("Consonantal Y only has one variation"));
761 } else if (shorterVowels
[character
]) {
763 makeCarrier(character
, character
, options
)
764 .addAbove(shorterVowels
[character
], {from
: ""}),
768 } else if (character
=== "'" && options
.language
=== "english" && tehta
=== "e") {
769 // final e' in english should be equivalent to diaresis
771 makeColumn("short-carrier", {from
: ""})
772 .addAbove("e", {from
: "e"})
774 } else if (character
=== "" && options
.language
=== "english" && tehta
=== "e") {
775 // tehta deliberately consumed in this one case, not passed forward
777 makeColumn("short-carrier", {from
: ""})
778 .addBelow("i-below", {from
: "e", silent
: true})
781 return callback(null, tehta
, tehtaFrom
)(character
);
786 exports
.parseTengwaAnnotations
= parseTengwaAnnotations
;
787 function parseTengwaAnnotations(callback
, column
, length
, options
) {
788 return parseFollowingAbove(function (column
) {
789 return parseFollowingBelow(function (column
) {
790 return parseFollowing(callback
, column
);
791 }, column
, length
, options
);
795 // add a following-w above the current character if the next character is W and
796 // there is room for it.
797 function parseFollowingAbove(callback
, column
) {
798 if (column
.canAddAbove("w", "w")) {
799 return function (character
) {
800 if (character
=== "w") {
801 return callback(column
.addAbove("w", {from
: "e"}));
803 return callback(column
)(character
);
807 return callback(column
);
811 function parseFollowingBelow(callback
, column
, length
, options
) {
812 return function (character
) {
813 if (character
=== "ë" && options
.language
!== "english") {
816 if (character
=== "y" && column
.canAddBelow("y")) {
817 return callback(column
.addBelow("y", {from
: "y"}));
818 } else if (character
=== "e" && column
.canAddBelow("i-below")) {
819 return Parser
.countPrimes(function (primes
) {
820 return function (character
) {
821 if (Parser
.isFinal(character
) && options
.language
=== "english" && length
> 2) {
824 column
.addBelow("i-below", {from
: "e", silent
: true})
829 column
.addError("Following E has only one variation.");
831 return callback(column
)("e")(character
);
835 return callback(column
.varies())("e")(character
);
838 column
.addError("Following E has only one variation.");
840 return callback(column
.addBelow("i-below", {from
: "e", eilent
: true}))(character
);
846 return callback(column
)(character
);
851 function parseFollowing(callback
, column
) {
852 return function (character
) {
853 if (character
=== "s") {
854 if (column
.canAddBelow("s")) {
855 return Parser
.countPrimes(function (primes
, rewind
) {
857 return callback(column
.addBelow("s", {from
: "s"}).varies());
860 column
.addError("Only one alternate form for following S.");
862 return rewind(callback(column
)("s"));
866 return Parser
.countPrimes(function (primes
, rewind
) {
867 return function (character
) {
868 if (Parser
.isFinal(character
)) { // end of word
869 if (column
.canAddFollowing("s-final") && primes
-- === 0) {
870 column
.addFollowing("s-final", {from
: "s"});
871 } else if (column
.canAddFollowing("s-inverse") && primes
-- === 0) {
872 column
.addFollowing("s-inverse", {from
: "s"});
873 if (column
.canAddFollowing("s-final")) {
876 } else if (column
.canAddFollowing("s-extended") && primes
-- === 0) {
877 column
.addFollowing("s-extended", {from
: "s"});
878 if (column
.canAddFollowing("s-inverse")) {
881 } else if (column
.canAddFollowing("s-flourish") && primes
-- === 0) {
882 column
.addFollowing("s-flourish", {from
: "s"});
883 if (column
.canAddFollowing("s-extended")) {
887 // rewind primes for subsequent alterations
888 var state
= callback(column
)("s");
889 while (primes
-- > 0) {
894 return callback(column
)(character
);
896 return rewind(callback(column
)("s"))(character
);
902 return callback(column
)(character
);