This repository has been archived by the owner on May 8, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
scfg.js
223 lines (190 loc) · 6.93 KB
/
scfg.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
/**
* SCFG = Syncronous Context Free Grammar
*/
/**
* Initialize a Syncronous Context Free Grammar.
*
* @param grammarMap - a hash whose keys are nonterminals and whose values are rule-lists.
* Each rule list is a hash whose keys are source strings and whose values are target-strings.
* See the demo program, at the bottom of this file, for an example.
* @param grammarRoot (String) - the start-symbol (aka root) of the grammar.
*/
var SynchronousContextFreeGrammar = function(grammarMap, grammarRoot) {
this.grammarMap = grammarMap;
this.grammarRoot = grammarRoot;
}
SynchronousContextFreeGrammar.prototype = {
/**
* @return the start symbol (aka root) of this grammar.
*/
root: function() {
return this.grammarRoot;
},
/**
* return an array of all nonterminals in the grammar.
*/
nonterminals: function() {
return Object.keys(this.grammarMap);
},
/**
* @return boolean - true if the given nonterminal exists in the grammar.
*/
hasNonterminal: function(nonterminal) {
return normalizedNonterminal(nonterminal) in this.grammarMap;
},
/**
* @return a hash {source1: target1, source2: target2, ...} with translations of a single nonterminal.
*/
translationsOfNonterminal: function (nonterminal) {
return this.grammarMap[normalizedNonterminal(nonterminal)];
},
/**
* @return a hash {variable1: regexp1, variable2: regexp2, ...}, taken from the @Variables section of the grammar.
*/
variables: function (nonterminal) {
return this.grammarMap["@Variables"] || {};
},
/**
* Generate all strings from the given startNonterminal, with the given maxDepth.
* @return hash {source1: target1, source2: target2, ...}
* See demo program at bottom of current file.
*/
expand: function(startNonterminal, maxDepth, variableDescriptionType, verbosity) {
if (!this.expandedGrammarMap) expandedGrammarMap = {};
if (startNonterminal in expandedGrammarMap)
return expandedGrammarMap[startNonterminal];
if (verbosity) console.log("expand from "+startNonterminal);
var translationsFromStartNonterminal = this.translationsOfNonterminal(startNonterminal);
if (translationsFromStartNonterminal==null)
throw new Error("No translations from startNonterminal "+startNonterminal);
if (maxDepth<=0) // don't expand nonterminal anymore - prevent infinite recursion
return translationsFromStartNonterminal;
// expand each nonterminal in turn:
for (var nonterminal in this.grammarMap) {
var newTranslations = {};
for (var source in translationsFromStartNonterminal) {
var target = translationsFromStartNonterminal[source];
if (!!~source.indexOf(nonterminal) || !!~target.indexOf(nonterminal)) { // source contains nonterminal - expand it recursively
var expansions = this.expand(nonterminal, maxDepth-1, variableDescriptionType, verbosity);
for (var expansionSource in expansions) {
var expansionTarget = expansions[expansionSource];
newTranslations[source.replace(nonterminal, expansionSource)] =
target.replace(nonterminal, expansionTarget);
}
} else { // source and target do not contain nonterminal - just add a simple translation.
newTranslations[source]=target;
}
}
translationsFromStartNonterminal = newTranslations;
}
// expand the terminal variables (replace with examples):
// currently disabled
if (variableDescriptionType!=null) {
var newTranslations = {};
for (var source in translationsFromStartNonterminal) {
var target = translationsFromStartNonterminal[source];
// var matcher = TextVariablesUtils.INSTANTIATED_VARIABLE_PATTERN.matcher(source);
// String newSource = source;
// while (matcher.find()) {
// String variable = matcher.group();
// String value = (variableDescriptionType.equals(TextVariableDescriptionParameterColonType.class)? matcher.group(1): matcher.group(2));
// newSource = newSource.replace(variable, value);
// target = target.replace(variable, value);
// }
// newTranslations.put(newSource, target);
}
translationsFromStartNonterminal = newTranslations;
}
expandedGrammarMap[startNonterminal] = translationsFromStartNonterminal;
if (verbosity) console.log("expanded from "+startNonterminal+": "+JSON.stringify(translationsFromStartNonterminal));
return translationsFromStartNonterminal;
} // end function expand
};
/**
* A nonterminal may be indexed, for example, "<number>2".
* This function removes the index and returns the base variable, e.g., "<number>".
*/
function normalizedNonterminal(nonterminal) {
return nonterminal.replace(/>\d+/,">");
}
/*
* FORMATTING OF GRAMMAR FILES:
*/
var headingPattern = /[=]+\s*(.*?)\s*[=]+\s*/;
var listItemPattern = /[*]+\s*(.*)/;
var commentPattern = /\s*#.*?$/;
var separatorPattern = /\s*\/\s*/;
module.exports = {
// convert generated grammer to the json dataset format
embedSet: function(expandedGrammar) {
var dset = []
for (var input in expandedGrammar)
{
dset.push({
'input':input,
'output':[expandedGrammar[input]],
})
}
return dset
},
/**
* Create a new SCFG from the given string.
*
* The format of the string is:
*
* == root-nonterminal ==
* * source1 / target1
* * source2 / target2
* * ...
*
* == nonterminal-1 ==
* * source1 / target1
* * source2 / target2
* * ...
*
* etc.
*
* Comments start with '#' and end at the end of line.
*
* The first heading is the start symbol of the grammar (aka root).
*/
fromString: function(grammarString) {
var grammarLines = grammarString.split(/[\r\n]/);
var grammarMap = {};
var currentTitle = null;
var grammarRoot = null;
grammarLines.forEach(function(line) {
line = line.replace(commentPattern, "");
line = line.trim();
if ((matcher = headingPattern.exec(line))) {
currentTitle = matcher[1].trim();
if (!grammarRoot)
grammarRoot = currentTitle;
} else if ((matcher = listItemPattern.exec(line)) && currentTitle) {
var pair = matcher[1];
var fields = pair.split(separatorPattern);
if (fields.length<2) // skip empty lines
return;
if (!(currentTitle in grammarMap))
grammarMap[currentTitle] = {};
grammarMap[currentTitle][fields[0].trim()] = fields[1].trim();
}
});
return new SynchronousContextFreeGrammar(grammarMap, grammarRoot);
} // end function fromString
};
// DEMO PROGRAM:
if (process.argv[1] === __filename) {
// console.log("scfg.js demo start");
var fs = require('fs');
var grammar = module.exports.fromString(fs.readFileSync("grammars/NegotiationGrammarEmployer.txt",'utf8'));
// console.log("\nGRAMMAR:\n");
// console.dir(grammar);
// console.log("\nEXPANDED GRAMMAR:\n");
var expandedGrammar = grammar.expand(grammar.root(), 10, null, 0);
//console.dir(expandedGrammar);
console.log(JSON.stringify(module.exports.embedSet(expandedGrammar), null, 4))
// for (var input in expandedGrammar)
// console.log(input+" / "+ expandedGrammar[input]);
//console.log("scfg.js demo end");
}