UNPKG

mmir-lib

Version:

MMIR (Mobile Multimodal Interaction and Relay) library

787 lines (667 loc) 23.7 kB
define([ 'mmirf/parserModule','mmirf/parsingResult','mmirf/templateProcessor' ,'mmirf/templateLexer','mmirf/ES3Lexer','mmirf/ES3Parser','mmirf/contentLexer','mmirf/contentParser' ,'mmirf/scriptLexer','mmirf/scriptParser','mmirf/antlr3' ], /** * A Utility class for parsing (eHTML) templates.<br> * Utility functions for parsing templates (and template elements, e.g. JS-parts of template expressions) * * @class * @name ParserUtils * @memberOf mmir.parser * @static * @hideconstructor * * @public * @example mmir.require('mmirf/parseUtils').parse(str, view) */ function( parser, ParsingResult, templateProcessor , MmirTemplateLexer, ES3Lexer, ES3Parser, MmirScriptContentLexer, MmirScriptContentParser , MmirScriptLexer, MmirScriptParser, org ){ /** * add extend-function for MmirTemplateLexer objects: * attaches the main processing functionality for parsing the template files * * (NOTE the extend-function is called in the constructor, see MmirTemplate.g and MmirTemplateLexer.js) * * @type Function * @memberOf MmirTemplateLexer.prototype */ MmirTemplateLexer.__mmirExtend = templateProcessor; /** * @memberOf ES3Parser.prototype * @function getVarReferences */ ES3Parser.prototype.getVarReferences = function(){ var size = this.ampersatIdentifiers.length; if(size === 0){ return null; } var varRefs = new Array(size); for(var i=0; i < size; ++i){ var ref = this.ampersatIdentifiers[i]; var refObj = new ParsingResult(ref); // refObj.start = ref.start; //correct end-position (token's stop-index is exactly the last char-index, whereas ParsingResult's end-position is token.stopIndex + 1) refObj.end = refObj.getEnd() + 1; refObj.type = parser.element.VAR_REFERENCE; varRefs[i] = refObj; } return varRefs; }; ////////////////////////////////////helper for debugging / printing error details //////////////////////// /** * -2: internal debug * -1: interanl info * 0: debug * 1: info * 2: warn * 3: error * TODO make this set-able (export getter/setter? use configurationManager?) * * @private * @memberOf mmir.parser.ParserUtils# */ var errorLevel = 2; /** * HELPER print internal debug messages during parsing (VERY VERBOSE) * * @private * @memberOf mmir.parser.ParserUtils# */ function _print(msg){//FIXME if ( errorLevel <= -2 ) console.log(msg); }; parser.print = _print; /** * HELPER print internal, informational messages during parsing (VERBOSE) * * @private * @memberOf mmir.parser.ParserUtils# */ function _printInfo(prefix, msg){//FIXME if ( errorLevel <= -1 ) console.info(parser.parserCreatePrintMessage(prefix,msg)); }; parser.printInfo = _printInfo; /** * HELPER print debug messages during parsing * * @private * @memberOf mmir.parser.ParserUtils# */ function _parserPrintDebug(prefix, msg, source){//FIXME if ( errorLevel <= 0 ) console.debug(parser.parserCreatePrintMessage(prefix,msg, source)); }; parser.parserPrintDebug = _parserPrintDebug; /** * HELPER print informational messages during parsing * * @private * @memberOf mmir.parser.ParserUtils# */ function _parserPrintInfo(prefix, msg, source){//FIXME if ( errorLevel <= 1 ) console.info(parser.parserCreatePrintMessage(prefix,msg, source)); }; parser.parserPrintInfo = _parserPrintInfo; /** * HELPER print warnings during parsing * * @private * @memberOf mmir.parser.ParserUtils# */ function _parserPrintWarning(prefix, msg, source){//FIXME if ( errorLevel <= 2 ) console.warn(parser.parserCreatePrintMessage(prefix,msg, source)); }; parser.parserPrintWarning = _parserPrintWarning; /** * HELPER print errors during parsing * * @private * @memberOf mmir.parser.ParserUtils# */ function _parserPrintError(prefix, msg, source){ if ( errorLevel <= 3 ) console.error(parser.parserCreatePrintMessage(prefix,msg, source)); }; parser.parserPrintError = _parserPrintError; /** * HELPER: attach internal print-functions to all classes (ie. prototypes) in the list * * @private * @memberOf mmir.parser.ParserUtils# */ var _attachInternalPrintFunc = function(list){ var _prototype; for(var i=0, size=list.length; i < size; ++i){ _prototype = list[i].prototype; _prototype.printInfo = parser.printInfo; _prototype.printDebug = parser.print; } }; //attach the internal debug/print functions to all lexers/parsers: // (-> see import-list in define() above) _attachInternalPrintFunc([ MmirTemplateLexer, ES3Lexer, ES3Parser, MmirScriptContentLexer, MmirScriptContentParser , MmirScriptLexer, MmirScriptParser ]); /** * @type mmir.view.View * @private * @memberOf mmir.parser.ParserUtils# */ var _currentParsedView = null;//FIXME make this an argument in the printXXX functions (e.g. the current mechanism will not work, if templates are parsed concurrently/in parallel/using threads) /** * Creates a message with parsing-information. * * In case the <code>msg</code> is an error message containing relative/incorrect location information, * an heuristic will be used to fix the location information; in addition the references location * will be extracted from the source-String and a "pointer-String" will be generated, e.g. * <pre> * source: " @{ mmmm.['sd']=wer ;}@" * pointer: " ^" * </pre> * * @function * @param {String} prefix * a prefix for the message * @param {String} msg * the original message (may contain location-information "line <line_i>:<position_j>") * @param {Object} [tokenSource] OPTIONAL * the token-source, from where the error/message was triggered * If the argument has the field <code>tokenSource.offset</code> (Number) * if will be used to correct/fix the location information in the original message. * If the argument has the fields <code>tokenSource.start</code> (Number) and * <code>tokenSource.end</code> (Number), then this will be used to correct/fix * the location information in the original message text. * @param {Object} [viewObj] OPTIONAL * currently not used! * (will replace _currentParsedView in the future!) * * @private * @memberOf mmir.parser.ParserUtils# */ var parserCreatePrintMessage = (function(){//return function(prefix, msg, tokenSource, viewObj) /** * * Get the index in the String str, where line number lineNo * starts. * * New lines begin after \n, \r\n, or \r. * * If lineNo is <= 1, the function returns always 0. * * If the lineNo is greater than the count of lines in str, the string length itself is returned. * * <p> * NOTE used by {@link #parserCreatePrintMessage} * * @function * @param {String} str the string * @param {Number} lineNo the line number (first line is 1) * * @private * @memberOf mmir.parser.ParserUtils.parserCreatePrintMessage */ var getIndexForLine = (function(){ var detectLinebreak = /(\r?\n|\r)/igm; return function(str, lineNo){ if(lineNo <= 1){ return 0; } var match; var count = 1; while(match = detectLinebreak.exec(str)){ //ASSERT: lineNo >= 2 if(++count == lineNo){ break; } } //reset regexpr: detectLinebreak.lastIndex = 0; if(match){ return match.index + match[1].length; } //request line-no. >= 2 AND loop "detect enough" linebreaks => the request line index starts after strings ends => return string's length return str.length; }; })();//END getIndexForLine /** * * Get the line in the String str, in which the char at index is included. * * New lines begin after \n, \r\n, or \r, * e.g. for line X: * <pre> * ...\r\n * ^ * </pre> * the line number will be X (i.e. the line-break itself is still included in the current line). * <p> * If index is < 0, the function returns always 1. * <p> * If the index is greater than str.length, -1 is returned. * <p> * NOTE used by {@link #extractErrorPosition} * * @function * @param {String} str the string * @param {Number} index the char index for which to find the line number (first line is 1) * * @private * * @memberOf mmir.parser.ParserUtils.parserCreatePrintMessage * */ var getLineForIndex = (function(){ var detectLinebreak = /(\r?\n|\r)/ig; return function(str, index){ if(index < 0){ return 1; } if(index >= str.length){ return -1; } //ASSERT index is at least within line 1 var match; var count = 1; var isNextLineFound = false; var currentPos = -1; var lastPos = 0; while(match = detectLinebreak.exec(str)){ currentPos = match.index + match[1].length; if(currentPos > index){ isNextLineFound = true; break; } lastPos = currentPos; ++count; } //reset regexpr: detectLinebreak.lastIndex = 0; return { line : count, index: index - lastPos }; }; })();//END getLineForIndex /** * * NOTE used by {@link #parserCreatePrintMessage} * * @private * @function * @memberOf mmir.parser.ParserUtils.parserCreatePrintMessage */ var extractErrorPosition = (function(){ var detectLineNo = /line (\d+):(-?\d+)/i; return function extractErrorPositionImpl(msg, offset, originalContent, tokenSource){ // console.log('\nTEST1_extractErrorPositionImpl with arguments '+arguments.length+'\n'); var result = detectLineNo.exec(msg); //reset regexpr: detectLineNo.lastIndex = 0; // console.log('\nTEST2_result for "'+msg+'": '+result+'\n'); var pos = null; if(result){ var line = parseInt(result[1],10); var index = parseInt(result[2],10); var isCorrected = false; if(tokenSource){ //if we have "invalid" position-info.: // -> the error probably occured at the very beginning of the parsed expression // -> try to extract position from parent parser/lexer if(line === 0 || index === -1){ line = tokenSource.getLine(); index = tokenSource.getCharPositionInLine(); } //if there is an offest supplied by the tokenSource -> use it: if(tokenSource.offset){ var iOffset = tokenSource.offset; // if(line === 1){ // // // //this position information is derived from a script-eval (-> ConentElement.ScriptEvalError) // // -> need to increase offset by 1 or 2, since all script-elements have // // an additional, internal offset of 1 or 2 (this is only an heuristical value...) // // e.g. @( ... // // @{ ... // // @for( ... // iOffset += 2; // } var contentOffset = getLineForIndex(originalContent, iOffset); //if it is "relatively" the first line, we need to adjust to index // (i.e. the position within the line) if(line === 1){ index += contentOffset.index; } //adjust the line, i.e. make "relative" -> "absolute" line number line += contentOffset.line - 1; isCorrected = true; } } pos = { line: line, index: index }; // console.log('\nTEST3_pos: '+JSON.stringify(pos)+', offset: '+offset+'\n'); if(offset && offset !== 0){ // console.log('\nTEST4_offset: '+offset+'\n'); var newLine = line; var newIndex = index; if( ! isCorrected){ var lineOffset = getLineForIndex(originalContent, offset); if(line < 2){ newIndex = lineOffset.index + index; pos.originalIndex = index; pos.index = newIndex; } newLine = lineOffset.line + line - 1; pos.originalLine = line; pos.line = newLine; } var fixed = msg.substring(0,result.index + 'line '.length) + newLine + ':' + newIndex + msg.substring(result.index + result[0].length); pos.text = fixed; // pos.originalContent = originalContent; // pos.offset = offset + pos.index; } else { pos.text = msg; } } else if(tokenSource && tokenSource.start && tokenSource.end){ pos = getLineForIndex(originalContent, tokenSource.start); pos.text = ' near /'; } return pos; }; })();//END extractErrorPosition /** * Create a message for parsing-information. * * In case the <code>msg</code> is an error message containing relative/incorrect location information, * an heuristic will be used to fix the location information; in addition the references location * will be extracted from the source-String and a "pointer-String" will be generated, e.g. * <pre> * source: " @{ mmmm.['sd']=wer ;}@" * pointer: " ^" * </pre> * * @private * * @param {String} prefix * a prefix for the message * @param {String} msg * the original message (may contain location-information "line <line_i>:<position_j>") * @param {Object} [tokenSource] OPTIONAL * the token-source, from where the error/message was triggered * If the argument has the field <code>tokenSource.offset</code> (Number) * if will be used to correct/fix the location information in the original message. * If the argument has the fields <code>tokenSource.start</code> (Number) and * <code>tokenSource.end</code> (Number), then this will be used to correct/fix * the location information in the original message text. * @param {Object} [viewObj] OPTIONAL * currently not used! * (will replace _currentParsedView in the future!) */ return function parserCreatePrintMessageImpl(prefix, msg, tokenSource, viewObj){//FIXME var currentView = _currentParsedView; if(currentView != null){ var rootView = null; var details = ''; if(currentView.getController){ details += 'CTRL("' + currentView.getController().getName() + '")'; } if(currentView.getView){ if(details.length > 0){ details += '->'; } details += 'VIEW("' + currentView.getView().getName() + '")'; rootView = currentView.getView(); } if(details.length > 0){ details += '->'; } details += currentView.constructor.name; if(currentView.getName){ details += '("' + currentView.getName() + '")'; } if(rootView && typeof currentView.getStart !== 'undefined'){ var pos = extractErrorPosition(msg, currentView.getOffset(), rootView.getDefinition(), tokenSource); // console.log('\nTEST_A_pos: '+JSON.stringify(pos)+', offset: '+currentView.getStart() +'\n'); if(pos){ msg = pos.text; //msg += '\n\t at line '+pos.line+', index '+pos.index; var content = rootView.getDefinition(); var line = null; var offset = currentView.getStart(); if(content){ var start = getIndexForLine(content, pos.line); var end = start; var len = content.length; while(end < len && (content[end] != '\r' && content[end] != '\n')){ ++end; } line = content.substring(start,end); } if(line){ //marker for "pointing" the error var marker = []; for(var i=0; i < pos.index; ++i){ if(line[i] == '\t'){ //need to include tabs themselves, since they // take more than 1 char-positions when displayed: marker.push('\t'); } else { marker.push(' '); } } //add marker symbol, that points to error in the line above: marker.push('^'); msg += ' at line '+pos.line+':'; msg += '\n "'+line+'"'; //<- the line with the error msg += '\n '+marker.join(''); //<- the marker line (will only be correctly aligned for fixed-width fonts) } } } return prefix + 'in ' + details + ' - ' + msg; } else { return prefix+msg; } };//END parserCreatePrintMessage })();//END var parserCreatePrintMessage = ... parser.parserCreatePrintMessage = parserCreatePrintMessage; //////////////////////////////////// END: helper for debugging, error details etc. //////////////////////// /** * Object containing the instance of the class ParserUtils * * @type ParserUtils * @private * * @memberOf mmir.parser.ParserUtils# */ var instance = null; /** * @private * @memberOf mmir.parser.ParserUtils# */ var isDebug = true;//TODO read/set from configuration MmirTemplateLexer.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] TemplateLexer: ', msg, this); }; // MmirTemplateParser.prototype.emitErrorMessage = function(msg) { // parser.parserPrintError('[ERROR] TemplateParser: ',msg); // }; ES3Lexer.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] JavaScriptLexer_ES3: ', msg, this); }; ES3Parser.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] JavaScriptParser_ES3: ', msg, this.getTokenStream().getTokenSource()); }; MmirScriptLexer.prototype.emitErrorMessage = function(msg) { var mode = this.isStatementMode()? 'Statement' : 'Block'; parser.parserPrintError('[ERROR] Script'+mode+'Lexer: ',msg, this); }; MmirScriptParser.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] ScriptParser: ',msg, this.getTokenStream().getTokenSource()); }; MmirScriptContentLexer.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] ContentLexer: ',msg, this); }; MmirScriptContentParser.prototype.emitErrorMessage = function(msg) { parser.parserPrintError('[ERROR] ContentParser: ',msg, this.getTokenStream().getTokenSource()); }; /** * @private * @memberOf mmir.parser.ParserUtils# */ function internalParse(text) { var input = new org.antlr.runtime.ANTLRStringStream(text);//FIXME change, how dependency 'mmirf/antlr3' is exported? var lexer = new MmirTemplateLexer(input); lexer.isDebug = isDebug; var tokens = new org.antlr.runtime.CommonTokenStream(lexer);//FIXME change, how dependency 'mmirf/antlr3' is exported? var result = {}; result.rawTemplateText = tokens.toString(); result.scripts = lexer.includeScripts; result.styles = lexer.includeStyles; result.localizations = lexer.locales; result.ifs = lexer.ifs; result.fors = lexer.fors; result.yields = lexer.yields; result.contentFors = lexer.yieldContents; result.helpers = lexer.helpers; result.partials = lexer.renderPartials; result.escapes = lexer.escape; result.scriptStatements = lexer.scriptStatements; result.scriptBlocks = lexer.scriptBlocks; result.vars = lexer.vars; result.comments = lexer.comments; //end: parsing results lexer = null; return result; } /** * @private * @memberOf mmir.parser.ParserUtils# */ function internalParseJS(text, entryRuleName, offset) { var input = new org.antlr.runtime.ANTLRStringStream(text); var lexer = new ES3Lexer(input); lexer.isDebug = isDebug; lexer.offset = offset; var tokens = new org.antlr.runtime.CommonTokenStream(lexer); var parser = new ES3Parser(tokens); parser.offset = offset; if(!entryRuleName){ // var parseResult = parser.program();//<- parse with main rule 'program' in ES3Parser } else { // var parseResult = parser[entryRuleName]();//<- parse with main rule 'program' in ES3Parser } var result = new Object(); result.rawTemplateText = tokens.toString(); var varRefs = parser.getVarReferences(); if(varRefs){ result.varReferences = varRefs; } else { result.varReferences = []; } //TODO handle potentially global var-declaration (i.e. assignments without preceding var, where the variable is undefined yet) //end: parsing results lexer = null; parser = null; return result; } // var getVarReferences = function(parser){ // // var size = parser.ampersatIdentifiers.length; // // if(size === 0){ // return null; // } // // var varRefs = new Array(size); // for(var i=0; i < size; ++i){ // var ref = parser.ampersatIdentifiers[i]; // // var refObj = new mmir.parser.ParsingResult(ref); //// refObj.start = ref.start; // // //correct end-position (token's stop-index is exactly the last char-index, whereas ParsingResult's end-position is token.stopIndex + 1) // refObj.end = refObj.getEnd() + 1; // // refObj.type = parser.element.VAR_REFERENCE; // // varRefs[i] = refObj; // } // return varRefs; // }; /** * Constructor-Method of Singleton mmir.parser.ParserUtils * * @constructs ParserUtils * @memberOf mmir.parser.ParserUtils.prototype * @private * @ignore * */ function constructor(){ //private members (currently none) /** @lends mmir.parser.ParserUtils.prototype */ return { //public members: /** * Parse a text as view template (e.g. *.ehtml files). * * @param {String} rawTemplateString the text that should be parsed * @param {Object} [view] (optional) the view to which the <tt>rawTemplateString</tt> belongs (only used for error messages) * @returns {mmir.parser.ParsingResult} the parsing result * * @public * @memberOf mmir.parser.ParserUtils.prototype */ parse: function(rawTemplateString, view){ if(view){ _currentParsedView = view; } else { _currentParsedView = null; } return internalParse(rawTemplateString); }, /** * Parse a text as JavaScript. * * @param {String} rawTemplateString the text that should be parsed * @param {String} [parseEntryRuleName] (optional) specifies the JavaScript element that should be parsed for * @param {Object} [view] (optional) the view to which the <tt>rawTemplateString</tt> belongs (only used for error messages) * @returns {mmir.parser.ParsingResult} the parsing result * * @public * @memberOf mmir.parser.ParserUtils.prototype */ parseJS: function(rawTemplateString, parseEntryRuleName, view, inViewOffset){ //in case only 2 or 3 arguments are present: is 2nd the View object? if(!inViewOffset && typeof parseEntryRuleName !== 'string' && parseEntryRuleName !== null && typeof parseEntryRuleName === 'object'){ if(typeof view === 'number'){ inViewOffset = view; } view = parseEntryRuleName; parseEntryRuleName = null; } if(view){ _currentParsedView = view; } else { _currentParsedView = null; } return internalParseJS(rawTemplateString, parseEntryRuleName, inViewOffset); } };//END: return{} }//END: constructor() instance = new constructor(); //FIXME should the renderer be exported to parser.ParserUtils here? parser.ParserUtils = instance; return instance; });//END define(..., function(){