todomvc
Version:
> Helping you select an MV\* framework
2,386 lines (1,986 loc) • 277 kB
JavaScript
// source /src/license.txt
/*!
* MaskJS v0.10.0
* Part of the Atma.js Project
* http://atmajs.com/
*
* MIT license
* http://opensource.org/licenses/MIT
*
* (c) 2012, 2014 Atma.js and other contributors
*/
// end:source /src/license.txt
// source /src/umd-head.js
(function (root, factory) {
'use strict';
var _global = typeof window === 'undefined' || window.navigator == null
? global
: window,
_exports, _document;
if (typeof exports !== 'undefined' && (root == null || root === exports || root === _global)){
// raw commonjs module
root = exports;
}
_document = _global.document;
_exports = root || _global;
function construct(){
return factory(_global, _exports, _document);
}
if (typeof define === 'function' && define.amd) {
return define(construct);
}
// Browser OR Node
return construct();
}(this, function (global, exports, document) {
'use strict';
// end:source /src/umd-head.js
// source /ref-utils/lib/utils.embed.js
// source /src/coll.js
var coll_each,
coll_remove,
coll_map,
coll_indexOf,
coll_find;
(function(){
coll_each = function(coll, fn, ctx){
if (ctx == null)
ctx = coll;
if (coll == null)
return coll;
var imax = coll.length,
i = 0;
for(; i< imax; i++){
fn.call(ctx, coll[i], i);
}
return ctx;
};
coll_indexOf = function(coll, x){
if (coll == null)
return -1;
var imax = coll.length,
i = 0;
for(; i < imax; i++){
if (coll[i] === x)
return i;
}
return -1;
};
coll_remove = function(coll, x){
var i = coll_indexOf(coll, x);
if (i === -1)
return false;
coll.splice(i, 1);
return true;
};
coll_map = function(coll, fn, ctx){
var arr = new Array(coll.length);
coll_each(coll, function(x, i){
arr[i] = fn.call(this, x, i);
}, ctx);
return arr;
};
coll_find = function(coll, fn, ctx){
var imax = coll.length,
i = 0;
for(; i < imax; i++){
if (fn.call(ctx || coll, coll[i], i))
return true;
}
return false;
};
}());
// end:source /src/coll.js
// source /src/polyfill/arr.js
if (Array.prototype.forEach === void 0) {
Array.prototype.forEach = function(fn, ctx){
coll_each(this, fn, ctx);
};
}
if (Array.prototype.indexOf === void 0) {
Array.prototype.indexOf = function(x){
return coll_indexOf(this, x);
};
}
// end:source /src/polyfill/arr.js
// source /src/polyfill/str.js
if (String.prototype.trim == null){
String.prototype.trim = function(){
var start = -1,
end = this.length,
code;
if (end === 0)
return this;
while(++start < end){
code = this.charCodeAt(start);
if (code > 32)
break;
}
while(--end !== 0){
code = this.charCodeAt(end);
if (code > 32)
break;
}
return start !== 0 && end !== length - 1
? this.substring(start, end + 1)
: this;
};
}
// end:source /src/polyfill/str.js
// source /src/polyfill/fn.js
if (Function.prototype.bind == null) {
var _Array_slice;
Function.prototype.bind = function(){
if (arguments.length < 2 && typeof arguments[0] === "undefined")
return this;
var fn = this,
args = _Array_slice.call(arguments),
ctx = args.shift();
return function() {
return fn.apply(ctx, args.concat(_Array_slice.call(arguments)));
};
};
}
// end:source /src/polyfill/fn.js
// source /src/is.js
var is_Function,
is_Array,
is_ArrayLike,
is_String,
is_Object,
is_notEmptyString,
is_rawObject;
(function() {
is_Function = function(x) {
return typeof x === 'function';
};
is_Object = function(x) {
return x != null && typeof x === 'object';
};
is_Array = is_ArrayLike = function(arr) {
return arr != null
&& typeof arr === 'object'
&& typeof arr.length === 'number'
&& typeof arr.slice === 'function'
;
};
is_String = function(x) {
return typeof x === 'string';
};
is_notEmptyString = function(x) {
return typeof x === 'string' && x !== '';
};
is_rawObject = function(obj) {
if (obj == null || typeof obj !== 'object')
return false;
return obj.constructor === Object;
};
}());
// end:source /src/is.js
// source /src/obj.js
var obj_getProperty,
obj_setProperty,
obj_extend,
obj_create;
(function(){
obj_getProperty = function(obj, path){
if ('.' === path) // obsolete
return obj;
var chain = path.split('.'),
imax = chain.length,
i = -1;
while ( obj != null && ++i < imax ) {
obj = obj[chain[i]];
}
return obj;
};
obj_setProperty = function(obj, path, val) {
var chain = path.split('.'),
imax = chain.length - 1,
i = -1,
key;
while ( ++i < imax ) {
key = chain[i];
if (obj[key] == null)
obj[key] = {};
obj = obj[key];
}
obj[chain[i]] = val;
};
obj_extend = function(a, b){
if (b == null)
return a || {};
if (a == null)
return obj_create(b);
for(var key in b){
a[key] = b[key];
}
return a;
};
obj_create = Object.create || function(x) {
var Ctor = function(){};
Ctor.prototype = x;
return new Ctor;
};
}());
// end:source /src/obj.js
// source /src/arr.js
var arr_remove,
arr_each,
arr_indexOf,
arr_contains;
(function(){
arr_remove = function(array, x){
var i = array.indexOf(x);
if (i === -1)
return false;
array.splice(i, 1);
return true;
};
arr_each = function(arr, fn, ctx){
arr.forEach(fn, ctx);
};
arr_indexOf = function(arr, x){
return arr.indexOf(x);
};
arr_contains = function(arr, x){
return arr.indexOf(x) !== -1;
};
}());
// end:source /src/arr.js
// source /src/fn.js
var fn_proxy,
fn_apply,
fn_doNothing;
(function(){
fn_proxy = function(fn, ctx) {
return function(){
return fn_apply(fn, ctx, arguments);
};
};
fn_apply = function(fn, ctx, args){
var l = args.length;
if (0 === l)
return fn.call(ctx);
if (1 === l)
return fn.call(ctx, args[0]);
if (2 === l)
return fn.call(ctx, args[0], args[1]);
if (3 === l)
return fn.call(ctx, args[0], args[1], args[2]);
if (4 === l)
return fn.call(ctx, args[0], args[1], args[2], args[3]);
return fn.apply(ctx, args);
};
fn_doNothing = function(){
return false;
};
}());
// end:source /src/fn.js
// source /src/refs.js
var _Array_slice = Array.prototype.slice,
_Array_splice = Array.prototype.splice,
_Array_indexOf = Array.prototype.indexOf,
_Object_create = obj_create,
_Object_hasOwnProp = Object.hasOwnProperty;
// end:source /src/refs.js
// end:source /ref-utils/lib/utils.embed.js
// source /src/scope-vars.js
var __rgxEscapedChar = {
"'": /\\'/g,
'"': /\\"/g,
'{': /\\\{/g,
'>': /\\>/g,
';': /\\>/g
},
__cfg = {
// Relevant to node.js only. Disable compo caching
allowCache: true
};
// end:source /src/scope-vars.js
// source /src/util/util.js
/**
* - arr (Array) - array that was prepaired by parser -
* every even index holds interpolate value that was in #{some value}
* - model: current model
* - type (String const) (node | attr): tell custom utils what part we are
* interpolating
* - cntx (Object): current render context object
* - element (HTMLElement):
* type node - this is a container
* type attr - this is element itself
* - name
* type attr - attribute name
* type node - undefined
*
* -returns Array | String
*
* If we rendere interpolation in a TextNode, then custom util can return not only string values,
* but also any HTMLElement, then TextNode will be splitted and HTMLElements will be inserted within.
* So in that case we return array where we hold strings and that HTMLElements.
*
* If custom utils returns only strings, then String will be returned by this function
*
*/
function util_interpolate(arr, type, model, ctx, element, controller, name) {
var imax = arr.length,
i = -1,
array = null,
string = '',
even = true,
utility,
value,
index,
key,
handler;
while ( ++i < imax ) {
if (even === true) {
if (array == null){
string += arr[i];
} else{
array.push(arr[i]);
}
} else {
key = arr[i];
value = null;
index = key.indexOf(':');
if (index === -1) {
value = obj_getPropertyEx(key, model, ctx, controller);
} else {
utility = index > 0
? key.substring(0, index).trim()
: '';
if (utility === '') {
utility = 'expression';
}
key = key.substring(index + 1);
handler = custom_Utils[utility];
if (handler == null) {
log_error('Undefined custom util `%s`', utility);
continue;
}
value = handler(key, model, ctx, element, controller, name, type);
}
if (value != null){
if (typeof value === 'object' && array == null){
array = [string];
}
if (array == null){
string += value;
} else {
array.push(value);
}
}
}
even = !even;
}
return array == null
? string
: array
;
}
// end:source /src/util/util.js
// source /src/util/attr.js
var attr_extend;
(function(){
attr_extend = function (a, b) {
if (a == null) {
return b == null
? {}
: obj_create(b);
}
if (b == null)
return a;
var key;
for(key in b) {
if ('class' === key && typeof a[key] === 'string') {
a[key] += ' ' + b[key];
continue;
}
a[key] = b[key];
}
return a;
};
}());
// end:source /src/util/attr.js
// source /src/util/template.js
function Template(template) {
this.template = template;
this.index = 0;
this.length = template.length;
}
Template.prototype = {
skipWhitespace: function () {
var template = this.template,
index = this.index,
length = this.length;
for (; index < length; index++) {
if (template.charCodeAt(index) > 32 /*' '*/) {
break;
}
}
this.index = index;
return this;
},
skipToAttributeBreak: function () {
var template = this.template,
index = this.index,
length = this.length,
c;
do {
c = template.charCodeAt(++index);
/* if c == # && next() == { - continue */
if (c === 35 && template.charCodeAt(index + 1) === 123) {
// goto end of template declaration
this.index = index;
this.sliceToChar('}');
this.index++;
return;
}
}
while (c !== 46 && c !== 35 && c !== 62 && c !== 123 && c !== 32 && c !== 59 && index < length);
//while(!== ".#>{ ;");
this.index = index;
},
sliceToChar: function (c) {
var template = this.template,
index = this.index,
start = index,
isEscaped = false,
value, nindex;
while ((nindex = template.indexOf(c, index)) > -1) {
index = nindex;
if (template.charCodeAt(index - 1) !== 92 /*'\\'*/) {
break;
}
isEscaped = true;
index++;
}
value = template.substring(start, index);
this.index = index;
return isEscaped ? value.replace(__rgxEscapedChar[c], c) : value;
}
};
// end:source /src/util/template.js
// source /src/util/array.js
var arr_pushMany;
(function(){
arr_pushMany = function(arr, arrSource){
if (arrSource == null || arr == null)
return;
var il = arr.length,
jl = arrSource.length,
j = -1
;
while( ++j < jl ){
arr[il + j] = arrSource[j];
}
};
}());
// end:source /src/util/array.js
// source /src/util/string.js
// end:source /src/util/string.js
// source /src/util/object.js
var obj_getPropertyEx,
obj_toDictionary;
(function(){
obj_getPropertyEx = function(path, model, ctx, ctr){
if (path === '.')
return model;
var props = path.split('.'),
value = model,
i = -1,
imax = props.length,
key = props[0],
start_i
;
if ('$c' === key) {
value = ctr;
i++;
}
else if ('$a' === key) {
value = ctr && ctr.attr;
i++;
}
else if ('$u' === key) {
value = customUtil_$utils;
i++;
}
else if ('$ctx' === key) {
value = ctx;
i++;
}
start_i = i;
while (value != null && ++i < imax) {
value = value[props[i]];
}
if (value == null && start_i === -1) {
var $scope;
while (ctr != null){
$scope = ctr.scope;
if ($scope != null) {
value = getProperty_($scope, props, 0, imax);
if (value != null)
return value;
}
ctr = ctr.parent;
}
}
return value;
};
obj_toDictionary = function(obj){
var array = [],
i = 0,
key
;
for(key in obj){
array[i++] = {
key: key,
value: obj[key]
};
}
return array;
};
// = private
function getProperty_(obj, props, i, imax) {
var val = obj;
while(i < imax && val != null){
val = val[props[i]];
i++;
}
return val;
}
}());
// end:source /src/util/object.js
// source /src/util/listeners.js
var listeners_on,
listeners_off,
listeners_emit;
(function(){
listeners_on = function(event, fn) {
(bin[event] || (bin[event] = [])).push(fn);
};
listeners_off = function(event, fn){
if (fn == null) {
bin[event] = [];
return;
}
arr_remove(bin[event], fn);
};
listeners_emit = function(event){
var fns = bin[event];
if (fns == null)
return;
var imax = fns.length,
i = -1,
args = _Array_slice.call(arguments, 1)
;
while ( ++i < imax)
fns[i].apply(null, args);
};
// === private
var bin = {
compoCreated: null,
error: null
};
}());
// end:source /src/util/listeners.js
// source /src/util/reporters.js
var throw_,
parser_error,
parser_warn,
log_warn,
log_error;
(function(){
throw_ = function(error){
log_error(error);
listeners_emit('error', error);
};
parser_error = function(msg, str, i, token, state, file){
var error = createMsg('error', msg, str, i, token, state, file);
log_error(error.message);
log_warn(error.stack);
listeners_emit('error', error);
};
parser_warn = function(msg, str, i, token, state, file){
var error = createMsg('warn', msg, str, i, token, state, file);
log_warn(error.message);
log_warn(error.stack);
listeners_emit('error', error);
};
if (typeof console === 'undefined') {
log_warn = log_error = function(){};
}
else {
var bind = Function.prototype.bind;
log_warn = bind.call(console.warn , console, 'MaskJS [Warn] :');
log_error = bind.call(console.error, console, 'MaskJS [Error] :');
}
var ParserError = createError('Error'),
ParserWarn = createError('Warning');
function createError(type) {
function ParserError(msg, orig, index){
this.type = 'Parser' + type;
this.message = msg;
this.original = orig;
this.index = index;
this.stack = prepairStack();
}
inherit(ParserError, Error);
return ParserError;
}
function prepairStack(){
var stack = new Error().stack;
if (stack == null)
return null;
return stack
.split('\n')
.slice(6, 8)
.join('\n');
}
function inherit(Ctor, Base){
if (Object.create)
Ctor.prototype = Object.create(Base.prototype);
}
function createMsg(type, msg, str, index, token, state, filename){
msg += formatToken(token)
+ formatFilename(str, index, filename)
+ formatStopped(type, str, index)
+ formatState(state)
;
var Ctor = type === 'error'
? ParserError
: ParserWarn;
return new Ctor(msg, str, index);
}
function formatToken(token){
if (token == null)
return '';
if (typeof token === 'number')
token = String.fromCharCode(token);
return ' Invalid token: `'+ token + '`';
}
function formatFilename(str, index, filename) {
if (index == null && !filename)
return '';
var lines = str.substring(0, index).split('\n'),
line = lines.length,
row = index + 1 - lines.slice(0, line - 2).join('\n').length;
return ' at '
+ (filename || '')
+ '(' + line + ':' + row + ')';
}
function formatState(state){
var states = {
'2': 'tag',
'3': 'tag',
'5': 'attribute key',
'6': 'attribute value',
'8': 'literal',
'var': 'VarStatement',
'expr': 'Expression'
};
if (state == null || states[state] == null)
return '';
return '\n , when parsing ' + states[state];
}
function formatStopped(type, str, index){
if (index == null)
return '';
var stopped = str.substring(index);
if (stopped.length > 30)
stopped = stopped.substring(0, 30) + '...';
return '\n Parser ' + type + ' at: ' + stopped;
}
}());
// end:source /src/util/reporters.js
// source /src/custom/exports.js
var custom_Utils,
custom_Statements,
custom_Attributes,
custom_Tags,
custom_Tags_defs,
customUtil_get,
customUtil_$utils,
customUtil_register,
customTag_register
;
(function(){
initialize();
// source tag.js
(function(repository){
customTag_register = function(name, Handler){
if (Handler != null && typeof Handler === 'object') {
//> static
Handler.__Ctor = wrapStatic(Handler);
}
repository[name] = Handler;
};
function wrapStatic(proto) {
function Ctor(node, parent) {
this.tagName = node.tagName;
this.attr = node.attr;
this.expression = node.expression;
this.nodes = node.nodes;
this.nextSibling = node.nextSibling;
this.parent = parent;
this.components = null;
}
Ctor.prototype = proto;
return Ctor;
}
}(custom_Tags));
// end:source tag.js
// source util.js
(function(repository) {
customUtil_$utils = {};
customUtil_register = function(name, mix) {
if (is_Function(mix)) {
repository[name] = mix;
return;
}
repository[name] = createUtil(mix);
if (mix.arguments === 'parsed')
customUtil_$utils[name] = mix.process;
};
customUtil_get = function(name) {
return name != null
? repository[name]
: repository
;
};
// = private
function createUtil(obj) {
if (obj.arguments === 'parsed')
return processParsedDelegate(obj.process);
var fn = fn_proxy(obj.process || processRawFn, obj);
// <static> save reference to the initial util object.
// Mask.Bootstrap need the original util
// @workaround
fn.util = obj;
return fn;
}
function processRawFn(expr, model, ctx, element, controller, attrName, type) {
if ('node' === type) {
this.nodeRenderStart(expr, model, ctx, element, controller);
return this.node(expr, model, ctx, element, controller);
}
// asume 'attr'
this.attrRenderStart(expr, model, ctx, element, controller, attrName);
return this.attr(expr, model, ctx, element, controller, attrName);
}
function processParsedDelegate(fn) {
return function(expr, model, ctx, element, controller) {
var args = ExpressionUtil
.evalStatements(expr, model, ctx, controller);
return fn.apply(null, args);
};
}
}(custom_Utils));
// end:source util.js
function initialize() {
custom_Utils = {
expression: function(value, model, ctx, element, controller){
return ExpressionUtil.eval(value, model, ctx, controller);
},
};
custom_Statements = {};
custom_Attributes = {
'class': null,
id: null,
style: null,
name: null,
type: null
};
custom_Tags = {
/*
* Most common html tags
* http://jsperf.com/not-in-vs-null/3
*/
div: null,
span: null,
input: null,
button: null,
textarea: null,
select: null,
option: null,
h1: null,
h2: null,
h3: null,
h4: null,
h5: null,
h6: null,
a: null,
p: null,
img: null,
table: null,
td: null,
tr: null,
pre: null,
ul: null,
li: null,
ol: null,
i: null,
em: null,
b: null,
strong: null,
form: null,
audio: null,
video: null,
canvas: null,
svg: null
};
// use on server to define reserved tags and its meta info
custom_Tags_defs = {};
}
}());
// end:source /src/custom/exports.js
// source /src/expression/exports.js
/**
* ExpressionUtil
*
* Helper to work with expressions
**/
var ExpressionUtil = (function(){
// source 1.scope-vars.js
var index = 0,
length = 0,
cache = {},
template, ast;
var op_Minus = '-', //1,
op_Plus = '+', //2,
op_Divide = '/', //3,
op_Multip = '*', //4,
op_Modulo = '%', //5,
op_LogicalOr = '||', //6,
op_LogicalAnd = '&&', //7,
op_LogicalNot = '!', //8,
op_LogicalEqual = '==', //9,
op_LogicalEqual_Strict = '===', // 111
op_LogicalNotEqual = '!=', //11,
op_LogicalNotEqual_Strict = '!==', // 112
op_LogicalGreater = '>', //12,
op_LogicalGreaterEqual = '>=', //13,
op_LogicalLess = '<', //14,
op_LogicalLessEqual = '<=', //15,
op_Member = '.', // 16
punc_ParantheseOpen = 20,
punc_ParantheseClose = 21,
punc_BracketOpen = 22,
punc_BracketClose = 23,
punc_BraceOpen = 24,
punc_BraceClose = 25,
punc_Comma = 26,
punc_Dot = 27,
punc_Question = 28,
punc_Colon = 29,
punc_Semicolon = 30,
go_ref = 31,
go_acs = 32,
go_string = 33,
go_number = 34,
go_objectKey = 35;
var type_Body = 1,
type_Statement = 2,
type_SymbolRef = 3,
type_FunctionRef = 4,
type_Accessor = 5,
type_AccessorExpr = 6,
type_Value = 7,
type_Number = 8,
type_String = 9,
type_Object = 10,
type_Array = 11,
type_UnaryPrefix = 12,
type_Ternary = 13;
var state_body = 1,
state_arguments = 2;
var precedence = {};
precedence[op_Member] = 1;
precedence[op_Divide] = 2;
precedence[op_Multip] = 2;
precedence[op_Minus] = 3;
precedence[op_Plus] = 3;
precedence[op_LogicalGreater] = 4;
precedence[op_LogicalGreaterEqual] = 4;
precedence[op_LogicalLess] = 4;
precedence[op_LogicalLessEqual] = 4;
precedence[op_LogicalEqual] = 5;
precedence[op_LogicalEqual_Strict] = 5;
precedence[op_LogicalNotEqual] = 5;
precedence[op_LogicalNotEqual_Strict] = 5;
precedence[op_LogicalAnd] = 6;
precedence[op_LogicalOr] = 6;
// end:source 1.scope-vars.js
// source 2.ast.js
var Ast_Body,
Ast_Statement,
Ast_Value,
Ast_Array,
Ast_Object,
Ast_FunctionRef,
Ast_SymbolRef,
Ast_Accessor,
Ast_AccessorExpr,
Ast_UnaryPrefix,
Ast_TernaryStatement
;
(function(){
Ast_Body = function(parent) {
this.parent = parent;
this.type = type_Body;
this.body = [];
this.join = null;
};
Ast_Statement = function(parent) {
this.parent = parent;
};
Ast_Statement.prototype = {
constructor: Ast_Statement,
type: type_Statement,
join: null,
body: null
};
Ast_Value = function(value) {
this.type = type_Value;
this.body = value;
this.join = null;
};
Ast_Array = function(parent){
this.type = type_Array;
this.parent = parent;
this.body = new Ast_Body(this);
};
Ast_Object = function(parent){
this.type = type_Object;
this.parent = parent;
this.props = {};
}
Ast_Object.prototype = {
nextProp: function(prop){
var body = new Ast_Statement(this);
this.props[prop] = body;
return body;
},
};
Ast_FunctionRef = function(parent, ref) {
this.parent = parent;
this.type = type_FunctionRef;
this.body = ref;
this.arguments = [];
this.next = null;
}
Ast_FunctionRef.prototype = {
constructor: Ast_FunctionRef,
newArgument: function() {
var body = new Ast_Body(this);
this.arguments.push(body);
return body;
}
};
Ast_SymbolRef = function(parent, ref) {
this.type = type_SymbolRef;
this.parent = parent;
this.body = ref;
this.next = null;
};
Ast_Accessor = function(parent, ref) {
this.type = type_Accessor;
this.parent = parent;
this.body = ref;
this.next = null;
};
Ast_AccessorExpr = function(parent){
this.parent = parent;
this.body = new Ast_Statement(this);
this.body.body = new Ast_Body(this.body);
this.next = null;
};
Ast_AccessorExpr.prototype = {
type: type_AccessorExpr,
getBody: function(){
return this.body.body;
}
};
Ast_UnaryPrefix = function(parent, prefix) {
this.parent = parent;
this.prefix = prefix;
};
Ast_UnaryPrefix.prototype = {
constructor: Ast_UnaryPrefix,
type: type_UnaryPrefix,
body: null
};
Ast_TernaryStatement = function(assertions){
this.body = assertions;
this.case1 = new Ast_Body(this);
this.case2 = new Ast_Body(this);
};
Ast_TernaryStatement.prototype = {
constructor: Ast_TernaryStatement,
type: type_Ternary,
case1: null,
case2: null
};
}());
// end:source 2.ast.js
// source 2.ast.utils.js
var ast_handlePrecedence,
ast_append;
(function(){
ast_append = function(current, next) {
switch(current.type) {
case type_Body:
current.body.push(next);
return next;
case type_Statement:
if (next.type === type_Accessor || next.type === type_AccessorExpr) {
return (current.next = next)
}
/* fall through */
case type_UnaryPrefix:
return (current.body = next);
case type_SymbolRef:
case type_FunctionRef:
case type_Accessor:
case type_AccessorExpr:
return (current.next = next);
}
return util_throw('Invalid expression');
};
ast_handlePrecedence = function(ast) {
if (ast.type !== type_Body){
if (ast.body != null && typeof ast.body === 'object')
ast_handlePrecedence(ast.body);
return;
}
var body = ast.body,
i = 0,
length = body.length,
x, prev, array;
for(; i < length; i++){
ast_handlePrecedence(body[i]);
}
for(i = 1; i < length; i++){
x = body[i];
prev = body[i-1];
if (precedence[prev.join] > precedence[x.join])
break;
}
if (i === length)
return;
array = [body[0]];
for(i = 1; i < length; i++){
x = body[i];
prev = body[i-1];
var prec_Prev = precedence[prev.join];
if (prec_Prev > precedence[x.join] && i < length - 1){
var start = i,
nextJoin,
arr;
// collect all with join smaller or equal to previous
// 5 == 3 * 2 + 1 -> 5 == (3 * 2 + 1);
while (++i < length){
nextJoin = body[i].join;
if (nextJoin == null)
break;
if (prec_Prev <= precedence[nextJoin])
break;
}
arr = body.slice(start, i + 1);
x = ast_join(arr);
ast_handlePrecedence(x);
}
array.push(x);
}
ast.body = array;
};
// = private
function ast_join(bodyArr){
if (bodyArr.length === 0)
return null;
var body = new Ast_Body(bodyArr[0].parent);
body.join = bodyArr[bodyArr.length - 1].join;
body.body = bodyArr;
return body;
}
}());
// end:source 2.ast.utils.js
// source 3.util.js
var util_resolveRef,
util_throw;
(function(){
util_throw = function(msg, token){
return parser_error(msg
, template
, index
, token
, 'expr'
);
};
util_resolveRef = function(astRef, model, ctx, controller) {
var current = astRef,
key = astRef.body,
object,
value,
args,
i,
imax
;
if ('$c' === key) {
value = controller;
var next = current.next,
nextBody = next != null && next.body;
if (nextBody != null && value[nextBody] == null){
if (next.type === type_FunctionRef
&& typeof Compo.prototype[nextBody] === 'function') {
// use fn from prototype if possible, like `closest`
object = controller;
value = Compo.prototype[nextBody];
current = next;
} else {
// find the closest controller, which has the property
while (true) {
value = value.parent;
if (value == null)
break;
if (value[nextBody] == null)
continue;
object = value;
value = value[nextBody];
current = next;
break;
}
}
if (value == null) {
// prepair for warn message
key = '$c.' + nextBody;
current = next;
}
}
}
else if ('$a' === key)
value = controller && controller.attr;
else if ('$u' === key)
value = customUtil_$utils;
else if ('$ctx' === key)
value = ctx;
else {
// scope resolver
if (model != null) {
object = model;
value = model[key];
}
if (value == null) {
while (controller != null) {
object = controller.scope;
if (object != null)
value = object[key];
if (value != null)
break;
controller = controller.parent;
}
}
}
if (value == null) {
if (current == null || current.next != null){
// notify that value is not in model, ctx, controller;
log_warn('<mask:expression> Accessor error:', key);
}
return null;
}
do {
if (current.type === type_FunctionRef) {
args = [];
i = -1;
imax = current.arguments.length;
while( ++i < imax )
args[i] = expression_evaluate(current.arguments[i], model, ctx, controller);
value = value.apply(object, args);
}
if (value == null || current.next == null)
break;
current = current.next;
key = current.type === type_AccessorExpr
? expression_evaluate(current.body, model, ctx, controller)
: current.body
;
object = value;
value = value[key];
if (value == null)
break;
} while (true);
return value;
};
}());
// end:source 3.util.js
// source 4.parser.helper.js
var parser_skipWhitespace,
parser_getString,
parser_getNumber,
parser_getArray,
parser_getObject,
parser_getRef,
parser_getDirective
;
(function(){
parser_skipWhitespace = function() {
var c;
while (index < length) {
c = template.charCodeAt(index);
if (c > 32)
return c;
index++;
}
return null;
};
parser_getString = function(c) {
var isEscaped = false,
_char = c === 39 ? "'" : '"',
start = index,
nindex, string;
while ((nindex = template.indexOf(_char, index)) > -1) {
index = nindex;
if (template.charCodeAt(nindex - 1) !== 92 /*'\\'*/ ) {
break;
}
isEscaped = true;
index++;
}
string = template.substring(start, index);
if (isEscaped === true) {
string = string.replace(__rgxEscapedChar[_char], _char);
}
return string;
};
parser_getNumber = function() {
var start = index,
code, isDouble;
while (true) {
code = template.charCodeAt(index);
if (code === 46) {
// .
if (isDouble === true) {
util_throw('Invalid number', code);
return null;
}
isDouble = true;
}
if ((code >= 48 && code <= 57 || code === 46) && index < length) {
index++;
continue;
}
break;
}
return +template.substring(start, index);
};
parser_getRef = function() {
var start = index,
c = template.charCodeAt(index),
ref;
if (c === 34 || c === 39) {
// ' | "
index++;
ref = parser_getString(c);
index++;
return ref;
}
while (true) {
if (index === length)
break;
c = template.charCodeAt(index);
if (c === 36 || c === 95) {
// $ _
index++;
continue;
}
if ((48 <= c && c <= 57) || // 0-9
(65 <= c && c <= 90) || // A-Z
(97 <= c && c <= 122)) { // a-z
index++;
continue;
}
// - [removed] (exit on not allowed chars) 5ba755ca
break;
}
return template.substring(start, index);
};
parser_getDirective = function(code) {
if (code == null && index === length)
return null;
switch (code) {
case 40:
// (
return punc_ParantheseOpen;
case 41:
// )
return punc_ParantheseClose;
case 123:
// {
return punc_BraceOpen;
case 125:
// }
return punc_BraceClose;
case 91:
// [
return punc_BracketOpen;
case 93:
// ]
return punc_BracketClose;
case 44:
// ,
return punc_Comma;
case 46:
// .
return punc_Dot;
case 59:
// ;
return punc_Semicolon;
case 43:
// +
return op_Plus;
case 45:
// -
return op_Minus;
case 42:
// *
return op_Multip;
case 47:
// /
return op_Divide;
case 37:
// %
return op_Modulo;
case 61:
// =
if (template.charCodeAt(++index) !== code) {
util_throw(
'Assignment violation: View can only access model/controllers', '='
);
return null;
}
if (template.charCodeAt(index + 1) === code) {
index++;
return op_LogicalEqual_Strict;
}
return op_LogicalEqual;
case 33:
// !
if (template.charCodeAt(index + 1) === 61) {
// =
index++;
if (template.charCodeAt(index + 1) === 61) {
// =
index++;
return op_LogicalNotEqual_Strict;
}
return op_LogicalNotEqual;
}
return op_LogicalNot;
case 62:
// >
if (template.charCodeAt(index + 1) === 61) {
index++;
return op_LogicalGreaterEqual;
}
return op_LogicalGreater;
case 60:
// <
if (template.charCodeAt(index + 1) === 61) {
index++;
return op_LogicalLessEqual;
}
return op_LogicalLess;
case 38:
// &
if (template.charCodeAt(++index) !== code) {
util_throw(
'Not supported: Bitwise AND', code
);
return null;
}
return op_LogicalAnd;
case 124:
// |
if (template.charCodeAt(++index) !== code) {
util_throw(
'Not supported: Bitwise OR', code
);
return null;
}
return op_LogicalOr;
case 63:
// ?
return punc_Question;
case 58:
// :
return punc_Colon;
}
if ((code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
(code === 95) ||
(code === 36)) {
// A-Z a-z _ $
return go_ref;
}
if (code >= 48 && code <= 57) {
// 0-9 .
return go_number;
}
if (code === 34 || code === 39) {
// " '
return go_string;
}
util_throw(
'Unexpected or unsupported directive', code
);
return null;
};
}());
// end:source 4.parser.helper.js
// source 5.parser.js
/*
* earlyExit - only first statement/expression is consumed
*/
function expression_parse(expr, earlyExit) {
if (earlyExit == null)
earlyExit = false;
template = expr;
index = 0;
length = expr.length;
ast = new Ast_Body();
var current = ast,
state = state_body,
c, next, directive;
outer: while (true) {
if (index < length && (c = template.charCodeAt(index)) < 33) {
index++;
continue;
}
if (index >= length)
break;
directive = parser_getDirective(c);
if (directive == null && index < length) {
break;
}
if (directive === punc_Semicolon) {
if (earlyExit === true)
return [ast, index];
break;
}
if (earlyExit === true) {
var p = current.parent;
if (p != null && p.type === type_Body && p.parent == null) {
// is in root body
if (directive === go_ref)
return [ast, index];
}
}
if (directive === punc_Semicolon) {
break;
}
switch (directive) {
case punc_ParantheseOpen:
current = ast_append(current, new Ast_Statement(current));
current = ast_append(current, new Ast_Body(current));
index++;
continue;
case punc_ParantheseClose:
var closest = type_Body;
if (state === state_arguments) {
state = state_body;
closest = type_FunctionRef;
}
do {
current = current.parent;
} while (current != null && current.type !== closest);
if (closest === type_Body) {
current = current.parent;
}
if (current == null) {
util_throw('OutOfAst Exception', c);
break outer;
}
index++;
continue;
case punc_BraceOpen:
current = ast_append(current, new Ast_Object(current));
directive = go_objectKey;
index++;
break;
case punc_BraceClose:
while (current != null && current.type !== type_Object){
current = current.parent;
}
index++;
continue;
case punc_Comma:
if (state !== state_arguments) {
state = state_body;
do {
current = current.parent;
} while (current != null &&
current.type !== type_Body &&
current.type !== type_Object
);
index++;
if (current == null) {
util_throw('Unexpected comma', c);
break outer;
}
if (current.type === type_Object) {
directive = go_objectKey;
break;
}
continue;
}
do {
current = current.parent;
} while (current != null && current.type !== type_FunctionRef);
if (current == null) {
util_throw('OutOfAst Exception', c);
break outer;
}
current = current.newArgument();
index++;
continue;
case punc_Question:
ast = new Ast_TernaryStatement(ast);
current = ast.case1;
index++;
continue;
case punc_Colon:
current = ast.case2;
index++;
continue;
case punc_Dot:
c = template.charCodeAt(index + 1);
if (c >= 48 && c <= 57) {
directive = go_number;
} else {
directive = current.type === type_Body
? go_ref
: go_acs
;
index++;
}
break;
case punc_BracketOpen:
if (current.type === type_SymbolRef ||
current.type === type_AccessorExpr ||
current.type === type_Accessor
) {
current = ast_append(current, new Ast_AccessorExpr(current))
current = current.getBody();
index++;
continue;
}
current = ast_append(current, new Ast_Array(current));
current = current.body;
index++;
continue;
case punc_BracketClose:
do {
current = current.parent;
} while (current != null &&
current.type !== type_AccessorExpr &&
current.type !== type_Array
);
index++;
continue;
}
if (current.type === type_Body) {
current = ast_append(current, new Ast_Statement(current));
}
if ((op_Minus === directive || op_LogicalNot === directive) && current.body == null) {
current = ast_append(current, new Ast_UnaryPrefix(current, directive));
index++;
continue;
}
switch (directive) {
case op_Minus:
case op_Plus:
case op_Multip:
case op_Divide:
case op_Modulo:
case op_LogicalAnd:
case op_LogicalOr:
case op_LogicalEqual:
case op_LogicalEqual_Strict:
case op_LogicalNotEqual:
case op_LogicalNotEqual_Strict:
case op_LogicalGreater:
case op_LogicalGreaterEqual:
case op_LogicalLess:
case op_LogicalLessEqual:
while (current && current.type !== type_Statement) {
current = current.parent;
}
if (current.body == null) {
return util_throw(
'Unexpected operator', c
);
}
current.join = directive;
do {
current = current.parent;
} while (current != null && current.type !== type_Body);
if (current == null) {
return util_throw(
'Unexpected operator' , c
);
}
index++;
continue;
case go_string:
case go_number:
if (current.body != null && current.join == null) {
return util_throw(
'Directive expected', c
);
}
if (go_string === directive) {
index++;
ast_append(current, new Ast_Value(parser_getString(c)));
index++;
}
if (go_number === directive) {
ast_append(current, new Ast_Value(parser_getNumber(c)));
}
continue;
case go_ref:
case go_acs:
var ref = parser_getRef();
if (directive === go_ref) {
if (ref === 'null')
ref = null;
if (ref === 'false')
ref = false;
if (ref === 'true')
ref = true;
if (typeof ref !== 'string') {
ast_append(current, new Ast_Value(ref));
continue;
}
}
while (index < length) {
c = template.charCodeAt(index);
if (c < 33) {
index++;
continue;
}
break;
}
if (c === 40) {
// (
// function ref
state = state_arguments;
index++;
var fn = ast_append(current, new Ast_FunctionRef(current, ref));
current = fn.newArgument();
continue;
}
var Ctor = directive === go_ref
? Ast_SymbolRef
: Ast_Accessor
current = ast_append(current, new Ctor(current, ref));
break;
case go_objectKey:
if (parser_skipWhitespace() === 125)
continue;
var key = parser_getRef();
if (parser_skipWhitespace() !== 58) {
//:
return util_throw(
'Object parser. Semicolon expeted', c
);
}
index++;
current = current.nextProp(key);
directive = go_ref;
continue;
}
}
if (current.body == null &&
current.type === type_Statement) {
return util_throw(
'Unexpected end of expression', c
);
}
ast_handlePrecedence(ast);
return ast;
}
// end:source 5.parser.js
// source 6.eval.js
function expression_evaluate(mix, model, ctx, controller) {
var result, ast;
if (null == mix)
return null;
if ('.' === mix)
return model;
if (typeof mix === 'string'){
ast = cache.hasOwnProperty(mix) === true
? (cache[mix])
: (cache[mix] = expression_parse(mix))
;
}else{
ast = mix;
}
if (ast == null)
return null;
var type = ast.type,
i, x, length;
if (type_Body === type) {
var value, prev;
outer: for (i = 0, length = ast.body.length; i < length; i++) {
x = ast.body[i];
value = expression_evaluate(x, model, ctx, controller);
if (prev == null || prev.join == null) {
prev = x;
result = value;
continue;
}
if (prev.join === op_LogicalAnd) {
if (!result) {
for (; i < length; i++) {
if (ast.body[i].join === op_LogicalOr) {
break;
}
}
}else{
result = value;
}
}
if (prev.join === op_LogicalOr) {
if (result){
break outer;
}
if (value) {
result = value;
break outer;
}
}
switch (prev.join) {
case op_Minus:
result -= value;
break;
case op_Plus:
result += value;
break;
case op_Divide:
result /= value;
break;
case op_Multip:
result *= value;
break;
case op_Modulo:
result %= value;
break;
case op_LogicalNotEqual:
/* jshint eqeqeq: false */
result = result != value;
/* jshint eqeqeq: true */
break;
case op_LogicalNotEqual_Strict:
result = result !== value;
break;
case op_LogicalEqual:
/* jshint eqeqeq: false */
result = result == value;
/* jshint eqeqeq: true */
break;
case op_LogicalEqual_Strict:
result = result === value;
break;
case op_LogicalGreater:
result = result > value;
break;
case op_LogicalGreaterEqual:
result = result >= value;
break;
case op_LogicalLess:
result = result < value;
break;
case op_LogicalLessEqual:
result = result <= value;
break;
}
prev = x;
}
}
if (type_Statement === type) {
result = expression_evaluate(ast.body, model, ctx, controller);
if (ast.next == null)
return result;
//debugger;
return util_resolveRef(ast.next, result);
}
if (type_Value === type) {
return ast.body;
}
if (type_Array === type) {
var body = ast.body.body,
imax = body.length,
i = -1;
result = new Array(imax);
while( ++i < imax ){
result[i] = expression_evaluate(body[i], model, ctx, controller);
}
return result;
}
if (type_Object === type) {
result = {};
var props = ast.props;
for(var key in props){
result[key] = expression_evaluate(props[key], model, ctx, controller);
}
return result;
}
if (type_SymbolRef === type ||
type_FunctionRef === type ||
type_AccessorExpr === type ||
type_Accessor === type) {
return util_resolveRef(ast, model, ctx, controller);
}
if (type_UnaryPrefix === type) {
result = expression_evaluate(ast.body, model, ctx, controller);
switch (ast.prefix) {
case op_Minus:
result = -result;
break;
case op_LogicalNot:
result = !result;
break;
}
}
if (type_Ternary === type){
result = expression_evaluate(ast.body, model, ctx, controller);
result = expression_evaluate(result ? ast.case1 : ast.case2, model, ctx, controller);
}
return result;
}
// end:source 6.eval.js
// source 7.vars.helper.js
var refs_extractVars;
(function() {
/**
* extract symbol references
* ~[:user.name + 'px'] -> 'user.name'
* ~[:someFn(varName) + user.name] -> ['varName', 'user.name']
*
* ~[:someFn().user.name] -> {accessor: (Accessor AST function call) , ref: 'user.name'}
*/
refs_extractVars = function(expr, model, ctx, ctr){
if (typeof expr === 'string')
expr = expression_parse(expr);
return _extractVars(expr, model, ctx, ctr);
};
function _extractVars(expr, model, ctx, ctr) {
if (expr == null)
return null;
var exprType = expr.type,
refs, x;
if (type_Body === exprType) {
var body = expr.body,
imax = body.length,
i = -1;
while ( ++i < imax ){
x = _extractVars(body[i], model, ctx, ctr);
refs = _append(refs, x);
}
}
if (type_SymbolRef === exprType ||
type_Accessor === exprType ||
type_AccessorExpr === exprType) {
var path = expr.body,
next = expr.next,
nextType;
while (next != null) {
nextType = next.type;
if (type_FunctionRef === nextType) {
return _extractVars(next, model, ctx, ctr);
}
if ((type_SymbolRef !== nextType) &&
(type_Accessor !== nextType) &&
(type_AccessorExpr !== nextType)) {
log_error('Ast Exception: next should be a symbol/function ref');
return null;
}
var prop = nextType === type_AccessorExpr
? expression_evaluate(next.body, model, ctx, ctr)
: next.body
;
if (typeof prop !== 'string') {
log_warn('Can`t extract accessor name', path);
return null;
}
path += '.' + prop;
next = next.next;
}
return path;
}
switch (exprType) {
case type_Statement:
case type_UnaryPrefix:
case type_Ternary:
x = _extractVars(expr.body, model, ctx, ctr);
refs = _append(refs, x);
break;
}
// get also from case1 and case2
if (type_Ternary === exprType) {
x = _extractVars(ast.case1, model, ctx, ctr);
refs = _append(refs, x);
x = _extractVars(ast.case2, model, ctx, ctr);
refs = _append(refs, x);
}
if (type_FunctionRef === exprType) {
var args = expr.arguments,
imax = args.length,
i = -1;
while ( ++i < imax ){
x = _extractVars(args[i], model, ctx, ctr);
refs = _append(refs, x);
}
x = null;
var parent = expr;
outer: while ((parent = parent.parent)) {
switch (parent.type) {
case type_SymbolRef:
case type_Accessor:
case type_AccessorExpr:
x = parent.body + (x == null ? '' : '.' + x);
break;
case type_Body:
case type_Statement:
break outer;
default:
x = null;
break outer;
}
}
if (x != null) {
refs = _append(refs, x);
}
if (expr.next) {
x = _extractVars(expr.next, model, ctx, ctr);
refs = _append(refs, {accessor: _getAccessor(expr), ref: x});
}
}
return refs;
}
function _append(current, x) {
if (current == null) {
return x;
}
if (x == null) {
return current;
}
if (!(typeof current === 'object' && current.length != null)) {
current = [current];
}
if (!(typeof x === 'object' && x.length != null)) {
if (current.indexOf(x) === -1) {
current.push(x);
}
return current;
}