UNPKG

vox-core

Version:

Runtime de aplicaciones multiplataforma

998 lines (824 loc) 25.5 kB
/** * James! (08-03-2016) * E6Html * Formato de vw * Es como un formato html pero que se ejecuta en el servidor * Compila código EcmaScript v6 y async/await de EcmaScript v7 * Este formato está pensado para utilizarse en un servidor HTTP * * El formato sería por ejemplo así: <html> <head> <title><vw:expression>variablex</vw:expression></title> </head> <body> <script server-side async lang='es6'> // Se debe colocar el atributo server-side para que // el parser sepa que se debe procesar este script // El atributo async indica que es asíncrono // así que debe devolver un objeto core.VW.Task o utilizar // la sintaxis asíncrona que se muestra a continuación async ()=>{ var res= await algunReqAsincrono() return res.body } </script> </body> </html> Esto se traduciría a algo así: (context)->{ var Stream= context.response; var $content, $func; return async ()=>{ $content=`<html> <head> <title>`; Stream.write($content); Stream.write(context.htmlencode(variableX)); $content=`</title> </head> <body> `; $func= async ()=>{ var res= await algunReqAsincrono() return res.body } await $func() $content= ` </body> </html>` Stream.write($content); } } */ var fs=require("fs"); var EXPECTING_EXPRESSION=1; var EXPECTING_RAW=4; var EXPECTING_RAWEXPRESSION=2; var EXPECTING_HTML=0; var EXPECTING_SCRIPT=3; var EXPECTING_BASE64EXPRESSION=5; var EXPECTING_BASE64=6; var EXPECTING_PRE=7; var he= require("./htmlEncode"); var htmlparser2= require("htmlparser2"); var Parser= module.exports= function(){ this.expecting= EXPECTING_HTML; this.content=''; this.parser=new core.VW.Ecma2015.Parser(); } Parser.codeTags= ["vw:expression", "vw:rawexpression", "vw:base64", "vw:base64expression", "vw:raw", "script", "vw:if", "vw:else", "vw:elseif", "vw:section", "vw:foreach", "vw:import"]; Parser.prototype._writeExpressionR= function(){ this.$wrExp= { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "Stream" }, "property": { "type": "Identifier", "name": "write" } }, "arguments": [ { "type": "Identifier", "name": "$content" } ] } } return this.$wrExp; } Parser.prototype.__writeExpression64= function(code){ var parser= core.VW.Ecma2015.Parser.ecmaParser; var ast= parser.parse(code); var exp= this._writeExpressionE(); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión") } var arg=exp.expression.arguments[0]; arg.callee.property.name='base64decode'; arg.arguments[0]= ast; return exp; } Parser.prototype.__writeBase64= function(code){ var parser= core.VW.Ecma2015.Parser.ecmaParser; var ast= { "type": "Literal", "value": code, "raw": JSON.stringify(code) } var exp= this._writeExpressionE(); var arg=exp.expression.arguments[0]; arg.callee.property.name='base64decode'; arg.arguments[0]= ast; return exp; } Parser.prototype.__writeExpressionE= function(code){ var parser= core.VW.Ecma2015.Parser.ecmaParser; try{ var ast= parser.parse(code); } catch(e){ throw new core.VW.E6Html.ParseException("Error al analizar una expresión. ", e) } var exp= this._writeExpressionE(); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión") } exp.expression.arguments[0].arguments[0]= lexp; return exp; } Parser.prototype.__writeExpressionR= function(code){ var parser= core.VW.Ecma2015.Parser.ecmaParser; try{ var ast= parser.parse(code); } catch(e){ throw new core.VW.E6Html.ParseException("Error al analizar una expresión. ", e) } var exp= this._writeExpressionR(); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión") } exp.expression.arguments[0]= lexp; return exp; } Parser.prototype.__getE6HtmlObjectAst= function(){ return { "type": "Identifier", "name": "E6Html" } } Parser.prototype.__getE6HtmlDecAst= function(){ return { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": this.__getE6HtmlObjectAst(), "init": this.__getE6HtmlAst() } ], "kind": "var" } } Parser.prototype.__getE6HtmlAst= function(){ return { "type": "MemberExpression", "computed": false, "object": { "type": "MemberExpression", "computed": false, "object": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "core" }, "property": { "type": "Identifier", "name": "VW" } }, "property": { "type": "Identifier", "name": "E6Html" } }, "property": { "type": "Identifier", "name": "E6Html" } } } Parser.prototype._writeExpressionE= function(){ this.$weExp= { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "Stream" }, "property": { "type": "Identifier", "name": "write" } }, "arguments": [ { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": this.__getE6HtmlObjectAst(), "property": { "type": "Identifier", "name": "encode" } }, "arguments": [ { "type": "Identifier", "name": "$content" }, { "type": "Identifier", "name": "context" } ] } ] } } return this.$weExp; } Parser.prototype._writeText=function(){ if(this.content.length>0){ exp={ "type": "ExpressionStatement", "expression": { "type": "AssignmentExpression", "operator": "=", "left": { "type": "Identifier", "name": "$content" }, "right": { "type": "Literal", "value": this.content, "raw": JSON.stringify(this.content) } } } this.body.push(exp); this.body.push(this._writeExpressionR()); this.content=''; } } Parser.prototype.endlastExp= function(){ this._writeText(); this.nested.pop() this.body= this.nested[this.nested.length-1] this.currentExp=null } Parser.prototype.beginSection=function(options){ if(!this.nested){ this.nested=[this.body] } if(!options.name){ throw new core.VW.E6Html.ParseException("Debe especificar un nombre para la sección") } var body=[] var exp= { "type": "ExpressionStatement", "expression": { "type": "AssignmentExpression", "operator": "=", "left": { "type": "MemberExpression", "computed": true, "object": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "context" }, "property": { "type": "Identifier", "name": "sections" } }, "property": { "type": "Literal", "value": options.name, "raw": JSON.stringify(options.name) } }, "right": { "type": "FunctionExpression", "id": null, "params": [], "defaults": [], "body": { "type": "BlockStatement", "body": body }, "generator": false, "expression": false, "async": true } } } this.currentExp= exp this.nested.push(body) this.body.push(exp) this.body= body } Parser.prototype.beginIfExp= function(test){ if(!this.nested){ this.nested=[this.body] } if(!test) throw new core.VW.E6Html.ParseException("Se esperaba el atributo `expression`") var parser= core.VW.Ecma2015.Parser.ecmaParser; var ast= parser.parse(test); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión") } var body=[] var exp= { "type": "IfStatement", "test": lexp, "consequent": { "type": "BlockStatement", "body": body }, "alternate": null } this.currentExp= exp this.nested.push(body) this.body.push(exp) this.body= body } Parser.prototype.beginForEach= function(options){ if(!this.nested){ this.nested=[this.body] } var parser= core.VW.Ecma2015.Parser.ecmaParser; var ast= parser.parse(options.expression); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión") } var name= options.name var body=[] var exp= { "type": "ForOfStatement", "left": { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": name }, "init": null } ], "kind": "var" }, "right": lexp, "body": { "type": "BlockStatement", "body": body } } this.currentExp= exp this.nested.push(body) this.body.push(exp) this.body= body } Parser.prototype.beginElseExp= function(){ if(!this.currentExp || this.currentExp.type!="IfStatement"){ throw new core.VW.E6Html.ParseException("Una expresión `vw:else` o `vw:elseif` debe estar dentro de una expresión `vw:if`") } var body=[] var exp= this.currentExp.alternate= { "type": "BlockStatement", "body": body } this.currentExp= exp this.nested.push(body) this.body= body } Parser.prototype.beginElseIfExp= function(test){ this.beginElseExp() this.beginIfExp(test) } Parser.prototype.writeImport= function(options){ var name= options; var prop= { "type": "Identifier", "name": "include" } if(options["section"]!==undefined){ prop.name="section" } var args=[ { "type": "Literal", "value": options.name, "raw": JSON.stringify(options.name) } ] if(options.argument){ // Pasar argumentos ... var parser= core.VW.Ecma2015.Parser.ecmaParser; var ast= parser.parse(options.argument); var lexp; if(ast.body.length!=1 || !(lexp=ast.body[0].expression)){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión en el atributo `argument` de import") } args.push(lexp) } var exp= { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "context" }, "property": prop }, "arguments": args } exp={ "type": "ExpressionStatement", "expression": { "type": "AwaitExpression", "argument": exp, "delegate": false } } this.body.push(exp) } Parser.prototype.onopentag= function(name,attribs){ var exp; if(this.expecting==EXPECTING_RAWEXPRESSION || this.expecting==EXPECTING_EXPRESSION || this.expecting==EXPECTING_BASE64 || this.expecting==EXPECTING_BASE64EXPRESSION){ this.scriptText+= "<" + name for(var id in attribs){ this.scriptText+= id if(attribs[id]) this.scriptText+= "=\""+attribs[id]+"\"" } this.scriptText+=">" this.especialOpen= true return } if(this.expecting!= EXPECTING_HTML && this.expecting != EXPECTING_PRE){ throw new core.VW.E6Html.ParseException("Error de sintaxis cerca del tag " + name); } var script, especial=Parser.codeTags.indexOf(name)>=0; if(especial && name=="script"){ if(attribs["server-side"]!==undefined){ script= especial= true; } else{ especial= false } } if(especial){ this._writeText(); if(name=="vw:expression"){ this.expecting= EXPECTING_EXPRESSION; } else if(name=="vw:rawexpression"){ this.expecting= EXPECTING_RAWEXPRESSION; } else if(name=="vw:base64"){ this.expecting= EXPECTING_BASE64; } else if(name=="vw:base64expression"){ this.expecting= EXPECTING_BASE64EXPRESSION; } else if(name=="vw:raw"){ this.expecting= EXPECTING_RAW; } else if(script){ this.expecting= EXPECTING_SCRIPT; this.scriptParameters= attribs; } else if(name=="vw:if"){ this.expecting= EXPECTING_HTML; this.beginIfExp(attribs["expression"]) } else if(name=="vw:else"){ this.expecting= EXPECTING_HTML; this.beginElseExp() } else if(name=="vw:elseif"){ this.expecting= EXPECTING_HTML; this.beginElseIfExp(attribs["expression"]) } else if(name=="vw:section"){ this.expecting= EXPECTING_HTML; this.beginSection(attribs) } else if(name=="vw:import"){ this.expecting= EXPECTING_HTML; this.writeImport(attribs) } else if(name=="vw:foreach"){ this.expecting= EXPECTING_HTML; this.beginForEach(attribs) } } else{ this.content+= "<" + name + " "; if(name=="script" /*|| name=="pre"*/){ this.expecting= EXPECTING_PRE } for(var id in attribs){ var val= attribs[id]; var i, raw= false this.content+= id+"=\""; while((i= val.indexOf("${"))>=0){ // Es una expression ... if(val[i-1]=="#"){ i-- raw= true } var text= val.substring(0,i); if(text.length>0){ this.content += he.encode(text); } var y= val.indexOf("}"); if(!y){ throw new core.VW.E6Html.ParseException("Se esperaba `}` para completar la expresión"); } this._writeText(); if(raw) i++ var exp= val.substring(i+2, y); val= val.substring(y+1); try{ if(raw) this.body.push(this.__writeExpressionR(exp)); else this.body.push(this.__writeExpressionE(exp)); }catch(e){ throw new core.VW.E6Html.ParseException("Error al compilar la expresión " + exp + ". " + e.message, e) } } this.content += val + '" '; } this.content+= ">"; } } Parser.prototype.ontext= function(text){ if(this.expecting == EXPECTING_EXPRESSION){ this.scriptText= this.scriptText||"" this.scriptText += text //vw.log(text) //this.body.push(this.__writeExpressionE(text)); } else if(this.expecting == EXPECTING_RAWEXPRESSION){ this.scriptText= this.scriptText||"" this.scriptText += text //this.body.push(this.__writeExpressionR(text)); } else if(this.expecting == EXPECTING_BASE64EXPRESSION){ this.scriptText= this.scriptText||"" this.scriptText += text //this.body.push(this.__writeExpression64(text)); } else if(this.expecting == EXPECTING_BASE64){ this.scriptText= this.scriptText||"" this.scriptText += text //this.body.push(this.__writeBase64(text)); } else if(this.expecting==EXPECTING_SCRIPT){ this.scriptText= this.scriptText||"" this.scriptText += text //this._procesarScript(text); } else{ var i; var encode=this.expecting!=EXPECTING_PRE && this.expecting!=EXPECTING_RAW if(this.expecting==EXPECTING_HTML){ text= text.replace(/\r\n+|\r+/g, "\n") text= text.replace(/\t+/g, " ") } while((i= text.indexOf("${"))>=0){ // Es una expression ... var text1= text.substring(0,i); if(text1.length>0){ this.content += encode ? he.encode(text1): text1; } var y= text.indexOf("}", i+2); if(!y){ throw new core.VW.E6Html.ParseException("Se esperaba `}` para completar la expresión"); } this._writeText(); var exp= text.substring(i+2, y); text= text.substring(y+1); try{ this.body.push(encode?this.__writeExpressionE(exp):this.__writeExpressionR(exp)); } catch(e){ throw new core.VW.E6Html.ParseException("Error al compilar la expresión " + exp + ". " + e.message, e) } } this.content += encode?he.encode(text):text; } } Parser.prototype._procesarScript= function(text){ var lang=this.scriptParameters.lang; if(lang==undefined || lang.toLowerCase()=="ecmascript" || lang.toLowerCase()=="ecmascript6"){ this.scriptText="" var async= this.scriptParameters.async!==undefined; var ast=core.VW.Ecma2015.Parser.ecmaParser.parse(text,{sourceType:'module'}); var body= ast.body; if(body.length==0){ return; } if(async){ // Debe devolver una función el script if(body.length!=1 || !body[0].expression){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión"); } var exp= body[0].expression; if(exp.type.indexOf("FunctionExpression")<0){ throw new core.VW.E6Html.ParseException("Se esperaba una expresión de Función"); } this.body.push({ "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "await" }, "arguments": [ exp ] } }) } else{ for(var i=0;i< body.length;i++){ this.body.push(body[i]); } } } else{ throw new core.VW.E6Html.ParseException("El lenguaje " + lang + "no está soportado del lado servidor"); } } Parser.prototype.onclosetag= function(tagname){ var s1 if(this.expecting == EXPECTING_EXPRESSION){ s1= this.scriptText this.scriptText='' this.body.push(this.__writeExpressionE(s1)); } else if(this.expecting == EXPECTING_RAWEXPRESSION){ s1= this.scriptText this.scriptText='' this.body.push(this.__writeExpressionR(s1)); } else if(this.expecting == EXPECTING_BASE64EXPRESSION){ s1= this.scriptText this.scriptText='' this.body.push(this.__writeExpression64(s1)); } else if(this.expecting == EXPECTING_BASE64){ s1= this.scriptText this.scriptText='' this.body.push(this.__writeBase64(s1)); } var nod=false var especial=Parser.codeTags.indexOf(tagname)>=0; if(especial){ if(tagname=="vw:if"||tagname=="vw:else"||tagname=="vw:elseif"||tagname=="vw:section"||tagname=="vw:foreach"){ this.endlastExp(); nod= true } else if(tagname=="script" && this.expecting==EXPECTING_SCRIPT){ this.id=this.id|0 this.id++ this._procesarScript(this.scriptText) } nod= nod || tagname=="vw:import" } if((this.expecting== EXPECTING_HTML||this.expecting==EXPECTING_PRE) && !nod){ this.content+= "</" + tagname + ">"; } this.expecting= EXPECTING_HTML; } Parser.prototype.parse= function(/*string */code){ this.body=[]; var ast= { "type": "Program", "body": [ { "type": "ExpressionStatement", "expression": { "type": "ArrowFunctionExpression", "id": null, "params": [ { "type": "Identifier", "name": "context" } ], "defaults": [], "body": { "type": "BlockStatement", "body": [ this.__getE6HtmlDecAst(), { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "Stream" }, "init": { "type": "MemberExpression", "computed": false, "object": { "type": "Identifier", "name": "context" }, "property": { "type": "Identifier", "name": "out" } } } ], "kind": "var" }, { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "$content" }, "init": { "type": "Literal", "value": "", "raw": "''" } } ], "kind": "var" }, { "type": "ReturnStatement", "argument": { "type": "ArrowFunctionExpression", "id": null, "params": [], "defaults": [], "body": { "type": "BlockStatement", "body": this.body }, "async":true, "generator": false, "expression": false } } ] }, "generator": false, "expression": false } } ], "sourceType": "script" }; var parser = new htmlparser2.Parser(this,{decodeEntities:true}); parser.write(code); parser.end(); parser=null; this._writeText(); // Ahora procesar el ast generado var l= this.parser.parseASTAndGenerate(ast); ast=null; return l; } Parser.prototype.parseFile= function(/*string */file){ var raw = fs.readFileSync(file, 'utf8'); raw = (raw.charCodeAt(0) === 0xFEFF) ? raw.substring(1) : raw return this.parse(raw); }