1
+ import CFG from "./CFG.js" ;
2
+ import Rule from "./Rule.js" ;
3
+ import ArrayHelper from "../helper/ArrayHelper.js" ;
4
+ import ObjectHelper from "../helper/ObjectHelper.js" ;
5
+ import Terminal from "./Terminal.js" ;
6
+ import Variable from "./Variable.js" ;
7
+
8
+ export default class CFGNormalise {
9
+ /**
10
+ * Normalises the CFG into Chomsky normal form
11
+ *
12
+ * @param {CFG } cfg
13
+ * @returns {CFG }
14
+ */
15
+ static normalise ( cfg ) {
16
+ let newCFG = cfg . simplify ( ) ;
17
+ let newRules = this . step4 ( [ ...cfg . rules ] , newCFG ) ;
18
+ return CFG . fromRules ( newRules , newCFG . startVariable ) ;
19
+ }
20
+
21
+ /**
22
+ * Finally, we convert all remaining rules into the proper form. We replace each
23
+ * rule A → u1u2 ··· uk, where k ≥ 3 and each ui is a variable or terminal symbol,
24
+ * with the rules A → u1A1, A1 → u2A2, A2 → u3A3, ... , and Ak−2 → uk−1uk.
25
+ * The Ai’s are new variables. We replace any terminal ui in the preceding rule(s)
26
+ * with the new variable Ui and add the rule Ui → ui.
27
+ * @param rules
28
+ * @param cfg
29
+ */
30
+ static step4 ( rules , cfg ) {
31
+ let newRules = [ ] ;
32
+ let switcherRules = [ ] ;
33
+ Rule . sort ( rules ) . forEach ( rule => {
34
+ if ( rule . outputList . length > 2 ) {
35
+ let generatedRules = this . generateRulesForStep4 ( rule , cfg ) ;
36
+ let matchingRules = [ ] ;
37
+ generatedRules . forEach ( rule => {
38
+ let equivalentRule = this . findEquivalentRule ( rule , switcherRules ) ;
39
+ if ( equivalentRule ) {
40
+ matchingRules . push ( [ rule . inputVariable , equivalentRule . inputVariable ] ) ;
41
+ }
42
+ } ) ;
43
+ generatedRules = generatedRules . map ( rule => {
44
+ let matchingRule = matchingRules . find ( r => ObjectHelper . equals ( r [ 0 ] , rule . inputVariable ) ) ;
45
+ if ( matchingRule ) {
46
+ return null ;
47
+ } else {
48
+ return new Rule ( rule . inputVariable , rule . outputList . map ( output => {
49
+ let match = matchingRules . find ( r => ObjectHelper . equals ( r [ 0 ] , output ) ) ;
50
+ if ( match ) {
51
+ return match [ 1 ] ;
52
+ } else {
53
+ return output ;
54
+ }
55
+ } ) )
56
+ }
57
+ } ) . filter ( x => x !== null ) ;
58
+
59
+ switcherRules = switcherRules . concat ( generatedRules ) ;
60
+ } else {
61
+ ArrayHelper . push_distinct ( newRules , rule ) ;
62
+ }
63
+ } ) ;
64
+
65
+ newRules = newRules . concat ( switcherRules ) ;
66
+
67
+ return this . replaceTerminals ( newRules , cfg ) ;
68
+ }
69
+
70
+ static generateRulesForStep4 ( rule , cfg ) {
71
+ let nextVariable = cfg . nextVariable ( ) ;
72
+ let generatedRules = [ new Rule ( rule . inputVariable , [ rule . outputList [ 0 ] , nextVariable ] ) ] ;
73
+
74
+ //
75
+ for ( let i = 1 , len = rule . outputList . length - 2 ; i < len ; i ++ ) {
76
+ let nextNextVariable = cfg . nextVariable ( ) ;
77
+ generatedRules . push ( new Rule (
78
+ nextVariable ,
79
+ [ rule . outputList [ i ] , nextNextVariable ]
80
+ ) ) ;
81
+ nextVariable = nextNextVariable ;
82
+ }
83
+
84
+ generatedRules . push ( new Rule (
85
+ nextVariable ,
86
+ rule . outputList . slice ( rule . outputList . length - 2 , rule . outputList . length )
87
+ ) ) ;
88
+
89
+ return generatedRules ;
90
+ }
91
+
92
+ static findEquivalentRule ( rule , rules ) {
93
+ for ( let i = 0 , len = rules . length ; i < len ; i ++ ) {
94
+ if ( ObjectHelper . equals ( rule . outputList , rules [ i ] . outputList ) ) {
95
+ return rules [ i ] ;
96
+ }
97
+ }
98
+ return null ;
99
+ }
100
+
101
+ static replaceTerminals ( rules , cfg ) {
102
+ let terminalSwitching = { } ;
103
+ let newRules = [ ] ;
104
+ rules . forEach ( rule => {
105
+ if ( rule . isMixedOutput ( ) ) {
106
+ newRules . push ( new Rule ( rule . inputVariable , rule . outputList . map ( o => {
107
+ if ( o instanceof Terminal ) {
108
+ if ( ! terminalSwitching [ o . id ] ) {
109
+ terminalSwitching [ o . id ] = new Rule ( cfg . nextVariable ( ) , [ o ] ) ;
110
+ }
111
+ return terminalSwitching [ o . id ] . inputVariable ;
112
+ } else {
113
+ return o ;
114
+ }
115
+ } ) ) ) ;
116
+ } else {
117
+ newRules . push ( rule ) ;
118
+ }
119
+ } )
120
+
121
+ for ( let k in terminalSwitching ) {
122
+ newRules . push ( terminalSwitching [ k ] ) ;
123
+ }
124
+
125
+ return newRules ;
126
+ }
127
+
128
+ static newStart ( rules , cfg ) {
129
+ let newRules = rules ;
130
+ newRules . push ( new Rule ( Variable . S0 , [ Variable . S ] ) ) ;
131
+ return [ newRules , Variable . S0 ] ;
132
+ }
133
+ }
0 commit comments