UNPKG

lite

Version:

A cross platform template engine base on xml/html and javascript expression.

490 lines (475 loc) 15.9 kB
/* * List Template * License LGPL(您可以在任何地方免费使用,但请不要吝啬您对框架本身的改进) * http://www.xidea.org/project/lite/ * @author jindw * @version $Id: template.js,v 1.4 2008/02/28 14:39:06 jindw Exp $ */ var VAR_LITE_EL_TEMP = "$__el_tmp" var FOR_STATUS_KEY = '$__for'; /** * 将Lite的表达式结构转化为php表达式 */ function stringifyPHPEL(el,context){ var type = el[0]; if(type<=0){//value return stringifyValue(el,context) }else if(getTokenParamIndex(type) ==3){//两个操作数 return stringifyInfix(el,context); }else{ return stringifyPrefix(el,context); } } function stringifyValue(el,context){ var param = el[1]; switch(el[0]){ case VALUE_CONSTANTS: return stringifyPHP(param); case VALUE_VAR: if(param == 'for'){ return FOR_STATUS_KEY; }else{ return '$'+param; } case VALUE_LIST: case VALUE_MAP: return "array()"; } } function typesOnly(t1,t2){ var i = arguments.length; var a = 0; while(--i>1){ a |= arguments[i]; } var t = t1 | t2; return (t & a) == t; } function stringifyADD(el,context){ var t = getELType(el); var value1 = stringifyPHPEL(el[1],context); var value2 = stringifyPHPEL(el[2],context); if(t == TYPE_NUMBER){ return value1+'+'+value2;//动态部分要考虑 array的问题,这里不需要考虑 }else if(t == TYPE_STRING){ if(/[\d]$/.test(value1)){ value1+=' '; } if(/^[\d]/.test(value2)){ value2=' '+value2; } //还需要处理 null,true,false 字面量的问题 var t1 = getELType(el[1]); var t2 = getELType(el[2]); //console.error(t1,t2) if(typesOnly(t1,t2,TYPE_STRING,TYPE_NUMBER)){ return value1+'.'+value2; } } //字符串加法不复合交换律 // if(typesOnly(t1,t1,TYPE_NULL,TYPE_NUMBER,TYPE_BOOLEAN)){ // return "lite_op__add_nx("+value1+','+value2+")" // } // if(typesOnly(t2,t2,TYPE_NULL,TYPE_NUMBER,TYPE_BOOLEAN)){ // return "lite_op__add_nx("+value2+','+value1+")" // } return "lite_op__add("+value1+','+value2+")" } //var TYPE_NULL = 1<<offset++; //var TYPE_BOOLEAN = 1<<offset++; //var TYPE_NUMBER = 1<<offset++; //var TYPE_STRING = 1<<offset++; //var TYPE_ARRAY = 1<<offset++; //var TYPE_MAP = 1<<offset++; function stringifyEQ(el,context,opc){ var t1 = getELType(el[1]); var t2 = getELType(el[2]); var value1 = stringifyPHPEL(el[1],context); var value2 = stringifyPHPEL(el[2],context); opc = opc || '=='; if(t1 == TYPE_STRING || t2 == TYPE_STRING){//0 == 'ttt'=>false return "strcmp("+value1+","+value2+") "+opc+"0"; } if(t1 === TYPE_NULL || t2 === TYPE_NULL){ return value1+opc+'='+value2; } if(typesOnly(t1,t2,TYPE_NUMBER,TYPE_BOOLEAN) // ||typesOnly(t1,t2,TYPE_BOOLEAN,TYPE_STRING)//'0' ==false; // ||typesOnly(t1,t2,TYPE_NUMBER,TYPE_STRING)//'0' == 0 // ||typesOnly(t1,t2,TYPE_NUMBER,TYPE_BOOLEAN)//'0' ==false;" ||typesOnly(t1,t2,TYPE_ARRAY,TYPE_MAP,TYPE_STRING)//'' ==array() => false,忽略array.toString ==//php ; ||t1.toString(2).replace(/0/g,'').length==1 && t1 == t2){ return value1+opc+value2; } return (opc=='!='?'!':'')+"lite_op__eq("+value1+','+value2+")" } var math = { "E":2.718281828459045, "PI":3.141592653589793, "LN2":0.6931471805599453, "LN10":2.302585092994046, "LOG2E":1.4426950408889634, "LOG10E":0.4342944819032518, "SQRT1_2":0.7071067811865476, "SQRT2":1.4142135623730951 } function stringifyGET(el,context){ var arg1 = el[1]; var arg2 = el[2]; var value1 = stringifyPHPEL(el[1],context); var value2 = stringifyPHPEL(el[2],context); if(arg2[0] == VALUE_CONSTANTS){ var prop = arg2[1]; if( prop != 'length'){ //这里有可能要抛警告 if(arg1[0] == VALUE_VAR){ var owner = arg1[1]; if(owner == 'Math' && !(owner in context.scope.defMap && owner in context.scope.varMap && owner in context.scope.paramMap)){ if(typeof math[prop] == 'number'){ return '('+math[prop]+')'; } } } if(!/^[^(][\s\S]*\)$/.test(value1) && !/^(true|false|null|[\d\.]+)$/.test(value1)){//php bug method(args)[index]非法 return value1+'['+value2+']'; } } } return "lite_op__get("+value1+','+value2+")" } /** * return [owner,prop,args] */ function parseInvoke(el){ var method = el[1]; if(method[0] == OP_GET){//member_call var ownerEL = method[1]; var propEL = method[2]; if(ownerEL[0] == VALUE_VAR){ var varName = ownerEL[1]; } if(propEL[0] == VALUE_CONSTANTS){ var prop = propEL[1]; } return [varName||ownerEL,prop||propEL,el[2]]; }else{//function_call if(method[0] == VALUE_VAR){ var varName = method[1]; } return [varName||method,null,el[2]] } } function stringifyPHPEL2ID(el,context,id){ if(typeof el != 'string'){ return stringifyPHPEL(el,context) }else if(id){ return '$'+el; } return "'"+el+"'"; } function stringifyINVOKE(el,context){ var info = parseInvoke(el); var owner = info[0]; var prop = info[1]; var args = stringifyPHPEL(info[2],context); if(prop){//member_call if(typeof prop == 'string'){ // random=>rand(0, PHP_INT_MAX // sin,sqrt,tan,cos,acos,asin,atan,atan2 // max,min,,floor,round,abs,ceil,exp,log,pow, if(owner === 'Math'){ var mp = /^(?:sin|sqrt|tan|cos|acos|asin|atan|atan2|max|min||floor|round|abs|ceil|exp|log|pow)$/; if(prop == 'random'){ return '(rand(0, 0xFFFF)/0xFFFF)'; }else if(mp.test(prop)){ return args.replace('array',prop); }else{ console.warn("Math 不支持方法:"+prop+";Math 支持的方法有:random|"+mp.source.replace(/[^\w\|]/g,'')) } }else if(owner === 'JSON'){ if(prop == "parse"){ return args.replace('array','json_decode').slice(0,-1)+',true)'; }else if(prop =='stringify'){ return args.replace('array','json_encode'); }else{ console.warn("JSON 不支持方法:"+prop+";JSON 只支持:stringify和parse方法") } }else if(prop == 'reverse' && args == 'array()' && owner[0] == OP_INVOKE){ var info2 = parseInvoke(owner); //console.error(info2); if(info2[1] == 'concat'){ owner = info2[0]; owner = stringifyPHPEL2ID(owner,context,true) args = stringifyPHPEL(info2[2],context); return "lite_op__invoke("+owner+",'concat_reverse',"+args+")" } } } owner = stringifyPHPEL2ID(owner,context,true) prop = stringifyPHPEL2ID(prop,context) return "lite_op__invoke("+owner+","+prop+","+args+")" //value1 = value1.replace(/.*?,([\s\S]+)\)/,'array($1)'); }else if(typeof owner == 'string'){ if((owner in GLOBAL_DEF_MAP || owner in context.scope.defMap) && !(owner in context.scope.varMap || owner in context.scope.paramMap)){ //静态编译方式 return args.replace('array',"lite__"+owner) }else{ //动态调用方式 //console.error("!!!!!!!!!!!!",context.scope.varMap); if(owner in context.scope.varMap || owner in context.scope.paramMap){ var fn = '$'+owner; }else{ var fn = "isset($"+owner+")?$"+owner+":'"+owner+"'"; } return 'lite_op__invoke('+fn+',null,'+args+')'; } }else{ //console.error("??????????",typeof owner,owner,context.scope.varMap); owner = stringifyPHPEL2ID(owner,context,true) //console.error(owner); return 'lite_op__invoke('+owner+',null,'+args+')'; //throw new Error("Invalid Invoke EL"); } } /** * 翻译中缀运算符 */ function stringifyInfix(el,context){ var type = el[0]; if(type == OP_ADD){ return stringifyADD(el,context) }else if(type == OP_EQ){ return stringifyEQ(el,context,'==') }else if(type == OP_NE){ return stringifyEQ(el,context,'!='); }else if(type == OP_GET){ return stringifyGET(el,context); }else if(type == OP_INVOKE){ return stringifyINVOKE(el,context); } var opc = findTokenText(el[0]); var value1 = stringifyPHPEL(el[1],context); var value2 = stringifyPHPEL(el[2],context); switch(type){ case OP_JOIN: if("array()"==value1){ return "array("+value2+")" }else{ return value1.slice(0,-1)+','+value2+")" } case OP_PUT: value2 = stringifyPHP(getTokenParam(el))+"=>"+value2+")"; if("array()"==value1){ return "array("+value2 }else{ return value1.slice(0,-1)+','+value2 } case OP_QUESTION: //1?2:3 => [QUESTION_SELECT, // [QUESTION,[CONSTANTS,1],[CONSTANTS,2]], // [CONSTANTS,3] // ] //throw new Error("表达式异常:QUESTION 指令翻译中应该被QUESTION_SELECT跳过"); return null;//前面有一个尝试,此处应返回null,而不是抛出异常。 case OP_QUESTION_SELECT: /** ${a?b:c} ${a?b1?b2:b3:c} ${222+2|a?b1?b2:b3:c} */ var arg1 = el[1]; var test = stringifyPHPEL(arg1[1],context); var value1 = stringifyPHPEL(arg1[2],context); //return '('+LITE_INVOKE+OP_NOT+','+test+')?'+value2+":"+value1+')'; return '('+php2jsBoolean(arg1[1],test)+'?'+value1+':'+value2+')' case OP_AND://&& if(isSimplePHPEL(value1)){ return '('+php2jsBoolean(el[1],value1)+'?'+value2+':'+value1+')' } return '(('+php2jsBoolean(el[1],value1,VAR_LITE_EL_TEMP)+')?'+value2+':'+VAR_LITE_EL_TEMP+')' case OP_OR://|| if(isSimplePHPEL(value1)){ return '('+php2jsBoolean(el[1],value1)+'?'+value1+':'+value2+')' } return '(('+php2jsBoolean(el[1],value1,VAR_LITE_EL_TEMP)+')?'+VAR_LITE_EL_TEMP+':'+value2 +')' } value1 = addELQute(el,el[1],value1) value2 = addELQute(el,el[2],null,value2) if(opc == '-' || opc == '+'){//-1--2=> -1- -2 if(value2.indexOf(opc) == 0){ opc += ' '; } } return value1 + opc + value2; } function stringifyPHP(value) { switch (typeof value) { case 'string': return '\'' + value.replace(/[\\']/g,"\\$&")+ '\''; case 'number': if(isNaN(value)){ value = 'null'; } return ''+value; case 'undefined': return 'null'; case 'object': if (!value) { return 'null'; } var buf = []; if (value instanceof Array) { var i = value.length; while (i--) { buf[i] = stringifyPHP(value[i]) || 'null'; } return 'array(' + buf.join(',') + ')'; }else if(value instanceof RegExp){ return "array('class'=>'RegExp','source'=>'"+value.replace(/[\\']/g,"\\$&")+"')"; } for (var k in value) { var v = stringifyPHP(value[k]); if (v) { buf.push(stringifyPHP(k) + '=>' + v); } } return 'array(' + buf.join(',') + ')'; default://boolean return String(value); } } /** * 翻译前缀运算符 */ function stringifyPrefix(el,context){ var type = el[0]; var el1 = el[1]; var value2 = stringifyPHPEL(el1,context); var param = getTokenParam(el,context); if(type == OP_NOT){//! //return value1+'['+value2+']'; var rtv = php2jsBoolean(el1,value2); if(!isSimplePHPEL(rtv)){ rtv = '('+rtv+')'; } return '!'+rtv; } value2 = addELQute(el,el[1],null,value2) var opc = findTokenText(type); return opc+value2; } /** * 如果不是变量或者常量,则必须设置零时变量 */ function php2jsBoolean(el,value,keepValue,context){ if(!value){ value = stringifyPHPEL(el,context); } var op = el[0]; if(op<=0){ switch(op){ case VALUE_CONSTANTS: if(keepValue){ if(el[1]){ return '(('+keepValue+'='+value+')||true)'; }else{ return '(('+keepValue+'='+value+')&&false)'; } // }else if(booleanVar){//keepValue 和 booleanVar 只选一个 // if(el[1]){ // return '('+booleanVar+'=true)'; // }else{ // return '('+booleanVar+'=false)'; // } }else{ return !!el[1]+''; } case VALUE_VAR: break; case VALUE_LIST: case VALUE_MAP: default: if(keepValue){ return '(('+keepValue+'='+value+')||true)' }else{ return 'true'; } } } //var TYPE_NULL = 1<<offset++; //var TYPE_BOOLEAN = 1<<offset++; //var TYPE_NUMBER = 1<<offset++; //var TYPE_STRING = 1<<offset++; //var TYPE_ARRAY = 1<<offset++; //var TYPE_MAP = 1<<offset++; //var TYPE_ANY = (1<<offset++) -1; var type = getELType(el); if(!((type & TYPE_STRING)||(type & TYPE_ARRAY)||(type & TYPE_MAP))){ if(!keepValue){//持续优化 return value; }else{ return '('+keepValue +'='+value+')'; } } if(isSimplePHPEL(value) && !keepValue){ var rtv = value; keepValue = value; }else{ keepValue = keepValue || VAR_LITE_EL_TEMP; var rtv = "("+keepValue +"="+ value+")" } if((type & TYPE_ARRAY)||(type & TYPE_MAP)){ rtv+=' || 0 < '+keepValue; } if((type & TYPE_STRING)){ rtv+=" || '0' ==="+keepValue; } return '('+rtv+')' } function isSimplePHPEL(value){ return value.match(/^([\w_\$]+|[\d\.]+)$/) } /** * 获取某个运算符号的优先级 */ function getELPriority(el) { return getPriority(el[0]); } if(typeof require == 'function'){ exports.stringifyPHPEL=stringifyPHPEL; exports.stringifyPHP=stringifyPHP; exports.php2jsBoolean=php2jsBoolean; exports.isSimplePHPEL=isSimplePHPEL; var getTokenParam=require('js-el/src/main/js/expression-token').getTokenParam; var getTokenParamIndex=require('js-el/src/main/js/expression-token').getTokenParamIndex; var findTokenText=require('js-el/src/main/js/expression-token').findTokenText; var getELType=require('js-el/src/main/js/expression-token').getELType; var addELQute=require('js-el/src/main/js/expression-token').addELQute; var OP_ADD=require('js-el/src/main/js/expression-token').OP_ADD; var OP_AND=require('js-el/src/main/js/expression-token').OP_AND; var OP_EQ=require('js-el/src/main/js/expression-token').OP_EQ; var OP_GET=require('js-el/src/main/js/expression-token').OP_GET; var OP_IN=require('js-el/src/main/js/expression-token').OP_IN; var OP_INVOKE=require('js-el/src/main/js/expression-token').OP_INVOKE; var OP_JOIN=require('js-el/src/main/js/expression-token').OP_JOIN; var OP_NE=require('js-el/src/main/js/expression-token').OP_NE; var OP_NOT=require('js-el/src/main/js/expression-token').OP_NOT; var OP_OR=require('js-el/src/main/js/expression-token').OP_OR; var OP_PUT=require('js-el/src/main/js/expression-token').OP_PUT; var OP_QUESTION=require('js-el/src/main/js/expression-token').OP_QUESTION; var OP_QUESTION_SELECT=require('js-el/src/main/js/expression-token').OP_QUESTION_SELECT; var TYPE_ANY=require('js-el/src/main/js/expression-token').TYPE_ANY; var TYPE_ARRAY=require('js-el/src/main/js/expression-token').TYPE_ARRAY; var TYPE_BOOLEAN=require('js-el/src/main/js/expression-token').TYPE_BOOLEAN; var TYPE_MAP=require('js-el/src/main/js/expression-token').TYPE_MAP; var TYPE_NULL=require('js-el/src/main/js/expression-token').TYPE_NULL; var TYPE_NUMBER=require('js-el/src/main/js/expression-token').TYPE_NUMBER; var TYPE_STRING=require('js-el/src/main/js/expression-token').TYPE_STRING; var VALUE_CONSTANTS=require('js-el/src/main/js/expression-token').VALUE_CONSTANTS; var VALUE_LIST=require('js-el/src/main/js/expression-token').VALUE_LIST; var VALUE_MAP=require('js-el/src/main/js/expression-token').VALUE_MAP; var VALUE_VAR=require('js-el/src/main/js/expression-token').VALUE_VAR; var getPriority=require('js-el/src/main/js/expression-tokenizer').getPriority; var GLOBAL_DEF_MAP=require('../js/parse/js-translator').GLOBAL_DEF_MAP; }