/**
* Constructs a LogParser to parse the provided raw log text.
*
* @classdesc
*
* <p>
* LogParser can be used to transform raw log text to {@link LogEvent}s The
* LogParser class per se is only responsible for dividing the raw text into
* different executions according to the supplied delimiter. It then creates one
* {@link ExecutionParser} for each execution to which to task for parsing is
* then delegated.
* </p>
*
* <p>
* The raw log potentially contains text for multiple executions. Delimiters
* demarcate where one execution's text ends and another begins. Labels can be
* given to executions by specifying a "trace" capture group within the
* delimiter regex. (So the label text must be part of the delimiter). This
* label can later be used to identify an execution. If an execution's text is
* not preceeded by a delimiter, it is given the empty string as its label.
* </p>
*
* @constructor
* @param {String} rawString the raw log text
* @param {NamedRegExp} delimiter a regex that specifies the delimiter. Anything
* that matches the regex will be treated as a delimiter. A delimiter
* acts to separate different executions.
* @param {NamedRegExp} regexp A regex that specifies the log parser. The parser
* must contain the named capture groups "clock", "event", and "host"
* representing the vector clock, the event string, and the host
* respectively.
*/
function LogParser(rawString, delimiter, regexp) {
/** @private */
this.rawString = rawString.trim();
/** @private */
this.delimiter = delimiter;
/** @private */
this.regexp = regexp;
/** @private */
this.labels = [];
/** @private */
this.executions = {};
var names = this.regexp.getNames();
if (names.indexOf("clock") < 0 || names.indexOf("host") < 0 || names.indexOf("event") < 0) {
var e = new Exception("The parser RegExp you entered does not have the necessary named capture groups.\n", true);
e.append("Please see the documentation for details.");
throw e;
}
if (this.delimiter != null) {
var currExecs = this.rawString.split(this.delimiter.no);
var currLabels = [ "" ];
if (this.delimiter.getNames().indexOf("trace") >= 0) {
var match;
while (match = this.delimiter.exec(this.rawString)) {
currLabels.push(match.trace);
}
}
for (var i = 0; i < currExecs.length; i++) {
if (currExecs[i].trim().length > 0) {
var currlabel = currLabels[i];
if(this.executions[currlabel]) {
throw new Exception("Execution names must be unique. There are multiple executions called \"" + currlabel + "\"", true);
}
this.executions[currlabel] = new ExecutionParser(currExecs[i], currlabel, regexp);
this.labels.push(currlabel);
}
}
}
else {
this.labels.push("");
this.executions[""] = new ExecutionParser(this.rawString, "", regexp);
}
}
/**
* Gets all of the labels of the executions. The ordering of labels in the
* returned array is guarenteed to be the same as the order in which they are
* encountered in the raw log text
*
* @returns {Array<String>} An array of all the labels.
*/
LogParser.prototype.getLabels = function() {
return this.labels.slice();
};
/**
* Returns the {@link LogEvent}s parsed by this. The ordering of LogEvents in
* the returned array is guaranteed to be the same as the order in which they
* were encountered in the raw log text
*
* @param {String} label The label of the execution you want to get log events
* from.
* @returns {Array<LogEvent>} An array of LogEvents
*/
LogParser.prototype.getLogEvents = function(label) {
if (!this.executions[label])
return null;
return this.executions[label].logEvents;
};
/**
* @classdesc
*
* ExecutionParser parses the raw text for one execution.
*
* @constructor
* @private
* @param {String} rawString The raw string of the execution's log
* @param {Label} label The label that should be associated with this execution
* @param {NamedRegExp} regexp The RegExp parser
*/
function ExecutionParser(rawString, label, regexp) {
/** @private */
this.rawString = rawString;
/** @private */
this.label = label;
/** @private */
this.timestamps = [];
/** @private */
this.logEvents = [];
var match;
while (match = regexp.exec(rawString)) {
var newlines = rawString.substr(0, match.index).match(/\n/g);
var ln = newlines ? newlines.length + 1 : 1;
var clock = match.clock;
var host = match.host;
var event = match.event;
var fields = {};
regexp.getNames().forEach(function(name, i) {
if (name == "clock" || name == "event")
return;
fields[name] = match[name];
});
var timestamp = parseTimestamp(clock, host, ln);
this.timestamps.push(timestamp);
this.logEvents.push(new LogEvent(event, timestamp, ln, fields));
}
if (this.logEvents.length == 0)
throw new Exception("The parser RegExp you entered does not capture any events for the execution " + label, true);
function parseTimestamp(clockString, hostString, line) {
try {
// Attempt to parse a clockString as plain JSON
clock = JSON.parse(clockString);
} catch (err1) {
try {
// Corner-case, attempt to interpret as JSON escaped with quotes
// Added to support TLA+ syntax: {\"w1\":1,\"w2\":1}
clockString = clockString.replace(/\\\"/g, "\"")
clock = JSON.parse(clockString);
} catch (err2) {
var exception = new Exception("An error occured while trying to parse the vector timestamp on line " + (line + 1) + ":");
// Checks if clockString has a string value
// if not, the error message is not user-friendly
// and we can't append it to the exception.
var isUserFriendly = clockString ? true : false;
if (isUserFriendly) {
exception.append(clockString, "code");
}
exception.append("The error message from the JSON parser reads:\n");
exception.append(err2.toString(), "italic");
exception.setUserFriendly(isUserFriendly);
throw exception;
}
}
try {
var ret = new VectorTimestamp(clock, hostString);
return ret;
}
catch (exception) {
exception.prepend("An error occured while trying to parse the vector timestamp on line " + (line + 1) + ":\n\n");
exception.append(clockString, "code");
exception.setUserFriendly(true);
throw exception;
}
}
}