Git Product home page Git Product logo

velocity.js's Issues

helper 如何使用

我的想法是扫描所有 vm,每个 vm 生成一个对应的 json。

应该用哪个 helper

#parse使用求教

关于模板中的#parse 宏,看文档和issue还是不知道在node中究竟怎样实际使用!
例如在a.vm文件中有一行:#parse("./b.vm");
b.vm是个vm模板文件,里面有#set宏!
求教如何写才能正确渲染出来a.vm的内容?

vm中直出的模板和JS模板如何在页面**存复用?

比如在页面中直出如下代码:

 #foreach($item in $list)
        xxxx
#end

同时,页面中还有JS模板,如下:

< script type="text/html">
#foreach($item in $list)
xxxx
#end
< /script>

如何避免JS标签里的内容在直出时不被解析?

不支持下划线变量?

Error: Lexical error on line 12. Unrecognized text.
...{css_pa_pa})#if($!__cssRef)  #SLITERAL
---------------------^

API修改

去掉Velocity.Parser,改为Velocity.parse。Velocity.parse等于以前的Velocity.Parser.parse。

无法调用 object 的方法

#set($static_root = $staticServer.getURI(""))
#set($js_file = {
    "js_arale":"build/js/arale.js?t=20110608",
    "js_ma_template":"build/js/ma/template.js?t=20110608",
    "js_pa_pa":"build/js/pa/pa.js?t=20110608",
    "js_swiff":"build/js/app/swiff.js?t=20110608",
    "js_alieditControl":"build/js/pa/alieditcontrol-update.js?t=20110608"
})

#foreach($item in $js_file.entrySet())
    $item.key = <script charset="utf-8" src="$staticServer.getURI("/$item.value")"></script>
#end 

js_file 应该是个 Map,有 entrySet 方法,这种应该如何解析比较好?有没有什么接口可以把 set 的值封装下。

#stop支持

stop计划支持吗?看了沉鱼的代码,好像支持。如果没有计划加进去,我先参考沉鱼的版本修改一下。

getParseString 不能返回为空

Error: Parse error on line 1:

^
Expecting 'COMMENT', 'HASH', 'DOLLAR', 'ID', 'CONTENT', got 'EOF'
at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:283:11)
at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:335:22)
at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:758:53)
at Velocity.utils.mixin.getParse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/parse.js:19:27)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:101:23)
at Array.forEach (native)
at Object.forEach.utils.(anonymous function) [as forEach] (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/utils.js:14:25)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:67:13)
at Velocity.utils.mixin.getMacro (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/blocks.js:95:20)
at Velocity.utils.mixin._render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/compile/compile.js:97:23)

有没有block、extends?

@yanni4night 这个问题,单独建立一个issue吧。

已有讨论

by shepherdwind

@yanni4night http://velocity.apache.org/engine/devel/user-guide.html 这份文档,没有看到block和extends的介绍啊,block是define么?

by yanni4night

@shepherdwind 我确实也没有,不知道大家怎么用,习惯了用twig的extends继承功能,在父模板中留坑,从子模板上溯到父模板渲染感觉比include方便。参考 http://badqiu.iteye.com/blog/558194

by shepherdwind

@yanni4night 继承的使用确实要好很多,twig的那种还蛮好的。在velocity模板中实现,还得增加新的block语法才行,这个现在velocityjs没有支持。
不过,我不是很明白,velocity是基于java的模板引擎,就算velocityjs支持继承的语法,又有什么用呢,velocityjs现在还是主要用于模拟java的velocity模板解析过程。

macors支持

对于#macro (test) 这种的在vm中调用的时候,现在貌似只能采用#test()这种调用方式。
可否也支持#test这种方式的调用呢?

set 对象数组出错

#set($a = [
  {'name': 1},
  {'name': 2}
])

错误

Error: Lexical error on line 2. Unrecognized text.
...set($a = [  {'name': 1},  {'name': 2}
----------------------^

又一个语法错误

Error: Parse error on line 416:
...ankName.length()>15)...#end #end</li>
-----------------------^
Expecting 'EOF', 'COMMENT', 'HASH', 'DOLLAR', 'ID', 'CONTENT', got 'DOT'

具体代码

<li class="name">
#if($stringUtil.isNotBlank($!switchBankName)) $stringUtil.left($!switchBankName,15)#if($!switchBankName.length()>15)...#end 
#end
</li>

Unicode escaping: why ? how to config ?

Hi, thanks for this awesome module, it works a charm!

in src/compile/index.js you have:

function Velocity(asts, config) {
  this.asts = asts;
  this.config = {
    // 自动输出为经过html encode输出
    escape: true,
    // 不需要转义的白名单
    unescape: {}
  };
  utils.mixin(this.config, config);
  this._state = { stop: false, break: false };
  this.init();
}

I would like to set escape to false. Escaping those: <, >, & with &lt;, &gt;, &amp; is not a behavior I want in my application.

For now I'm using a fork of your module with escape set to false, but I would love to use your code if this could be configured!

What do you think ? If you're busy, I can PR a configurable something.
Thanks!

PS : Why escaping in the first place ?
PPS : your MIT license is outdated (2012-2013) 😉

渲染 css 解析错误

    throw new Error(str);
          ^
Error: Parse error on line 15:
...ght: bold;   color: #666;    height:39px;    
----------------------^
Expecting 'NOESCAPE', 'SET', 'IF', 'ELSEIF', 'ELSE', 'END', 'FOREACH', 'ID', 'BREAK', 'INCLUDE', 'PARSE', 'EAVL', 'DEFINE', 'MACRO', 'CONTENT', got 'INTEGER'
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:261:11)
    at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:313:22)
    at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:710:53)
    at Object.Velocity.render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/velocity.js:21:21)
    at Object.<anonymous> (/Users/popomore/code/popomore/node-sofa/test.js:5:22)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.runMain (module.js:492:10)

如果不存在就忽略,可以不抛错。

jsonify use

Jsonify是velocityjs中最重要的一个Helper,主要用于解决数据同步问题。

Jsonify的基本**是,根据给定vm编译成一个只包含数据的vm,编译后的vm通过解析可以获得一个json字符串

调用接口重要有两个,一个是命令行方式,和lessc命令类似,另外node模块的方式,和调用velocity解释器的api相似。Jsonify在0.3.3才开始稳定。执行命令之前,确保是安装到了最新版本的velocityjs,对比

  • velocity -v
  • npm info velocity version

命令行方式调用

$ velocity -j xx.vm
$ velocity -j xx.vm > data.vm
$ velocity -j xx.js

当第二个参数不是vm文件,而是js的时候,可以自定义context函数和macros函数

var fs = require('fs');
module.exports = {
  files: __dirname + '/a.vm',
  context: {
    control : {
      setTemplate: function(file){
        var fullfile = __dirname + '/' + file;
        if (fs.existsSync(fullfile)) {
          return this.eval((fs.readFileSync(fullfile)).toString());
        } 
      }
    }
  }
};

node模块调用

作为node模块,调用方式为

var Velocity = require('velocityjs');
var Jsonify = Velocity.Jsonify;
var asts = Parser.parse(fs.readFileSync(file).toString());

var makeup = new Jsonify(asts);
console.log(makeup.toVTL())

编译规则

Jsonify执行过程中,把vm变量分为两者基本类型:

  1. 数据 $foo.bar => {foo: {bar: “$foo.bar”}}
  2. 函数 $foo.bar() => {foo: {bar: “function(){}”}}

主要处理数据,对于函数的处理是返回一段站位字符串function(){},因为函数无法描述在json中。数据主要分为三种不同的结构:

  1. 字符串/数字/布尔等简单数据,这种情况处理起来最简单
  2. 数组数据,来源于#foreach #foreach($a in $foo)
  3. map集合,来源于#foreach #foeach($a in $foo.keySet())
  4. 数据对象,同样来自于#foreach,和2类似,只是数组的元素是对象,而不是基本数据类型

遇到macro调用时,会找到macro形参对应的真实变量,并且支持递归。

例子:example.vm

  #macro(a $a $b)
    $a $b
  #end

  #macro(b $aa $bb)
    #a($aa, $bb)
  #end

  #b($aaa $bbb())

通过命令执行

$ velocity -j example.vm
{
  "aaa": "$aaa",
  "bbb": "function(){}"
}

内部api使用/宏重写

Jsonify构造器接受三个参数

  1. asts velocity语法树对象,通过Parser.parse(vmText)获得
  2. context
  3. macros

第二个参数和第三个参数是可选的,用于定义vm中的一些宏或者方法,plum中webx规则使用了内部api,下面看一个例子:

文件a.vm

$ctrl.load("b.vm")
#parse("c.vm")

b.vm

$hello.b

c.vm

$hello.b

a.js

var velocity = require('velocityjs');
var fs = require('fs');
var str     = fs.readFileSync(__dirname + '/a.vm').toString();
var Jsonify = velocity.Jsonify;
var Parser  = velocity.Parser;

function loadVFile(txt){
  var file = __dirname + '/'+ txt;
  if (fs.existsSync(file)) {
    this.eval(fs.readFileSync(file).toString());
  }
}

var markup = new Jsonify(Parser.parse(str), {ctrl: {load: loadVFile}}, {parse: loadVFile});
console.log(markup.toVTL());

执行a.js

$ node a.js
{
  "hello": {
    "b": "$hello.b",
    "c": "$hello.c"
  }
}

上面a.vm中,第一个函数$ctrl.load调用的是Jsonify构造器第二个参数context中的方法,第二个宏#parse(“c.vm”),处理来自Jsonify构造器第三个参数。也就是说,Jsonify中提供一套api,可以通过这些api来在执行过程中执行自定义的函数,比如加载另外一个vm,进行解析。需要注意的是,在loadVFile函数定义中,必须使用this.eval这个api来解析获得的字符串。

$!a.b, 若a不存在,会报错

$!a.b, 若a不存在,线上解析没问题,mock会js报错

推荐修改src/compile/references.js

utils.some(ast.path, function(property){
  //第三个参数,返回后面的参数ast
  ret = ret && this.getAttributes(property, ret, ast);
}, this);

jsonify 的问题

报了错,但不太好描述,不太清楚内部实现,在内部打了些 log

TypeError: Cannot set property 'amount' of undefined
    at Jsonify.module.exports.utils.mixin.setRef (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/jsonify.js:78:21)
    at Jsonify.module.exports.utils.mixin.toBasicType (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/jsonify.js:54:12)
    at Jsonify.utils.mixin.getReferences (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/references.js:159:14)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:59:14)
    at Array.forEach (native)
    at Object.forEach.utils.(anonymous function) [as forEach] (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/utils.js:14:25)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:41:11)
    at Jsonify.utils.mixin.getBlockIf (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:67:12)
    at Jsonify.utils.mixin.getBlock (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:43:20)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:76:38)

https://github.com/shepherdwind/velocity.js/blob/master/src/helper/jsonify/jsonify.js#L58

应该是 setRef 的错,发现 75 行 context 为空,查了下 vm 应该是这个变量 $!consumeRecordListQueryResult.totalAmount.amount

但在之前的 context 中未解析出来

{
  ...
  consumeRecordListQueryResult: '{@31@}',
  ...
}

但不知道为什么没解析出来,查了下还有其他变量

consumeRecordListQueryResult.totalCount
consumeRecordListQueryResult.records.paginator

解析出错

$!tradeDetailModel.goodsInfoModel.goodsTitle[<a href="$personalModule.setTarget('/p.htm').addQueryData('id',$!stringUtil.substringAfter($!tradeDetailModel.goodsInfoModel.goodsId,'guarantee.'))" target="_blank">商品页面</a>]

源码如上,虽然这样写很坑爹,$!{tradeDetailModel.goodsInfoModel.goodsTitle} 这样写应该更好,但是否可以避免。

Error: Parse error on line 135:
...nfoModel.goodsTitle[<a href="$personalMo
-----------------------^
Expecting 'DOLLAR', '-', 'CONTENT', 'CLOSE_BRACKET', 'BOOL', 'INTEGER', 'STRING', 'EVAL_STRING', got '<'
    at Object.parseError (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:267:11)
    at Object.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:319:22)
    at Object.exports.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/parse/index.js:736:53)
    at Object.Velocity.render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/velocity.js:22:21)
    at Jsonify.macro.parse (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/lib/model/macro.js:25:23)
    at Jsonify.utils.mixin.getMacro (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/block.js:126:17)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:66:14)
    at Array.forEach (native)
    at Object.forEach.utils.(anonymous function) [as forEach] (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/utils.js:14:25)
    at Jsonify._render (/Volumes/Macintosh HD/AliDrive/code/popomore/node-sofa/node_modules/velocityjs/src/helper/jsonify/index.js:41:11)

数值解析错误

#set($ratio=$height*1.0/$width*1.0)

错误

Error: Lexical error on line 2. Unrecognized text.
...set($ratio=$height*1.0/$width*1.0)<div 
-----------------------^
    at Object.parseError (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:267:11)
    at Object.parseError (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:378:28)
    at Object.lexer.next (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:514:25)
    at Object.lex (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:519:22)
    at lex (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:289:28)
    at Object.parse (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:302:26)
    at Object.exports.parse (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/src/parse/index.js:736:53)
    at getVTL (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity-cli.js:139:21)
    at Object.jsonify (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity-cli.js:130:17)
    at Object.<anonymous> (/Users/popomore/nvm/v0.8.10/lib/node_modules/velocityjs/bin/velocity:23:11)

自定义block语法支持

比如

#cms(1)
  <div class="abs-right">
    #H(1,"第一个链接")
  </div>
#end

解析过程

 var ast = Velocity.parse(vm, { cms: true });

获得ast

{ type: 'cms',
  args: [ { type: 'integer', value: '1' } ],
  pos: { first_line: 1, last_line: 1, first_column: 0, last_column: 7 } }

macro 能否支持自定义

在 render 的时候可以传自定义的 macro,可以覆盖页面上的宏

var macro = {
  parse: function(){},
  shtml: function(){}
};
velocity.render(str, context, macro)

context this 指向的问题

var velocity = require('velocityjs');
var data = 'a = $a.get()';

function b(c) {
  this.c = c;
}
b.prototype.get = function() {
  return this.c;
};

var property = velocity.render(data, {
  a: new b(1)
});
console.log(property); // 输出 a = $a.get()

get 方法调用实例上属性的时候输出错误,如果改成字符串是正确的。

#parse用法问题

parse找不到文件不报错么?为什么定义了getParseString #parse还一直解析为空?为什么不定义renderFile接口,这样就可以找到include或parse的文件了?

对context中set开头变量的取值错误

在上下文中有set开头的变量,在取值的时候会被错误判断为函数处理造成取不到值

compile / references.js中hasSetMethod需要加个对 ast.type 的判断

   if (lastId.indexOf('set') !== 0 || ast.type != 'method') 

jsonify 处理macro时局部变量和全局变量同名时死循环

文件a.vm代码如下:

#macro(showMsg $msg)
  #if($msg)
    <div class="valid-under" style="height: 26px; ">
      <p class="estate error">
      <span class="label">$msg</span>
      </p>
    </div>
  #end
#end
#showMsg($msg)

执行命令

$ velocity -j a.vm

出现死循环,报错

/Users/eward/code/velocity.js/src/helper/jsonify/references.js:156
        var index = local.variable.indexOf(ref.id);
                                   ^
RangeError: Maximum call stack size exceeded

Print error stack when error throwed

Make error message more helpful. For example:

// vm.vm
111

#foo($name)

// vm1.vm

hello #parse("vm.vm")
// source.vm


#parse("vm1.vm")

context like this:

  var compile = new Compile(Parser.parse(getFileString('source.vm')))
  var macros = {
    foo: function(name){
      throw new Error('Run error')
    },
    parse: function(name){
      return this.eval(getFileString(name));
    }
  }
  compile.render({}, macros)
 Error: Run error
      at #foo($name) L/N 3:0
      at #parse("vm.vm") L/N 2:5
      at #parse("vm1.vm") L/N 3:0
      at Velocity.<anonymous> (/Users/eward/code/velocity/src/compile/compile.js:78:25)
      at Array.forEach (native)
      at Object.utils.(anonymous function) [as forEach] (/Users/eward/code/velocity/src/utils.js:9:25)

vm 解析失败

var velocity = require('velocity.js');

var str = '#set($js_file = [ ' +
  '"js/app/apww.js"'+
  '])' +
  '#foreach($item in $js_file)' +
  's($item)' +
  '#end';

var property = velocity.render(str, {
  s: function() {
    return s.replace('.js', '');
  }
});
console.log(property)

#foreach中的#set全局变量

在foreach中的#set,如果是已经在context中有的,应该是操作context而非local

#set($total = 0)
#foreach($i in [1,2,3])
    #set($total = $total + $i)
#end
$total

应该输出6而非0

src\compile\set.js中的再加一个判断可以解决,不知道会不会有其他问题

if (this.condition && this.condition.indexOf('macro:') === 0) {
        context = this.context;
} else if (this.context[ref.id] != null ){
        context = this.context;
}

直接判断vm变量的问题

if($!css_pureui) ... #end 为何还是进入内层,按理说$css_pureui未定义应该输出为undefined表达式返回false才对啊

#if and to many newlines

Hi,
at first thanks for this create library!!!

If i use a #if comes after a line with a newline and the statement is also followed by a new line the rendered file contains two newlines. But there should be only one. The same issue i had with the #end statement.
If i use a #set statement outside of an #if, the output is like expected, there is only one newline.
But an #set statement inside an #if will also add a newline.

Unit-Tests is worth a thousand words :)

    it('multiline: #set multiline', function(){
      var vm = "$bar.foo()\n#set($foo=$bar)\n..."
      assert.equal("$bar.foo()\n...", render(vm)) 
      // all fine here, test pass
    })

    it('multiline: #if multiline', function(){
      var vm = "$bar.foo()\n#if(1>0)\n...#end"
      assert.equal("$bar.foo()\n...", render(vm)) 
      // assert failed, output is: "$bar.foo()\n\n..."
    })

    it('multiline: #set #set', function(){
      var vm = "$bar.foo()\n...\n#set($foo=$bar)\n#set($foo=$bar)"
      assert.equal("$bar.foo()\n...\n", render(vm))
      // all fine here, test pass
    })

    it('multiline: #if multiline #set', function(){
      var vm = "$bar.foo()\n#if(1>0)\n#set($foo=$bar)\n...#end"
      assert.equal("$bar.foo()\n...", render(vm)) 
      // assert failed, output is: "$bar.foo()\n\n\n..."
    })

    it('multiline: #if multiline #set #end', function(){
      var vm = "$bar.foo()\n#if(1>0)...\n#set($foo=$bar)\n#end"
      assert.equal("$bar.foo()\n...\n", render(vm)) 
      // assert failed, output is: "$bar.foo()\n...\n\n\n"
    })

regards,
David

不支持取模

好像测试用例里也没有

Error: Lexical error on line 361. Unrecognized text.
...if(0==$velocityCount%2) class="split" #e
-----------------------^
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:263:11)
    at Object.parseError (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:374:28)
    at Object.lexer.next (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:510:25)
    at Object.lex (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:515:22)
    at lex (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:285:28)
    at Object.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:298:26)
    at Object.exports.parse (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/parse/index.js:712:53)
    at Object.Velocity.render (/Users/popomore/code/popomore/node-sofa/node_modules/velocity.js/src/velocity.js:21:21)
    at Object.module.exports.parse (/Users/popomore/code/popomore/node-sofa/lib/render.js:93:38)
    at /Users/popomore/code/popomore/node-sofa/lib/server.js:30:21

属性值为null时异常

$one.two
context = {
  one:{two:null} 
}

在context对象中的属性为null时,会报异常,之前用的版本没问题,应该是新版本引入的。

错误栈

TypeError: Cannot read property '$stop' of null
  at Velocity.<anonymous> (D:\workspace\static-server\node_modules\velocityjs\src\compile\references.js:117:39)
  at Array.some (native)
  at Object.utils.(anonymous function) [as some] (D:\workspace\static-server\node_modules\velocityjs\src\utils.js:14:25)
  at Velocity.utils.mixin.getReferences (D:\workspace\static-server\node_modules\velocityjs\src\compile\references.js:107:15)
  at Velocity.<anonymous> (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:86:25)
  at Array.forEach (native)
  at Object.utils.(anonymous function) [as forEach] (D:\workspace\static-server\node_modules\velocityjs\src\utils.js:14:25)
  at Velocity.utils.mixin._render (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:82:13)
  at Velocity.utils.mixin.render (D:\workspace\static-server\node_modules\velocityjs\src\compile\compile.js:48:22)
  at Object.Velocity.render (D:\workspace\static-server\node_modules\velocityjs\src\velocity.js:33:18)
  at Object.<anonymous> (D:\workspace\static-server\test\test_velocity.coffee:9:18, <js>:14:21)
  at Object.<anonymous> (D:\workspace\static-server\test\test_velocity.coffee:1:1, <js>:18:4)
  at Module._compile (module.js:456:26)

查看代码发现

 //第三个参数,返回后面的参数ast
          ret = this.getAttributes(property, ret, ast.path.slice(i + 1), ast);

//提前结束计算,某些情况下,连缀运行,后面的运算影响前面的结果,这种
          //情况需要特殊处理,比如$control.setTempalte('a.vm').setParameter('a', 'b')
          //第一个函数就返回结果,这时候,函数返回对象为
          //{$stop: true, $return: 'string'}
          //$stop表示停滞,$return:返回结果
          if (ret === undefined || ret.$stop === true) {
            ret = ret && ret.$stop ? ret.$return : ret;
            return true;
          }

如果getAttributes返回null的话,ret === undefined为false,ret.$stop就报错了,改为==或者加一个null判断即可

macro中的变量作用域

velocity中macro中使用#set定义的变量默认情况下是外部可见的
http://velocity.apache.org/engine/releases/velocity-1.5/user-guide.html#velocimacros

velocimacro.context.localscope - This property has the possible values true or false, and the default is false. When true, any modifications to the context via #set() within a Velocimacro are considered 'local' to the Velocimacro, and will not permanently affect the context.

所以

#macro (local)
#set($val =1)
$val
#end
#local()
$val

会被解析为

1
1

而不是

1
$val

set 命令bug

set变量层级大于2时,后面的set指令会覆盖前面set的值

即形如下面的VM指令

#set($a.b.c1 = 1 )
#set($a.b.c2 = 2)

执行后 a = {b:{c2:2}},c1会被覆盖掉

出错代码:

var Velocity = require('velocityjs');
var tpl = '#set($a = "{}")\n#set($a.b = "{}")\n#set($a.b.c1 = 1)\n#set($a.b.c2 = 2)\n<h1>$a.b.c1</h1>';
console.log(Velocity.render(tpl, {}));

原因:
src/compile/set.js 第61行,重置了前面的设置

语法解析式中局部变量的实现

晚上和汤进伟讨论vim和emac的区别,各自的优势,这两个神器,一说总是有无限的话题。每个软件都有各自的特色,每个人有各自的用法,所谓神器就是每个人都能以各自舒适的方式使用。从语法提示的角度来说,文本编辑器的确实比不上IDE可以内置一个webkit,一个nodejs。小汤说,不就是一个语法解释器嘛,我也想实现一个js的语法解释器。那已经有了很多了啊。我想用lisp写,就是对抽象语法树获取到了以后,应该怎么做还是不明白,比如局部变量如何实现,比如,for循环如何实现。

这个问题,我毕竟做过velocityjs,还是可以谈谈这样的问题的。上次那篇文章,如何实现velocity语法解释器,写得有点大,感觉没说清楚,现在,主要说明一下语法解释器中局部变量的实现过程。

准备数据

这里首先说明一下,语法解释器基本上包括两个过程,抽象语法树生成和解释执行过程。前面那个过程有成熟的解决方案,比如c语言的Bison,js的Jison,还有Racc for ruby的,这些都是Parser Generator,姑且称之为语法分析生成器。它们得到的是输入字符串对应的语法结构,这些语法结构是自定义语法使用者自定义的。整个过程原理本文不详细说,总结一下就是,一个字符串输入,得到一个结构化数据。用js语法可以这样描述:

var text = "hello $name";
var Parse = require("./path/to/parse.js");
var result = Parse.parse(text);
// result = ["hello", {type: "var", id: "name"}];

上面只是一个简单的例子,当输入text变得复杂的时候,分析器得到的结果会复杂得多,但这个结构是使用者自己随意定义的。还是以velocity模板为例,模板语言有一个特点,模板有一部分是不含任何语法的字符串,中间参杂着语法结构。我定义vm语法结构规则是这样的:

  1. 不含结构的语法字符串,输出是一个原样字符串
  2. 语法结构用一个对象描述
  3. 整体上,语法结构由一个数组构成,数组的元素是上面两个结构

举几个实例,方便大家明白:

  1. foreach循环结构
var text = '#foreach($foo in $bars) get $foo #end';
var result = [
  {type: "foreach", from: {type: "var", id: "bars"}},
   " get ", 
   {type: "var", id: "foo"}, 
   " ", 
   {type: "end"}
 ];
  1. macro函数结构
var text = '#macro(sayHello $bar)  hello $bar #end';
var result = [
    {type: "macro", id: "sayHello", args: {type: "var", id: "bar"}},
    " hello ",
    {type: "var", id: "bar"},
    {type: "end"}
];

上面的两个例子是我们定义好的数据结构,这些数据结构,就是velocity语法对应的语法解析树,虽然名字里面有树,但实际上,这是一个数组,而且是一位数组,其实是不是树无所谓,关键是,这个结构是通过前面的字符串分析得到的,这个结构是有意义的,通过这个结构我们后面可以对之进行运算。下面主要讲一下,语法解析树得到之后需要执行的过程。

实现原理

前面说明了很多,主要是数据准备。这个数据结构非常重要,什么样的数据结构决定了后面运算过程。现在有了这样的数据了,就好像一台机器的零件完成,下面只要对其进行组装就好了。

来点画面感的东东吧,在一个流水线上,放着一排水果:苹果,香蕉,橘子等等,中间有一个位置放着一个篮子,里面写着:爸爸的惊喜——意思是,这个篮子里面将要放入爸爸给的惊喜,但现在还不知道是什么。然后,如果你想知道这一排水果分别是什么,那么你可以,一个一个把水果拿到另外一个流水线上,遇到篮子的时候,安装篮子的提示,去找爸爸要礼物,然后把礼物放到篮子占的位置。到最后一个水果,所有的水果你就能够知道了。这也就是模板解释器运算的过程。

上面的流水线运行过程,你知道,从某某人那里去拿礼物,这意味着,你需要去找到这个人,找人的过程,就是变量查找的过程。再看下,篮子上写着爸爸的礼物,爸爸这个可以改为一个名字,比如小明,这个时候我们需要去找到小明。找小明需要从一个地方去找,而且这个地方只有一个叫小明的人。这时候,我们引入一个花名册吧,在流水线的旁边放一个花名册,这样可以通过花名册找到小明,或者其他人。

在流水线中,我们遇到一个篮子,上面写着一个谜语,如果答对了,后面三个水果都可以拿去,如果没答对,就跳过后面三个。这个时候,我们可以把这个篮子和后面三个水果看做一个子流水线,在这条流水线的运行过程,和主线没有什么区别。这时候,我们引入了子流水线过程。

再加入一个循环的篮子,上面写着,找到花名册中名字是两个字的人,把这个人暂时起个名字小华,后面的5个项目组成一个子流水线。这时候,把主线暂停,只有子流水线在运行。可以想象成有两条平行的流水线了,这时候,子流水线隶属于主流水线,在子线上面的篮子需要找花名册的时候,还是能够查询住流水线的。同时,每条流水线都有属于自己的花名册,在这个子名册中,小华是通过子流水线运行之初通过前面那个循环的篮子赋予的,循环的篮子每找到一个名字,把这个名字赋予小华,然后子流水线运行一遍。

上面的例子,大致说明了foreach循环的时候,局部变量如何运行的。velocity的解析过程类似于上面的流水线,变量查找就是从流水线的花名册中找到名字。遇到foreach循环的时候,一条子流水线分离出来,每次自流水线运行的时候,主流水线赋予自流水线一个私有的花名册,在子流水线中,名字查找说先找自己的花名册,然后再到主花名册中去找。两个流水线本质上是没有区别的,所以,自流水线又可以分离出子流水线,构成foreach的嵌套。另外,macro函数执行的时候,情况是一样的,macro的内容相当于一条子流水线,marco运行时,上层流水线把形参赋予marco私有的花名册,这就是velocity解释器中局部变量实现的基本原理。

实现代码

上面是我自己想到的描述,可能不是很清晰。下面简单实现一下整个过程:

var compile = function(asts, context){
  this.asts = asts;
  this.context = context;
  this.local = {};
  this.conditions = [];
};

compile.prototype = {

  constructor: compile,

  render: function(asts){

    var str = '';
    var block = [];
    var index = 0;
    asts = asts || this.asts;

    // 流水线依次运行
    utils.forEach(asts, function(ast){

      var type = ast.type;

      //foreach if macro时,index加1
      if (utils.indexOf(type, BLOCK_TYPES) > -1) index ++;

      if (index) {
        type === 'end' && index--;
        // 如果index不为1,说明block还没有闭合,比如#foreach #if #else #end #end
        // 遇到if的时候,index是2,遇到第一个end的时候,index为1,此时流水线没有
        // 完整,需要等到下一个end,此时,block收集了中间所有的元素,构成一个流
        // 水线的元素,在下面调用getBlock启动子流水线
        if (index) return block.push(ast);
      }

      if(type == 'references') {
        str += this.getReferences(ast, true);
      } else if(type === 'end') {
        //使用slide获取block的拷贝
        str += this.getBlock(block.slice());
        // 清空数组
        block = [];
      } else {
        str += ast;
      }

    }, this);

    return str;
  },

  getReferences: function(ast){

    var name = ast.name;
    var local = this.local;
    // 默认赋值全局变量
    var ret = this.context[name];

    // 判断是否是局部变量,this.conditions依次记录当前状态堆栈,从第一个开始,一
    // 直向上查找,如果变量在当前局部环境中找到,那么返回局部变量的值,局部变量
    // 贮存在this.local[contextId]中
    utils.some(this.conditions, function(contextId){
      //局部环境中找到
      if (id in local[contextId]) {
        ret = local[contextId][name];
        return true;
      }
    });

    return ret;
  },

  getBlock: function(block){

    var ast = block[0];
    //创建唯一的contextId
    var contextId = 'foreach:' + utils.guid();

    if (ast.type === 'foreach') {

      utils.forEach(ast.from, function(val, i){

        //构造临时变量
        var local[ast.to] = val;
        local['foreach']['count'] = i + 1;
        local['foreach']['index'] = i;
        local['foreach']['hasNext'] = i + 1 < len;
        local['velocityCount'] = i + 1;
        //贮存local变量
        this.local[contextId] = local;
        // push contextId到堆栈,流水线开始
        this.conditions.unshift(contextId);
        ret += this.render(_block);
        // 流水线结束
        this.conditions.pop();

      }, this);

    }
  }

};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.