1 
  2 
  3 define(['constants'],
  4 /**
  5  * Module that provides a generator for parser-compiler WebWorker instances:
  6  * 
  7  * initializes the WebWorker instance and provides functions for communicating with
  8  * the WebWorker.
  9  * 
 10  * @class
 11  * @constant
 12  * @public
 13  * @name AsyncGenerator
 14  * @memberOf mmir.env.grammar
 15  * 
 16  * @requires WebWorker
 17  * 
 18  * @example
 19  * 
 20  * //initializes a WebWorker at MMIR's worker path, i.e. at constants.getWorkerPath() + "file.name":
 21  * var asyncCompiler = asyncGen.createWorker("jisonCompiler.js");
 22  * 
 23  * //create job ID (must be unique for each job):
 24  * var taskId = '1';
 25  * 
 26  * //register callback for the job ID:
 27  * asyncCompiler.addCallback(taskId, function(evtData){
 28  * 		
 29  * 		if(evtData.error){
 30  * 			//... do something
 31  * 		} else {
 32  * 			var hasError = evtData.hasError;
 33  * 			var grammarParser = evtData.def;
 34  * 
 35  * 			//... do something
 36  * 		}
 37  * 
 38  * });
 39  * 
 40  * //start a compile job on the worker:
 41  * asyncCompiler.postMessage({
 42  * 		cmd: 'parse',
 43  *  	id: taskId,
 44  *  	config: options,		//compile options (specific to compiler engine)
 45  *  	text: grammarDefinition	//parser definition (using the syntax of the specific compiler engine)
 46  * });
 47  * 
 48  */		
 49 function(constants){
 50 	
 51 var COMPILER_WORKER_POSTFIX = 'Compiler.js';
 52 
 53 /**
 54  * Factory for creating WebWorkers that compile grammar-parser
 55  * 
 56  * @class AsyncCompiler
 57  */
 58 return {
 59 	/** @scope AsyncCompiler.prototype */
 60 	
 61 	/** 
 62 	 * Create a WebWorker for compiling grammars.
 63 	 * 
 64 	 * The returned WebWorker provides 2 additional convenience functions
 65 	 * for communicating with the WebWorker thread:
 66 	 * 
 67 	 * <code>addCallback(taskId : String, callback : Function)</code>
 68 	 * where <em>taskId</em> is a unique String identifying a single compile-job
 69 	 * and <em>callback</em> handles messages that come back from the WebWorker thread:
 70 	 * 
 71 	 * The <code>callback(eventData)</code> will invoke the callback with the message's data
 72 	 * The property <code>eventData.done</code> has a special function:
 73 	 * 		 if this property is present with a TRUTHY value, then callback will be removed, i.e. 
 74 	 *       not listening anymore for the specified taskId.
 75 	 *       
 76 	 * After adding a <em>callback</em>, messages to the WebWorker thread can be send with
 77 	 * the <code>postMessage({id: taskId, cmd: 'parse', ...})</code>
 78 	 * 
 79 	 * @param {String} parserEngineId
 80 	 * 			the name of the file that holds the WebWorker implementation.
 81 	 * 			The file must be located in MMIR's {@link Constants#getWorkerPath}
 82 	 * 			directory.
 83 	 * 
 84 	 * @returns {CompileWebWorker}
 85 	 * 			a WebWorker that provides some convenience functions for
 86 	 * 			communicating with the WebWorker/thread.
 87 	 * 
 88 	 * @memberOf AsyncCompiler#
 89 	 */
 90 	createWorker: function(parserEngineId){
 91 		
 92 
 93 		/**  @memberOf CompileWebWorker# */
 94 		var workerPath = constants.getWorkerPath() + parserEngineId + COMPILER_WORKER_POSTFIX;
 95 
 96 		/** 
 97 		 * web-worker for compile jobs
 98 		 * 
 99 		 * @class CompileWebWorker
100 		 * @memberOf AsyncCompiler.createWorker
101 		 */
102 		var asyncCompiler = new Worker(workerPath);
103 
104 		/**
105 		 * dictionary for currently active compilations
106 		 * 
107 		 * <pre>
108 		 * [id] -> callback-function()
109 		 * </pre>  
110 		 * 
111 		 * @memberOf CompileWebWorker#
112 		 */
113 		asyncCompiler._activeTaskIds = {};
114 
115 		/**
116 		 * listener for init-message
117 		 * 
118 		 * DEFAULT: prints a message to the console
119 		 * 
120 		 * Init Message:
121 		 *  * success:
122 		 *    <code>{init:true}</code>
123 		 *  * failed:
124 		 *    <code>{init:false,error:"message"}</code>
125 		 * 
126 		 * @memberOf CompileWebWorker#
127 		 */
128 		asyncCompiler._oninit = function(evtData){
129 			if(evtData.init){
130 				console.log('initialized: '+JSON.stringify(evtData));
131 			} else {
132 				console.error('failed to initialize: '+JSON.stringify(evtData));
133 			}
134 		};
135 		
136 		/**
137 		 * HELPER generate & setup oninit signal for sync + async modules.
138 		 * 
139 		 * Side Effects:
140 		 * generates and sets #_oninit
141 		 * 
142 		 * @param {ParserGen} syncGen
143 		 * 				the sync parser generator
144 		 * @param {Deferred} asyncDef
145 		 * 				the deferred that should be resolved when async generator is initialized
146 		 * @param {requirejs} require
147 		 * 				the require instance that is used for loading the MMIR framework
148 		 * 
149 		 * @returns {AsyncInitMesssage}
150 		 * 				the message object that should be sent to the async generator's WebWorker
151 		 * 
152 		 * @memberOf CompileWebWorker#
153 		 */
154 		asyncCompiler.prepareOnInit = function(syncGen, asyncDef, require){
155 
156 			//setup async init-signaling:
157 			var isSyncGenInit = false;
158 			var isAsyncGenInit = false;
159 			
160 			//HELPER signal as "completely initialized" when sync & async modules are initialized:
161 			var checkInit = function(){
162 				if(isSyncGenInit && isAsyncGenInit){
163 					asyncDef.resolve();
164 				}
165 			};
166 
167 			var initSyncGenDef = syncGen.init();//<- get init-signal for sync-generator
168 			initSyncGenDef.always(function(){
169 				isSyncGenInit = true;
170 				checkInit();
171 			});
172 
173 			this._oninit = function(initEvt){
174 				if(initEvt.init === false){
175 					console.error('Failed to initialize '+JSON.stringify(initEvt));
176 				} else {
177 					isAsyncGenInit = true;
178 				}
179 				checkInit();
180 			};
181 			return {cmd:'init', engineUrl: require.toUrl(syncGen.engineId)};
182 
183 		};
184 
185 		/**
186 		 * Error handler for errors signaled by the webworker
187 		 * 
188 		 * @memberOf CompileWebWorker#
189 		 */
190 		asyncCompiler._onerror = function(errorMsg, error){
191 			console.error(errorMsg, error);
192 		};
193 
194 		/**
195 		 * HELPER: register a callback (usage: see jisonGen.compileGrammar() below)
196 		 * 
197 		 * @memberOf CompileWebWorker#
198 		 */
199 		asyncCompiler.addCallback = function(id, cb){
200 			this._activeTaskIds[id] = cb;
201 		};
202 
203 		/**
204 		 * handler for messages/event from web-worker:
205 		 * 
206 		 * @memberOf CompileWebWorker#
207 		 */
208 		asyncCompiler.onmessage = function(evt){
209 			
210 			var id = evt.data.id;
211 			if(id){
212 				
213 				if(this._activeTaskIds[id]){
214 					
215 					this._activeTaskIds[id](evt.data);
216 					
217 				} else {
218 					console.warn('no callback registered for ID "'+id+'" -> ' + JSON.stringify(evt.data));
219 				}
220 				
221 				//this was the final message for this ID -> remove callback-function from "active tasks" dictionary:
222 				if(evt.data.done){
223 					this._activeTaskIds[id] = void(0);
224 				}
225 			}
226 			else if(evt.data.error){
227 				
228 				this._onerror('Error in '+workerPath+': '+evt.data.error, evt.data);
229 			}
230 			else if(typeof evt.data.init === 'boolean'){
231 				
232 				asyncCompiler._oninit(evt.data);
233 			}
234 			else {
235 				
236 				this._onerror('ERROR unknown message -> ' + JSON.stringify(evt.data), evt.data);
237 			}
238 			
239 		};
240 		
241 		return asyncCompiler;
242 	}
243 };
244 
245 });
246