(function() {
var listFromHtmlCollection = function(collection) {
var lst = [];
for (var idx = 0; idx < collection.length; idx++) {
lst.push(collection[idx]);
}
return lst;
};
var DomlEvaluator = function() {
var me = this;
// members of the Types object are functions that take a string and return
// an appropriate value as a native type
me.Types = {
'number': function(val) {
if (val.indexOf('.')) {
return parseFloat(val);
} else {
return parseInt(val, 10);
}
},
'string': function(val) {
return val;
},
'boolean': function(val) {
if (val === 'true') {
return true;
}
if (val === 'false') {
return false;
}
throw new Error('Invalid boolean value ' + val);
}
};
// members of the BinaryOperators object are functions that take two arguments
// and return the result of that operation
me.BinaryOperators = {
'!=': function(a, b) {
return a != b;
},
'<': function(a, b) {
return a < b;
},
'>': function(a, b) {
return a > b;
},
'-': function(a, b) {
return a - b;
},
'+': function(a, b) {
return a + b;
},
'==': function(a, b) {
return a == b;
},
'<=': function(a, b) {
return a <= b;
},
'>=': function(a, b) {
return a >= b;
}
};
// members of the Statements object are functions that take a dataset, children, and context
// and return the result of the statement
me.Statements = {
'statement-sequence': function(dataset, children, context) {
var retval = null;
children.forEach(function(child) {
retval = me.evaluate(child, context);
});
return retval;
},
'while': function(dataset, children, context) {
var condition = children[0];
var body = children[1];
while (me.evaluate(condition, context)) {
me.evaluate(body, context);
}
},
'compare': function(dataset, children, context) {
var operator = me.BinaryOperators[dataset.op];
var lhs = me.evaluate(children[0], context);
var rhs = me.evaluate(children[1], context);
var result = operator(lhs, rhs);
return result;
},
'variable': function(dataset, children, context) {
return context[dataset.name];
},
'constant': function(dataset, children, context) {
return me.Types[dataset.type](dataset.val);
},
'branch': function(dataset, children, context) {
if (me.evaluate(children[0], context)) {
return me.evaluate(children[1], context);
} else if (children.length > 2) {
return me.evaluate(children[2], context);
}
},
'assign': function(dataset, children, context) {
context[dataset.name] = me.evaluate(children[0], context);
return context[dataset.name];
},
'bin-op': function(dataset, children, context) {
var operator = me.BinaryOperators[dataset.op];
var lhs = me.evaluate(children[0], context);
var rhs = me.evaluate(children[1], context);
return operator(lhs, rhs);
},
'return': function(dataset, children, context) {
return me.evaluate(children[0], context);
},
'print': function(dataset, children, context) {
var args = children.map(function(child) {
return me.evaluate(child, context);
});
console.log.apply(this, args);
},
'function': function(dataset, children, context) {
var argNames = dataset.args.split(',');
return function(argValues) {
for (var idx = 0; idx < argNames.length; idx++) {
var argName = argNames[idx];
context[argName] = me.evaluate(argValues[idx], context);
}
me.evaluate(children[0], context);
}
},
'call': function(dataset, children, context) {
return context[dataset.name](children);
},
'repeat': function(dataset, children, context) {
var times = me.evaluate(children[0], context);
for (var n = 0; n < times; n++) {
if (dataset.iteration) {
context[dataset.iteration] = n;
}
me.evaluate(children[1], context);
}
},
'input': function(dataset, children, context) {
var msg = dataset.prompt || dataset.name;
var rawInput = prompt(dataset.prompt);
context[dataset.name] = me.Types[dataset.type](rawInput);
return context[dataset.name];
}
};
// members of the Readability object are functions which take an element
// and return an array of additional text nodes which should be added
// to enhance readability
me.Decorators = {
'assign': function(e) {
return [ e.dataset.name ];
},
'variable': function(e) {
return [ e.dataset.name ];
},
'constant': function(e) {
return [ e.dataset.type + '(' + e.dataset.val + ')' ];
},
'compare': function(e) {
return [ e.dataset.op ];
},
'bin-op': function(e) {
return [ e.dataset.op ];
},
'input': function(e) {
return [ e.dataset.name ];
},
'call': function(e) {
return [ e.dataset.name ];
},
'function': function(e) {
return [ e.dataset.args ];
},
'repeat': function(e) {
return [ e.dataset.iteration ];
}
};
me.evaluate = function(node, context) {
if (typeof context === 'undefined') {
context = {};
}
var value = me.Statements[node.className](node.dataset, listFromHtmlCollection(node.children), context);
return value;
};
me.appendText = function(node, text) {
var n = document.createElement('var');
n.innerText = text;
node.appendChild(n);
};
me.decorate = function(root) {
var classes = Object.keys(me.Decorators);
for (var idx = 0; idx < classes.length; idx++) {
var klass = classes[idx];
var els = listFromHtmlCollection(root.getElementsByClassName(klass));
els.forEach(function(el) {
me.Decorators[klass](el).forEach(function(text) {
me.appendText(el, text);
});
});
}
};
me.run = function(root) {
var t0 = performance.now();
me.evaluate(root);
var t1 = performance.now();
me.decorate(root);
console.debug('Evaluated DOML program in ' + (t1 - t0) + 'ms');
}
};
var Doml = {
Evaluator: DomlEvaluator,
runOnLoad: function(root) {
document.addEventListener('DOMContentLoaded', function() {
(new DomlEvaluator()).run(document.body.children[0]);
});
}
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Doml;
} else if (typeof window !== 'undefined') {
window.Doml = Doml;
} else {
throw new Error('Unable to register Doml');
}
})();