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(){