-
Notifications
You must be signed in to change notification settings - Fork 5
/
termSequence.js
1361 lines (1280 loc) · 58.8 KB
/
termSequence.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Methods, which `grammar` inherits, that create `NSymbol` instances that
* produce term sequences.
*
* All possible reductions (i.e., subtrees) of term sequences yield only
* display text, no semantics. Hence, all subtrees each term sequence produces
* are semantically identical. `flattenTermSequence` flattens these symbols'
* parse nodes to single terminal nodes with a single display text.
*/
var util = require('../../../util/util')
var g = require('../grammar')
var NSymbol = require('../NSymbol')
var grammarUtil = require('../grammarUtil')
var termSequenceUtil = require('./termSequenceUtil')
/**
* Instruct `util.getModuleCallerLocation()` to skip the `termSequence` module
* when searching the call stack for instantiation file paths of `NSymbol`
* instances that `termSequence` methods create. For use in error messages.
*/
util.skipFileInLocationRetrieval()
/**
* The enumerated type of term sequence types. For use when assigning the
* `type` parameter with `g.newTermSequence()`.
*
* Designed as an enumerated type, though maps properties to strings instead
* of numbers to include the type names when printed to console.
*
* @type {Object.<string, string>}
*/
exports.termTypes = {
INVARIABLE: 'invariable',
NOUN: 'noun',
PRONOUN: 'pronoun',
VERB: 'verb',
VERB_PRESENT: 'verb-present',
VERB_PAST: 'verb-past',
}
/**
* The term wrapper parameterization, for use in
* `termSequenceSchema.acceptedTerms` by `termSequence.newTermSequence()` and
* `termSequenceSchema.substitutedTerms` by
* `NSymbol.prototype.addTermSequenceSubstitutedTerm()`, to specify properties
* specific to the wrapped `term`.
*
* @typedef {Object} TermWrapper
* @property {string|NSymbol|NSymbol[]} term The terminal symbol, terminal
* rule set, term sequence, or term sequence pair.
* @property {number} [costPenalty] The cost penalty added to the rule's base
* cost.
* @property {number[]} [noInsertionIndexes] If `term` is a sequence pair
* (array), the index(es) of its term sequence(s) for which to forbid
* insertion rules.
*/
var termWrapperSchema = {
term: { type: [ String, NSymbol, Array ], required: true },
costPenalty: { type: Number },
noInsertionIndexes: { type: Array, arrayType: Number },
}
/**
* Creates an `NSymbol` that produces a terminal rule sequence forming a term
* or phrase, comprised of terminal rules, terminal rule sets, and nested term
* sequences.
*
* Each item in `options.acceptedTerms` and `options.substitutedTerms` must be
* one of the following:
* 1. A terminal symbol.
* • I.e., an invariable term which `pfsearch` can not inflect when parsing.
* • Can not contain whitespace.
* 2. A terminal rule set created by `verbTermSet.newVerb()`,
* `verbTermSet.newTenseVerb()`, `pronounTermSet.newPronoun()`,
* `nounTermSet.newCountNoun()`, or `nounTermSet.newMassNoun()`.
* 3. A terminal rule sequence created by this method or
* `termSequence.newTermSequenceBinarySymbol()`.
* 4. An ordered pair containing any combination of #2, #3, or nested ordered
* pairs from which to recursively create new term sequences.
*
* In addition, items in `options.acceptedTerms` and
* `options.substitutedTerms` may also be the following:
* 5. `TermWrapper` - A term with a cost penalty and/or insertion restriction,
* parametrized as follows:
* • {string|NSymbol|NSymbol[]} TermWrapper.term - Any of #1-4 above.
* • {number} [TermWrapper.costPenalty] - The cost penalty added to the
* rule's base cost.
* • {number[]} [TermWrapper.noInsertionIndexes] - If `TermWrapper.term` is
* a sequence pair (array), the index(es) of its term sequence(s) for
* which to forbid insertion rules.
*
* The `defaultText` value (or merger of `defaultText` values) of the first
* term in `options.acceptedTerms` is used as the `text` value of the rules
* for the items in `options.substitutedTerms`, if any, which substitutes the
* text those rules would otherwise produce.
*
* All nonterminal rules the new `NSymbol` produces are marked
* `isTermSequence`, which instructs `flattenTermSequence` to do the
* following:
* 1. For non-edit rules, `flattenTermSequence` merges the `text` values of
* the matched terminal rules each produces.
* 2. For insertion rules, `flattenTermSequence` traverses the single child
* node, gets the `text` values of the matched terminal rules, and merges
* those `text` values with the rule's insertion `text` according to its
* `insertedSymIdx` property.
* 3. For substitution rules, `flattenTermSequence` uses the rule's `text`
* value and ignores the matched terminal rules each produces.
*
* For all three, `flattenTermSequence` creates a new, terminal `ruleProps`
* for the rule's node with the `text` value defined as specified, which
* `pfsearch` uses to generate display text. `flattenTermSequence` also always
* traverses the matched terminal rules to get the input tense of any matched
* verb terminal rules, which `pfsearch` uses if the parent rule has matching
* `acceptedTense`.
*
* `options.type` must be one of the following:
* 1. 'invariable'
* • Each term sequence in `options.acceptedTerms` and
* `options.substitutedTerms` has `termSequenceType` of 'invariable'.
* • `options.acceptedTerms`and `options.substitutedTerms` can contain
* terminal symbols (which are invariable).
* • Enables use of `options.insertionCost`, assigned to the first terminal
* symbol in `options.acceptedTerms`.
* • `options.substitutedTerms` can contain term sequences with
* `termSequenceType` of 'pronoun', though is rare.
* • These restrictions ensure every accepted and substituted term sequence
* is invariable and produces no conjugative text objects.
* 2. 'pronoun'
* • Each term sequence (or terminal rule set) in `options.acceptedTerms`
* has `termSequenceType` of 'pronoun' and each term sequence in
* `options.substitutedTerms` has `termSequenceType` of 'pronoun' or
* 'invariable'.
* • Term sequence pairs in `options.acceptedTerms` must contain one
* 'pronoun' and one 'invariable' term sequence, and every pair in
* `options.substitutedTerms` must contain either one 'pronoun' and one
* 'invariable' term sequence or two 'invariable' term sequences.
* • These restrictions ensure every accepted term sequence produces exactly
* one pronoun terminal rule set, created by `g.newPronoun()`, and every
* substituted sequence produces exactly one pronoun terminal rule set or
* is invariable.
* 3. 'noun'
* • Each term sequence (or terminal rule set) in `options.acceptedTerms`
* and `options.substitutedTerms` has `termSequenceType` of 'noun'.
* • Term sequence pairs in `options.acceptedTerms` and
* `options.substitutedTerms` must contain one 'noun' and one 'invariable'
* term sequence.
* • These restrictions ensure each term sequence produces exactly one noun
* terminal rule set, created by `g.newCountNoun()` or `g.newMassNoun()`.
* 4-6. 'verb', 'verb-present', 'verb-past'
* • Each term sequence (or terminal rule set) in `options.acceptedTerms`
* and `options.substitutedTerms` has `termSequenceType` of 'verb',
* 'verb-present', or 'verb-past' (as `options.type` defines).
* • Hence, each term sequences includes verb forms of either all
* grammatical tenses ('verb'), present tense and excludes past tense
* forms ('verb-present'), or past tense and excludes present tense forms
* ('verb-past').
* • Term sequence pairs in `options.acceptedTerms` and
* `options.substitutedTerms` must contain one 'verb' and one 'invariable'
* term sequence.
* • Term sequence pairs in `options.acceptedTerms` in and
* `options.substitutedTerms` must contain one
* 'verb'/'verb-present'/'verb-past' term sequence (as `options.type`
* defines) and one 'invariable' term sequence.
* • These restrictions ensure each term sequence produces exactly one verb
* terminal rule set, created by `g.newVerb()` or `g.newTenseVerb()` of
* matching tense.
*
* The returned `NSymbol` has the following properties:
* • name - `options.symbolName`.
* • isTermSequence - `true`. Enables nesting within term sequences and
* flattening in `flattenTermSequence`.
* • termSequenceType - `options.type`.
* • defaultText - The `defaultText` value (or merger of `defaultText` values)
* of the first term sequence (or terminal rule set) in
* `options.acceptedTerms`. For use when nesting this `NSymbol` in another
* term sequence.
* • insertionCost - `options.insertionCost`, if defined.
*
* @memberOf termSequence
* @param {Object} options The options object.
* @param {string} options.symbolName The name for the new `NSymbol`.
* @param {string} options.type The term sequence type, as explained above.
* @param {number} [options.insertionCost] The insertion cost for the term
* sequence, assigned to the first terminal symbol in
* `options.acceptedTerms`, if any. Enables the creation of insertion rules
* using the new nonterminal symbol that produces this set. Only permitted
* if `options.type` is 'invariable'.
* @param {(string|NSymbol|NSymbol|TermWrapper[])[]} options.acceptedTerms
* The terminal symbols, terminal rule sets, term sequences, and term
* sequence pairs (with a cost penalty and/or insertion restriction if
* `TermWrapper`) to accept when input, as explained above.
* @param {(string|NSymbol|NSymbol[]|TermWrapper)[]}
* [options.substitutedTerms] The terminal symbols, terminal rule sets, term
* sequences, and term sequence pairs (with a cost penalty and/or insertion
* restriction if `TermWrapper`) to substitute when matched with the first
* item in `options.acceptedTerms`, as explained above.
* @returns {NSymbol} Returns the new `NSymbol` for the terminal rule
* sequence.
*/
var termSequenceSchema = {
symbolName: { type: String, required: true },
type: { values: Object.keys(exports.termTypes).map(term => exports.termTypes[term]), required: true },
insertionCost: Number,
acceptedTerms: { type: Array, arrayType: [ String, NSymbol, Array, Object ], required: true },
substitutedTerms: { type: Array, arrayType: [ String, NSymbol, Array, Object ] },
}
exports.newTermSequence = function (options) {
if (util.illFormedOpts(termSequenceSchema, options) || isIllFormedTermSequenceOptions(options)) {
throw new Error('Ill-formed term sequence')
}
// Create the `NSymbol` that produces the terminal rule sequence.
var termSeqSym = g.newSymbol(options.symbolName)
// The insertion cost to assign to the first terminal symbol in
// `options.acceptedTerms`, if any.
var insertionCost = options.insertionCost
/**
* The `defaultText` value (or merger of `defaultText` values) of the
* first term sequence (or terminal rule set) in `options.acceptedTerms`.
*
* For use as the `text` value of the rules for the term sequences in
* `options.substitutedTerms`, if any, which substitutes the `text` they
* produce.
*
* Can be an invariable term string, a conjugative text object, or an
* array of both.
*/
var defaultText
options.acceptedTerms.forEach(function (term, i) {
// The cost penalty incurred when `term` is matched in input.
var costPenalty = 0
/**
* If `term` is an ordered pair, the pair's indexes for which to
* instruct `createInsertionRules` to prevent the creation of insertion
* rules at that index.
*/
var noInsertionIndexes
if (term.constructor === Object) {
if (isIllFormedTermWrapper(term)) {
throw new Error('Ill-formed term sequence')
}
costPenalty = term.costPenalty || 0
noInsertionIndexes = term.noInsertionIndexes
term = term.term
}
/**
* If `term` is a terminal symbol.
*
* An example:
* `[term-funding]` -> "funding", text: "funding"
*/
if (term.constructor === String) {
// Check `term` lacks whitespace and forbidden characters.
if (termSequenceUtil.isIllFormedTerminalSymbol(term)) {
throw new Error('Ill-formed terminal symbol')
}
// Only permit terminal symbols as accepted term sequences for
// 'invariable' sequences.
if (options.type !== exports.termTypes.INVARIABLE) {
util.logErrorAndPath('Terminal symbol provided as accepted term to parent term sequence', util.stylize(termSeqSym.name), 'of `type`', util.stylize(options.type), '(only permitted for sequences of type', util.stylize(exports.termTypes.INVARIABLE) + '):', util.stylize(term), options)
throw new Error('Ill-formed term sequence')
}
var newTerminalRule = {
isTerminal: true,
rhs: term,
text: term,
costPenalty: costPenalty,
}
// Assign `options.insertionCost`, if defined, to the first accepted
// terminal symbol.
if (insertionCost !== undefined) {
newTerminalRule.insertionCost = insertionCost
/**
* Track when `options.insertionCost` has been assigned to the first
* // terminal symbol in `options.acceptedTerms`, which may not be
* the first element in the array.
*/
insertionCost = undefined
}
termSeqSym.addRule(newTerminalRule)
// If `term` is the first item in `options.acceptedTerms`, save it as
// display text for `options.substitutedTerms`, if any.
if (i === 0) {
defaultText = term
}
}
/**
* If `term` is a term sequence created by
* `termSequence.newTermSequence()` or
* `termSequence.newTermSequenceBinarySymbol()` (or a terminal rule set
* created by `verbTermSet.newVerb()`, `verbTermSet.newTenseVerb()`,
* `pronounTermSet.newPronoun()`, `nounTermSet.newCountNoun()`, or
* `nounTermSet.newMassNoun()`).
*
* An example where `term` is the 'verb' terminal rule set `[make]`:
* `[create]` -> `[make]` -> "make", text: `{make-verb-forms}`
*
* An example where `term` it the 'verb' mulit-term sequence `[work-on]`:
* `[contribute-to]` -> `[work-on]` -> `[work]` -> "work", text: `{work-verb-forms}`
* -> `[on]` -> "on", text: "on"
*/
else if (term.isTermSequence) {
if (term.termSequenceType !== options.type) {
util.logErrorAndPath('Term sequence', util.stylize(term.name), 'of type', util.stylize(term.termSequenceType), 'provided to parent term sequence of type', util.stylize(options.type) + ':', options)
throw new Error('Ill-formed term sequence')
}
/**
* Even though this rule is unary and does not require `text` merging,
* `NSymbol` still assigns the rule property `isTermSequence` to
* instruct `flattenTermSequence` to bring the `text` up to this
* rule's node level, allowing `gramProps` to conjugate the `text`
* (`gramProps` only conjugates the immediate child nodes).
*
* Even if `term` is a terminal rule set, for which the `text` value
* of every terminal rule is identical, do not assign that `text` to
* this rule as if it were a substitution. Although the parsing result
* will be identical, leave them distinguishable for now.
*/
termSeqSym.addRule({
rhs: [ term ],
costPenalty: costPenalty,
})
/**
* If `term` is the first item in `options.acceptedTerms`, save its
* `defaultText` value as display text for `options.substitutedTerms`,
* if any.
* • If `term` is a terminal rule set, `defaultText` is the identical
* `text` of all terminal rules in the set.
* • If `term` is a term sequence, `defaultText` is the display text
* of the first accepted term sequence it produces.
*/
if (i === 0) {
defaultText = term.defaultText
}
}
/**
* If `term` is an ordered pair containing any combination of term
* sequences, terminal rule sets, or nested ordered pairs from which to
* recursively create new term sequences, for which one item has term
* sequence type `options.type` and the other has type 'invariable'.
*
* An example of two terminal rule sets:
* `[contribute-to]` -> `[contribute]` -> "contribute", text: `{contribute-verb-forms}`
* -> `[to]` -> "to", text: "to"
*
* An example of a terminal rule set (`[have]`) and a term sequence
* (`[in-common]`):
* `[have-in-common]` -> `[have]` -> "have", text: `{have-verb-forms}`
* -> `[in-common]` -> `[in]` -> "in", text: "in"
* -> `[common]` -> "common", text: "common"
*
* An example of a terminal rule set (`[have]`) and a nested ordered pair
* of terminal rule sets (`[in]` and `[common]`) from which to recursively
* create a new term sequence (`[in-common]`):
* `[have-in-common]` -> `[have]` -> "have", text: `{have-verb-forms}`
* -> [ `[in]` `[common] ] -> `[in-common]` -> `[in]` -> "in", text: "in"
* -> `[common]` -> "common", text: "common"
*/
else if (term.constructor === Array) {
/**
* Recursively flatten any nested ordered pairs to `NSymbol` instances
* that produce the term sequence.
*
* Pass `options.type` to check the resulting term term sequence pair
* produces only one term sequence of type `options.type` and all
* others are 'invariable'.
*/
var termSeqPair = flattenNestedTermSequencePairs(term, options.type)
/**
* If `term` is the first item in `options.acceptedTerms`, merge the
* `defaultText` values of the pair's term sequences as display text
* for for `options.substitutedTerms`, if any.
* • If an item in the pair is a verb, its `defaultText` is the
* identical `text` of all terminal rules in the set.
* • If an item in the pair is a term sequence, its `defaultText` is
* the display text of the first accepted term sequence it produces.
*
* Get `defaultText` before modifying `termSeqPair` below for
* `noInsertionIndexes`.
*/
if (i === 0) {
defaultText = grammarUtil.mergeTextPair(termSeqPair[0].defaultText, termSeqPair[1].defaultText)
}
/**
* If defined, prevent creation of insertion rules for the term
* sequence(s) in `termSeqPair` at the specified index(es).
*
* Temporarily map `termSeqPair` to use `RHSSymbolWrapper`. Will later
* extend `termSequence.newTermSequence()` to accept the
* `RHSSymbolWrapper` format.
*/
if (noInsertionIndexes) {
noInsertionIndexes.forEach(function (noInsertIndex) {
termSeqPair[noInsertIndex] = {
symbol: termSeqPair[noInsertIndex],
noInsert: true,
}
})
}
/**
* `flattenTermSequence` will traverse this rule's child nodes, merge
* the `text` values of the matched terminal rules it produces, and
* create a new, terminal `ruleProps` for the rule's node that
* instructs `pfsearch` to use as display text and not traverse
* further.
*/
termSeqSym.addRule({
rhs: termSeqPair,
costPenalty: costPenalty,
})
}
else {
util.logErrorAndPath('Accepted term is neither a terminal symbol, terminal rule set, term sequence, nor a term sequence pair:', term, options)
throw new Error('Ill-formed term sequence')
}
})
/**
* Extend `termSeqSym` with term sequence properties. Enables nesting of
* `termSeqSym` in other term sequences with matching term sequence type.
*
* Invoke before invoking `termSeqSym.addTermSequenceSubstitutedTerm()`,
* which checks for the `NSymbol` properties this method adds.
*/
termSeqSym._toTermSequence({
type: options.type,
/**
* Either the `defaultText` value (or merger of `defaultText` values) of
* the term sequence or terminal rule set in its first rule, or is the
* display text of its first rule if terminal.
*/
defaultText: defaultText,
insertionCost: options.insertionCost,
})
/**
* If provided, create nonterminal substitution rules with `defaultText`
* as the `text` value for each rule. The `text` value instructs
* `flattenTermSequence` to use display text from these rules' nodes and
* discard the `text` values the nodes' descendants produce.
*/
if (options.substitutedTerms) {
options.substitutedTerms.forEach(function (term) {
termSeqSym.addTermSequenceSubstitutedTerm(term)
})
}
return termSeqSym
}
/**
* Checks if `options`, which was passed to `termSequence.newTermSequence()`
* or `termSequence.newTermSequenceBinarySymbol()`, is ill-formed. If so,
* prints an error.
*
* @private
* @static
* @param {Object} options The term sequence options object to inspect.
* @returns {boolean} Returns `true` if `options` is ill-formed, else
* `false`.
*/
function isIllFormedTermSequenceOptions(options) {
if (options.insertionCost !== undefined) {
// Check `options.insertionCost` only exists for invariable term sequences
// (the only type permitted to have accepted terminal rules).
if (options.type !== exports.termTypes.INVARIABLE) {
util.logErrorAndPath('Term sequence', util.stylize(options.symbolName), 'has `insertionCost` but does not have `type`', util.stylize(exports.termTypes.INVARIABLE) + ':', options)
return true
}
// Check `options.acceptedTerms` contains a terminal symbol for which to
// assign `options.insertionCost`.
if (!options.acceptedTerms.some(term => term.constructor === String)) {
util.logErrorAndPath('Term sequence', util.stylize(options.symbolName), 'has `insertionCost` but no terminal symbol (i.e., string) in `options.acceptedTerms` to which to assign it:', options)
return true
}
}
return false
}
/**
* Checks if `termWrapper`, which is a `TermWrapper` instance passed to
* `termSequence.newTermSequence(termSeqOptions)` in
* `termSeqOptions.acceptedTerms` or `termSeqOptions.substitutedTerms`, is
* ill-formed. If so, prints an error.
*
* @private
* @static
* @param {TermWrapper} termWrapper The substituted term wrapper to inspect.
* @returns {boolean} Returns `true` if `termWrapper` is ill-formed, else
* `false`.
*/
function isIllFormedTermWrapper(termWrapper) {
if (util.illFormedOpts(termWrapperSchema, termWrapper)) {
return true
}
if (termWrapper.noInsertionIndexes) {
if (termWrapper.term.constructor !== Array) {
util.logErrorAndPath('Term wrapper with `noInsertionIndexes` used with a `term` of type', util.colors.cyan(termWrapper.term.constructor.name), 'instead of a term sequence pair:', termWrapper)
return true
}
if (termWrapper.noInsertionIndexes.some(idx => termWrapper.term[idx] === undefined)) {
util.logErrorAndPath('Term wrapper `noInsertionIndexes` contains an out-of-bounds term sequence pair index:', termWrapper)
return true
}
}
return false
}
/**
* Adds a nonterminal term sequence substitution rule to this `NSymbol` for
* RHS `term` with `this.defaultText` as its `text` value.
*
* This substitution rule instructs `pfsearch` to use display text from this
* rule and discard the `text` values the rule's descendants produce.
*
* `term` must adhere to the term sequence type restrictions outlined in the
* description of `termSequence.newTermSequence()`.
*
* For use by `termSequence.newTermSequence()`.
*
* @memberOf NSymbol
* @param {string|NSymbol|NSymbol[]|TermWrapper} term The terminal symbol,
* terminal rule set, term sequence, or term sequence pair (with a cost
* penalty and/or insertion restriction if `TermWrapper`) to substitute when
* matched with `this.defaultText`.
* @returns {NSymbol} Returns this `NSymbol` instance.
*/
NSymbol.prototype.addTermSequenceSubstitutedTerm = function (term) {
if (forbidsSubstitutedTerm(this, term)) {
throw new Error('Ill-formed substituted term sequence')
}
// The cost penalty incurred when `term` is matched in input (and
// substituted).
var costPenalty = 0
/**
* If `term` is an ordered pair, the pair's indexes for which to instruct
* `createInsertionRules` to prevent the creation of insertion rules at
* that index.
*/
var noInsertionIndexes
if (term.constructor === Object) {
if (isIllFormedTermWrapper(term)) {
throw new Error('Ill-formed substituted term sequence')
}
costPenalty = term.costPenalty || 0
noInsertionIndexes = term.noInsertionIndexes
term = term.term
}
/**
* If `term` is a terminal symbol.
*
* A substitution example:
* `[prep-day]` -> "in", text: "on"
*/
if (term.constructor === String) {
// Check `term` lacks whitespace and forbidden characters.
if (termSequenceUtil.isIllFormedTerminalSymbol(term)) {
throw new Error('Ill-formed terminal symbol')
}
// Only permit terminal symbols as substituted term sequences for
// 'invariable' or 'pronoun' sequences.
if (this.termSequenceType !== exports.termTypes.INVARIABLE && this.termSequenceType !== exports.termTypes.PRONOUN) {
util.logErrorAndPath('Terminal symbol provided as substituted term to parent term sequence', util.stylize(this.name), 'of type', util.stylize(this.termSequenceType), '(only permitted for sequences of type', util.stylize(exports.termTypes.INVARIABLE), 'or', util.stylize(exports.termTypes.PRONOUN) + '):', util.stylize(term))
throw new Error('Ill-formed substituted term sequence')
}
// `pfsearch` uses this rule's `text` as display text instead of the
// matched terminal symbol it produces (i.e., `term`).
return this.addRule({
isTerminal: true,
rhs: term,
text: this.defaultText,
costPenalty: costPenalty,
})
}
/**`
* If `term` is a term sequence created by
* `termSequence.newTermSequence()` or
* `termSequence.newTermSequenceBinarySymbol()` (or a terminal rule set
* created by `verbTermSet.newVerb()`, `verbTermSet.newTenseVerb()`,
* `pronounTermSet.newPronoun()`, `nounTermSet.newCountNoun()`, or
* `nounTermSet.newMassNoun()`).
*
* A substitution example where `term` is the 'verb' terminal rule set
* `[love]`: `[like]` -> `[love]`, text: `{like-verb-forms}`
*
* A substitution example where `term` the 'verb' multi-term sequence
* `[help-with]`: `[contribute-to]` -> `[help-with]`, text: `[ {contribute-verb-forms}, "to" ]`
*/
if (term.isTermSequence) {
/**
* Check substituted term sequence, `term`, is of matching term sequence
* type, `this.termSequenceType`.
*
* Verb sequences that only produce the verb forms of a specific
* grammatical tense are distinguished like all other term sequence
* types; e.g., 'verb-past'.
*/
if (!termSequenceUtil.isTermSequenceType(term, this.termSequenceType)) {
/**
* 'pronoun' - Substituted term sequences must be of type 'pronoun' or
* 'invariable'. For example:
* `[1-sg]` -> "myself", text: {1-sg-pronoun-forms}
*/
if (this.termSequenceType === exports.termTypes.PRONOUN) {
if (!termSequenceUtil.isTermSequenceType(term, exports.termTypes.INVARIABLE)) {
util.logErrorAndPath('Substituted term sequence', util.stylize(term.name), 'of type', util.stylize(term.termSequenceType), 'provided to parent term sequence', util.stylize(this.name), 'is neither of required type', util.stylize(this.termSequenceType), 'nor', util.stylize(exports.termTypes.INVARIABLE) + ':', util.deleteUndefinedObjectProps(term))
throw new Error('Ill-formed substituted term sequence')
}
}
/**
* 'invariable' - Substituted term sequences must be of type
* 'invariable' or 'pronoun', though the latter is rare and therefore
* not noted in the error message. For example:
* `[1-sg-poss-det]` -> `[1-sg]`, text: "my"
*
* 'verb', 'verb-present', 'verb-past': Substituted term sequences must
* be of matching type.
*/
else if (this.termSequenceType !== exports.termTypes.INVARIABLE || !termSequenceUtil.isTermSequenceType(term, exports.termTypes.PRONOUN)) {
util.logErrorAndPath('Substituted term sequence', util.stylize(term.name), 'of type', util.stylize(term.termSequenceType), 'provided to parent term sequence', util.stylize(this.name), 'of type', util.stylize(this.termSequenceType) + ':', util.deleteUndefinedObjectProps(term))
throw new Error('Ill-formed substituted term sequence')
}
}
// `pfsearch` uses this rule's `text` as display text instead of the`text`
// values its RHS produces.
return this.addRule({
rhs: [ term ],
text: this.defaultText,
costPenalty: costPenalty,
})
}
/**
* If `term` is an ordered pair containing any combination of term
* sequences, terminal rule sets, or nested ordered pairs from which to
* recursively create new term sequences, for which one item has term
* sequence type `this.termSequenceType` and the other has type
* 'invariable'.
*
* A substitution example of two terminal rule sets:
* `[contribute-to]` -> `[help]` `[with]`, text: `[ {contribute-verb-forms}, "to" ]`
*
* A substitution example of a terminal rule set (`[have]`) and a term
* sequence (`[in-common]`):
* `[share]` -> `[have]` `[in-common]`, text: `{share-verb-forms}`
*
* A substitution example of a terminal rule set (`[have]`) and a nested
* ordered pair of terminal rule sets (`[in]` and `[common]`) from which to
* recursively create a new term sequence (`[in-common]`):
* `[share]` -> `[have]` [ `[in]` `[common] ] -> `[have]` `[in-common]`, text: `{share-verb-forms}`
*/
if (term.constructor === Array) {
/**
* Recursively flatten any nested ordered pairs to `NSymbol` instances
* that produce the term sequence.
*
* Pass `this.termSequenceType` to check the resulting term term
* sequence pair produces only one term sequence of type
* `this.termSequenceType` and all others are 'invariable'.
*
* Pass `true` as third argument to specify `term` is a substituted term
* sequence pair and has the same term sequence type exceptions as the
* substituted term sequences above (e.g., invariable term sequence
* pairs a pronoun substitutions).
*/
var termSeqPair = flattenNestedTermSequencePairs(term, this.termSequenceType, true)
/**
* If defined, prevent creation of insertion rules for the term
* sequence(s) in `termSeqPair` at the specified index(es).
*
* Temporarily map `termSeqPair` to use `RHSSymbolWrapper`. Will later
* extend `termSequence.newTermSequence()` to accept the
* `RHSSymbolWrapper` format.
*/
if (noInsertionIndexes) {
noInsertionIndexes.forEach(function (noInsertIndex) {
termSeqPair[noInsertIndex] = {
symbol: termSeqPair[noInsertIndex],
noInsert: true,
}
})
}
// `pfsearch` uses this rule's `text` as display text instead of the
// `text` values its RHS produces.
return this.addRule({
rhs: termSeqPair,
text: this.defaultText,
costPenalty: costPenalty,
})
}
util.logErrorAndPath('Substituted term is neither a terminal symbol, terminal rule set, term sequence, nor a term sequence pair:', term)
throw new Error('Ill-formed substituted term sequence')
}
/**
* Checks if `termSequenceSym` forbids the addition of a substituted term
* sequence. If so, prints an error.
*
* For use by `NSymbol.prototype.addTermSequenceSubstitutedTerm()`.
*
* @private
* @static
* @param {NSymbol} termSequenceSym The term sequence to inspect.
* @param {string|NSymbol|NSymbol[]|TermWrapper} term The terminal symbol,
* terminal rule set, term sequence, or term sequence pair to add as
* substitute rule to `termSequenceSym`, for use in error messages.
* @returns {boolean} Returns `true` if `termSequenceSym` forbids
* substituted term sequences.
*/
function forbidsSubstitutedTerm(termSequenceSym, term) {
if (!termSequenceSym.isTermSequence) {
util.logErrorAndPath('Attempting to add a substituted term to', util.stylize(termSequenceSym.name), ', which is not a term sequence (created with `g.newTermSequence()`:', term.constructor === String ? util.stylize(term) : util.deleteUndefinedObjectProps(term))
return true
}
if (termSequenceSym.isTermSet) {
util.logErrorAndPath('Attempting to add a substituted term to terminal rule set', util.stylize(termSequenceSym.name) + ':', term.constructor === String ? util.stylize(term) : util.deleteUndefinedObjectProps(term))
return true
}
return false
}
/**
* Creates an `NSymbol` with a single binary rule with `options.termPair` as
* its `rhs`, which produces a terminal sequence forming a phrase comprised of
* terminal rule sets and nested term sequences.
*
* Each item in `options.termPair` must be one of the following:
* 1. A terminal rule set created by `verbTermSet.newVerb()`,
* `verbTermSet.newTenseVerb()`, `pronounTermSet.newPronoun()`,
* `nounTermSet.newCountNoun()`, or `nounTermSet.newMassNoun()`.
* 2. A terminal rule sequence created by `termSequence.newTermSequence()` or
* this method.
* 3. An ordered pair containing any combination of #1, #2, or nested ordered
* pairs from which to recursively create new term sequences.
*
* The single rule the new `NSymbol` produces is marked `isTermSequence`,
* which instructs `flattenTermSequence` to merge the `text` values of the
* matched terminal rules this rule produces.
* • `flattenTermSequence` creates a new, terminal `ruleProps` for the rule's
* node with the `text` value, which `pfsearch` uses as display text.
* `flattenTermSequence` also always traverses the matched terminal rules to
* get the input tense of any matched verb terminal rules, which `pfsearch`
* uses if the parent rule has matching `acceptedTense`.
*
* `options.type` must be one of the following:
* 1. 'invariable'
* • Each term sequence `options.termPair` produces is comprised of
* sequences with `termSequenceType` of 'invariable'.
* • If `_isSubstitution` is `true`, `options.termPair` can contain a term
* sequence with `termSequenceType` of 'pronoun', though is rare.
* 2. 'pronoun'
* • Each term sequence `options.termPair` produces includes one term with
* `termSequenceType` of 'pronoun' and all other terms are 'invariable'.
* • If `_isSubstitution` is `true`, `options.termPair` may otherwise
* produce only 'invariable' terms.
* 3. 'noun'
* • Each term sequence `options.termPair` produces includes one term with
* `termSequenceType` of 'noun' and all other terms are 'invariable'.
* 4-6. 'verb', 'verb-present', 'verb-past'
* • Each term sequence `options.termPair` produces includes one term with
* `termSequenceType` of 'verb', 'verb-present', or 'verb-past' (as
* `options.type` defines), and all other terms are 'invariable'.
*
* The private parameters `_isSubstitution` and `_isNestedTermSequence` are
* used internally for nested term sequence pair type checks in
* `isIllFormedTermSequencePair()`:
* • If `_isNestedTermSequence` is true`, manually checks whether
* `options.termPair` produces a term of non-invariable type.
* – For use when `options.termPair` was defined as a nested term sequence
* pair within another term sequence pair passed to this method or
* `termSequence.newTermSequence()`.
* - `options.type` only specifies if the entire base sequence (after
* flattening) contains a single term of type `options.type`, not every
* bigram within the sequence. E.g., do not check if the bigram "in
* common" within the verb sequence "have in common" contains a verb.
*
* The returned `NSymbol` has the following properties:
* • name - The concatenation of the `NSymbol` names in `options.termPair`
* (after flattening nested ordered pairs).
* • isTermSequence - `true`. Enables nesting within term sequences and
* flattening in `flattenTermSequence`.
* • termSequenceType - `options.type` if `_isNestedTermSequence` is falsey,
* else the type of the non-invariable term in `options.termPair`, else
* 'invariable' if both terms in `options.termPair` are invariable.
* • defaultText - The merger of the `defaultText` values of the term
* sequences in `options.termPair`. For use when nesting this `NSymbol` in
* another term sequence.
*
* @memberOf termSequence
* @param {Object} options The options object.
* @param {string} options.type The term sequence type, as documented above.
* @param {(NSymbol|NSymbol[])[]} options.termPair The ordered pair of term
* sequences and/or nested term sequence pairs.
* @param {boolean} [_isSubstitution] Specify `options.termPair` is
* substituted when matched in input. Necessary for term sequence pair type
* check exceptions for substitutions of certain types.
* @param {boolean} [_isNestedTermSequence] Specify `options.termPair` was
* defined as a nested term sequence pair within another term sequence pair
* passed to this method or `termSequence.newTermSequence()`.
* @returns {NSymbol} Returns the new `NSymbol` for the terminal rule
* sequence.
*/
var binaryTermSequenceSchema = {
type: termSequenceSchema.type,
termPair: { type: Array, arrayType: [ NSymbol, Array ], required: true },
}
exports.newTermSequenceBinarySymbol = function (options, _isSubstitution, _isNestedTermSequence) {
if (util.illFormedOpts(binaryTermSequenceSchema, options) || isIllFormedTermSequenceOptions(options)) {
throw new Error('Ill-formed binary term sequence')
}
/**
* Recursively flatten any nested ordered pairs to `NSymbol` instances
* that produce the term sequence.
* • Check `options.termPair` is a valid ordered pair.
*
* Pass `options.type` to check the resulting term sequence pair does
* contain a term sequence of type other than `options.type` or
* 'invariable'.
*
* Pass `_isSubstitution` to specify `options.termPair` is a substituted
* term sequence pair with the substituted term sequence type exceptions
* specified in the method description.
*
* Pass `_isNestedTermSequence` to specify whether `options.termPair` is
* nested term sequence pair within another term sequence pair. If falsey,
* checks `termSeqPair` produces only one term sequence of type
* `options.type` and all others are 'invariable'.
* • If `true`, does not check if `options.termPair` contains a sequence
* of type `options.type` because `options.type` only specifies if the
* entire base sequence contains a single term of type `options.type`, not
* every bigram within the sequence. E.g., do not check if the bigram "in
* common" within the verb sequence "have in common" contains a verb.
*/
var termSeqPair = flattenNestedTermSequencePairs(options.termPair, options.type, _isSubstitution, _isNestedTermSequence)
var termA = termSeqPair[0]
var termB = termSeqPair[1]
/**
* Create the `NSymbol` that produces the terminal rule sequence.
*
* When parsing, `flattenTermSequence` will traverse this rule's child
* nodes, merge the `text` values of the matched terminal rules it
* produces, and create a new, terminal `ruleProps` for the rule's node to
* instruct `pfsearch` to use as display text and not traverse further.
*/
var termSeqSym = g.newBinaryRule({
rhs: termSeqPair,
})
var termSequenceType
if (_isNestedTermSequence) {
/**
* Manually check whether `termSeqSym` produces a term of non-invariable
* type when `options.termPair` was defined as a nested term sequence
* pair within another term sequence pair passed to this method or
* `termSequence.newTermSequence()`. This is necessary because
* `options.type` only specifies if the entire base sequence (after
* flattening) contains a single term of type `options.type`, not every
* bigram within the sequence.
*
* For example, consider the following verb sequence passed to either of
* the two aforementioned methods:
* `[ `[have]`, [ `[in]` `[common]` ] ]`
* This method creates a new `NSymbol` and binary rule for the nested
* array, `[ `[in]` `[common]` ]`, but can not use the `options.type`
* value passed with the root array because it is incorrect for this
* child rule within the verb sequence.
*
* Further, consider the following restructuring of the same verb
* sequence:
* `[ [ `[have]` `[in]` ], `[common]` ]`
* As the previous example demonstrates, this method can not rely on
* `options.type` for nested term sequences. Hence, it manually checks
* if such nested pairs contain a non-invariable type.
*
* Perform this check after invoking `flattenNestedTermSequencePairs()`
* with `options.termPair` above to ensure either both terms are
* invariable, or one is invariable and the other is a conjugative
* sequence of type `options.type`.
*
* If `termA` is invariable, then `termB` is either invariable and the
* whole sequence is invariable, or `termB` is the conjugative sequence
* and defines this sequence's type. Else, `termB` is the conjugative
* sequence and defines this sequence's type.
*/
termSequenceType = termA.termSequenceType === exports.termTypes.INVARIABLE ? termB.termSequenceType : termA.termSequenceType
} else {
/**
* Define the `termSeqSym` term sequence type, which specifies each term
* sequence `termSeqSym` produces contains a single term of type
* `options.type` and all other terms within the sequence are of type
* 'invariable'.
*/
termSequenceType = options.type
}
// Extend `termSeqSym` with term sequence properties. Enables nesting of
// `termSeqSym` in other term sequences with matching term sequence type.
return termSeqSym._toTermSequence({
type: termSequenceType,
// The merger of `defaultText` values of the term sequences in
// `options.termPair`.
defaultText: grammarUtil.mergeTextPair(termA.defaultText, termB.defaultText),
})
}
/**
* Checks `termSeqPair` is a valid term sequence pair and recursively
* flattens any nested ordered pairs to `NSymbol` instances that produce the
* nested term sequence. If `termSeqPair` is invalid, throws an exception.
*
* Each item in `termSeqPair` must be one of the following:
* 1. A terminal rule set created by `verbTermSet.newVerb()`,
* `verbTermSet.newTenseVerb()`, `pronounTermSet.newPronoun()`,
* `nounTermSet.newCountNoun()`, or `nounTermSet.newMassNoun()`.
* 2. A terminal rule sequence created by `termSequence.newTermSequence()`
* or `termSequence.newTermSequenceBinarySymbol()`.
* 3. An ordered pair containing any combination of #1, #2, or nested
* ordered pairs from which to recursively create new term sequences with
* `termSequence.newTermSequenceBinarySymbol()`.
*
* Performs the following `termSeqPair` checks after flattening any nested
* term sequence pairs:
* • Checks `termSeqPair` does not contain two non-invariable term
* sequences.
* • Checks `termSeqPair` does not contain non-invariable term sequences of
* type other than `termSequenceType`.
* • If `isNestedTermSequence` is falsey, checks `termSeqPair` contains
* contains one invariable term sequence and one of type `termSequenceType`.
*
* Returns the flattened ordered pair of `NSymbol` instances for use as a
* term sequence binary rule's `rhs`.
*
* @private
* @static
* @param {(NSymbol|NSymbol[])[]} termSeqPair The term sequence ordered pair
* to validate and flatten.
* @param {string} termSequenceType The required term sequence type of
* `termSeqPair`.
* @param {boolean} isSubstitution Specify `termSeqPair` is substituted when
* matched in input. Necessary for term sequence pair type check exceptions
* for substitutions of certain types.
* @param {boolean} [isNestedTermSequence] Specify `termSeqPair` was defined
* as a nested term sequence pair within another term sequence pair passed
* to `termSequence.newTermSequenceBinarySymbol()` or
* `termSequence.newTermSequence()`.
* @returns {NSymbol[]} Returns `termSeqPair` with any nested term sequences
* pairs flattened.
*/
function flattenNestedTermSequencePairs(termSeqPair, termSequenceType, isSubstitution, isNestedTermSequence) {
// Check `termSeqPair` is an ordered pair, does not contains `undefined`.
if (termSequenceUtil.isIllFormedOrderedPair(termSeqPair)) {
throw new Error('Ill-formed term sequence pair')
}
for (var t = 0, termSeqLen = termSeqPair.length; t < termSeqLen; ++t) {
var termSym = termSeqPair[t]
if (termSym.constructor === Array) {
/**
* Recursively create an `NSymbol` that produces a single binary rule
* for the term sequence of this nested ordered pair.