UNPKG

11.9 kBJavaScriptView Raw
1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15'use strict';
16
17const ErgoCompiler = require('./compiler');
18const Script = require('./script');
19const ParseException = require('@accordproject/concerto-cto').ParseException;
20const CompilerException = require('./compilerexception');
21const TypeException = require('./typeexception');
22const SystemException = require('./systemexception');
23
24/**
25 * <p>
26 * Manages a set of scripts.
27 * </p>
28 * @private
29 * @class
30 * @memberof module:ergo-compiler
31 */
32class ScriptManager {
33
34 /**
35 * Create the ScriptManager.
36 * <p>
37 * <strong>Note: Only to be called by framework code. Applications should
38 * retrieve instances from {@link BusinessNetworkDefinition}</strong>
39 * </p>
40 * @param {String} target - compiler target (either: 'es6', or 'java')
41 * @param {ModelManager} modelManager - The ModelManager to use for this ScriptManager
42 * @param {Object} options - e.g., { warnings: true }
43 */
44 constructor(target, modelManager, options) {
45 this.target = target;
46 this.modelManager = modelManager;
47 this.scripts = {};
48 this.compiledScript = null;
49 this.warnings = options && options.warnings || false;
50 this.sourceTemplates = [];
51 }
52
53 /**
54 * Change the compilation target. Note: This might force recompilation if logic has already been compiled.
55 * @param {String} target - compiler target (either: 'es6', or 'java')
56 * @param {boolean} recompile - whether to force recompilation of the logic
57 */
58 changeTarget(target, recompile) {
59 this.target = target;
60 if (recompile) { this.compileLogic(true); }
61 }
62
63 /**
64 * Creates a new Script from a string.
65 *
66 * @param {string} identifier - the identifier of the script
67 * @param {string} language - the language identifier of the script
68 * @param {string} contents - the contents of the script
69 * @returns {Script} - the instantiated script
70 */
71 createScript(identifier, language, contents) {
72 return new Script(this.modelManager, identifier, language, contents);
73 }
74
75 /**
76 * Modify an existing Script from a string.
77 *
78 * @param {string} identifier - the identifier of the script
79 * @param {string} language - the language identifier of the script
80 * @param {string} contents - the contents of the script
81 */
82 modifyScript(identifier, language, contents) {
83 this.updateScript(new Script(this.modelManager, identifier, language, contents));
84 }
85
86 /**
87 * Adds a template file (as a string) to the ScriptManager.
88 * @param {string} templateFile - The template file as a string
89 * @param {string} fileName - an optional file name to associate with the template file
90 */
91 addTemplateFile(templateFile,fileName) {
92 this.sourceTemplates.push({ 'name' : fileName, 'content': templateFile });
93 }
94
95 /**
96 * Adds a Script to the ScriptManager
97 * @param {Script} script - The script to add to the ScriptManager
98 */
99 addScript(script) {
100 this.scripts[script.getIdentifier()] = script;
101 }
102
103 /**
104 * Update an existing Script in the ScriptManager
105 * @param {Script} script - The script to add to the ScriptManager
106 */
107 updateScript(script) {
108 if (!this.scripts[script.getIdentifier()]) {
109 throw new Error('Script file does not exist');
110 }
111 this.addScript(script);
112 }
113
114 /**
115 * Remove the Script
116 * @param {string} identifier - The identifier of the script to remove
117 * delete.
118 */
119 deleteScript(identifier) {
120 if (!this.scripts[identifier]) {
121 throw new Error('Script file does not exist');
122 }
123 delete this.scripts[identifier];
124 }
125
126 /**
127 * Get the array of Script instances
128 * @return {Script[]} The Scripts registered
129 * @private
130 */
131 getScripts() {
132 let keys = Object.keys(this.scripts);
133 let result = [];
134
135 for(let n=0; n < keys.length;n++) {
136 result.push(this.scripts[keys[n]]);
137 }
138
139 return result;
140 }
141
142 /**
143 * Get the array of all Script instances, including compiled ones
144 * @return {Script[]} The Scripts registered, including compiled ones
145 * @private
146 */
147 getAllScripts() {
148 let result = this.getScripts();
149 if (this.compiledScript !== null) {
150 result.push(this.compiledScript);
151 }
152 return result;
153 }
154
155 /**
156 * Get a single combined Script
157 * @return {string} The source for all Scripts registered, including compiled ones
158 * @private
159 */
160 getCombinedScripts() {
161 let allJsScripts = '';
162
163 this.getAllScripts().forEach(function (element) {
164 if (element.getLanguage() === '.js') {
165 allJsScripts += element.getContents();
166 }
167 }, this);
168
169 return allJsScripts;
170 }
171
172 /**
173 * Target kind
174 * @param {string} target - the target language
175 * @return {string} the kind of language ('.js', '.ergo', '.java')
176 */
177 getTargetKind(target) {
178 if (target === 'ergo') {
179 return '.ergo';
180 } else if (target === 'java') {
181 return '.java';
182 } else {
183 return '.js';
184 }
185 }
186
187 /**
188 * Get the array of Script instances for the given language
189 * @param {string} target - the target language
190 * @return {Script[]} The Scripts registered
191 * @private
192 */
193 getScriptsForTarget(target) {
194 const language = this.getTargetKind(target);
195 const scripts = this.getAllScripts();
196 let keys = Object.keys(scripts);
197 let result = [];
198
199 for(let n=0; n < keys.length;n++) {
200 if (scripts[keys[n]].getLanguage() === language) {
201 result.push(scripts[keys[n]]);
202 }
203 }
204 return result;
205 }
206
207 /**
208 * Gets all the Ergo logic
209 * @return {Array<{name:string, content:string}>} the name and content of each Ergo file
210 */
211 getLogic() {
212 let logic = [];
213 const scripts = this.getScriptsForTarget('ergo');
214 scripts.forEach(function (script) {
215 logic.push({ 'name' : script.getIdentifier(), 'content' : script.getContents() });
216 });
217 return logic;
218 }
219
220 /**
221 * Remove all registered scripts
222 */
223 clearScripts() {
224 this.scripts = {};
225 this.compiledScript = null;
226 }
227
228 /**
229 * Get the Script associated with an identifier
230 * @param {string} identifier - the identifier of the Script
231 * @return {Script} the Script
232 * @private
233 */
234 getScript(identifier) {
235 return this.scripts[identifier];
236 }
237
238 /**
239 * Get the compiled Script
240 * @return {Script} the Script
241 * @private
242 */
243 getCompiledScript() {
244 return this.compileLogic(false);
245 }
246
247 /**
248 * Get the compiled JavaScript
249 * @return {string} the Script
250 * @private
251 */
252 getCompiledJavaScript() {
253 const compiledScript = this.compiledScript;
254 let allJsScripts = '';
255
256 if (compiledScript) {
257 allJsScripts += compiledScript.getContents();
258 } else {
259 throw new Error('Did not find any compiled JavaScript logic');
260 }
261
262 return allJsScripts;
263 }
264
265 /**
266 * Get the identifiers of all registered scripts
267 * @return {string[]} The identifiers of all registered scripts
268 */
269 getScriptIdentifiers() {
270 return Object.keys(this.scripts);
271 }
272
273 /**
274 * Throw the right kind of error
275 * @param {object} error - Ergo compiler error
276 * @throws {BaseFileException}
277 */
278 static _throwCompilerException(error) {
279 let fileLocation = {};
280
281 // Convert from Ergo file location to Concerto file location
282 fileLocation.start = {
283 line: error.locstart.line,
284 column: error.locstart.column
285 };
286 fileLocation.end = {
287 line: error.locend.line,
288 column: error.locend.column
289 };
290
291 if (error.kind === 'CompilationError') {
292 throw new CompilerException(error.message, fileLocation, error.fullMessage, error.fileName);
293 } else if (error.kind === 'ParseError') {
294 throw new ParseException(error.message, fileLocation, error.fileName, error.fullMessage, 'ergo-compiler');
295 } else if (error.kind === 'TypeError') {
296 throw new TypeException(error.message, fileLocation, error.fullMessage, error.fileName);
297 } else {
298 throw new SystemException(error.message, fileLocation, error.fullMessage, error.fileName);
299 }
300 }
301
302 /**
303 * Compile the Ergo logic
304 * @param {boolean} force - whether to force recompilation of the logic
305 * @return {object} The script compiled to JavaScript
306 */
307 compileLogic(force) {
308 if (this.compiledScript && !force) {
309 return this.compiledScript;
310 }
311 const codeExt = this.target === 'java' ? '.java' : '.js';
312 let sourceErgo = this.getLogic();
313 if (sourceErgo === undefined || sourceErgo.length === 0 && this.sourceTemplates.length === 0) {
314 const allJsScripts = this.getCombinedScripts();
315 if (allJsScripts === '') {
316 return null;
317 }
318 this.compiledScript = new Script(this.modelManager, 'main'+codeExt, codeExt, allJsScripts, null);
319 } else {
320 // Do not link to runtime for Java target, only for JavaScript
321 const link = this.target === 'java' ? false : true;
322 const compiledErgo = ErgoCompiler.compileToJavaScript(sourceErgo,this.modelManager.getModels(),this.sourceTemplates,this.target,link,this.warnings);
323 if (Object.prototype.hasOwnProperty.call(compiledErgo,'error')) {
324 ScriptManager._throwCompilerException(compiledErgo.error);
325 }
326 this.compiledScript = new Script(this.modelManager, 'main'+codeExt, codeExt, compiledErgo.success, compiledErgo.contractName);
327 }
328 return this.compiledScript;
329 }
330
331 /**
332 * Helper method to retrieve all function declarations
333 * @returns {Array} a list of function declarations
334 */
335 allFunctionDeclarations() {
336 let allScripts = this.getAllScripts();
337 const functionDeclarations = allScripts
338 .map((ele) => {
339 return ele.getFunctionDeclarations();
340 }).reduce((flat, next) => {
341 return flat.concat(next);
342 },[]);
343 return functionDeclarations;
344 }
345
346 /**
347 * Looks for the presence of a function in the JavaScript logic
348 * @param {string} name - the function name
349 */
350 hasFunctionDeclaration(name) {
351 // get the function declarations of either init or dispatch
352 const funDecls = this.allFunctionDeclarations();
353 if (!funDecls.some((ele) => { return ele.getName() === name; })) {
354 throw new Error(`Function ${name} was not found in logic`);
355 }
356 }
357 /**
358 * Checks that the logic has a dispatch function
359 */
360 hasDispatch() {
361 this.hasFunctionDeclaration('__dispatch');
362 }
363
364 /**
365 * Checks that the logic has an init function
366 */
367 hasInit() {
368 this.hasFunctionDeclaration('__init');
369 }
370
371}
372
373module.exports = ScriptManager;