///
///
module tf.graph.parser {
/**
* Parses a native js value, which can be either a string, boolean or number.
*
* @param value The value to be parsed.
*/
function parseValue(value: string): string|number|boolean {
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
let firstChar = value[0];
if (firstChar === "\"") {
return value.substring(1, value.length - 1);
}
let num = parseFloat(value);
return isNaN(num) ? value : num;
}
/**
* Fetches a text file and returns a promise of the result.
*/
export function readPbTxt(filepath: string): Promise {
return new Promise(function(resolve, reject) {
d3.text(filepath, function(error, text) {
if (error) {
reject(error);
return;
}
resolve(text);
});
});
}
/**
* Fetches and parses a json file and returns a promise of the result.
*/
export function readJson(filepath: string): Promise {
return new Promise(function(resolve, reject) {
d3.json(filepath, function(error, text) {
if (error) {
reject(error);
return;
}
resolve(text);
});
});
}
/**
* Reads the graph and stats file (if available), parses them and returns a
* promise of the result.
*/
export function readAndParseData(dataset: {path: string, statsPath: string},
pbTxtContent: string, tracker: ProgressTracker):
Promise<{ nodes: TFNode[], statsJson: Object }|void> {
let graphPbTxt;
let statsJson;
return runAsyncTask("Reading graph.pbtxt", 20, () => {
return pbTxtContent || readPbTxt(dataset.path);
}, tracker)
.then(function(text) {
graphPbTxt = text;
return runAsyncTask("Reading stats.pbtxt", 20, () => {
return (dataset != null && dataset.statsPath != null) ?
readJson(dataset.statsPath) : null;
}, tracker);
})
.then(function(json) {
statsJson = json;
return runAsyncTask("Parsing graph.pbtxt", 60, () => {
return parsePbtxt(graphPbTxt);
}, tracker);
})
.then(function(nodes) {
return {
nodes: nodes,
statsJson: statsJson
};
})
.catch(function(reason) {
throw new Error("Failure parsing graph definition");
});
}
/**
* Parses a proto txt file into a javascript object.
*
* @param input The string contents of the proto txt file.
* @return The parsed object.
*/
export function parsePbtxt(input: string): TFNode[] {
let output: { [name: string]: any; } = { node: [] };
let stack = [];
let path: string[] = [];
let current: { [name: string]: any; } = output;
function splitNameAndValueInAttribute(line: string) {
let colonIndex = line.indexOf(":");
let name = line.substring(0, colonIndex).trim();
let value = parseValue(line.substring(colonIndex + 2).trim());
return {
name: name,
value: value
};
}
/**
* Since proto-txt doesn't explicitly say whether an attribute is repeated
* (an array) or not, we keep a hard-coded list of attributes that are known
* to be repeated. This list is used in parsing time to convert repeated
* attributes into arrays even when the attribute only shows up once in the
* object.
*/
let ARRAY_ATTRIBUTES: {[attrPath: string] : boolean} = {
"node": true,
"node.input": true,
"node.attr": true,
"node.attr.value.list.type": true,
"node.attr.value.shape.dim": true,
"node.attr.value.tensor.string_val": true,
"node.attr.value.tensor.tensor_shape.dim": true
};
/**
* Adds a value, given the attribute name and the host object. If the
* attribute already exists, but is not an array, it will convert it to an
* array of values.
*
* @param obj The host object that holds the attribute.
* @param name The attribute name (key).
* @param value The attribute value.
* @param path A path that identifies the attribute. Used to check if
* an attribute is an array or not.
*/
function addAttribute(obj: Object, name: string,
value: Object|string|number|boolean, path: string[]): void {
// We treat "node" specially since it is done so often.
let existingValue = obj[name];
if (existingValue == null) {
obj[name] = path.join(".") in ARRAY_ATTRIBUTES ? [value] : value;
} else if (Array.isArray(existingValue)) {
existingValue.push(value);
} else {
obj[name] = [existingValue, value];
}
}
// Run through the file a line at a time.
let startPos = 0;
while (startPos < input.length) {
let endPos = input.indexOf("\n", startPos);
if (endPos === -1) {
endPos = input.length;
}
let line = input.substring(startPos, endPos);
startPos = endPos + 1;
if (!line) {
continue;
}
switch (line[line.length - 1]) {
case "{": // create new object
let name = line.substring(0, line.length - 2).trim();
let newValue: { [name: string]: any; } = {};
stack.push(current);
path.push(name);
addAttribute(current, name, newValue, path);
current = newValue;
break;
case "}":
current = stack.pop();
path.pop();
break;
default:
let x = splitNameAndValueInAttribute(line);
addAttribute(current, x.name, x.value, path.concat(x.name));
break;
}
}
return output["node"];
}
} // Close module tf.graph.parser.