ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
385 lines (384 loc) • 10.7 kB
JavaScript
enyo.kind({
name: "analyzer.Documentor",
kind: "analyzer.AnalyzerDebug",
group: "public",
constructor: function(inTokens) {
this.comment = [];
// Debug mode is off by default. Could be dynamically turn on by calling AnalyzerDebug._debugEnabled = true;
this.debug = analyzer.AnalyzerDebug._debugEnabled;
return this.parse(inTokens);
},
parse: function(inTokens) {
var it = new analyzer.Iterator(inTokens);
return this.walk(it);
},
walk: function(it, inState) {
var objects = [], node, obj;
if (this.debug) {
this.logMethodEntry(it, "inState " + inState + " >>" + JSON.stringify(it.value) + "<<");
}
while (it.next()) {
node = it.value;
if (this.debug) {
this.logProcessing(it, node);
}
if (node.kind == "comment") {
this.cook_comment(node.token);
}
else if (node.token == "enyo.kind" && it.future && it.future.kind == "association") {
obj = this.cook_kind(it);
}
else if (node.token == "enyo.singleton" && it.future && it.future.kind == "association") {
obj = this.cook_singleton(it);
}
else if (node.kind == "assignment") {
obj = this.cook_assignment(it);
}
else if (node.kind == "association") {
// closure? [( [function] [ (...) ] [ {...} ] ) [ () ] ]
// closure? [( [function] [ (...) ] [ {...} ] [ () ] ) ]
var fn, body, objs;
if (node.children &&
(node.children.length == 1 && node.children[0].kind == "function") ||
(node.children.length == 2 && node.children[0].kind == "function" && node.children[1].kind == "association")) {
// closure
fn = node.children[0];
if (fn.children && fn.children.length == 2) {
body = fn.children[1];
objs = this.walk(new analyzer.Iterator(body.children));
// add whatever was in the closure to the main object list
objects = objects.concat(objs);
}
// skip the closure invocation [()] in the form where that follows parens
if (node.children.length == 1 && it.future && it.future.kind == "association") {
it.next();
}
}
}
if (obj) {
objects.push(obj);
obj = null;
}
}
if (this.debug) {
this.logMethodExit(it);
}
return objects;
},
cook_kind: function(it) {
if (this.debug) {
this.logMethodEntry(it, ">>" + JSON.stringify(it.value) + "<<");
}
// Get inProps[name].value[0].token
var val = function(inProps, inName) {
var i = analyzer.Documentor.indexByName(inProps, inName), p;
if (i >= 0) {
p = inProps[i];
inProps.splice(p, 1);
}
return p && p.value && p.value.length && p.value[0].token;
};
/*
// Set inProps[name].properties to inProps[name].value[0].properties
var flatten = function(inProps, inName) {
var p = analyzer.Documentor.findByName(inProps, inName);
var v = p && p.value && p.value.length && p.value[0];
if (v) {
p.properties = p.value[0].properties;
}
};
*/
//
var obj = this.make("kind", it.value);
// arguments
it.next();
// kind takes one argument
var args = it.value.children;
// if it's a block
if (args && args[0] && args[0].kind == "block") {
// these are the properties
obj.properties = this.cook_block(args[0].children);
// process special properties
obj.name = analyzer.Documentor.stripQuotes(val(obj.properties, "name") || "");
obj.superkind = analyzer.Documentor.stripQuotes(val(obj.properties, "kind") || "enyo.Control");
if (obj.superkind == "null") {
obj.superkind = null;
}
// Store block information for the kind
obj.block = { start: args[0].start, end: args[0].end };
// remove excess value nodes
//flatten(obj.properties, "published");
}
if (this.debug) {
this.logMethodExit(it);
}
return obj;
},
cook_singleton: function(inNodes) {
var obj = this.cook_kind(inNodes);
obj.type = "singleton";
return obj;
},
cook_block: function(inNodes) {
if (this.debug) {
this.logMethodEntry();
}
var props = [];
for (var i=0, n; (n=inNodes[i]); i++) {
if (this.debug) {
this.logProcessing(null, n);
}
if (n.kind == "comment") {
this.cook_comment(n.token);
}
else if (n.kind == "assignment") {
var prop = this.make("property", n);
if (n.children) {
prop.value = [this.walkValue(new analyzer.Iterator(n.children))];
if (n.commaTerminated === undefined) {
prop.commaTerminated = n.children[0].commaTerminated || false;
if (n.children[0].commaTerminated === undefined) {
if (this.debug) {
this.logMsg("NO COMMA TERMINATED INFO");
}
}
} else {
prop.commaTerminated = n.commaTerminated;
}
}
props.push(prop);
}
}
if (this.debug) {
this.logMethodExit();
}
return props;
},
walkValue: function(it, inState) {
if (this.debug) {
this.logMethodEntry(it, "inState: " + inState + " >>" + JSON.stringify(it.value) + "<<");
}
while (it.next()) {
var node = it.value, obj;
if (this.debug) {
this.logProcessing(it, node);
}
if (node.kind == "comment") {
this.cook_comment(node.token);
}
else if (node.kind == "block") {
obj = this.make("block", node);
obj.properties = this.cook_block(node.children);
if (this.debug) {
this.logMethodExit(it, "inState: " + inState + " >>" + JSON.stringify(it.value) + "<<");
}
return obj;
}
else if (node.kind == "array") {
obj = this.cook_array(it);
if (this.debug) {
this.logMethodExit(it);
}
return obj;
}
else if (node.kind == "function") {
obj = this.cook_function(it);
if (this.debug) {
this.logMethodExit(it, "inState: " + inState + " >>" + JSON.stringify(it.value) + "<<");
}
return obj;
}
else {
obj = this.make("expression", node);
var t = node.token;
while (it.next()) {
t += it.value.token;
}
obj.token = t;
if (this.debug) {
this.logMethodExit(it);
}
return obj;
}
}
if (this.debug) {
this.logMethodExit(it);
}
},
cook_function: function(it) {
if (this.debug) {
this.logMethodEntry(it, ">>" + JSON.stringify(it.value) + "<<");
}
var node = it.value;
var obj = this.make("expression", node);
obj.commaTerminated = node.commaTerminated;
obj.block = {start: node.children[1].start, end: node.children[1].end};
obj['arguments'] = enyo.map(node.children[0].children, function(n) { return n.token; });
if (this.debug) {
this.logMethodExit(it);
}
return obj;
},
cook_array: function(it) {
if (this.debug) {
this.logMethodEntry(it, ">>" + JSON.stringify(it.value) + "<<");
}
var node = it.value;
var obj = this.make("array", node);
var nodes = node.children;
if (nodes) {
var elts = [];
for (var i=0, n, v; (n=nodes[i]); i++) {
if (n.children) { // Skip nodes without children such as comments
v = this.walkValue(new analyzer.Iterator(n.children));
if (v) {
elts.push(v);
}
}
}
obj.properties = elts;
}
if (this.debug) {
this.logMethodExit(it);
}
return obj;
},
cook_assignment: function(it) {
if (this.debug) {
this.logMethodEntry(it, ">>" + JSON.stringify(it.value) + "<<");
}
var node = it.value;
var obj = this.make("global", node);
if (node.children) {
if (node.children[0] && node.children[0].token == "function") {
obj.type = "function";
}
obj.value = [this.walkValue(new analyzer.Iterator(node.children))];
}
if (this.debug) {
this.logMethodExit();
}
return obj;
},
make: function(inType, inNode) {
return {
line: inNode.line,
start: inNode.start,
end: inNode.end,
height: inNode.height,
token: inNode.token,
//
name: inNode.token,
type: inType,
group: this.group,
comment: this.consumeComment()
//
//kind: inType,
//node: inNode
};
},
// matches "/** [multi-line comment] */" and "//* single line comment"
commentRx: /\/\*\*([\s\S]*)\*\/|\/\/\*(.*)/m,
// comments that match a special format are instructions for this parser
// * some are pragmas are read and acted on
// * other comments are collected and attached to the next emitted node
cook_comment: function(inToken) {
if (this.debug) {
this.logMethodEntry();
}
var m = inToken.match(this.commentRx);
if (m) {
m = m[1] ? m[1] : m[2];
// separate pragmas from doc comments
var p = this.extractPragmas(m);
// act on pragmas
this.honorPragmas(p);
}
if (this.debug) {
this.logMethodExit();
}
},
extractPragmas: function(inString) {
var pragmaRx = /^[*\s]*@\s*(\S*)\s*/g,
pragmas = [],
s = inString;
if (s.length) {
s = inString.replace(pragmaRx, function(m, p) {
pragmas.push(p);
return "";
});
// if there is non-pragma content left, add it to comments
if (s.length) {
this.comment.push(s);
}
}
return pragmas;
},
honorPragmas: function(inPragmas) {
var groups = {'protected': 1, 'public': 1};
for (var i=0, p; (p=inPragmas[i]); i++) {
if (groups[p]) {
//console.log(p);
this.group = p;
}
}
},
consumeComment: function() {
var comment = this.comment.join(" ");
// clear Comment
this.comment = [];
// Remove leading indent so markdown spacing is intact.
// Assumes first non-empty line in comment is block-left.
var md = analyzer.Documentor.removeIndent(comment);
//md = md.replace("<", "<");
return md;
},
statics: {
indexByProperty: function(inObjects, inProperty, inValue) {
for (var i=0, o; (o=inObjects[i]); i++) {
if (o[inProperty] == inValue) {
return i;
}
}
return -1;
},
findByProperty: function(inObjects, inProperty, inValue) {
return inObjects[this.indexByProperty(inObjects, inProperty, inValue)];
},
indexByName: function(inObjects, inName) {
return this.indexByProperty(inObjects, "name", inName);
},
findByName: function(inObjects, inName) {
return inObjects[this.indexByName(inObjects, inName)];
},
stripQuotes: function(inString) {
var c0 = inString.charAt(0);
var s = (c0 == '"' || c0 == "'") ? 1 : 0;
var cl = inString.charAt(inString.length - 1);
var e = (cl == '"' || cl == "'") ? -1 : 0;
return (s || e) ? inString.slice(s, e) : inString;
},
// Remove leading indent so markdown spacing is intact.
// Assumes first non-empty line in comment is block-left.
removeIndent: function(inString) {
var indent = 0;
var lines = inString.split(/\r?\n/);
var i, l;
for (i=0; (l=lines[i]) != null; i++) {
if (l.length > 0) {
indent = l.search(/\S/);
if (indent < 0) {
indent = l.length;
}
break;
}
}
if (indent) {
for (i=0; (l=lines[i]) != null; i++) {
lines[i] = l.slice(indent);
}
}
return lines.join("\n");
}
}
});