├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── AUTHORS ├── History.md ├── Makefile ├── README.md ├── bin └── velocity ├── docs ├── differ-from-java-edition.md └── grammar.md ├── examples ├── data-dump-method │ ├── context.js │ ├── how-to.md │ ├── index.vm │ └── velocity-config.js ├── data-dump │ ├── context.js │ ├── how-to.md │ ├── index.vm │ └── velocity-config.js ├── data-structure-method │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── data-structure │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── dependency │ ├── root1 │ │ ├── a.vm │ │ ├── b.vm │ │ ├── c.vm │ │ ├── d.vm │ │ ├── e.txt │ │ └── index.vm │ ├── root2 │ │ └── f.vm │ └── velocity-config.js ├── directive │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── errors │ ├── child.vm │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── expression │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── hello │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── macro │ ├── context.js │ ├── index.vm │ ├── macro.vm │ └── velocity-config.js ├── method-lookup │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── parse │ ├── context.js │ ├── root1 │ │ ├── index.vm │ │ └── plain-text.txt │ ├── root2 │ │ └── root2.vm │ ├── root3 │ │ └── root3.vm │ └── velocity-config.js └── reference │ ├── context.js │ ├── index.vm │ └── velocity-config.js ├── index.js ├── lib ├── common.js ├── data │ ├── data-direc.js │ ├── data-expr.js │ ├── data-formatter.js │ ├── data-ref.js │ ├── data-stats.js │ └── index.js ├── dep │ ├── index.js │ ├── render.js │ └── scan.js ├── engine │ ├── engine-direc.js │ ├── engine-expr.js │ ├── engine-ref.js │ ├── engine-stack.js │ ├── engine-stats.js │ ├── index.js │ ├── lex.js │ ├── lex.yy │ ├── velocity.js │ ├── velocity.l │ └── velocity.yy ├── handle-cfg.js ├── index.js └── logger.js ├── package.json └── test ├── engine.test.js ├── fixtures ├── if-else-end.vm ├── if-elseif-elseif-end.vm ├── if-elseif-end.vm ├── if-end.vm └── macro-result.txt ├── parser.test.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | .DS_Store 4 | node_modules/ 5 | *.swp 6 | *.swo 7 | .sw* 8 | logs/ 9 | lib-cov/ 10 | lib/parser/test.vm 11 | coverage/ 12 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .git/ 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 14 | "indent" : false, // {int} Number of spaces to use for indentation 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : false, // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : false, // true: Require all defined variables be used 28 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 29 | "trailing" : false, // true: Prohibit trailing whitespaces 30 | "maxparams" : false, // {int} Max number of formal params allowed per function 31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 32 | "maxstatements" : false, // {int} Max number statements per function 33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 34 | "maxlen" : false, // {int} Max number of characters per line 35 | 36 | // Relaxing 37 | "asi" : true, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 38 | "boss" : true, // true: Tolerate assignments where comparisons would be expected 39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 40 | "eqnull" : false, // true: Tolerate use of `== null` 41 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 42 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 43 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 44 | // (ex: `for each`, multiple try/catch, function expression…) 45 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 46 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 47 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 48 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 49 | "iterator" : false, // true: Tolerate using the `__iterator__` property 50 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 51 | "laxbreak" : true, // true: Tolerate possibly unsafe line breakings 52 | "laxcomma" : false, // true: Tolerate comma-first style coding 53 | "loopfunc" : false, // true: Tolerate functions being defined in loops 54 | "multistr" : true, // true: Tolerate multi-line strings 55 | "proto" : false, // true: Tolerate using the `__proto__` property 56 | "scripturl" : false, // true: Tolerate script-targeted URLs 57 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 58 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 59 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 60 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 61 | "validthis" : false, // true: Tolerate using this in a non-constructor function 62 | 63 | // Environments 64 | "browser" : true, // Web Browser (window, document, etc) 65 | "couch" : false, // CouchDB 66 | "devel" : true, // Development/debugging (alert, confirm, etc) 67 | "dojo" : false, // Dojo Toolkit 68 | "jquery" : false, // jQuery 69 | "mootools" : false, // MooTools 70 | "node" : true, // Node.js 71 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 72 | "prototypejs" : false, // Prototype and Scriptaculous 73 | "rhino" : false, // Rhino 74 | "worker" : false, // Web Workers 75 | "wsh" : false, // Windows Scripting Host 76 | "yui" : false, // Yahoo User Interface 77 | "noyield" : true, // allow generators without a yield 78 | 79 | // Legacy 80 | "nomen" : false, // true: Prohibit dangling `_` in variables 81 | "onevar" : false, // true: Allow only one `var` statement per function 82 | "passfail" : false, // true: Stop on first error 83 | "white" : false, // true: Check against strict whitespace and indentation rules 84 | 85 | // Custom Globals 86 | "globals" : { // additional predefined global variables 87 | // mocha 88 | "describe": true, 89 | "it": true, 90 | "before": true, 91 | "afterEach": true, 92 | "beforeEach": true, 93 | "after": true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | examples/ 3 | test/ 4 | Makefile 5 | .jshintrc 6 | .jshintignore 7 | coverage/ 8 | docs/* 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '14' 5 | - '12' 6 | script: 7 | - "npm run test && npm run test-cov" 8 | after_script: 9 | - "npm i codecov.io && cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js" 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Ordered by date of first contribution. 2 | # Auto-generated by 'contributors' on Tue, 20 May 2014 11:58:54 GMT. 3 | # https://github.com/xingrz/node-contributors 4 | 5 | fool2fish (https://github.com/fool2fish) 6 | jsw0528 (https://github.com/jsw0528) 7 | fengmk2 (https://github.com/fengmk2) 8 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.7.3 / 2021-05-07 3 | ================== 4 | 5 | **others** 6 | * [[`993285d`](http://github.com/fool2fish/velocity/commit/993285d6838c0544e3788b928d62a5dfca01d366)] - deps: upgrade tracer (#44) (Yiyu He <>) 7 | * [[`7f74dc6`](http://github.com/fool2fish/velocity/commit/7f74dc6246979a6cad29a1dcd1fee885808e46e3)] - Add test scripts. (沉鱼 <>) 8 | * [[`e403cda`](http://github.com/fool2fish/velocity/commit/e403cdab1bddbff858588aad600b0c332e1548a8)] - Update version to v0.7.2 (fool2fish <>), 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.test.js 2 | REPORTER = spec 3 | TIMEOUT = 2000 4 | MOCHA_OPTS = 5 | 6 | install: 7 | @npm install --registry=https://registry.npmmirror.com 8 | 9 | jshint: install 10 | @./node_modules/.bin/jshint . 11 | 12 | test: install 13 | @NODE_ENV=test ./node_modules/.bin/mocha \ 14 | --reporter $(REPORTER) \ 15 | --timeout $(TIMEOUT) \ 16 | $(MOCHA_OPTS) \ 17 | $(TESTS) 18 | 19 | test-cov cov: 20 | @NODE_ENV=test node --harmony \ 21 | node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha \ 22 | -- -u exports \ 23 | --reporter $(REPORTER) \ 24 | --timeout $(TIMEOUT) \ 25 | $(MOCHA_OPTS) \ 26 | $(TESTS) 27 | @./node_modules/.bin/cov coverage 28 | 29 | test-all: jshint test test-cov 30 | 31 | autod: install 32 | @./node_modules/.bin/autod -w 33 | @$(MAKE) install 34 | 35 | contributors: install 36 | @./node_modules/.bin/contributors -f plain -o AUTHORS 37 | 38 | .PHONY: test 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # velocity 2 | 3 | [![Build Status](https://secure.travis-ci.org/fool2fish/velocity.png?branch=master)](http://travis-ci.org/fool2fish/velocity) 4 | 5 | [![NPM](https://nodei.co/npm/velocity.png?downloads=true&stars=true)](https://nodei.co/npm/velocity/) 6 | 7 | A node velocity template engine. 8 | 9 | Node 版 velocity 模板引擎。 10 | 11 | [Differ from Java edition](https://github.com/fool2fish/velocity/blob/master/docs/differ-from-java-edition.md) 12 | 13 | [Bug and suggestion](https://github.com/fool2fish/velocity/issues/new) 14 | 15 | [Change log](https://github.com/fool2fish/velocity/releases) 16 | 17 | --- 18 | 19 | ## 0. Features 20 | 21 | - Full implement of velocity syntax. 22 | - View template dependencies. 23 | - Extract data structure from templates. 24 | - Generate intermediate template to dump the context. 25 | 26 | ## 1. Installment 27 | 28 | ``` 29 | $ npm install velocity -g 30 | ``` 31 | 32 | ## 2. Quick Start 33 | 34 | Some examples are ready for you: 35 | 36 | ``` 37 | $ git clone https://github.com/fool2fish/velocity.git 38 | $ cd examples 39 | ``` 40 | 41 | #### Try a simple one 42 | 43 | Command: 44 | 45 | ``` 46 | $ cd hello 47 | $ velocity 48 | ``` 49 | 50 | Output: 51 | 52 | ``` 53 | Hello, velocity! 54 | ``` 55 | 56 | #### More examples 57 | 58 | - [Diagnose error](https://github.com/fool2fish/velocity/tree/master/examples/errors) 59 | - [Method lookup](https://github.com/fool2fish/velocity/tree/master/examples/method-lookup) 60 | - [View dependencies](https://github.com/fool2fish/velocity/tree/master/examples/dependency) 61 | - [Extract data structure from template](https://github.com/fool2fish/velocity/tree/master/examples/data-structure) 62 | - [Generate intermediate template to dump the context](https://github.com/fool2fish/velocity/tree/master/examples/data-dump) 63 | 64 | See all [examples](https://github.com/fool2fish/velocity/tree/master/examples). 65 | 66 | 67 | ## 3. Use In Modules 68 | 69 | ### Render a template 70 | 71 | ``` 72 | var Engine = require('velocity').Engine 73 | var engine = new Engine( {{options}} ) 74 | var result = engine.render( {{context}} ) 75 | console.log(result) 76 | ``` 77 | 78 | ### Get the AST 79 | 80 | ``` 81 | var parser = require('velocity').parser 82 | var content = fs.readFileSync( {{path/to/template}} , {encoding: {{encoding}}) 83 | var ast = parser.parse(content) 84 | console.log(ast) 85 | ``` 86 | 87 | ### Extract data structure from template and save to file 88 | 89 | ``` 90 | var Data = require('velocity').Data 91 | var data = new Data({ 92 | output: 'path/save/data/structure', 93 | ... 94 | }) 95 | var reselt = data.extract({{optionalExistedContext}}) 96 | ``` 97 | 98 | ## 4. Options 99 | 100 | All options are very simple, you can view them in terminal: 101 | 102 | ``` 103 | $ velocity -h 104 | ``` 105 | Option `config` specifies a config file path. All [examples](https://github.com/fool2fish/velocity/tree/master/examples) have a config file. 106 | 107 | ## License 108 | 109 | (The MIT License) 110 | 111 | Copyright (c) 2014 fool2fish and other contributors 112 | 113 | Permission is hereby granted, free of charge, to any person obtaining 114 | a copy of this software and associated documentation files (the 115 | 'Software'), to deal in the Software without restriction, including 116 | without limitation the rights to use, copy, modify, merge, publish, 117 | distribute, sublicense, and/or sell copies of the Software, and to 118 | permit persons to whom the Software is furnished to do so, subject to 119 | the following conditions: 120 | 121 | The above copyright notice and this permission notice shall be 122 | included in all copies or substantial portions of the Software. 123 | 124 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 125 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 126 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 127 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 128 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 129 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 130 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 131 | -------------------------------------------------------------------------------- /bin/velocity: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | var commander = require('commander') 6 | var colorful = require('colorful') 7 | var utilx = require('utilx') 8 | 9 | var pkg = require('../package') 10 | var velocity = require('..') 11 | var logger = require('../lib/logger') 12 | 13 | var cwd = process.cwd() 14 | 15 | 16 | commander 17 | .description(pkg.description) 18 | .option('-v, --version', 'output version number') 19 | .option('-d, --debug', 'show debug message') 20 | .option('-O, --root ', 'template root path', utilx.split) 21 | .option('-M, --macro ', 'global macro file/input', utilx.split) 22 | .option('-t, --template ', 'template file/input') 23 | .option('-c, --context ', 'context file/input') 24 | .option('-e, --encoding ', 'encoding') 25 | .option('-o, --output ', 'output file path') 26 | .option('-R, --reverse', 'view reversed dependencies') 27 | .option('--data', 'extract data structure from template') 28 | .option('--dump', 'generate intermediate template to dump the context') 29 | .on('version', function() { 30 | console.log('\n ' + colorful.cyan(pkg.version) + '\n') 31 | process.exit(0) 32 | }) 33 | .helpInformation = utilx.cGetHelp(pkg) 34 | 35 | 36 | commander.parse(process.argv) 37 | 38 | 39 | var cfg = utilx.cGetCfg(commander) 40 | 41 | // Merge config file 42 | var cfgFile = './velocity-config.js' 43 | if (utilx.isExistedFile(cfgFile)) { 44 | var projCfg = require(path.resolve(cfgFile)) 45 | cfg = utilx.mix(cfg, projCfg) 46 | } 47 | 48 | 49 | // Extract data structure 50 | if (cfg.data || cfg.dump) { 51 | var data = new velocity.Data(cfg) 52 | var result = data.extract(cfg.context) 53 | 54 | if (cfg.output) { 55 | console.log('\nOutput is saved to ' + cfg.output + '\n') 56 | } else { 57 | console.log(result.str) 58 | } 59 | 60 | // Render Content 61 | } else if (cfg.context){ 62 | var engine = new velocity.Engine(cfg) 63 | var result = engine.render(cfg.context) 64 | 65 | if (cfg.output) { 66 | console.log('\nOutput is saved to ' + cfg.output + '\n') 67 | } else { 68 | console.log(result) 69 | } 70 | 71 | // View dependencies 72 | } else { 73 | velocity.dep(cfg) 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/differ-from-java-edition.md: -------------------------------------------------------------------------------- 1 | # Differ From Java Edition(v1.6.x ~ v1.7) 2 | 3 | ## Syntax 4 | 5 | Source Code | Java | Node | Suggestion 6 | ----------- | ------ | -------|------------ 7 | | all right value is escaped| not excaped| 8 | $_a | may be legal or illegal, up to the version | legal | 9 | \$a | <text, $a>(v1.6.x)
<ref, \$a>(v1.7) | <text, $a> | 10 | $\\!a | <text, $!a>(v1.6.x)
<ref, $\\!a>(v1.7) | <text, $\\!a> | 11 | $a(...) | <ref, $a> <text, (...)> | illegal | ${a}(...) 12 | $a.b\[0\](...) | <ref, $a.b[0]> <text, (...)> | illegal | ${a.b[0]}(...) 13 | $a.b(c) | <ref, $a.b> <'('> <ref, c> <')'> | illegal | $a.b($c) 14 | $a.b(c.d) | illegal | illegal | $a.b($c.d) 15 | $map.put(k, v) | illegal(v1.6.x)
legal(v1.7) | illegal | 16 | $map['key'] | illegal(v1.6.x)
legal(v1.7) | legal | 17 | $list.get(0) | illegal(v1.6.x)
legal(v1.7) | legal | 18 | macro name:
a--b| illegal | legal | 19 | marcro name:
a-_b
a__b
a1 | legal | legal 20 | \#set($a.b = 1) | legal | illegal | left hand of assignment expression must be an id 21 | \#set($a={})
\#set($a.b.c=1) | $a.b=1
$a.b.c=undefined | illegal | 22 | \#macro(name $arg=default) | legal(v1.6.x)
illegal<v1.7> | illegal | no default value 23 | \#include($a $b) | legal | illegal | \#include($a, $b) 24 | \#include($a, $b) | legal | legal | 25 | null | legal(v1.6.x)
illegal(v1.7) | legal | 26 | !null | illegal | true | 27 | 28 | ## Method look up 29 | 30 | If the method is not found, node `velocity` will look up possible value follow rules below. 31 | 32 | #### String length 33 | 34 | ``` 35 | $string.length() -> string.length 36 | ``` 37 | 38 | #### Array or map 39 | 40 | ``` 41 | $array.size() -> array.length 42 | $array.isEmpty() -> !array.length 43 | $array.get(idx) -> array[idx] 44 | 45 | $map.get(key) -> map[property] 46 | $map.keySet() -> Object.keys(map) 47 | $map.entrySet() -> Object.keys(map).map(function(key){return {key: key, value: map[key]}}) 48 | 49 | // below are not supported 50 | $array.set(idx, value) 51 | $array.setIdx(value) 52 | $map.set(key, value) 53 | $map.setKey(value) 54 | $map.put(key, value) 55 | $map.putKey(value) 56 | ``` 57 | 58 | #### Get property 59 | 60 | ``` 61 | $obj.isName() -> !!obj.name 62 | $obj.getName() -> obj.name 63 | $obj.getname() -> obj.name 64 | $obj.get('name') -> obj.name 65 | ``` 66 | 67 | ###### NOTE: node `velocity` won't see if there is possible method when meets property. -------------------------------------------------------------------------------- /docs/grammar.md: -------------------------------------------------------------------------------- 1 | # Grammar 2 | 3 | element | reference | int | float | exp | string | eval string 4 | ------------|----------- | ----- | --------- | -------------- | ------ | ----------- 5 | | $a, $a.b
$a[1], $a.b() | 1, -1 | 1.0, -1.0 | $a + $b
$a > $b
$a && $b | 'str' | "str" 6 | argument | | | | x 7 | index | | | x | x 8 | range item | | | x | x | x | x 9 | list item | | | | x 10 | map key | | | | x 11 | map value | | | | x 12 | -------------------------------------------------------------------------------- /examples/data-dump-method/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | util: { 3 | add: function(a, b) { return a + b } 4 | }, 5 | user: { 6 | addr: function() { 7 | return { 8 | zipcode: '310000', 9 | city: 'Hangzhou' 10 | } 11 | } 12 | }, 13 | method: { 14 | foo: function() { 15 | return { 16 | bar: function(arg) { 17 | return { 18 | any: true 19 | } 20 | }, 21 | prop: 'I am a property' 22 | } 23 | } 24 | }, 25 | arg: 'arg' 26 | } 27 | -------------------------------------------------------------------------------- /examples/data-dump-method/how-to.md: -------------------------------------------------------------------------------- 1 | # data dump 2 | 3 | 1. run `$ velocity` at current dir, it generates a intermediate template `./data-dump.vm` for dump. 4 | 5 | 2. replace content of `velocity-config.js` with 6 | 7 | ``` 8 | module.exports = { 9 | template: './data-dump.vm', 10 | context: './context.js', 11 | output:'./context-dump.js' 12 | } 13 | ``` 14 | 15 | 3. run `$ velocity` again. 16 | 17 | Now you get the context file `./context-dump.js` from the runtime. -------------------------------------------------------------------------------- /examples/data-dump-method/index.vm: -------------------------------------------------------------------------------- 1 | $util.add(3, 4) 2 | 3 | #set($addr = $user.addr()) 4 | $addr.zipcode 5 | $addr.city 6 | 7 | $method.foo().bar($arg).any 8 | $method.foo().prop 9 | 10 | -------------------------------------------------------------------------------- /examples/data-dump-method/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | output:'./data-dump.vm', 4 | dump:true 5 | } 6 | -------------------------------------------------------------------------------- /examples/data-dump/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'The Dream School', 3 | headmaster: 'Jim', 4 | grades: [ 5 | { 6 | name: 'senior', 7 | classes: [ 8 | { 9 | name: 'senior-class-1', 10 | headTeacher: 'Sam', 11 | students: [ 12 | 'student-a', 13 | 'student-b' 14 | ] 15 | }, 16 | { 17 | name: 'senior-class-2', 18 | headTeacher: 'Mary', 19 | students: [ 20 | 'student-c', 21 | 'student-d' 22 | ] 23 | } 24 | ] 25 | }, 26 | { 27 | name: 'junior', 28 | classes: [ 29 | { 30 | name: 'junior-class-1', 31 | headTeacher: 'Ann', 32 | students: [ 33 | 'student-e', 34 | 'student-f' 35 | ] 36 | }, 37 | { 38 | name: 'junior-class-2', 39 | headTeacher: 'John', 40 | students: [ 41 | 'student-g', 42 | 'student-h' 43 | ] 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /examples/data-dump/how-to.md: -------------------------------------------------------------------------------- 1 | # data dump 2 | 3 | 1. run `$ velocity` at current dir, it generates a intermediate template `./data-dump.vm` for dump. 4 | 5 | 2. replace content of `velocity-config.js` with 6 | 7 | ``` 8 | module.exports = { 9 | template: './data-dump.vm', 10 | context: './context.js', 11 | output:'./context-dump.js' 12 | } 13 | ``` 14 | 15 | 3. run `$ velocity` again. 16 | 17 | Now you get the context file `./context-dump.js` from the runtime. -------------------------------------------------------------------------------- /examples/data-dump/index.vm: -------------------------------------------------------------------------------- 1 | SchoolName: $name 2 | Headmaster: $headmaster 3 | 4 | $detailInfo.isFirstClass() 5 | 6 | Grades: 7 | #foreach($grade in $grades) 8 | GradeName: $grade.name 9 | Classes: 10 | #foreach($class in $grade.classes) 11 | ClassName: $class.name 12 | HeadTeacher: $class.headTeacher 13 | Students: 14 | #foreach($student in $class.students) 15 | $student #if($foreach.hasNext),#end 16 | #end 17 | #end 18 | #end 19 | -------------------------------------------------------------------------------- /examples/data-dump/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | output:'./data-dump.vm', 4 | dump:true 5 | } 6 | -------------------------------------------------------------------------------- /examples/data-structure-method/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | method: { 3 | foo: function () { 4 | // some comment 5 | var a = 10 6 | var b = '\' \t \\' 7 | return a + b 8 | }, 9 | bar: function() {}, 10 | __barReturn: { 11 | b: 1, 12 | c: undefined 13 | } 14 | }, 15 | obj2: { 16 | name: 'fool2fish' 17 | } 18 | } -------------------------------------------------------------------------------- /examples/data-structure-method/index.vm: -------------------------------------------------------------------------------- 1 | $method.foo($arg).any 2 | $method.bar().a($arg).prop 3 | 4 | #set($purl = $url.parse($href, true)) 5 | $purl.queryString 6 | 7 | $str.length() 8 | $arr.size() 9 | $obj.entrySet() 10 | $obj2.get('name') 11 | $obj2.getEmail() 12 | $obj2.isEmpty() 13 | -------------------------------------------------------------------------------- /examples/data-structure-method/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | data: true, 3 | root: './', 4 | template: './index.vm', 5 | context: './context.js', 6 | output: './new-context.js' 7 | } 8 | -------------------------------------------------------------------------------- /examples/data-structure/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | id: '000000001', 3 | user: { 4 | name: 'fool2fish', 5 | email: 'fool2fish@gmail.com', 6 | undefinedIsPreserved: undefined 7 | }, 8 | employeeList: [ 9 | { name: 'Bob' }, 10 | { name: 'Sue'} 11 | ] 12 | } -------------------------------------------------------------------------------- /examples/data-structure/index.vm: -------------------------------------------------------------------------------- 1 | $id 2 | 3 | $user 4 | $user.favorites[0] 5 | $user.favorites[$favIdx] 6 | 7 | $order.orderId 8 | 9 | #set($temp = 1) 10 | #set($addr = $order.addr) 11 | $addr.zipCode 12 | $addr.city 13 | $addr.country 14 | 15 | #foreach($employee in $employeeList) 16 | $velocityCount 17 | $foreach.index: $employee.name - $employee.email 18 | #end 19 | 20 | #set($list = [0 .. 10]) 21 | #foreach($item in $list) 22 | $argInForeach 23 | #end 24 | 25 | #define($userInfo) 26 | $user.name 27 | $user.email 28 | #end 29 | $userInfo 30 | 31 | #macro(hello $date) 32 | $date.year $date.month $date.day 33 | #end 34 | #hello($today) 35 | 36 | #set($f = {$g: $h}) 37 | $f[$g].i ## any property of $f won't be handled 38 | #set($j = [$k]) 39 | $j[0].l ## any property of $j won't be handled -------------------------------------------------------------------------------- /examples/data-structure/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | data: true, 3 | root: './', 4 | template: './index.vm', 5 | context: './context.js', 6 | output: './new-context.js' 7 | } 8 | -------------------------------------------------------------------------------- /examples/dependency/root1/a.vm: -------------------------------------------------------------------------------- 1 | File /a.vm 2 | #parse('/c.vm') 3 | 4 | -------------------------------------------------------------------------------- /examples/dependency/root1/b.vm: -------------------------------------------------------------------------------- 1 | File /b.vm 2 | #parse('/d.vm') 3 | -------------------------------------------------------------------------------- /examples/dependency/root1/c.vm: -------------------------------------------------------------------------------- 1 | File /c.vm 2 | #parse('/d.vm') 3 | #include('/e.txt') -------------------------------------------------------------------------------- /examples/dependency/root1/d.vm: -------------------------------------------------------------------------------- 1 | File /d.vm -------------------------------------------------------------------------------- /examples/dependency/root1/e.txt: -------------------------------------------------------------------------------- 1 | File /e.txt -------------------------------------------------------------------------------- /examples/dependency/root1/index.vm: -------------------------------------------------------------------------------- 1 | File /index.vm 2 | #parse('/a.vm') 3 | #parse("/b.vm") 4 | #parse('/path/not/exist') 5 | #parse($pathWithVariable) 6 | -------------------------------------------------------------------------------- /examples/dependency/root2/f.vm: -------------------------------------------------------------------------------- 1 | File /f.vm -------------------------------------------------------------------------------- /examples/dependency/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: ['./root1', './root2'], 3 | template: './root1/index.vm' 4 | } 5 | -------------------------------------------------------------------------------- /examples/directive/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'fool2fish', 3 | github: 'https://github.com/fool2fish', 4 | favorites: ['food', 'travel', 'comic', '...'], 5 | date: { 6 | year: 2014, 7 | month: 5, 8 | day: 20 9 | }, 10 | method: { 11 | foo: function(){return 'method.foo() is called!'}, 12 | bar: function(){} 13 | }, 14 | dataForEval: '@@$name@@' 15 | } 16 | -------------------------------------------------------------------------------- /examples/directive/index.vm: -------------------------------------------------------------------------------- 1 | #foreach($num in [0 .. 2]) 2 | $num 3 | #end 4 | 5 | #foreach($item in ['a', 'b', 'c']) 6 | $item 7 | #end 8 | 9 | #foreach($fav in $favorites) 10 | $foreach.index: $fav 11 | #end 12 | 13 | #evaluate('$dataForEval') 14 | #evaluate("$dataForEval") 15 | 16 | #define($introduce) 17 | My name is $name 18 | #end 19 | $introduce 20 | $introduce 21 | 22 | #macro(sayhi, $name) 23 | Hello, $name! 24 | #end 25 | #sayhi('velocity') 26 | #sayhi($name) 27 | 28 | #set($homepage = 'http://fool2fish.cn') 29 | Homepage: $homepage 30 | 31 | 32 | #if ($date.weather == 'sunny') 33 | Today is sunny. 34 | #elseif ($date.weather) 35 | Today is not sunny. 36 | #else 37 | Cannot get the weather info. 38 | #end 39 | -------------------------------------------------------------------------------- /examples/directive/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | context: './context.js' 4 | } 5 | -------------------------------------------------------------------------------- /examples/errors/child.vm: -------------------------------------------------------------------------------- 1 | ## Parse error 2 | #set(4 = $a) 3 | -------------------------------------------------------------------------------- /examples/errors/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | string: 'string', 3 | emptyString: '', 4 | method: { 5 | foo: function() { throw new Error('I just want to throw an error') }, 6 | bar: function() { 7 | return { 8 | toString: function() { 9 | throw new Error('I just want to throw an error when call toString method') 10 | } 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/errors/index.vm: -------------------------------------------------------------------------------- 1 | ## Function calling error 2 | ## #parse('/child.vm') 3 | 4 | ## An error thrown by toString method 5 | $method.bar() 6 | 7 | ## Error threw by method 8 | ## $method.foo() 9 | 10 | ## Start of range is not an integer 11 | ## #set($range1 = [$string .. 5]) 12 | 13 | ## End of range is not an integer 14 | ## #set($range2 = [5 .. $string]) 15 | 16 | ## Key of map is not a non-empty string 17 | ## #set($map = {$emptyString: 'value'}) 18 | 19 | ## Right operand of division is zero 20 | ## #set($division = 3 / 0) 21 | 22 | ## Right operand of modulo is zero 23 | ## #set($modulo = 3 % 0) 24 | 25 | ## Left operand of assignment expression is not an identifier 26 | ## #set($a.b = 1) 27 | 28 | ## Left operand of #foreach is not an identifier 29 | ## #foreach($a.b in $list)#end 30 | 31 | ## Param of #include is not a non-empty string 32 | ## #include($emptyString) 33 | 34 | ## Param of #include not exists or is not subpath of root 35 | ## #include('/file/not/exist') 36 | 37 | ## Param of #parse is not a non-empty string 38 | ## #parse($emptyString) 39 | 40 | ## Param of #parse not exists or is not subpath of root 41 | ## #parse('/file/not/exist') 42 | 43 | ## Param of #evaluate is not a string 44 | ## #evaluate(1) 45 | 46 | ## Param of #define is not an identifier 47 | ## #define($a.b)#end 48 | 49 | ## Param of #macro is not an identifier 50 | ## macro(bar $a.b)#end 51 | 52 | ## Call undefined macro 53 | ## #undefinedMacro() 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/errors/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: './', 3 | template: './index.vm', 4 | context: './context.js' 5 | } 6 | -------------------------------------------------------------------------------- /examples/expression/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'fool2fish', 3 | start: 1 4 | } 5 | -------------------------------------------------------------------------------- /examples/expression/index.vm: -------------------------------------------------------------------------------- 1 | #set($range = [$start..5]) 2 | Range: $range 3 | 4 | #set($list = [$name, true]) 5 | List: $list 6 | 7 | #set($map = {'nick': $name}) 8 | Map: { 'nick': '$map.nick' } 9 | 10 | #set($bool = !true) 11 | Unary operation: $bool 12 | 13 | #set($int = 0) 14 | Integer: $int 15 | 16 | #set($float = 10.1) 17 | Float: $float 18 | 19 | #set($add = $start + 5) 20 | Addition: $add 21 | 22 | #set($str = 'my name is $name') 23 | Dstring: $str 24 | 25 | #set($dstr = "my name is $name") 26 | Dstring: $dstr 27 | -------------------------------------------------------------------------------- /examples/expression/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | context: './context.js' 4 | } 5 | -------------------------------------------------------------------------------- /examples/hello/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'velocity' 3 | } 4 | -------------------------------------------------------------------------------- /examples/hello/index.vm: -------------------------------------------------------------------------------- 1 | Hello, ${name}! 2 | -------------------------------------------------------------------------------- /examples/hello/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | context: './context.js' 4 | } 5 | -------------------------------------------------------------------------------- /examples/macro/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'fool2fish', 3 | github: 'https://github.com/fool2fish', 4 | favorites: ['food', 'travel', 'comic', '...'] 5 | } 6 | -------------------------------------------------------------------------------- /examples/macro/index.vm: -------------------------------------------------------------------------------- 1 | #commonMacro($id $name $github) 2 | #commonMacro('a', 'b', 'c') 3 | 4 | #@blockMacro('one') 5 | Block macro is called 6 | #end 7 | #@blockMacro('two') 8 | Block macro is called again 9 | #end 10 | 11 | #macro(localMacro, $arg1, $arg2) 12 | Argument of local macro: $arg1, $arg2 13 | #end 14 | #localMacro($id, 'secondParam') 15 | #localMacro($favorites) 16 | -------------------------------------------------------------------------------- /examples/macro/macro.vm: -------------------------------------------------------------------------------- 1 | #macro(commonMacro, $a, $b, $c) 2 | Arguments of commonMacro: [$a, $b, $c] 3 | #end 4 | 5 | #macro(blockMacro, $a) 6 | Argument of blockMacro: $a 7 | Body content:$bodyContent 8 | #end 9 | -------------------------------------------------------------------------------- /examples/macro/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | macro: './macro.vm', 3 | template: './index.vm', 4 | context: './context.js' 5 | } 6 | -------------------------------------------------------------------------------- /examples/method-lookup/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | string: 'abcde', 3 | list: [1, 2, 3], 4 | map: { 5 | 'a': 'value-a', 6 | 'b': 'value-b', 7 | 'enough': true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/method-lookup/index.vm: -------------------------------------------------------------------------------- 1 | String: $string 2 | String length: $string.length() 3 | 4 | List: $list 5 | List size: $list.size() 6 | List.get(index): $list.get(0) 7 | Is list empty? $list.isEmpty() 8 | 9 | Map 10 | Map.entrySet(): 11 | #foreach($kv in $map.entrySet()) 12 | Key: $kv.key, Value: $kv.value 13 | #end 14 | Map.keySet(): $map.keySet() 15 | Map.get(key): $map.get('a') 16 | Map.isKey: $map.isEnough() -------------------------------------------------------------------------------- /examples/method-lookup/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | context: './context.js' 4 | } 5 | -------------------------------------------------------------------------------- /examples/parse/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'fool2fish', 3 | github: 'https://github.com/fool2fish', 4 | favorites: ['food', 'travel', 'comic', '...'] 5 | } 6 | -------------------------------------------------------------------------------- /examples/parse/root1/index.vm: -------------------------------------------------------------------------------- 1 | #include('/plain-text.txt') 2 | #parse('/root2.vm') 3 | ## Special for Alipay 4 | #cmsparse('/root3.vm') 5 | -------------------------------------------------------------------------------- /examples/parse/root1/plain-text.txt: -------------------------------------------------------------------------------- 1 | Plain text content. 2 | -------------------------------------------------------------------------------- /examples/parse/root2/root2.vm: -------------------------------------------------------------------------------- 1 | /root2/root2.vm content: 2 | Name: $name 3 | -------------------------------------------------------------------------------- /examples/parse/root3/root3.vm: -------------------------------------------------------------------------------- 1 | /root3/root3.vm content: 2 | Github: $github 3 | -------------------------------------------------------------------------------- /examples/parse/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: ['./root1', './root2', './root3'], 3 | template: './root1/index.vm', 4 | context: './context.js' 5 | } 6 | -------------------------------------------------------------------------------- /examples/reference/context.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'fool2fish', 3 | github: 'https://github.com/fool2fish', 4 | favorites: ['food', 'travel', 'comic', '...'], 5 | date: { 6 | year: 2014, 7 | month: 5, 8 | day: 20 9 | }, 10 | method: { 11 | foo: function(){return 'method.foo() is called!'}, 12 | bar: function(){} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/reference/index.vm: -------------------------------------------------------------------------------- 1 | ID: $!id 2 | Name: ${name} 3 | Github: $github 4 | Most favorite: $favorites[0] 5 | 6 | $date.day - $date.month - $date.year 7 | 8 | $method.foo() 9 | $method.bar() 10 | 11 | $nonexist 12 | $nonexist.property 13 | $nonexist.property.method() 14 | -------------------------------------------------------------------------------- /examples/reference/velocity-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | template: './index.vm', 3 | context: './context.js' 4 | } 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib') 2 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var utilx = require('utilx') 4 | 5 | var logger = require('./logger') 6 | var STATS = require('./data/data-stats') 7 | 8 | 9 | exports.getRelPath = function(p, root) { 10 | if (!root) return 11 | var fullPath = path.resolve(p) 12 | for (var i = 0; i < root.length; i++) { 13 | var r = root[i] 14 | if (fullPath.indexOf(r) === 0) { 15 | return path.relative(r, fullPath) 16 | } 17 | } 18 | } 19 | 20 | exports.getFullPath = function(relPath, root) { 21 | if (!root) return 22 | for (var i = 0; i < root.length; i++) { 23 | var r = root[i] 24 | var fullPath = path.join(r, relPath) 25 | if (utilx.isExistedFile(fullPath)) { 26 | return fullPath 27 | } 28 | } 29 | } 30 | 31 | exports.getRoot = function(relPath, root) { 32 | if (!root) return 33 | for (var i = 0; i < root.length; i++) { 34 | var r = root[i] 35 | if (utilx.isExistedFile(path.join(r, relPath))) { 36 | return r 37 | } 38 | } 39 | } 40 | 41 | exports.extractContent = function(lines, pos) { 42 | var fl = pos.first_line 43 | var ll = pos.last_line 44 | var fc = pos.first_column 45 | var lc = pos.last_column 46 | 47 | if (fl === ll) { 48 | return lines[fl - 1].substring(fc, lc) 49 | } 50 | 51 | var rt = [] 52 | for (var i = fl; i <= ll; i++) { 53 | var line = lines[i - 1] 54 | if (i === fl) { 55 | line = line.substring(fc) 56 | } else if (i === ll) { 57 | line = line.substring(0, lc) 58 | } 59 | rt.push(line) 60 | } 61 | return rt.join(require('os').EOL) 62 | } 63 | 64 | 65 | exports.isId = function(node) { 66 | return node.object.type === 'Identifier' 67 | } 68 | 69 | exports.isLiteralString = function(n) { 70 | if (n.type === 'String') return true 71 | if (n.type === 'DString' && n.value.search(/\$|#/) === -1) return true 72 | return false 73 | } 74 | 75 | exports.getOrigin = function(n) { 76 | if (!n) return 77 | if (n.__origin) return n.__origin 78 | var stats = n.__stats 79 | if (stats === STATS.CERTAIN || stats === STATS.CERTAIN_FUNC || stats === STATS.UNCERTAIN) return n 80 | } 81 | 82 | exports.markError = function(line, pos) { 83 | if (!line || !pos) return '' 84 | 85 | var rt = line + '\n' 86 | rt += utilx.generateLine(pos.first_column, ' ') 87 | var l = (pos.first_line === pos.last_line ? pos.last_column : line.length) - pos.first_column 88 | rt += utilx.generateLine(l, '^') + '\n' 89 | return rt 90 | } 91 | 92 | 93 | exports.getRealLoc = function(loc) { 94 | var templ = loc[0] 95 | var pos = loc[1] 96 | pos = templ.offset || pos 97 | for (templ; templ; templ = templ.__parent) { 98 | if (templ.isFile || !templ.__parent) break 99 | } 100 | return [templ, pos] 101 | } 102 | 103 | 104 | exports.loc2str = function(loc) { 105 | var templ = loc[0] 106 | var pos = loc[1] 107 | var fullPath = templ.isFile ? templ.fullPath : trim(templ.raw) 108 | return fullPath + ' (' + pos.first_line + ':' + pos.first_column + ')' 109 | } 110 | 111 | 112 | /* 113 | * Template code: 114 | * 115 | * #parse($emptyString) 116 | * #set(4 = $a) 117 | * 118 | * Parse result: 119 | * 120 | * Parse error on line 2: 121 | * ...($emptyString)#set(4 = $a) 122 | * ----------------------^ 123 | * Expecting '$', got 'INTEGER' 124 | * 125 | * Note that a line break before `#set` directive in source code 126 | * Because of this, it is a little complex to calculate the columns info 127 | * So they are always be 0 128 | */ 129 | var lineStrReg = /^.+on line (\d+)/m 130 | var colMarkReg = /^\-*\^/m 131 | exports.getPosFromParserError = function(e) { 132 | var msg = e.message 133 | var matchedLine = msg.match(lineStrReg) 134 | var matchedCol = msg.match(colMarkReg) 135 | var pos = { 136 | first_line: 1, 137 | last_line: 1, 138 | first_column: 0, 139 | last_column: 0 140 | } 141 | 142 | if (matchedLine && matchedCol) { 143 | var line = parseInt(matchedLine[1]) 144 | pos.first_line = line 145 | pos.last_line = line 146 | } 147 | 148 | return pos 149 | } 150 | 151 | function trim(str, len) { 152 | len = len || 40 153 | str = str.substr(0, len).replace(/\n/g, '\\n') 154 | if (str.length > 40) str += '...' 155 | return str 156 | } 157 | 158 | exports.perfectContext = function(context) { 159 | if (utilx.isExistedFile(context)) { 160 | var p = path.resolve(context) 161 | ;delete require.cache[p] 162 | context = require(p) 163 | } else if (utilx.isObject(context)) { 164 | // do nothing 165 | } else if (utilx.isString(context)) { 166 | try { 167 | context = eval('(' + context + ')') 168 | } catch(e) { 169 | logger.error('Illegal context:', context) 170 | } 171 | } else { 172 | context = {} 173 | } 174 | return context 175 | } 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /lib/data/data-direc.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var utilx = require('utilx') 3 | 4 | var common = require('../common') 5 | var logger = require('../logger') 6 | 7 | var STATS = require('./data-stats') 8 | var BREAK = { __stats: STATS.BREAK } 9 | 10 | 11 | module.exports = { 12 | If: function(node) { 13 | var test = node.test 14 | this[test.type](test) 15 | 16 | var cons = node.consequent 17 | cons && this[cons.type](cons) 18 | 19 | var alter = node.alternate 20 | alter && this[alter.type](alter) 21 | }, 22 | 23 | Foreach: function(node) { 24 | var that = this 25 | var left = node.left 26 | var leftr 27 | if (!common.isId(left)) { 28 | this.throwError('Left operand of #foreach is not an identifier.', left.pos) 29 | } 30 | var name = left.object.name 31 | 32 | var right = node.right 33 | var rightr = this[right.type](right) 34 | var o 35 | 36 | if (rightr) { 37 | var stats = rightr.__stats 38 | if (stats !== STATS.CERTAIN && stats !== STATS.UNCERTAIN) { 39 | leftr = BREAK 40 | 41 | } else { 42 | if (stats === STATS.UNCERTAIN) { 43 | rightr.__stats = STATS.CERTAIN 44 | rightr.__value = [] 45 | } 46 | 47 | o = rightr.__value 48 | if (utilx.isArray(o)) { 49 | if (!node.body) return 50 | if (!o[0]) o[0] = { __stats: STATS.UNCERTAIN } 51 | leftr = { 52 | __stats: STATS.LEFT, 53 | __value: o[0] 54 | } 55 | } else { 56 | leftr = BREAK 57 | } 58 | } 59 | 60 | } else { 61 | leftr = BREAK 62 | } 63 | 64 | var ctx = { 65 | foreach: BREAK, 66 | velocityCount: BREAK 67 | } 68 | ctx[name] = leftr 69 | this.pushContext(ctx) 70 | 71 | if (leftr.__stats === STATS.BREAK) { 72 | this[node.body.type](node.body) 73 | 74 | } else { 75 | o.forEach(function(item, idx) { 76 | leftr.__value = o[idx] 77 | that[node.body.type](node.body) 78 | }) 79 | } 80 | 81 | this.popContext() 82 | }, 83 | 84 | Include: function(node) { 85 | var args = node.arguments 86 | 87 | for (var i = 0; i < args.length; i++) { 88 | var arg = args[i] 89 | this[arg.type](arg) 90 | } 91 | }, 92 | 93 | Parse: function(node) { 94 | var arg = node.argument 95 | this[arg.type](arg) 96 | 97 | if (!common.isLiteralString(arg)) return 98 | 99 | var relPath = arg.value 100 | if (!utilx.isNonEmptyString(relPath)) 101 | this.throwError('Param of #parse is not a non-empty string.', arg.pos) 102 | 103 | var fullPath = common.getFullPath(relPath, this.cfg.root) 104 | if (!fullPath) 105 | this.throwError ('Param of #parse not exists or is not subpath of root.', arg.pos) 106 | 107 | this.Extract(null, { 108 | isFile: true, 109 | raw: relPath, 110 | relPath: relPath, 111 | fullPath: fullPath 112 | }) 113 | }, 114 | 115 | Evaluate: function(node) { 116 | var arg = node.argument 117 | this[arg.type](arg) 118 | 119 | if (!arg.type === 'String') return 120 | this.Extract(null, { 121 | isFile: false, 122 | raw: arg.value, 123 | offset: this.template.offset || arg.pos 124 | }) 125 | }, 126 | 127 | // only analyze define 128 | Define: function(node) { 129 | if (!common.isId(node.name)) { 130 | this.throwError('Param of #define is not an identifier.', node.name.pos) 131 | } 132 | var name = node.name.object.name 133 | 134 | var cur = this.topContext[name] 135 | var origin = common.getOrigin(cur) 136 | 137 | this.topContext[name] = { 138 | __stats: STATS.DEFINE, 139 | __origin: origin 140 | } 141 | 142 | if (!node.body) return 143 | var body = node.body 144 | this[body.type](body) 145 | }, 146 | 147 | Macro: function(node, templ) { 148 | node.__arguments = [] 149 | var args = node.arguments 150 | for (var i = 0; i < args.length; i++) { 151 | var arg = args[i] 152 | if (!common.isId(arg)) { 153 | this.throwError('Param of #macro is not an identifier.', arg.pos) 154 | } 155 | node.__arguments.push(arg.object.name) 156 | } 157 | 158 | if (templ) { 159 | node.__template = templ 160 | this.macro[node.name] = node 161 | } else { 162 | this.template.__macro[node.name] = node 163 | } 164 | }, 165 | 166 | MacroCall: function(node) { 167 | var name = node.name 168 | 169 | if (name === 'cmsparse') return 170 | 171 | var definition = this.template.__macro[name] || this.macro[name] 172 | if (!definition) { 173 | logger.warn('Call undefined macro <', name, '>') 174 | return 175 | } 176 | if (!definition.body) return 177 | 178 | var definitionTempl = name in this.template.__macro ? this.template : definition.__template 179 | 180 | var ctx = {} 181 | var args = node.arguments 182 | var argsLen = args.length 183 | var keys = definition.__arguments 184 | 185 | for (var i = 0; i < keys.length; i++) { 186 | var key = keys[i] 187 | 188 | if (i < argsLen) { 189 | var arg = args[i] 190 | var argr = this[arg.type](arg) 191 | ctx[key] = { 192 | __stats: STATS.LEFT, 193 | __value: argr || BREAK 194 | } 195 | 196 | } else { 197 | ctx[key] = { 198 | __stats: STATS.LEFT, 199 | __value: BREAK 200 | } 201 | } 202 | } 203 | 204 | if (node.isBlock && node.body) { 205 | var body = node.body 206 | var bodyr = this[body.type](body) 207 | ctx.bodyContent = bodyr || BREAK 208 | } 209 | 210 | this.pushContext(ctx) 211 | this.pushTemplate(definitionTempl) 212 | this[definition.body.type](definition.body) 213 | this.popTemplate() 214 | this.popContext() 215 | 216 | }, 217 | 218 | 219 | Stop: function(node) {}, 220 | Break: function(node) {} 221 | } 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /lib/data/data-expr.js: -------------------------------------------------------------------------------- 1 | var utilx = require('utilx') 2 | 3 | var logger = require('../logger') 4 | var common = require('../common') 5 | var STATS = require('./data-stats') 6 | 7 | 8 | module.exports = { 9 | Range: function(node) { 10 | var start = node.start 11 | this[start.type](start) 12 | 13 | var end = node.end 14 | this[end.type](end) 15 | }, 16 | 17 | List: function(node) { 18 | for (var i = 0; i < node.elements.length; i++) { 19 | var el = node.elements[i] 20 | this[el.type](el) 21 | } 22 | }, 23 | 24 | Map: function(node) { 25 | for (var i = 0; i < node.mapItems.length; i++) { 26 | var mapItem = node.mapItems[i] 27 | 28 | var prop = mapItem.property 29 | this[prop.type](prop) 30 | 31 | var value = mapItem.value 32 | this[value.type](value) 33 | } 34 | }, 35 | 36 | UnaryExpr: function(node) { 37 | var arg = node.argument 38 | this[arg.type](arg) 39 | }, 40 | 41 | BinaryExpr: function(node) { 42 | var left = node.left 43 | this[left.type](left) 44 | 45 | var right = node.right 46 | this[right.type](right) 47 | }, 48 | 49 | AssignExpr: function(node) { 50 | var left = node.left 51 | if (!common.isId(left)) { 52 | this.throwError('Left operand of assignment expression is not an identifier.', left.pos) 53 | } 54 | var name = left.object.name 55 | 56 | var right = node.right 57 | var rightr = this[right.type](right) 58 | 59 | var cur = this.topContext[name] 60 | var origin = common.getOrigin(cur) 61 | 62 | this.topContext[name] = { 63 | __stats: STATS.LEFT, 64 | __value: rightr || { __stats: STATS.BREAK }, 65 | __origin: origin 66 | } 67 | }, 68 | 69 | DString: function(node) { 70 | this.Extract(null, { 71 | isFile: false, 72 | raw: node.value, 73 | offset: this.template.offset || node.pos 74 | }) 75 | }, 76 | 77 | 78 | Boolean: noop, 79 | Null: noop, 80 | Integer: noop, 81 | Float: noop, 82 | String: noop, 83 | Text: noop, 84 | BText: noop, 85 | 86 | Comment: noop, 87 | BComment: noop 88 | } 89 | 90 | 91 | function noop(node) {} 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /lib/data/data-formatter.js: -------------------------------------------------------------------------------- 1 | var utilx = require('utilx') 2 | var inspect = require('util').inspect 3 | var STATS = require('./data-stats') 4 | var logger = require('../logger') 5 | 6 | 7 | // expand raw data to intermediate format 8 | function expand(data) { 9 | var rt 10 | 11 | if (utilx.isObject(data)) { 12 | rt = {} 13 | Object.keys(data).forEach(function(k) { 14 | // ignore return value derived from template 15 | if (isRtVal(data, k)) return 16 | innerExpand(data, k, rt) 17 | }) 18 | return rt 19 | } 20 | 21 | if (utilx.isArray(data)) { 22 | rt = [] 23 | data.forEach(function(v, k) { 24 | innerExpand(data, k, rt) 25 | }) 26 | return rt 27 | } 28 | } 29 | 30 | function innerExpand(from, k, to/*private params*/, k2) { 31 | var v = from[k] 32 | var n 33 | 34 | if (utilx.isFunction(v)) { 35 | n = { __stats: STATS.CERTAIN_FUNC } 36 | var rtKey = '__' + k + 'Return' 37 | 38 | // derived from template 39 | if (rtKey in from) { 40 | n.__argc = v.length 41 | innerExpand(from, rtKey, n, '__return') 42 | 43 | // defined by user 44 | } else { 45 | n.__value = v 46 | } 47 | 48 | } else { 49 | var goon = utilx.isObject(v) || utilx.isArray(v) 50 | n = { 51 | __stats: STATS.CERTAIN, 52 | __value: goon ? expand(v) : v 53 | } 54 | } 55 | 56 | to[k2 || k] = n 57 | } 58 | 59 | 60 | // restore intermediate data to normal format 61 | function clean(data) { 62 | var rt 63 | if (utilx.isObject(data)) { 64 | rt = {} 65 | Object.keys(data).forEach(function(k) { 66 | var n = data[k] 67 | var stats = n.__stats 68 | if (stats === STATS.LEFT || stats === STATS.DEFINE) { 69 | if (n.__origin) { 70 | data[k] = n.__origin 71 | } else { 72 | return 73 | } 74 | } 75 | innerClean(data, k, rt) 76 | }) 77 | return rt 78 | } 79 | 80 | if (utilx.isArray(data)) { 81 | rt = [] 82 | data.forEach(function(n, k) { 83 | innerClean(data, k, rt) 84 | }) 85 | return rt 86 | } 87 | } 88 | 89 | var argsStr = 'a, b, c, d, e, f' 90 | 91 | function innerClean(from, k, to/*private params*/, k2) { 92 | var n = from[k] 93 | var stats = n.__stats 94 | var v = n.__value 95 | 96 | if (stats === STATS.CERTAIN) { 97 | if (utilx.isObject(v) || utilx.isArray(v)) { 98 | v = clean(v) 99 | } 100 | 101 | } else if (stats === STATS.CERTAIN_FUNC) { 102 | if ('__return' in n) { 103 | var argc = n.__argc 104 | var rtKey = '__' + k + 'Return' 105 | v = eval('(function(' + argsStr.substr(0, 3 * argc - 2) + '){ return this.' + rtKey + ' })') 106 | 107 | // let key precede rtKey 108 | to[k2 || k] = v 109 | innerClean(n, '__return', to, rtKey) 110 | return 111 | } 112 | 113 | } else if (stats === STATS.UNCERTAIN) { 114 | v = v || '' 115 | } 116 | 117 | to[k2 || k] = v 118 | } 119 | 120 | 121 | function tostr(data /* private params*/, indent) { 122 | indent = indent || 2 123 | var oldBr = '\n' + utilx.generateLine(indent - 2, ' ') 124 | var br = oldBr + ' ' 125 | 126 | if (utilx.isObject(data)) { 127 | var keys = Object.keys(data) 128 | var len = keys.length 129 | 130 | var rt = '{' 131 | keys.forEach(function(key, idx) { 132 | var item = data[key] 133 | var keyLiteral = key.indexOf('-') === -1 ? key : "'" + key + "'" 134 | rt += br + keyLiteral + ': ' + tostr(item, indent + 2) 135 | if (idx < len - 1) rt += ',' 136 | }) 137 | rt += oldBr + '}' 138 | return rt 139 | } 140 | 141 | if (utilx.isArray(data)) { 142 | var len = data.length 143 | var rt = '[' 144 | data.forEach(function(item, idx) { 145 | rt += br + tostr(item, indent + 2) 146 | if (idx < len - 1) rt += ',' 147 | }) 148 | rt += oldBr + ']' 149 | return rt 150 | } 151 | 152 | if (utilx.isFunction(data)) { 153 | return data.toString() 154 | } 155 | 156 | if (utilx.isString(data)) { 157 | return inspect(data) 158 | } 159 | 160 | return data 161 | } 162 | 163 | 164 | // generate intermedate template to dump the context 165 | function dump(data/* private params*/, literal, indent) { 166 | indent = indent || 2 167 | var oldBr = '\n' + utilx.generateLine(indent - 2, ' ') 168 | var br = oldBr + ' ' 169 | 170 | var isTop = literal ? false : true 171 | 172 | var rt = '' 173 | var head = isTop ? '' : '#if(' + literal + ')' 174 | var tail = isTop ? '' : '#{else}undefined#end' 175 | 176 | if (utilx.isObject(data)) { 177 | var keys = Object.keys(data) 178 | var len = keys.length 179 | 180 | rt = head + '{' 181 | keys.forEach(function(key, idx) { 182 | var item = data[key] 183 | var itemLiteral 184 | var keyLiteral = key.indexOf('-') === -1 ? key : "'" + key + "'" 185 | 186 | var funcName = isRtVal(data, key) 187 | if (funcName) { 188 | // won't dump for the return value of a method with params 189 | if (data[funcName].length) { 190 | rt += br + keyLiteral + ': ' + tostr(item, indent + 2) 191 | 192 | } else { 193 | itemLiteral = literal + '.' + funcName + '()' 194 | rt += br + keyLiteral + ': ' + dump(item, itemLiteral, indent + 2) 195 | } 196 | 197 | } else { 198 | itemLiteral = literal ? literal + '.' + key : '$!' + key 199 | rt += br + keyLiteral + ': ' + dump(item, itemLiteral, indent + 2) 200 | } 201 | 202 | if (idx < len - 1) rt += ',' 203 | }) 204 | 205 | rt += oldBr + '}' + tail 206 | return rt 207 | } 208 | 209 | if (utilx.isArray(data)) { 210 | // NOTE 211 | // if the itemLiteral is always the same 212 | // $list1.list2 will cause an error 213 | var itemLiteral = '$!item' + indent 214 | 215 | rt = head + '[' 216 | rt += '#foreach(' + itemLiteral + ' in ' + literal + ')' + 217 | '#if($velocityCount > 1),#end' + br + dump(data[0], itemLiteral, indent + 2) + 218 | '#end' 219 | rt += oldBr + ']' + tail 220 | return rt 221 | } 222 | 223 | if (utilx.isFunction(data)) { 224 | return data.toString() 225 | } 226 | 227 | if (utilx.isString(data)) { 228 | if (data === '') { 229 | return '\'' + literal + '\'' 230 | } else { 231 | return inspect(data) 232 | } 233 | } 234 | 235 | return literal 236 | } 237 | 238 | 239 | /* 240 | * data = { 241 | * method0: function() { ... }, 242 | * method1: function(){ ... }, 243 | * __method1Return: { ... }, 244 | * __method2Return: { ... } 245 | * } 246 | * 247 | * isRtVal(data, 'method0') => undefined 248 | * isRtVal(data, '__method1Return') => 'method1' 249 | * isRtVal(data, '__method2Return') => undefined 250 | */ 251 | 252 | var rtReg = /^__([a-zA-Z0-9-_]+)Return$/ 253 | function isRtVal(data, key) { 254 | var match = rtReg.exec(key) 255 | if (!match) return 256 | var name = match[1] 257 | if (name in data) return name 258 | return 259 | } 260 | 261 | 262 | exports.expand = expand 263 | exports.clean = clean 264 | exports.tostr = tostr 265 | exports.dump = dump 266 | 267 | -------------------------------------------------------------------------------- /lib/data/data-ref.js: -------------------------------------------------------------------------------- 1 | var utilx = require('utilx') 2 | 3 | var common = require('../common') 4 | var logger = require('../logger') 5 | 6 | var STATS = require('./data-stats') 7 | var BREAK = { __stats: STATS.BREAK } 8 | 9 | module.exports = { 10 | 11 | Reference: function(node) { 12 | var obj = node.object 13 | var objr = this[obj.type](obj) 14 | 15 | if (objr.__stats === STATS.DEFINE) return BREAK 16 | return objr 17 | }, 18 | 19 | Identifier: function(node) { 20 | var name = node.name 21 | var ctx = this.context 22 | for (ctx; ctx; ctx = ctx.__parent) { 23 | if (name in ctx) { 24 | if (ctx[name].__stats === STATS.LEFT) { 25 | return ctx[name].__value 26 | } else { 27 | return ctx[name] 28 | } 29 | } 30 | } 31 | 32 | this.topContext[name] = { __stats: STATS.UNCERTAIN } 33 | return this.topContext[name] 34 | }, 35 | 36 | Property: function(node) { 37 | var isFn = node.__isFunction 38 | var obj = node.object 39 | var objr = this[obj.type](obj) 40 | var stats = objr.__stats 41 | 42 | // stats === STATS.LEFT is impossible 43 | 44 | if (stats === STATS.BREAK) return BREAK 45 | if (stats === STATS.DEFINE) return BREAK 46 | if (stats === STATS.CERTAIN_FUNC) return BREAK 47 | 48 | if (stats === STATS.UNCERTAIN) { 49 | if (!isFn) objr.__stats = STATS.CERTAIN 50 | objr.__value = {} 51 | } 52 | 53 | // stats === STATS.CERTAIN || STATS.UNCERTAIN 54 | var o = objr.__value 55 | var p = node.property.name 56 | 57 | // NOTE 58 | // 59 | // $str.length() 60 | // ^^^^^^^^^^^ 61 | // 62 | // $arr.size() 63 | // ^^^^^^^^^ 64 | // 65 | // return of both references cannot go further 66 | // so it is ok when $a is not a object, then BREAK 67 | if (!utilx.isObject(o)) return BREAK 68 | 69 | if (!(p in o)) o[p] = { __stats: STATS.UNCERTAIN } 70 | 71 | if (o[p].__stats !== STATS.CERTAIN_FUNC && isFn) { 72 | o[p].__parent = objr 73 | o[p].__property = p 74 | } 75 | 76 | return o[p] 77 | }, 78 | 79 | Index: function(node) { 80 | var obj = node.object 81 | var objr = this[obj.type](obj) 82 | var stats = objr.__stats 83 | 84 | // stats === STATS.LEFT is impossible 85 | 86 | if (stats === STATS.BREAK) return BREAK 87 | if (stats === STATS.DEFINE) return BREAK 88 | if (stats === STATS.CERTAIN_FUNC) return BREAK 89 | 90 | var prop = node.property 91 | this[prop.type](prop) 92 | 93 | if (stats === STATS.UNCERTAIN) { 94 | if (common.isLiteralString(prop)) { 95 | objr.__stats = STATS.CERTAIN 96 | objr.__value = {} 97 | 98 | } else if (prop.type === 'Integer') { 99 | objr.__stats = STATS.CERTAIN 100 | objr.__value = [] 101 | 102 | } else { 103 | objr.__value = [] 104 | } 105 | } 106 | 107 | var o = objr.__value 108 | 109 | if (common.isLiteralString(prop)) { 110 | var p = prop.name 111 | if (!utilx.isObject(o)) return BREAK 112 | if (!o[p]) o[p] = { __stats: STATS.UNCERTAIN } 113 | return o[p] 114 | 115 | } else if (prop.type === 'Integer' || isCertainArray(objr)) { 116 | var p = 0 117 | if (!utilx.isArray(o)) return BREAK 118 | if (!o[p]) o[p] = { __stats: STATS.UNCERTAIN } 119 | return o[p] 120 | 121 | } else { 122 | return BREAK 123 | } 124 | }, 125 | 126 | Method: function(node) { 127 | var callee = node.callee 128 | callee.__isFunction = true 129 | var calleer = this[callee.type](callee) 130 | var stats = calleer.__stats 131 | 132 | var args = node.arguments 133 | var argsLen = args.length 134 | for (var i = 0; i < argsLen; i++) { 135 | var arg = args[i] 136 | this[arg.type](arg) 137 | } 138 | var arg0 139 | if (argsLen > 0) { 140 | var temp = args[0] 141 | if (common.isLiteralString(temp)) { 142 | arg0 = temp.value 143 | } else if (temp.type === 'Integer') { 144 | arg0 = 0 145 | } 146 | } 147 | 148 | // stats === STATS.LEFT is impossible 149 | 150 | if (stats === STATS.BREAK) return BREAK 151 | if (stats === STATS.DEFINE) return BREAK 152 | if (stats === STATS.CERTAIN) return BREAK 153 | 154 | if (stats === STATS.CERTAIN_FUNC) { 155 | // defined by user 156 | if ('__value' in calleer) { 157 | return BREAK 158 | 159 | // derived from template 160 | } else { 161 | return calleer.__return 162 | } 163 | } 164 | 165 | //stats === STATS.UNCERTAIN 166 | var parent = calleer.__parent 167 | // NOTE 168 | // from .Property, parent.__value must be a object 169 | var pIsCertain = parent.__stats === STATS.CERTAIN 170 | var prop = calleer.__property 171 | ; delete calleer.__parent 172 | ; delete calleer.__property 173 | 174 | parent.__stats = STATS.CERTAIN 175 | if (prop === 'length' && argsLen === 0 && !pIsCertain) { 176 | parent.__value = '' 177 | 178 | } else if (prop === 'size' && argsLen === 0 && !pIsCertain) { 179 | parent.__value = [] 180 | 181 | } else if (prop === 'isEmpty' && argsLen === 0 && !pIsCertain) { 182 | parent.__value = [] 183 | 184 | } else if (prop === 'entrySet' && argsLen === 0) { 185 | delete parent.__value[prop] 186 | 187 | } else if (prop === 'keySet' && argsLen === 0) { 188 | delete parent.__value[prop] 189 | 190 | } else if (prop === 'get' && argsLen == 1) { 191 | delete parent.__value[prop] 192 | 193 | if (!pIsCertain && utilx.isInteger(arg0)) { 194 | parent.__value = [{ __stats: STATS.UNCERTAIN }] 195 | return parent.__value[0] 196 | } else if (utilx.isString(arg0)) { 197 | parent.__value[arg0] = parent.__value[arg0] || { __stats: STATS.UNCERTAIN } 198 | return parent.__value[arg0] 199 | } 200 | 201 | } else if (prop.indexOf('get') === 0 && prop.length > 3 && argsLen === 0) { 202 | delete parent.__value[prop] 203 | 204 | var p = extractProp(prop) 205 | parent.__value[p] = parent.__value[p] || { __stats: STATS.UNCERTAIN } 206 | return parent.__value[p] 207 | 208 | } else if (prop.indexOf('is') === 0 && prop.length > 2 && argsLen === 0) { 209 | delete parent.__value[prop] 210 | 211 | var p = extractProp(prop) 212 | parent.__value[p] = parent.__value[p] || { 213 | __stats: STATS.CERTAIN, 214 | __value: true 215 | } 216 | return parent.__value[p] 217 | 218 | // it is really a function 219 | } else { 220 | calleer.__stats = STATS.CERTAIN_FUNC 221 | calleer.__argc = argsLen 222 | calleer.__return = { __stats: STATS.UNCERTAIN } 223 | return calleer.__return 224 | } 225 | 226 | return BREAK 227 | } 228 | } 229 | 230 | 231 | function extractProp(s) { 232 | s = s.replace(/^(get|set|is)/, '') 233 | return s[0].toLowerCase() + s.substr(1) 234 | } 235 | 236 | 237 | function isCertainArray(o) { 238 | return o.__stats === STATS.CERTAIN && utilx.isArray(o.__value) 239 | } 240 | -------------------------------------------------------------------------------- /lib/data/data-stats.js: -------------------------------------------------------------------------------- 1 | // see: https://github.com/fool2fish/blog/issues/7 2 | 3 | module.exports = { 4 | LEFT: 'LEFT', 5 | BREAK: 'BREAK', 6 | DEFINE: 'DEFINE', 7 | CERTAIN: 'CERTAIN', // certain types except function 8 | CERTAIN_FUNC: 'CERTAIN_FUNC', // certain function 9 | UNCERTAIN: 'UNCERTAIN' 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/data/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var utilx = require('utilx') 4 | var inherits = require('util').inherits 5 | var EventEmitter = require('events').EventEmitter 6 | 7 | var logger = require('../logger') 8 | var common = require('../common') 9 | var handleCfg = require('../handle-cfg') 10 | 11 | var parser = require('../engine/velocity') 12 | var formatter = require('./data-formatter') 13 | var STATS = require('./data-stats') 14 | 15 | 16 | function Data(cfg) { 17 | this.cfg = handleCfg(cfg) 18 | 19 | // global macro 20 | this.macro = {} 21 | if (this.cfg.macro) { 22 | this.GMacro(this.cfg.macro) 23 | } 24 | } 25 | 26 | 27 | Data.tostr = formatter.tostr 28 | Data.dump = formatter.dump 29 | 30 | 31 | inherits(Data, EventEmitter) 32 | 33 | 34 | // process global macro 35 | Data.prototype.GMacro = function(macro) { 36 | var that = this 37 | 38 | macro.forEach(function(m) { 39 | var content = m.isFile ? utilx.readFile(m.fullPath, that.cfg.encoding) : m.raw 40 | var ast = parser.parse(content) 41 | m.lines = content.split(/\r?\n/) 42 | ast.body.forEach(function(node){ 43 | if (node.type === 'Macro') { 44 | that.Macro(node, m) 45 | } 46 | }) 47 | }) 48 | 49 | // logger.debug('Macro', this.macro) 50 | } 51 | 52 | //////////////////////////// 53 | // the only public method // 54 | //////////////////////////// 55 | Data.prototype.extract = function(context) { 56 | if (utilx.isExistedFile(context)) { 57 | context = require(path.resolve(context)) 58 | } else { 59 | context = utilx.isObject(context) ? context : {} 60 | } 61 | context = formatter.expand(context) 62 | 63 | var interData = this.Extract(context) 64 | 65 | var raw = formatter.clean(interData) 66 | var str 67 | 68 | if (this.cfg.dump) { 69 | str = formatter.dump(raw) 70 | } else { 71 | str = formatter.tostr(raw) 72 | } 73 | 74 | if (this.cfg.output) { 75 | fs.writeFileSync( 76 | this.cfg.output, 77 | 'module.exports = ' + str + '\n' 78 | ) 79 | } 80 | 81 | return { 82 | raw: raw, 83 | str: str 84 | } 85 | } 86 | 87 | Data.prototype.Extract = function(context, template) { 88 | var that = this 89 | var templ = template || this.cfg.template 90 | 91 | if (context) this.pushContext(context) 92 | this.pushTemplate(templ) 93 | 94 | var content = templ.raw 95 | if (templ.isFile) { 96 | content = utilx.readFile(templ.fullPath, this.cfg.encoding) 97 | } 98 | 99 | templ.lines = content.split(/\r?\n/) 100 | 101 | try { 102 | var node = parser.parse(content) 103 | } catch (e) { 104 | this.throwError(e.message, common.getPosFromParserError(e)) 105 | } 106 | 107 | this[node.type](node) 108 | var rt = this.topContext 109 | this.popTemplate() 110 | if (context) this.popContext() 111 | return rt 112 | } 113 | 114 | Data.prototype.Statements = function(node) { 115 | for (var i = 0; i < node.body.length; i++) { 116 | var cn = node.body[i] 117 | this[cn.type](cn) 118 | } 119 | } 120 | 121 | Data.prototype.throwError = function (message, pos) { 122 | var loc = common.getRealLoc([this.template, pos]) 123 | var templ = loc[0] 124 | pos = loc[1] 125 | var line = templ.lines[pos.first_line - 1] 126 | 127 | var e = new Error(message) 128 | e.stack = common.markError(line, pos) + 129 | 'Error: ' + e.message + 130 | '\n at ' + common.loc2str(loc) 131 | throw e 132 | } 133 | 134 | utilx.mix( 135 | Data.prototype, 136 | require('../engine/engine-stack'), 137 | require('./data-ref'), 138 | require('./data-expr'), 139 | require('./data-direc') 140 | ) 141 | 142 | module.exports = Data 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /lib/dep/index.js: -------------------------------------------------------------------------------- 1 | var scan = require('./scan') 2 | var render = require('./render') 3 | var handleCfg = require('../handle-cfg') 4 | 5 | module.exports = function(cfg, needRender) { 6 | cfg = handleCfg(cfg) 7 | var data = scan(cfg) 8 | render(data, cfg.template.fullPath, cfg.reverse) 9 | return data 10 | } 11 | -------------------------------------------------------------------------------- /lib/dep/render.js: -------------------------------------------------------------------------------- 1 | var colorful = require('colorful') 2 | 3 | 4 | function render(data, fullPath, reverse /*private params*/, indent, upFullPath) { 5 | indent = indent || '1' 6 | var item = data[fullPath] 7 | 8 | console.log(wrapIndent(indent) + wrapPath(item)) 9 | 10 | var morePaths = reverse ? item.parents : item.children 11 | Object.keys(morePaths).forEach(function(downFullPath, idx, keys) { 12 | render(data, downFullPath, reverse, indent + (idx === keys.length - 1 ? '1' : '0'), fullPath) 13 | }) 14 | } 15 | 16 | /* 17 | * indent = (0|1)+ 18 | * 0: not the last child 19 | * 1: last child 20 | * the right most digit matches the node itself 21 | * the second right most digit matches the node's parent 22 | * and so on 23 | * 24 | * |-root 1 25 | * |-c11 10 26 | * | |-c111 100 27 | * | | |-c1111 1000 28 | * | | |-c1112 1001 29 | * | |-c112 101 30 | * |-c12 11 31 | */ 32 | function wrapIndent(indent) { 33 | var str = '' 34 | for (var i = 0; i < indent.length - 1; i++) { 35 | str += (indent[i] === '0' ? '| ' : ' ') 36 | } 37 | return colorful.gray(str + '|-') 38 | } 39 | 40 | 41 | function wrapPath(item) { 42 | var stat = item.stat 43 | var relPath = item.relPath || item.fullPath 44 | if (stat === 0) { 45 | return relPath 46 | } else if (stat === 1) { 47 | return colorful.red(relPath) 48 | } else { 49 | return colorful.yellow(relPath) 50 | } 51 | } 52 | 53 | 54 | module.exports = render 55 | -------------------------------------------------------------------------------- /lib/dep/scan.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var utilx = require('utilx') 4 | 5 | var logger = require('../logger') 6 | var common = require('../common') 7 | 8 | // files waiting for scan 9 | var QUEUE = {} 10 | 11 | // scanned files 12 | var FILES = {/* 13 | fullPath: { 14 | root: 'vm root', 15 | relPath: 'path relative to root', 16 | fullPath: 'full path', 17 | stat: 0(normal), 1(notExists), 2(hasVariable), 18 | parents: {fullPath: true, ...}, 19 | children: {fullPath: true, ...} 20 | }, 21 | ... 22 | */} 23 | 24 | 25 | function scan(fullPath, cfg) { 26 | if (fullPath in FILES) { 27 | delete QUEUE[fullPath] 28 | return 29 | } 30 | 31 | FILES[fullPath] = QUEUE[fullPath] 32 | delete QUEUE[fullPath] 33 | 34 | if (path.extname(fullPath) != '.vm') return 35 | 36 | var item = FILES[fullPath] 37 | if (item.stat !== 0) return 38 | 39 | var reg = /##.*$|#\*[\s\S]*?\*#|#\[\[[\s\S]*?\]\]#|#{?(?:include|parse|cmsparse)}?\(\s*([^\)]+?)\s*\)/gm 40 | var direcReg = /^#{?[a-zA-Z]/ 41 | var content = utilx.readFile(fullPath, cfg.encoding) 42 | 43 | var matched 44 | while ((matched = reg.exec(content)) != null) { 45 | if (direcReg.test(matched[0])) { 46 | // -____-" #include may has multiple params 47 | matched[1].split(/\s*,\s*|\s+/).forEach(function(cRelPath) { 48 | var child = addItem(cRelPath, cfg.root) 49 | child.parents[fullPath] = true 50 | item.children[child.fullPath] = true 51 | }) 52 | } 53 | } 54 | } 55 | 56 | 57 | // add path to QUEUE 58 | function addItem(p, root) { 59 | var info = {stat: 1, parents:{}, children:{}} 60 | 61 | // directory 62 | if (utilx.isExistedDir(p)) { 63 | fs.readdirSync(p).forEach(function(child) { 64 | addItem(path.join(p, child), root) 65 | }) 66 | return 67 | } 68 | 69 | // full path 70 | if (utilx.isExistedFile(p)) { 71 | info.fullPath = p 72 | info.relPath = common.getRelPath(p, root) 73 | if (info.relPath) { 74 | info.root = common.getRoot(info.relPath, root) 75 | info.stat = 0 76 | } 77 | 78 | // relative path 79 | } else { 80 | p = cleanRelPath(p) 81 | info.relPath = p 82 | info.root = common.getRoot(p, root) 83 | 84 | if (info.root) { 85 | info.fullPath = path.join(info.root, p) 86 | info.stat = 0 87 | } else { 88 | info.fullPath = path.join(root[0], p) 89 | info.stat = hasVariable(p) ? 2 : 1 90 | } 91 | } 92 | 93 | var fullPath = info.fullPath 94 | if (!QUEUE[fullPath] && !FILES[fullPath]) { 95 | QUEUE[fullPath] = info 96 | } 97 | 98 | return QUEUE[fullPath] || FILES[fullPath] 99 | } 100 | 101 | // 'path' -> path 102 | // "path" -> path 103 | // $path -> $path 104 | function cleanRelPath(relPath) { 105 | if (/'|"/.test(relPath)) { 106 | return relPath.substring(1, relPath.length - 1) 107 | } else { 108 | return relPath 109 | } 110 | } 111 | 112 | // '$path' -> 0 113 | // "path" -> 0 114 | // "$path" -> 1 115 | // $path -> 1 116 | // path -> 0 117 | function hasVariable(relPath) { 118 | return relPath[0] !== "'" && /\$\!?\{?[a-zA-Z].*\}?/.test(relPath) 119 | } 120 | 121 | 122 | // remove files not in (reverse) dependency tree 123 | function cleanFiles(fullPath) { 124 | var data = {} 125 | 126 | function _cleanFiles(fp) { 127 | data[fp] = FILES[fp] 128 | Object.keys(FILES[fp].parents).forEach(function(pfp) { 129 | _cleanFiles(pfp) 130 | }) 131 | } 132 | _cleanFiles(fullPath) 133 | 134 | FILES = data 135 | } 136 | 137 | 138 | module.exports = function (cfg) { 139 | QUEUE = {} 140 | FILES = {} 141 | 142 | var template = cfg.template 143 | if (!template.isFile) logger.error('Template is not a file.') 144 | 145 | var fullPath = template.fullPath 146 | var root = cfg.root 147 | 148 | if (cfg.reverse) { 149 | root.forEach(function(r) { addItem(r, root) }) 150 | Object.keys(QUEUE).forEach(function(p) { 151 | scan(p, cfg) 152 | }) 153 | cleanFiles(fullPath) 154 | 155 | } else { 156 | addItem(fullPath, root) 157 | while (Object.keys(QUEUE).length) { 158 | Object.keys(QUEUE).forEach(function(p) { 159 | scan(p, cfg) 160 | }) 161 | } 162 | } 163 | 164 | logger.debug(FILES) 165 | return FILES 166 | } 167 | 168 | -------------------------------------------------------------------------------- /lib/engine/engine-direc.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var utilx = require('utilx') 3 | 4 | var common = require('../common') 5 | var logger = require('../logger') 6 | var STATS = require('./engine-stats') 7 | 8 | 9 | module.exports = { 10 | If: function(node) { 11 | var test = node.test 12 | var testr = this[test.type](test) 13 | 14 | if (testr.stats !== STATS.SUCCESS) return testr 15 | 16 | if (testr.value && node.consequent) { 17 | return this[node.consequent.type](node.consequent) 18 | } else if (!testr.value && node.alternate) { 19 | return this[node.alternate.type](node.alternate) 20 | } else { 21 | return this.initSuccessInfo() 22 | } 23 | }, 24 | 25 | Foreach: function(node) { 26 | var left = node.left 27 | if (!isId(left)) { 28 | return this.initFailInfo('Left operand of #foreach is not an identifier.', left.pos) 29 | } 30 | 31 | var right = node.right 32 | var rightr = this[right.type](right) 33 | 34 | if (rightr.stats !== STATS.SUCCESS) return rightr 35 | 36 | var list = rightr.value 37 | if (!utilx.isArray(list)) return this.initSuccessInfo() 38 | if (!node.body) return this.initSuccessInfo() 39 | 40 | var result = this.initSuccessInfo() 41 | var ctx = {foreach: {}} 42 | this.pushContext(ctx) 43 | 44 | for (var i = 0; i < list.length; i++) { 45 | ctx[left.object.name] = list[i] 46 | ctx.foreach.index = i 47 | ctx.foreach.count = ctx.velocityCount = i + 1 48 | ctx.foreach.hasNext = i < list.length - 1 49 | 50 | var cr = this[node.body.type](node.body) 51 | if (cr.stats === STATS.SUCCESS) { 52 | result.value += cr.value 53 | } else if (cr.stats === STATS.BREAK) { 54 | result.value += cr.value 55 | break 56 | } else { 57 | this.mergeResult(result, cr) 58 | break 59 | } 60 | } 61 | 62 | this.popContext() 63 | return result 64 | }, 65 | 66 | Include: function(node) { 67 | var args = node.arguments 68 | var result = this.initSuccessInfo() 69 | 70 | for (var i = 0; i < args.length; i++) { 71 | var arg = args[i] 72 | var argr = this[arg.type](arg) 73 | 74 | if (argr.stats !== STATS.SUCCESS) { 75 | this.mergeResult(result, argr) 76 | break 77 | } 78 | 79 | var relPath = argr.value 80 | if (!utilx.isNonEmptyString(relPath)) { 81 | var fail = this.initFailInfo('Param of #include is not a non-empty string.', arg.pos) 82 | this.mergeResult(result, fail) 83 | break 84 | } 85 | 86 | var fullPath = common.getFullPath(relPath, this.cfg.root) 87 | if (fullPath) { 88 | result.value += utilx.readFile(fullPath, this.cfg.encoding) 89 | } else { 90 | var fail = this.initFailInfo('Param of #include not exists or is not subpath of root.', arg.pos) 91 | this.mergeResult(result, fail) 92 | break 93 | } 94 | } 95 | return result 96 | }, 97 | 98 | Parse: function(node) { 99 | var arg = node.argument 100 | var argr = this[arg.type](arg) 101 | 102 | if (argr.stats !== STATS.SUCCESS) return argr 103 | 104 | var relPath = argr.value 105 | if (!utilx.isNonEmptyString(relPath)) 106 | return this.initFailInfo('Param of #parse is not a non-empty string.', arg.pos) 107 | 108 | var fullPath = common.getFullPath(relPath, this.cfg.root) 109 | if (fullPath) { 110 | var result = this.Render(null, { 111 | isFile: true, 112 | raw: relPath, 113 | relPath: relPath, 114 | fullPath: fullPath 115 | }) 116 | if (result.stats !== STATS.SUCCESS) { 117 | result.stack.push(common.getRealLoc([this.template, node.pos])) 118 | } 119 | return result 120 | } else { 121 | return this.initFailInfo('Param of #parse not exists or is not subpath of root.', arg.pos) 122 | } 123 | }, 124 | 125 | Evaluate: function(node) { 126 | var arg = node.argument 127 | var argr = this[arg.type](arg) 128 | 129 | if (argr.stats !== STATS.SUCCESS) return argr 130 | 131 | var v = argr.value 132 | if (typeof v !== 'string') 133 | return this.initFailInfo('Param of #evaluate is not a string.', node.pos) 134 | 135 | if (!v) return this.initSuccessInfo() 136 | 137 | return this.Render(null, { 138 | ifFile: false, 139 | raw: v, 140 | offset: this.template.offset || arg.pos 141 | }) 142 | }, 143 | 144 | Define: function(node) { 145 | var name = node.name 146 | 147 | if (!isId(name)) { 148 | return this.initFailInfo('Param of #define is not an identifier.', name.pos) 149 | } 150 | 151 | this.template.__define[name.object.name] = node.body 152 | return this.initSuccessInfo() 153 | }, 154 | 155 | Macro: function(node, templ) { 156 | node.__arguments = [] 157 | var args = node.arguments 158 | for (var i = 0; i < args.length; i++) { 159 | var arg = args[i] 160 | if (!isId(arg)) { 161 | return this.initFailInfo('Param of #macro is not an identifier.', arg.pos) 162 | } 163 | node.__arguments.push(arg.object.name) 164 | } 165 | 166 | // global 167 | if (templ) { 168 | node.__template = templ 169 | this.macro[node.name] = node 170 | 171 | // local 172 | } else { 173 | this.template.__macro[node.name] = node 174 | } 175 | 176 | return this.initSuccessInfo() 177 | }, 178 | 179 | MacroCall: function(node) { 180 | var name = node.name 181 | 182 | if (name === 'cmsparse') { 183 | return this.Cmsparse(node) 184 | } 185 | 186 | var definition = this.template.__macro[name] || this.macro[name] 187 | if (!definition) { 188 | return this.initFailInfo('Call undefined macro.', node.pos) 189 | } 190 | var definitionTempl = name in this.template.__macro ? this.template : definition.__template 191 | 192 | if (!definition.body) return this.initSuccessInfo() 193 | 194 | var ctx = {} 195 | var args = node.arguments 196 | var argsLen = args.length 197 | var keys = definition.__arguments 198 | 199 | for (var i = 0; i < keys.length; i++) { 200 | var key = keys[i] 201 | 202 | if (i < argsLen) { 203 | var arg = args[i] 204 | var argr = this[arg.type](arg) 205 | 206 | if (argr.stats !== STATS.SUCCESS) return argr 207 | ctx[key] = argr.value 208 | 209 | } else { 210 | ctx[key] = undefined 211 | } 212 | } 213 | 214 | if (node.isBlock && node.body) { 215 | var body = node.body 216 | var bodyr = this[body.type](body) 217 | if (bodyr.stats !== STATS.SUCCESS) return bodyr 218 | ctx.bodyContent = bodyr.value 219 | } 220 | 221 | this.pushContext(ctx) 222 | this.pushTemplate(definitionTempl) 223 | var result = this[definition.body.type](definition.body) 224 | this.popTemplate() 225 | this.popContext() 226 | 227 | if (result.stats !== STATS.SUCCESS) { 228 | // at where the macro is called 229 | result.stack.push([this.template, node.pos]) 230 | } 231 | 232 | return result 233 | }, 234 | 235 | 236 | Stop: function(node) { 237 | return { 238 | stats: STATS.STOP, 239 | value: '', 240 | stack: [common.getRealLoc([this.template, node.pos])] 241 | } 242 | }, 243 | 244 | Break: function(node) { 245 | return { 246 | stats: STATS.BREAK, 247 | value: '', 248 | stack: [common.getRealLoc([this.template, node.pos])] 249 | } 250 | }, 251 | 252 | // special for alipay's cms 253 | Cmsparse: function(node) { 254 | var arg = node.arguments[0] 255 | var argr = this[arg.type](arg) 256 | 257 | if (argr.stats !== STATS.SUCCESS) return argr 258 | 259 | var relPath = argr.value 260 | if (!utilx.isNonEmptyString(relPath)) 261 | return this.initFailInfo('Param of #cmsparse is not a non-empty string.', arg.pos) 262 | 263 | var fullPath = common.getFullPath(relPath, this.cfg.root) 264 | if (fullPath) { 265 | var result = this.Render(null, { 266 | isFile: true, 267 | raw: relPath, 268 | relPath: relPath, 269 | fullPath: fullPath 270 | }) 271 | if (result.stats !== STATS.SUCCESS) { 272 | result.stack.push([this.template, node.pos]) 273 | } 274 | return result 275 | 276 | } else { 277 | return this.initFailInfo('Param of #cmsparse not exists or is not subpath of root.', arg.pos) 278 | } 279 | } 280 | } 281 | 282 | 283 | function isId(node) { 284 | return node.object.type === 'Identifier' 285 | } 286 | 287 | 288 | -------------------------------------------------------------------------------- /lib/engine/engine-expr.js: -------------------------------------------------------------------------------- 1 | var utilx = require('utilx') 2 | 3 | var logger = require('../logger') 4 | var STATS = require('./engine-stats') 5 | 6 | 7 | module.exports = { 8 | Range: function(node) { 9 | var start = node.start 10 | var startr = this[start.type](start) 11 | if (startr.stats !== STATS.SUCCESS) return startr 12 | if (!utilx.isInteger(startr.value)) { 13 | return this.initFailInfo('Start of range is not an integer.', start.pos) 14 | } 15 | 16 | var end = node.end 17 | var endr = this[end.type](end) 18 | 19 | if (endr.stats !== STATS.SUCCESS) return endr 20 | if (!utilx.isInteger(endr.value)) 21 | return this.initFailInfo('End of range is not an integer.', end.pos) 22 | 23 | return { 24 | stats: STATS.SUCCESS, 25 | value: generateList(startr.value, endr.value) 26 | } 27 | }, 28 | 29 | List: function(node) { 30 | var list = [] 31 | for (var i = 0; i < node.elements.length; i++) { 32 | var el = node.elements[i] 33 | var elr = this[el.type](el) 34 | 35 | if (elr.stats !== STATS.SUCCESS) return elr 36 | list.push(elr.value) 37 | } 38 | return { 39 | stats: STATS.SUCCESS, 40 | value: list 41 | } 42 | }, 43 | 44 | Map: function(node) { 45 | var map = {} 46 | 47 | for (var i = 0; i < node.mapItems.length; i++) { 48 | var mapItem = node.mapItems[i] 49 | 50 | var prop = mapItem.property 51 | var propr = this[prop.type](prop) 52 | 53 | if (propr.stats !== STATS.SUCCESS) return propr 54 | if (!utilx.isNonEmptyString(propr.value)) 55 | return this.initFailInfo('Key of map is not a non-empty string.', prop.pos) 56 | 57 | var value = mapItem.value 58 | var valuer = this[value.type](value) 59 | 60 | if (valuer.stats !== STATS.SUCCESS) return valuer 61 | map[propr.value] = valuer.value 62 | } 63 | 64 | return { 65 | stats: STATS.SUCCESS, 66 | value: map 67 | } 68 | }, 69 | 70 | UnaryExpr: function(node) { 71 | var arg = node.argument 72 | var argr = this[arg.type](arg) 73 | 74 | if (argr.stats !== STATS.SUCCESS) return argr 75 | 76 | return { 77 | stats: STATS.SUCCESS, 78 | value: !argr.value 79 | } 80 | }, 81 | 82 | BinaryExpr: function(node) { 83 | var left = node.left 84 | var leftr = this[left.type](left) 85 | 86 | if (leftr.stats !== STATS.SUCCESS) return leftr 87 | 88 | var leftv = leftr.value 89 | var op = node.operator 90 | 91 | if (leftv && op === '||') { 92 | return { 93 | stats: STATS.SUCCESS, 94 | value: true 95 | } 96 | } 97 | if (!leftv && op === '&&') { 98 | return { 99 | stats: STATS.SUCCESS, 100 | value: false 101 | } 102 | } 103 | 104 | var right = node.right 105 | var rightr = this[right.type](right) 106 | 107 | if (rightr.stats !== STATS.SUCCESS) return rightr 108 | 109 | var v 110 | var rightv = rightr.value 111 | 112 | if (op === '*') { v = leftv * rightv } 113 | else if (op === '/') { 114 | if (rightv === 0) { 115 | return this.initFailInfo('Right operand of division is zero.', right.pos) 116 | } 117 | v = leftv / rightv 118 | } 119 | else if (op === '%') { 120 | if (rightv === 0) { 121 | return this.initFailInfo('Right operand of modulo is zero.', right.pos) 122 | } 123 | v = leftv % rightv 124 | } 125 | else if (op === '+') { v = leftv + rightv } 126 | else if (op === '-') { v = leftv - rightv } 127 | else if (op === '>=') { v = leftv >= rightv } 128 | else if (op === '>') { v = leftv > rightv } 129 | else if (op === '<=') { v = leftv <= rightv } 130 | else if (op === '<') { v = leftv < rightv } 131 | else if (op === '==') { v = leftv == rightv } 132 | else if (op === '!=') { v = leftv != rightv } 133 | else if (op === '&&') { v = leftv && rightv } 134 | else if (op === '||') { v = leftv || rightv } 135 | 136 | return { 137 | stats: STATS.SUCCESS, 138 | value: v 139 | } 140 | }, 141 | 142 | AssignExpr: function(node) { 143 | var left = node.left 144 | if (left.object.type !== 'Identifier') { 145 | return this.initFailInfo('Left operand of assignment expression is not an identifier.', left.pos) 146 | } 147 | var property = left.object.name 148 | 149 | var right = node.right 150 | var rightr = this[right.type](right) 151 | if (rightr.stats !== STATS.SUCCESS) return rightr 152 | 153 | this.topContext[property] = rightr.value 154 | 155 | return this.initSuccessInfo() 156 | }, 157 | 158 | DString: function(node) { 159 | return this.Render(null, { 160 | isFile: false, 161 | raw: node.value, 162 | offset: this.template.offset || node.pos 163 | }) 164 | }, 165 | 166 | 167 | Boolean: literal, 168 | Null: literal, 169 | Integer: literal, 170 | Float: literal, 171 | String: literal, 172 | Text: literal, 173 | BText: literal, 174 | 175 | Comment: empty, 176 | BComment: empty 177 | } 178 | 179 | function literal(node) { 180 | return { 181 | stats: STATS.SUCCESS, 182 | value: node.value 183 | } 184 | } 185 | 186 | function empty(node) { 187 | return { 188 | stats: STATS.SUCCESS, 189 | value: '' 190 | } 191 | } 192 | 193 | // [0..3] -> [0, 1, 2, 3] 194 | function generateList(start, end) { 195 | var rt = [] 196 | var sign = start <= end ? 1 : -1 197 | var i 198 | for (i = start; (i - end) * sign <= 0; i += sign) { 199 | rt.push(i) 200 | } 201 | return rt 202 | } 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /lib/engine/engine-ref.js: -------------------------------------------------------------------------------- 1 | var utilx = require('utilx') 2 | 3 | var common = require('../common') 4 | var logger = require('../logger') 5 | var STATS = require('./engine-stats') 6 | 7 | 8 | module.exports = { 9 | 10 | Reference: function(node) { 11 | // call define 12 | if (common.isId(node)) { 13 | var name = node.object.name 14 | if (name in this.template.__define) { 15 | var def = this.template.__define[name] 16 | var result = this[def.type](def) 17 | if (result.stats !== STATS.SUCCESS) { 18 | // at where the #define is called 19 | result.stack.push(common.getRealLoc([this.template, node.pos])) 20 | } 21 | return result 22 | } 23 | } 24 | 25 | var obj = node.object 26 | var objr = this[obj.type](obj) 27 | 28 | if (objr.stats !== STATS.SUCCESS) return objr 29 | 30 | var v = objr.value 31 | if (v === undefined || v === null) { 32 | var result = { 33 | stats: STATS.SUCCESS, 34 | value: v, 35 | silent: node.silent 36 | } 37 | if (!node.silent) result.literal = common.extractContent(this.template.lines, node.pos) 38 | return result 39 | } else { 40 | return objr 41 | } 42 | }, 43 | 44 | Identifier: function(node) { 45 | var v 46 | var name = node.name 47 | var ctx = this.context 48 | for (ctx; ctx; ctx = ctx.__parent) { 49 | if (name in ctx) { 50 | v = ctx[name] 51 | break 52 | } 53 | } 54 | return { 55 | stats: STATS.SUCCESS, 56 | value: v 57 | } 58 | }, 59 | 60 | Prop: function(node) { 61 | return { 62 | stats: STATS.SUCCESS, 63 | value: node.name 64 | } 65 | }, 66 | 67 | Property: PropIdx, 68 | Index: PropIdx, 69 | 70 | Method: function(node) { 71 | var callee = node.callee 72 | var calleer = this[callee.type](callee) 73 | 74 | if (calleer.stats !== STATS.SUCCESS) return calleer 75 | 76 | var args = node.arguments 77 | var argsv = [] 78 | 79 | for (var i = 0; i < args.length; i++) { 80 | var arg = args[i] 81 | var argr = this[arg.type](arg) 82 | 83 | if (argr.stats !== STATS.SUCCESS) return argr 84 | argsv.push(argr.value) 85 | } 86 | 87 | if (!utilx.isFunction(calleer.value)) { 88 | var o = calleer.object 89 | var p = calleer.property 90 | var l = argsv.length 91 | 92 | // string.length() -> string.length 93 | if (utilx.isString(o) && p === 'length' && l === 0) { 94 | return { 95 | stats: STATS.SUCCESS, 96 | value: calleer.value 97 | } 98 | } 99 | 100 | // array.size() -> array.length 101 | if (utilx.isArray(o) && p === 'size' && l === 0) { 102 | return { 103 | stats: STATS.SUCCESS, 104 | value: o.length 105 | } 106 | } 107 | 108 | // array.isEmpty() -> !array.length 109 | if (utilx.isArray(o) && p === 'isEmpty' && l === 0) { 110 | return { 111 | stats: STATS.SUCCESS, 112 | value: !o.length 113 | } 114 | } 115 | 116 | // object.entrySet() -> [{key: k, value: v}, ...] 117 | if (utilx.isObject(o) && p === 'entrySet' && l === 0) { 118 | return { 119 | stats: STATS.SUCCESS, 120 | value: Object.keys(o).map(function(k) { 121 | return { 122 | key: k, 123 | value: o[k] 124 | } 125 | }) 126 | } 127 | } 128 | 129 | // object.keySet() -> [key1, key2, ...] 130 | if (utilx.isObject(o) && p === 'keySet' && l === 0) { 131 | return { 132 | stats: STATS.SUCCESS, 133 | value: Object.keys(o) 134 | } 135 | } 136 | 137 | 138 | var isArrOrObj = utilx.isArray(o) || utilx.isObject(o) 139 | var arg0 = argsv[0] 140 | var arg1 = argsv[1] 141 | 142 | // array.get(idx) -> array[idx] 143 | // object.get(key) -> object[key] 144 | if (isArrOrObj && p === 'get' && l === 1 && validateProp(arg0)) { 145 | return { 146 | stats: STATS.SUCCESS, 147 | value: o[arg0] 148 | } 149 | } 150 | 151 | // object.getKey() -> object[key] 152 | if (isArrOrObj && p.indexOf('get') === 0 && p.length > 3 && l === 0) { 153 | return { 154 | stats: STATS.SUCCESS, 155 | value: o[extractProp(p)] 156 | } 157 | } 158 | 159 | // array.set(idx, value) -> array[idx] = value 160 | // object.set(key, value) -> object[key] = value 161 | // if (isArrOrObj && p === 'set' && l === 2 && validateProp(arg0)) { 162 | // o[arg0] = arg1 163 | // return this.initSuccessInfo() 164 | // } 165 | 166 | // array.setIdx(value) -> array[idx] = value 167 | // object.setKey(value) -> object[key] = value 168 | // if (isArrOrObj && p.indexOf('set') === 0 && p.length > 3 && l === 1) { 169 | // o[extractProp(p)] = arg0 170 | // return this.initSuccessInfo() 171 | // } 172 | 173 | // object.isKey() -> object[key] 174 | if (utilx.isObject(o) && p.indexOf('is') === 0 && p.length > 2 && l === 0) { 175 | return { 176 | stats: STATS.SUCCESS, 177 | value: !!o[extractProp(p)] 178 | } 179 | } 180 | 181 | return { 182 | stats: STATS.SUCCESS, 183 | value: undefined 184 | } 185 | } 186 | 187 | try { 188 | var result = calleer.value.apply(calleer.object, argsv) 189 | // let return of set method be empty string 190 | if (result === undefined && calleer.property.indexOf('set') === 0) result = '' 191 | return { 192 | stats: STATS.SUCCESS, 193 | value: result 194 | } 195 | } catch (e) { 196 | return this.initFailInfo('Function calling error: ' + e.message, node.pos) 197 | } 198 | } 199 | } 200 | 201 | 202 | function PropIdx(node) { 203 | var obj = node.object 204 | var objr = this[obj.type](obj) 205 | var objv = objr.value 206 | 207 | if (objr.stats !== STATS.SUCCESS) return objr 208 | 209 | var prop = node.property 210 | var propr = this[prop.type](prop) 211 | var propv = propr.value 212 | 213 | if (propr.stats !== STATS.SUCCESS) return propr 214 | if (!validateProp(propr.value)) return this.initSuccessInfo() 215 | 216 | return { 217 | stats: STATS.SUCCESS, 218 | value: (objv || objv === '') ? objv[propv] : undefined, 219 | object: objv, 220 | property: propv 221 | } 222 | } 223 | 224 | 225 | function validateProp(v) { 226 | if (utilx.isInteger(v) && v >=0) return true 227 | if (utilx.isString(v) && v) return true 228 | return false 229 | } 230 | 231 | function extractProp(v) { 232 | v = v.replace(/^(get|set|is)/, '') 233 | return v[0].toLowerCase() + v.substr(1) 234 | } 235 | -------------------------------------------------------------------------------- /lib/engine/engine-stack.js: -------------------------------------------------------------------------------- 1 | var logger = require('../logger') 2 | 3 | module.exports = { 4 | 5 | pushContext: function(context) { 6 | if (this.context) { 7 | context.__parent = this.context 8 | } else { 9 | this.topContext = context 10 | } 11 | this.context = context 12 | // logger.debug('Push context', this.context) 13 | }, 14 | 15 | popContext: function() { 16 | // logger.debug('Pop context', this.context) 17 | if ('__parent' in this.context) { 18 | this.context = this.context.__parent 19 | } else { 20 | ; delete this.context 21 | ; delete this.topContext 22 | } 23 | }, 24 | 25 | pushTemplate: function(template) { 26 | // logger.debug('Push template', template.raw) 27 | 28 | // NOTE 29 | // because of global macro and define 30 | // a template may occurs more than one time in a template chain 31 | // e.g. ROOT<-A1<-B<-A2 32 | // A1 and A2 are the same thing 33 | // so when A2 is pushed to the chain 34 | // A.__parent points to B 35 | // we only use the chain top, so it dose't matter that A1.__parent is not right now 36 | // when A2 is popped out 37 | // we need A1.__parent points to ROOT 38 | // so .__history is required to do this thing 39 | template.__history = template.__history || [] 40 | template.__history.push(template.__parent) 41 | template.__parent = this.template 42 | template.__macro = template.__macro || {} 43 | template.__define = template.__define || {} 44 | this.template = template 45 | }, 46 | 47 | popTemplate: function() { 48 | // logger.debug('Pop template', this.template.raw) 49 | var templ = this.template 50 | var newTop = templ.__parent 51 | templ.__parent = templ.__history.pop() 52 | this.template = newTop 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/engine/engine-stats.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | STOP: 'STOP', 3 | BREAK: 'BREAK', 4 | FAIL: 'FAIL', 5 | SUCCESS: 'SUCCESS' 6 | } 7 | -------------------------------------------------------------------------------- /lib/engine/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var utilx = require('utilx') 4 | 5 | var logger = require('../logger') 6 | var common = require('../common') 7 | var handleCfg = require('../handle-cfg') 8 | 9 | var parser = require('./velocity') 10 | var STATS = require('./engine-stats') 11 | 12 | function Engine(cfg) { 13 | this.cfg = handleCfg(cfg) 14 | 15 | // global macro 16 | this.macro = {} 17 | if (this.cfg.macro) { 18 | this.GMacro(this.cfg.macro) 19 | } 20 | } 21 | 22 | // process global macro 23 | Engine.prototype.GMacro = function(macro) { 24 | var that = this 25 | 26 | macro.forEach(function(m) { 27 | var content = m.isFile ? utilx.readFile(m.fullPath, that.cfg.encoding) : m.raw 28 | var ast = parser.parse(content) 29 | m.lines = content.split(/\r?\n/) 30 | ast.body.forEach(function(node){ 31 | if (node.type === 'Macro') { 32 | that.Macro(node, m) 33 | } 34 | }) 35 | }) 36 | 37 | // logger.debug('Macro', this.macro) 38 | } 39 | 40 | //////////////////////////// 41 | // the only public method // 42 | //////////////////////////// 43 | Engine.prototype.render = function(context) { 44 | if (!context) { 45 | logger.error('context is required.') 46 | } 47 | 48 | context = common.perfectContext(context) 49 | 50 | var result = this.Render(context) 51 | 52 | if (result.stats === STATS.SUCCESS || result.stats === STATS.STOP) { 53 | if (this.cfg.output) fs.writeFileSync(this.cfg.output, result.value) 54 | return result.value 55 | 56 | } else { 57 | var origin = result.stack[0] 58 | var originTempl = origin[0] 59 | var originPos = origin[1] 60 | var originLine = originTempl.lines[originPos.first_line - 1] 61 | var stack = result.stack.map(function(item){ 62 | return common.loc2str(item) 63 | }) 64 | 65 | var e = new Error(result.message || 'Illegal #break.') 66 | e.stack = common.markError(originLine, originPos) + 67 | 'Error: ' + e.message + 68 | '\n at ' + 69 | stack.join('\n at ') 70 | throw e 71 | } 72 | } 73 | 74 | Engine.prototype.Render = function(context, template) { 75 | var that = this 76 | var templ = template || this.cfg.template 77 | 78 | // some time may not pass context in 79 | // e.g. called by .DString() 80 | if (context) this.pushContext(context) 81 | // very time call this method will pass template in 82 | // except the first time called by .render() 83 | this.pushTemplate(templ) 84 | 85 | var content = templ.raw 86 | if (templ.isFile) { 87 | content = utilx.readFile(templ.fullPath, this.cfg.encoding) 88 | } 89 | 90 | templ.lines = content.split(/\r?\n/) 91 | 92 | var rt 93 | try { 94 | var node = parser.parse(content) 95 | rt = this[node.type](node) 96 | } catch (e) { 97 | rt = this.initFailInfo(e.message, common.getPosFromParserError(e)) 98 | } 99 | 100 | this.popTemplate() 101 | if (context) this.popContext() 102 | 103 | return rt 104 | } 105 | 106 | Engine.prototype.Statements = function(node) { 107 | var result = this.initSuccessInfo() 108 | 109 | for (var i = 0; i < node.body.length; i++) { 110 | var cn = node.body[i] 111 | var cr = this[cn.type](cn) 112 | 113 | if (cr.stats === STATS.SUCCESS) { 114 | if (cn.type === 'Reference' && (cr.value === undefined || cr.value === null)) { 115 | if (!cr.silent) { 116 | result.value += cr.literal 117 | } 118 | 119 | } else { 120 | var v = cr.value 121 | // capture error thrown by toString method 122 | if (v && utilx.isFunction(v.toString)) { 123 | try { 124 | v = v.toString() 125 | } catch (e) { 126 | var newCr = this.initFailInfo('An error thrown by toString method\n\n' + e.stack + '\n', cn.pos) 127 | this.mergeResult(result, newCr) 128 | break 129 | } 130 | } 131 | result.value += v 132 | } 133 | 134 | } else { 135 | this.mergeResult(result, cr) 136 | break 137 | } 138 | } 139 | 140 | return result 141 | } 142 | 143 | 144 | utilx.mix( 145 | Engine.prototype, 146 | require('./engine-stack'), 147 | require('./engine-ref'), 148 | require('./engine-expr'), 149 | require('./engine-direc') 150 | ) 151 | 152 | 153 | // below are some assistant methods 154 | Engine.prototype.initSuccessInfo = function() { 155 | return { 156 | stats: STATS.SUCCESS, 157 | value: '' 158 | } 159 | } 160 | 161 | Engine.prototype.initFailInfo = function(msg, pos) { 162 | return { 163 | stats: STATS.FAIL, 164 | value: '', 165 | message: msg, 166 | stack: [common.getRealLoc([this.template, pos])] 167 | } 168 | } 169 | 170 | // merge not successful result 171 | Engine.prototype.mergeResult = function(target, src) { 172 | target.stats = src.stats 173 | target.value += src.value 174 | target.stack = src.stack 175 | if (src.stats === STATS.FAIL) 176 | target.message = src.message 177 | } 178 | 179 | module.exports = Engine 180 | -------------------------------------------------------------------------------- /lib/engine/lex.js: -------------------------------------------------------------------------------- 1 | /* parser generated by jison 0.4.13 */ 2 | /* 3 | Returns a Parser object of the following structure: 4 | 5 | Parser: { 6 | yy: {} 7 | } 8 | 9 | Parser.prototype: { 10 | yy: {}, 11 | trace: function(), 12 | symbols_: {associative list: name ==> number}, 13 | terminals_: {associative list: number ==> name}, 14 | productions_: [...], 15 | performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), 16 | table: [...], 17 | defaultActions: {...}, 18 | parseError: function(str, hash), 19 | parse: function(input), 20 | 21 | lexer: { 22 | EOF: 1, 23 | parseError: function(str, hash), 24 | setInput: function(input), 25 | input: function(), 26 | unput: function(str), 27 | more: function(), 28 | less: function(n), 29 | pastInput: function(), 30 | upcomingInput: function(), 31 | showPosition: function(), 32 | test_match: function(regex_match_array, rule_index), 33 | next: function(), 34 | lex: function(), 35 | begin: function(condition), 36 | popState: function(), 37 | _currentRules: function(), 38 | topState: function(), 39 | pushState: function(condition), 40 | 41 | options: { 42 | ranges: boolean (optional: true ==> token location info will include a .range[] member) 43 | flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) 44 | backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) 45 | }, 46 | 47 | performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), 48 | rules: [...], 49 | conditions: {associative list: name ==> set}, 50 | } 51 | } 52 | 53 | 54 | token location info (@$, _$, etc.): { 55 | first_line: n, 56 | last_line: n, 57 | first_column: n, 58 | last_column: n, 59 | range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) 60 | } 61 | 62 | 63 | the parseError function receives a 'hash' object with these members for lexer and parser errors: { 64 | text: (matched text) 65 | token: (the produced terminal token, if any) 66 | line: (yylineno) 67 | } 68 | while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { 69 | loc: (yylloc) 70 | expected: (string describing the set of expected tokens) 71 | recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) 72 | } 73 | */ 74 | var lex = (function(){ 75 | var parser = {trace: function trace() { }, 76 | yy: {}, 77 | symbols_: {"error":2,"root":3,"EOF":4,"statements":5,"statement":6,"TEXT":7,"BTEXT":8,"COMMENT":9,"BCOMMENT":10,"ID":11,"PROP":12,"$":13,"(":14,")":15,"[":16,"]":17,"{":18,"}":19,":":20,",":21,";":22,"..":23,"IN":24,"TRUE":25,"FALSE":26,"NULL":27,"==":28,"!=":29,">=":30,"<=":31,">":32,"<":33,"&&":34,"||":35,"!":36,"+":37,"-":38,"*":39,"/":40,"%":41,"=":42,"FLOAT":43,"INTEGER":44,"DSTRING":45,"STRING":46,"SET":47,"IF":48,"ELSEIF":49,"ELSE":50,"END":51,"FOREACH":52,"INCLUDE":53,"PARSE":54,"STOP":55,"BREAK":56,"EVALUATE":57,"DEFINE":58,"MACRO":59,"MACROCALL":60,"BMACROCALL":61,"WS":62,"$accept":0,"$end":1}, 78 | terminals_: {2:"error",4:"EOF",7:"TEXT",8:"BTEXT",9:"COMMENT",10:"BCOMMENT",11:"ID",12:"PROP",13:"$",14:"(",15:")",16:"[",17:"]",18:"{",19:"}",20:":",21:",",22:";",23:"..",24:"IN",25:"TRUE",26:"FALSE",27:"NULL",28:"==",29:"!=",30:">=",31:"<=",32:">",33:"<",34:"&&",35:"||",36:"!",37:"+",38:"-",39:"*",40:"/",41:"%",42:"=",43:"FLOAT",44:"INTEGER",45:"DSTRING",46:"STRING",47:"SET",48:"IF",49:"ELSEIF",50:"ELSE",51:"END",52:"FOREACH",53:"INCLUDE",54:"PARSE",55:"STOP",56:"BREAK",57:"EVALUATE",58:"DEFINE",59:"MACRO",60:"MACROCALL",61:"BMACROCALL",62:"WS"}, 79 | productions_: [0,[3,1],[3,2],[5,1],[5,2],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1]], 80 | performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { 81 | /* this == yyval */ 82 | 83 | var $0 = $$.length - 1; 84 | switch (yystate) { 85 | case 1: return []; 86 | break; 87 | case 2: return $$[$0-1]; 88 | break; 89 | case 3: this.$ = [$$[$0]]; 90 | break; 91 | case 4: this.$ = [$$[$0-1]].concat($$[$0]); 92 | break; 93 | case 5: this.$ = ['TEXT', $$[$0]]; 94 | break; 95 | case 6: this.$ = ['BTEXT', $$[$0]]; 96 | break; 97 | case 7: this.$ = ['COMMENT', $$[$0]]; 98 | break; 99 | case 8: this.$ = ['BCOMMENT', $$[$0]]; 100 | break; 101 | case 9: this.$ = ['ID', $$[$0]]; 102 | break; 103 | case 10: this.$ = ['PROP', $$[$0]]; 104 | break; 105 | case 11: this.$ = ['$']; 106 | break; 107 | case 12: this.$ = ['(']; 108 | break; 109 | case 13: this.$ = [')']; 110 | break; 111 | case 14: this.$ = ['[']; 112 | break; 113 | case 15: this.$ = [']']; 114 | break; 115 | case 16: this.$ = ['{']; 116 | break; 117 | case 17: this.$ = ['}']; 118 | break; 119 | case 18: this.$ = [':']; 120 | break; 121 | case 19: this.$ = [',']; 122 | break; 123 | case 20: this.$ = [';']; 124 | break; 125 | case 21: this.$ = ['..']; 126 | break; 127 | case 22: this.$ = ['IN']; 128 | break; 129 | case 23: this.$ = ['TRUE']; 130 | break; 131 | case 24: this.$ = ['FALSE']; 132 | break; 133 | case 25: this.$ = ['NULL']; 134 | break; 135 | case 26: this.$ = [$$[$0]]; 136 | break; 137 | case 27: this.$ = [$$[$0]]; 138 | break; 139 | case 28: this.$ = [$$[$0]]; 140 | break; 141 | case 29: this.$ = [$$[$0]]; 142 | break; 143 | case 30: this.$ = [$$[$0]]; 144 | break; 145 | case 31: this.$ = [$$[$0]]; 146 | break; 147 | case 32: this.$ = [$$[$0]]; 148 | break; 149 | case 33: this.$ = [$$[$0]]; 150 | break; 151 | case 34: this.$ = [$$[$0]]; 152 | break; 153 | case 35: this.$ = [$$[$0]]; 154 | break; 155 | case 36: this.$ = [$$[$0]]; 156 | break; 157 | case 37: this.$ = [$$[$0]]; 158 | break; 159 | case 38: this.$ = [$$[$0]]; 160 | break; 161 | case 39: this.$ = [$$[$0]]; 162 | break; 163 | case 40: this.$ = [$$[$0]]; 164 | break; 165 | case 41: this.$ = ['FLOAT', $$[$0]]; 166 | break; 167 | case 42: this.$ = ['INTEGER', $$[$0]]; 168 | break; 169 | case 43: this.$ = ['DSTRING', $$[$0]]; 170 | break; 171 | case 44: this.$ = ['STRING', $$[$0]]; 172 | break; 173 | case 45: this.$ = ['SET', $$[$0]]; 174 | break; 175 | case 46: this.$ = ['IF', $$[$0]]; 176 | break; 177 | case 47: this.$ = ['ELSEIF', $$[$0]]; 178 | break; 179 | case 48: this.$ = ['ELSE', $$[$0]]; 180 | break; 181 | case 49: this.$ = ['END', $$[$0]]; 182 | break; 183 | case 50: this.$ = ['FOREACH', $$[$0]]; 184 | break; 185 | case 51: this.$ = ['INCLUDE', $$[$0]]; 186 | break; 187 | case 52: this.$ = ['PARSE', $$[$0]]; 188 | break; 189 | case 53: this.$ = ['STOP', $$[$0]]; 190 | break; 191 | case 54: this.$ = ['BREAK', $$[$0]]; 192 | break; 193 | case 55: this.$ = ['EVALUATE', $$[$0]]; 194 | break; 195 | case 56: this.$ = ['DEFINE', $$[$0]]; 196 | break; 197 | case 57: this.$ = ['MACRO', $$[$0]]; 198 | break; 199 | case 58: this.$ = ['MACROCALL', $$[$0]]; 200 | break; 201 | case 59: this.$ = ['BMACROCALL', $$[$0]]; 202 | break; 203 | case 60: this.$ = ['WS']; 204 | break; 205 | } 206 | }, 207 | table: [{3:1,4:[1,2],5:3,6:4,7:[1,5],8:[1,6],9:[1,7],10:[1,8],11:[1,9],12:[1,10],13:[1,11],14:[1,12],15:[1,13],16:[1,14],17:[1,15],18:[1,16],19:[1,17],20:[1,18],21:[1,19],22:[1,20],23:[1,21],24:[1,22],25:[1,23],26:[1,24],27:[1,25],28:[1,26],29:[1,27],30:[1,28],31:[1,29],32:[1,30],33:[1,31],34:[1,32],35:[1,33],36:[1,34],37:[1,35],38:[1,36],39:[1,37],40:[1,38],41:[1,39],42:[1,40],43:[1,41],44:[1,42],45:[1,43],46:[1,44],47:[1,45],48:[1,46],49:[1,47],50:[1,48],51:[1,49],52:[1,50],53:[1,51],54:[1,52],55:[1,53],56:[1,54],57:[1,55],58:[1,56],59:[1,57],60:[1,58],61:[1,59],62:[1,60]},{1:[3]},{1:[2,1]},{4:[1,61]},{4:[2,3],5:62,6:4,7:[1,5],8:[1,6],9:[1,7],10:[1,8],11:[1,9],12:[1,10],13:[1,11],14:[1,12],15:[1,13],16:[1,14],17:[1,15],18:[1,16],19:[1,17],20:[1,18],21:[1,19],22:[1,20],23:[1,21],24:[1,22],25:[1,23],26:[1,24],27:[1,25],28:[1,26],29:[1,27],30:[1,28],31:[1,29],32:[1,30],33:[1,31],34:[1,32],35:[1,33],36:[1,34],37:[1,35],38:[1,36],39:[1,37],40:[1,38],41:[1,39],42:[1,40],43:[1,41],44:[1,42],45:[1,43],46:[1,44],47:[1,45],48:[1,46],49:[1,47],50:[1,48],51:[1,49],52:[1,50],53:[1,51],54:[1,52],55:[1,53],56:[1,54],57:[1,55],58:[1,56],59:[1,57],60:[1,58],61:[1,59],62:[1,60]},{4:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[2,5],17:[2,5],18:[2,5],19:[2,5],20:[2,5],21:[2,5],22:[2,5],23:[2,5],24:[2,5],25:[2,5],26:[2,5],27:[2,5],28:[2,5],29:[2,5],30:[2,5],31:[2,5],32:[2,5],33:[2,5],34:[2,5],35:[2,5],36:[2,5],37:[2,5],38:[2,5],39:[2,5],40:[2,5],41:[2,5],42:[2,5],43:[2,5],44:[2,5],45:[2,5],46:[2,5],47:[2,5],48:[2,5],49:[2,5],50:[2,5],51:[2,5],52:[2,5],53:[2,5],54:[2,5],55:[2,5],56:[2,5],57:[2,5],58:[2,5],59:[2,5],60:[2,5],61:[2,5],62:[2,5]},{4:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[2,6],17:[2,6],18:[2,6],19:[2,6],20:[2,6],21:[2,6],22:[2,6],23:[2,6],24:[2,6],25:[2,6],26:[2,6],27:[2,6],28:[2,6],29:[2,6],30:[2,6],31:[2,6],32:[2,6],33:[2,6],34:[2,6],35:[2,6],36:[2,6],37:[2,6],38:[2,6],39:[2,6],40:[2,6],41:[2,6],42:[2,6],43:[2,6],44:[2,6],45:[2,6],46:[2,6],47:[2,6],48:[2,6],49:[2,6],50:[2,6],51:[2,6],52:[2,6],53:[2,6],54:[2,6],55:[2,6],56:[2,6],57:[2,6],58:[2,6],59:[2,6],60:[2,6],61:[2,6],62:[2,6]},{4:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[2,7],17:[2,7],18:[2,7],19:[2,7],20:[2,7],21:[2,7],22:[2,7],23:[2,7],24:[2,7],25:[2,7],26:[2,7],27:[2,7],28:[2,7],29:[2,7],30:[2,7],31:[2,7],32:[2,7],33:[2,7],34:[2,7],35:[2,7],36:[2,7],37:[2,7],38:[2,7],39:[2,7],40:[2,7],41:[2,7],42:[2,7],43:[2,7],44:[2,7],45:[2,7],46:[2,7],47:[2,7],48:[2,7],49:[2,7],50:[2,7],51:[2,7],52:[2,7],53:[2,7],54:[2,7],55:[2,7],56:[2,7],57:[2,7],58:[2,7],59:[2,7],60:[2,7],61:[2,7],62:[2,7]},{4:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[2,8],17:[2,8],18:[2,8],19:[2,8],20:[2,8],21:[2,8],22:[2,8],23:[2,8],24:[2,8],25:[2,8],26:[2,8],27:[2,8],28:[2,8],29:[2,8],30:[2,8],31:[2,8],32:[2,8],33:[2,8],34:[2,8],35:[2,8],36:[2,8],37:[2,8],38:[2,8],39:[2,8],40:[2,8],41:[2,8],42:[2,8],43:[2,8],44:[2,8],45:[2,8],46:[2,8],47:[2,8],48:[2,8],49:[2,8],50:[2,8],51:[2,8],52:[2,8],53:[2,8],54:[2,8],55:[2,8],56:[2,8],57:[2,8],58:[2,8],59:[2,8],60:[2,8],61:[2,8],62:[2,8]},{4:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[2,9],17:[2,9],18:[2,9],19:[2,9],20:[2,9],21:[2,9],22:[2,9],23:[2,9],24:[2,9],25:[2,9],26:[2,9],27:[2,9],28:[2,9],29:[2,9],30:[2,9],31:[2,9],32:[2,9],33:[2,9],34:[2,9],35:[2,9],36:[2,9],37:[2,9],38:[2,9],39:[2,9],40:[2,9],41:[2,9],42:[2,9],43:[2,9],44:[2,9],45:[2,9],46:[2,9],47:[2,9],48:[2,9],49:[2,9],50:[2,9],51:[2,9],52:[2,9],53:[2,9],54:[2,9],55:[2,9],56:[2,9],57:[2,9],58:[2,9],59:[2,9],60:[2,9],61:[2,9],62:[2,9]},{4:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[2,10],17:[2,10],18:[2,10],19:[2,10],20:[2,10],21:[2,10],22:[2,10],23:[2,10],24:[2,10],25:[2,10],26:[2,10],27:[2,10],28:[2,10],29:[2,10],30:[2,10],31:[2,10],32:[2,10],33:[2,10],34:[2,10],35:[2,10],36:[2,10],37:[2,10],38:[2,10],39:[2,10],40:[2,10],41:[2,10],42:[2,10],43:[2,10],44:[2,10],45:[2,10],46:[2,10],47:[2,10],48:[2,10],49:[2,10],50:[2,10],51:[2,10],52:[2,10],53:[2,10],54:[2,10],55:[2,10],56:[2,10],57:[2,10],58:[2,10],59:[2,10],60:[2,10],61:[2,10],62:[2,10]},{4:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],17:[2,11],18:[2,11],19:[2,11],20:[2,11],21:[2,11],22:[2,11],23:[2,11],24:[2,11],25:[2,11],26:[2,11],27:[2,11],28:[2,11],29:[2,11],30:[2,11],31:[2,11],32:[2,11],33:[2,11],34:[2,11],35:[2,11],36:[2,11],37:[2,11],38:[2,11],39:[2,11],40:[2,11],41:[2,11],42:[2,11],43:[2,11],44:[2,11],45:[2,11],46:[2,11],47:[2,11],48:[2,11],49:[2,11],50:[2,11],51:[2,11],52:[2,11],53:[2,11],54:[2,11],55:[2,11],56:[2,11],57:[2,11],58:[2,11],59:[2,11],60:[2,11],61:[2,11],62:[2,11]},{4:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],17:[2,12],18:[2,12],19:[2,12],20:[2,12],21:[2,12],22:[2,12],23:[2,12],24:[2,12],25:[2,12],26:[2,12],27:[2,12],28:[2,12],29:[2,12],30:[2,12],31:[2,12],32:[2,12],33:[2,12],34:[2,12],35:[2,12],36:[2,12],37:[2,12],38:[2,12],39:[2,12],40:[2,12],41:[2,12],42:[2,12],43:[2,12],44:[2,12],45:[2,12],46:[2,12],47:[2,12],48:[2,12],49:[2,12],50:[2,12],51:[2,12],52:[2,12],53:[2,12],54:[2,12],55:[2,12],56:[2,12],57:[2,12],58:[2,12],59:[2,12],60:[2,12],61:[2,12],62:[2,12]},{4:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],17:[2,13],18:[2,13],19:[2,13],20:[2,13],21:[2,13],22:[2,13],23:[2,13],24:[2,13],25:[2,13],26:[2,13],27:[2,13],28:[2,13],29:[2,13],30:[2,13],31:[2,13],32:[2,13],33:[2,13],34:[2,13],35:[2,13],36:[2,13],37:[2,13],38:[2,13],39:[2,13],40:[2,13],41:[2,13],42:[2,13],43:[2,13],44:[2,13],45:[2,13],46:[2,13],47:[2,13],48:[2,13],49:[2,13],50:[2,13],51:[2,13],52:[2,13],53:[2,13],54:[2,13],55:[2,13],56:[2,13],57:[2,13],58:[2,13],59:[2,13],60:[2,13],61:[2,13],62:[2,13]},{4:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],17:[2,14],18:[2,14],19:[2,14],20:[2,14],21:[2,14],22:[2,14],23:[2,14],24:[2,14],25:[2,14],26:[2,14],27:[2,14],28:[2,14],29:[2,14],30:[2,14],31:[2,14],32:[2,14],33:[2,14],34:[2,14],35:[2,14],36:[2,14],37:[2,14],38:[2,14],39:[2,14],40:[2,14],41:[2,14],42:[2,14],43:[2,14],44:[2,14],45:[2,14],46:[2,14],47:[2,14],48:[2,14],49:[2,14],50:[2,14],51:[2,14],52:[2,14],53:[2,14],54:[2,14],55:[2,14],56:[2,14],57:[2,14],58:[2,14],59:[2,14],60:[2,14],61:[2,14],62:[2,14]},{4:[2,15],7:[2,15],8:[2,15],9:[2,15],10:[2,15],11:[2,15],12:[2,15],13:[2,15],14:[2,15],15:[2,15],16:[2,15],17:[2,15],18:[2,15],19:[2,15],20:[2,15],21:[2,15],22:[2,15],23:[2,15],24:[2,15],25:[2,15],26:[2,15],27:[2,15],28:[2,15],29:[2,15],30:[2,15],31:[2,15],32:[2,15],33:[2,15],34:[2,15],35:[2,15],36:[2,15],37:[2,15],38:[2,15],39:[2,15],40:[2,15],41:[2,15],42:[2,15],43:[2,15],44:[2,15],45:[2,15],46:[2,15],47:[2,15],48:[2,15],49:[2,15],50:[2,15],51:[2,15],52:[2,15],53:[2,15],54:[2,15],55:[2,15],56:[2,15],57:[2,15],58:[2,15],59:[2,15],60:[2,15],61:[2,15],62:[2,15]},{4:[2,16],7:[2,16],8:[2,16],9:[2,16],10:[2,16],11:[2,16],12:[2,16],13:[2,16],14:[2,16],15:[2,16],16:[2,16],17:[2,16],18:[2,16],19:[2,16],20:[2,16],21:[2,16],22:[2,16],23:[2,16],24:[2,16],25:[2,16],26:[2,16],27:[2,16],28:[2,16],29:[2,16],30:[2,16],31:[2,16],32:[2,16],33:[2,16],34:[2,16],35:[2,16],36:[2,16],37:[2,16],38:[2,16],39:[2,16],40:[2,16],41:[2,16],42:[2,16],43:[2,16],44:[2,16],45:[2,16],46:[2,16],47:[2,16],48:[2,16],49:[2,16],50:[2,16],51:[2,16],52:[2,16],53:[2,16],54:[2,16],55:[2,16],56:[2,16],57:[2,16],58:[2,16],59:[2,16],60:[2,16],61:[2,16],62:[2,16]},{4:[2,17],7:[2,17],8:[2,17],9:[2,17],10:[2,17],11:[2,17],12:[2,17],13:[2,17],14:[2,17],15:[2,17],16:[2,17],17:[2,17],18:[2,17],19:[2,17],20:[2,17],21:[2,17],22:[2,17],23:[2,17],24:[2,17],25:[2,17],26:[2,17],27:[2,17],28:[2,17],29:[2,17],30:[2,17],31:[2,17],32:[2,17],33:[2,17],34:[2,17],35:[2,17],36:[2,17],37:[2,17],38:[2,17],39:[2,17],40:[2,17],41:[2,17],42:[2,17],43:[2,17],44:[2,17],45:[2,17],46:[2,17],47:[2,17],48:[2,17],49:[2,17],50:[2,17],51:[2,17],52:[2,17],53:[2,17],54:[2,17],55:[2,17],56:[2,17],57:[2,17],58:[2,17],59:[2,17],60:[2,17],61:[2,17],62:[2,17]},{4:[2,18],7:[2,18],8:[2,18],9:[2,18],10:[2,18],11:[2,18],12:[2,18],13:[2,18],14:[2,18],15:[2,18],16:[2,18],17:[2,18],18:[2,18],19:[2,18],20:[2,18],21:[2,18],22:[2,18],23:[2,18],24:[2,18],25:[2,18],26:[2,18],27:[2,18],28:[2,18],29:[2,18],30:[2,18],31:[2,18],32:[2,18],33:[2,18],34:[2,18],35:[2,18],36:[2,18],37:[2,18],38:[2,18],39:[2,18],40:[2,18],41:[2,18],42:[2,18],43:[2,18],44:[2,18],45:[2,18],46:[2,18],47:[2,18],48:[2,18],49:[2,18],50:[2,18],51:[2,18],52:[2,18],53:[2,18],54:[2,18],55:[2,18],56:[2,18],57:[2,18],58:[2,18],59:[2,18],60:[2,18],61:[2,18],62:[2,18]},{4:[2,19],7:[2,19],8:[2,19],9:[2,19],10:[2,19],11:[2,19],12:[2,19],13:[2,19],14:[2,19],15:[2,19],16:[2,19],17:[2,19],18:[2,19],19:[2,19],20:[2,19],21:[2,19],22:[2,19],23:[2,19],24:[2,19],25:[2,19],26:[2,19],27:[2,19],28:[2,19],29:[2,19],30:[2,19],31:[2,19],32:[2,19],33:[2,19],34:[2,19],35:[2,19],36:[2,19],37:[2,19],38:[2,19],39:[2,19],40:[2,19],41:[2,19],42:[2,19],43:[2,19],44:[2,19],45:[2,19],46:[2,19],47:[2,19],48:[2,19],49:[2,19],50:[2,19],51:[2,19],52:[2,19],53:[2,19],54:[2,19],55:[2,19],56:[2,19],57:[2,19],58:[2,19],59:[2,19],60:[2,19],61:[2,19],62:[2,19]},{4:[2,20],7:[2,20],8:[2,20],9:[2,20],10:[2,20],11:[2,20],12:[2,20],13:[2,20],14:[2,20],15:[2,20],16:[2,20],17:[2,20],18:[2,20],19:[2,20],20:[2,20],21:[2,20],22:[2,20],23:[2,20],24:[2,20],25:[2,20],26:[2,20],27:[2,20],28:[2,20],29:[2,20],30:[2,20],31:[2,20],32:[2,20],33:[2,20],34:[2,20],35:[2,20],36:[2,20],37:[2,20],38:[2,20],39:[2,20],40:[2,20],41:[2,20],42:[2,20],43:[2,20],44:[2,20],45:[2,20],46:[2,20],47:[2,20],48:[2,20],49:[2,20],50:[2,20],51:[2,20],52:[2,20],53:[2,20],54:[2,20],55:[2,20],56:[2,20],57:[2,20],58:[2,20],59:[2,20],60:[2,20],61:[2,20],62:[2,20]},{4:[2,21],7:[2,21],8:[2,21],9:[2,21],10:[2,21],11:[2,21],12:[2,21],13:[2,21],14:[2,21],15:[2,21],16:[2,21],17:[2,21],18:[2,21],19:[2,21],20:[2,21],21:[2,21],22:[2,21],23:[2,21],24:[2,21],25:[2,21],26:[2,21],27:[2,21],28:[2,21],29:[2,21],30:[2,21],31:[2,21],32:[2,21],33:[2,21],34:[2,21],35:[2,21],36:[2,21],37:[2,21],38:[2,21],39:[2,21],40:[2,21],41:[2,21],42:[2,21],43:[2,21],44:[2,21],45:[2,21],46:[2,21],47:[2,21],48:[2,21],49:[2,21],50:[2,21],51:[2,21],52:[2,21],53:[2,21],54:[2,21],55:[2,21],56:[2,21],57:[2,21],58:[2,21],59:[2,21],60:[2,21],61:[2,21],62:[2,21]},{4:[2,22],7:[2,22],8:[2,22],9:[2,22],10:[2,22],11:[2,22],12:[2,22],13:[2,22],14:[2,22],15:[2,22],16:[2,22],17:[2,22],18:[2,22],19:[2,22],20:[2,22],21:[2,22],22:[2,22],23:[2,22],24:[2,22],25:[2,22],26:[2,22],27:[2,22],28:[2,22],29:[2,22],30:[2,22],31:[2,22],32:[2,22],33:[2,22],34:[2,22],35:[2,22],36:[2,22],37:[2,22],38:[2,22],39:[2,22],40:[2,22],41:[2,22],42:[2,22],43:[2,22],44:[2,22],45:[2,22],46:[2,22],47:[2,22],48:[2,22],49:[2,22],50:[2,22],51:[2,22],52:[2,22],53:[2,22],54:[2,22],55:[2,22],56:[2,22],57:[2,22],58:[2,22],59:[2,22],60:[2,22],61:[2,22],62:[2,22]},{4:[2,23],7:[2,23],8:[2,23],9:[2,23],10:[2,23],11:[2,23],12:[2,23],13:[2,23],14:[2,23],15:[2,23],16:[2,23],17:[2,23],18:[2,23],19:[2,23],20:[2,23],21:[2,23],22:[2,23],23:[2,23],24:[2,23],25:[2,23],26:[2,23],27:[2,23],28:[2,23],29:[2,23],30:[2,23],31:[2,23],32:[2,23],33:[2,23],34:[2,23],35:[2,23],36:[2,23],37:[2,23],38:[2,23],39:[2,23],40:[2,23],41:[2,23],42:[2,23],43:[2,23],44:[2,23],45:[2,23],46:[2,23],47:[2,23],48:[2,23],49:[2,23],50:[2,23],51:[2,23],52:[2,23],53:[2,23],54:[2,23],55:[2,23],56:[2,23],57:[2,23],58:[2,23],59:[2,23],60:[2,23],61:[2,23],62:[2,23]},{4:[2,24],7:[2,24],8:[2,24],9:[2,24],10:[2,24],11:[2,24],12:[2,24],13:[2,24],14:[2,24],15:[2,24],16:[2,24],17:[2,24],18:[2,24],19:[2,24],20:[2,24],21:[2,24],22:[2,24],23:[2,24],24:[2,24],25:[2,24],26:[2,24],27:[2,24],28:[2,24],29:[2,24],30:[2,24],31:[2,24],32:[2,24],33:[2,24],34:[2,24],35:[2,24],36:[2,24],37:[2,24],38:[2,24],39:[2,24],40:[2,24],41:[2,24],42:[2,24],43:[2,24],44:[2,24],45:[2,24],46:[2,24],47:[2,24],48:[2,24],49:[2,24],50:[2,24],51:[2,24],52:[2,24],53:[2,24],54:[2,24],55:[2,24],56:[2,24],57:[2,24],58:[2,24],59:[2,24],60:[2,24],61:[2,24],62:[2,24]},{4:[2,25],7:[2,25],8:[2,25],9:[2,25],10:[2,25],11:[2,25],12:[2,25],13:[2,25],14:[2,25],15:[2,25],16:[2,25],17:[2,25],18:[2,25],19:[2,25],20:[2,25],21:[2,25],22:[2,25],23:[2,25],24:[2,25],25:[2,25],26:[2,25],27:[2,25],28:[2,25],29:[2,25],30:[2,25],31:[2,25],32:[2,25],33:[2,25],34:[2,25],35:[2,25],36:[2,25],37:[2,25],38:[2,25],39:[2,25],40:[2,25],41:[2,25],42:[2,25],43:[2,25],44:[2,25],45:[2,25],46:[2,25],47:[2,25],48:[2,25],49:[2,25],50:[2,25],51:[2,25],52:[2,25],53:[2,25],54:[2,25],55:[2,25],56:[2,25],57:[2,25],58:[2,25],59:[2,25],60:[2,25],61:[2,25],62:[2,25]},{4:[2,26],7:[2,26],8:[2,26],9:[2,26],10:[2,26],11:[2,26],12:[2,26],13:[2,26],14:[2,26],15:[2,26],16:[2,26],17:[2,26],18:[2,26],19:[2,26],20:[2,26],21:[2,26],22:[2,26],23:[2,26],24:[2,26],25:[2,26],26:[2,26],27:[2,26],28:[2,26],29:[2,26],30:[2,26],31:[2,26],32:[2,26],33:[2,26],34:[2,26],35:[2,26],36:[2,26],37:[2,26],38:[2,26],39:[2,26],40:[2,26],41:[2,26],42:[2,26],43:[2,26],44:[2,26],45:[2,26],46:[2,26],47:[2,26],48:[2,26],49:[2,26],50:[2,26],51:[2,26],52:[2,26],53:[2,26],54:[2,26],55:[2,26],56:[2,26],57:[2,26],58:[2,26],59:[2,26],60:[2,26],61:[2,26],62:[2,26]},{4:[2,27],7:[2,27],8:[2,27],9:[2,27],10:[2,27],11:[2,27],12:[2,27],13:[2,27],14:[2,27],15:[2,27],16:[2,27],17:[2,27],18:[2,27],19:[2,27],20:[2,27],21:[2,27],22:[2,27],23:[2,27],24:[2,27],25:[2,27],26:[2,27],27:[2,27],28:[2,27],29:[2,27],30:[2,27],31:[2,27],32:[2,27],33:[2,27],34:[2,27],35:[2,27],36:[2,27],37:[2,27],38:[2,27],39:[2,27],40:[2,27],41:[2,27],42:[2,27],43:[2,27],44:[2,27],45:[2,27],46:[2,27],47:[2,27],48:[2,27],49:[2,27],50:[2,27],51:[2,27],52:[2,27],53:[2,27],54:[2,27],55:[2,27],56:[2,27],57:[2,27],58:[2,27],59:[2,27],60:[2,27],61:[2,27],62:[2,27]},{4:[2,28],7:[2,28],8:[2,28],9:[2,28],10:[2,28],11:[2,28],12:[2,28],13:[2,28],14:[2,28],15:[2,28],16:[2,28],17:[2,28],18:[2,28],19:[2,28],20:[2,28],21:[2,28],22:[2,28],23:[2,28],24:[2,28],25:[2,28],26:[2,28],27:[2,28],28:[2,28],29:[2,28],30:[2,28],31:[2,28],32:[2,28],33:[2,28],34:[2,28],35:[2,28],36:[2,28],37:[2,28],38:[2,28],39:[2,28],40:[2,28],41:[2,28],42:[2,28],43:[2,28],44:[2,28],45:[2,28],46:[2,28],47:[2,28],48:[2,28],49:[2,28],50:[2,28],51:[2,28],52:[2,28],53:[2,28],54:[2,28],55:[2,28],56:[2,28],57:[2,28],58:[2,28],59:[2,28],60:[2,28],61:[2,28],62:[2,28]},{4:[2,29],7:[2,29],8:[2,29],9:[2,29],10:[2,29],11:[2,29],12:[2,29],13:[2,29],14:[2,29],15:[2,29],16:[2,29],17:[2,29],18:[2,29],19:[2,29],20:[2,29],21:[2,29],22:[2,29],23:[2,29],24:[2,29],25:[2,29],26:[2,29],27:[2,29],28:[2,29],29:[2,29],30:[2,29],31:[2,29],32:[2,29],33:[2,29],34:[2,29],35:[2,29],36:[2,29],37:[2,29],38:[2,29],39:[2,29],40:[2,29],41:[2,29],42:[2,29],43:[2,29],44:[2,29],45:[2,29],46:[2,29],47:[2,29],48:[2,29],49:[2,29],50:[2,29],51:[2,29],52:[2,29],53:[2,29],54:[2,29],55:[2,29],56:[2,29],57:[2,29],58:[2,29],59:[2,29],60:[2,29],61:[2,29],62:[2,29]},{4:[2,30],7:[2,30],8:[2,30],9:[2,30],10:[2,30],11:[2,30],12:[2,30],13:[2,30],14:[2,30],15:[2,30],16:[2,30],17:[2,30],18:[2,30],19:[2,30],20:[2,30],21:[2,30],22:[2,30],23:[2,30],24:[2,30],25:[2,30],26:[2,30],27:[2,30],28:[2,30],29:[2,30],30:[2,30],31:[2,30],32:[2,30],33:[2,30],34:[2,30],35:[2,30],36:[2,30],37:[2,30],38:[2,30],39:[2,30],40:[2,30],41:[2,30],42:[2,30],43:[2,30],44:[2,30],45:[2,30],46:[2,30],47:[2,30],48:[2,30],49:[2,30],50:[2,30],51:[2,30],52:[2,30],53:[2,30],54:[2,30],55:[2,30],56:[2,30],57:[2,30],58:[2,30],59:[2,30],60:[2,30],61:[2,30],62:[2,30]},{4:[2,31],7:[2,31],8:[2,31],9:[2,31],10:[2,31],11:[2,31],12:[2,31],13:[2,31],14:[2,31],15:[2,31],16:[2,31],17:[2,31],18:[2,31],19:[2,31],20:[2,31],21:[2,31],22:[2,31],23:[2,31],24:[2,31],25:[2,31],26:[2,31],27:[2,31],28:[2,31],29:[2,31],30:[2,31],31:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31],37:[2,31],38:[2,31],39:[2,31],40:[2,31],41:[2,31],42:[2,31],43:[2,31],44:[2,31],45:[2,31],46:[2,31],47:[2,31],48:[2,31],49:[2,31],50:[2,31],51:[2,31],52:[2,31],53:[2,31],54:[2,31],55:[2,31],56:[2,31],57:[2,31],58:[2,31],59:[2,31],60:[2,31],61:[2,31],62:[2,31]},{4:[2,32],7:[2,32],8:[2,32],9:[2,32],10:[2,32],11:[2,32],12:[2,32],13:[2,32],14:[2,32],15:[2,32],16:[2,32],17:[2,32],18:[2,32],19:[2,32],20:[2,32],21:[2,32],22:[2,32],23:[2,32],24:[2,32],25:[2,32],26:[2,32],27:[2,32],28:[2,32],29:[2,32],30:[2,32],31:[2,32],32:[2,32],33:[2,32],34:[2,32],35:[2,32],36:[2,32],37:[2,32],38:[2,32],39:[2,32],40:[2,32],41:[2,32],42:[2,32],43:[2,32],44:[2,32],45:[2,32],46:[2,32],47:[2,32],48:[2,32],49:[2,32],50:[2,32],51:[2,32],52:[2,32],53:[2,32],54:[2,32],55:[2,32],56:[2,32],57:[2,32],58:[2,32],59:[2,32],60:[2,32],61:[2,32],62:[2,32]},{4:[2,33],7:[2,33],8:[2,33],9:[2,33],10:[2,33],11:[2,33],12:[2,33],13:[2,33],14:[2,33],15:[2,33],16:[2,33],17:[2,33],18:[2,33],19:[2,33],20:[2,33],21:[2,33],22:[2,33],23:[2,33],24:[2,33],25:[2,33],26:[2,33],27:[2,33],28:[2,33],29:[2,33],30:[2,33],31:[2,33],32:[2,33],33:[2,33],34:[2,33],35:[2,33],36:[2,33],37:[2,33],38:[2,33],39:[2,33],40:[2,33],41:[2,33],42:[2,33],43:[2,33],44:[2,33],45:[2,33],46:[2,33],47:[2,33],48:[2,33],49:[2,33],50:[2,33],51:[2,33],52:[2,33],53:[2,33],54:[2,33],55:[2,33],56:[2,33],57:[2,33],58:[2,33],59:[2,33],60:[2,33],61:[2,33],62:[2,33]},{4:[2,34],7:[2,34],8:[2,34],9:[2,34],10:[2,34],11:[2,34],12:[2,34],13:[2,34],14:[2,34],15:[2,34],16:[2,34],17:[2,34],18:[2,34],19:[2,34],20:[2,34],21:[2,34],22:[2,34],23:[2,34],24:[2,34],25:[2,34],26:[2,34],27:[2,34],28:[2,34],29:[2,34],30:[2,34],31:[2,34],32:[2,34],33:[2,34],34:[2,34],35:[2,34],36:[2,34],37:[2,34],38:[2,34],39:[2,34],40:[2,34],41:[2,34],42:[2,34],43:[2,34],44:[2,34],45:[2,34],46:[2,34],47:[2,34],48:[2,34],49:[2,34],50:[2,34],51:[2,34],52:[2,34],53:[2,34],54:[2,34],55:[2,34],56:[2,34],57:[2,34],58:[2,34],59:[2,34],60:[2,34],61:[2,34],62:[2,34]},{4:[2,35],7:[2,35],8:[2,35],9:[2,35],10:[2,35],11:[2,35],12:[2,35],13:[2,35],14:[2,35],15:[2,35],16:[2,35],17:[2,35],18:[2,35],19:[2,35],20:[2,35],21:[2,35],22:[2,35],23:[2,35],24:[2,35],25:[2,35],26:[2,35],27:[2,35],28:[2,35],29:[2,35],30:[2,35],31:[2,35],32:[2,35],33:[2,35],34:[2,35],35:[2,35],36:[2,35],37:[2,35],38:[2,35],39:[2,35],40:[2,35],41:[2,35],42:[2,35],43:[2,35],44:[2,35],45:[2,35],46:[2,35],47:[2,35],48:[2,35],49:[2,35],50:[2,35],51:[2,35],52:[2,35],53:[2,35],54:[2,35],55:[2,35],56:[2,35],57:[2,35],58:[2,35],59:[2,35],60:[2,35],61:[2,35],62:[2,35]},{4:[2,36],7:[2,36],8:[2,36],9:[2,36],10:[2,36],11:[2,36],12:[2,36],13:[2,36],14:[2,36],15:[2,36],16:[2,36],17:[2,36],18:[2,36],19:[2,36],20:[2,36],21:[2,36],22:[2,36],23:[2,36],24:[2,36],25:[2,36],26:[2,36],27:[2,36],28:[2,36],29:[2,36],30:[2,36],31:[2,36],32:[2,36],33:[2,36],34:[2,36],35:[2,36],36:[2,36],37:[2,36],38:[2,36],39:[2,36],40:[2,36],41:[2,36],42:[2,36],43:[2,36],44:[2,36],45:[2,36],46:[2,36],47:[2,36],48:[2,36],49:[2,36],50:[2,36],51:[2,36],52:[2,36],53:[2,36],54:[2,36],55:[2,36],56:[2,36],57:[2,36],58:[2,36],59:[2,36],60:[2,36],61:[2,36],62:[2,36]},{4:[2,37],7:[2,37],8:[2,37],9:[2,37],10:[2,37],11:[2,37],12:[2,37],13:[2,37],14:[2,37],15:[2,37],16:[2,37],17:[2,37],18:[2,37],19:[2,37],20:[2,37],21:[2,37],22:[2,37],23:[2,37],24:[2,37],25:[2,37],26:[2,37],27:[2,37],28:[2,37],29:[2,37],30:[2,37],31:[2,37],32:[2,37],33:[2,37],34:[2,37],35:[2,37],36:[2,37],37:[2,37],38:[2,37],39:[2,37],40:[2,37],41:[2,37],42:[2,37],43:[2,37],44:[2,37],45:[2,37],46:[2,37],47:[2,37],48:[2,37],49:[2,37],50:[2,37],51:[2,37],52:[2,37],53:[2,37],54:[2,37],55:[2,37],56:[2,37],57:[2,37],58:[2,37],59:[2,37],60:[2,37],61:[2,37],62:[2,37]},{4:[2,38],7:[2,38],8:[2,38],9:[2,38],10:[2,38],11:[2,38],12:[2,38],13:[2,38],14:[2,38],15:[2,38],16:[2,38],17:[2,38],18:[2,38],19:[2,38],20:[2,38],21:[2,38],22:[2,38],23:[2,38],24:[2,38],25:[2,38],26:[2,38],27:[2,38],28:[2,38],29:[2,38],30:[2,38],31:[2,38],32:[2,38],33:[2,38],34:[2,38],35:[2,38],36:[2,38],37:[2,38],38:[2,38],39:[2,38],40:[2,38],41:[2,38],42:[2,38],43:[2,38],44:[2,38],45:[2,38],46:[2,38],47:[2,38],48:[2,38],49:[2,38],50:[2,38],51:[2,38],52:[2,38],53:[2,38],54:[2,38],55:[2,38],56:[2,38],57:[2,38],58:[2,38],59:[2,38],60:[2,38],61:[2,38],62:[2,38]},{4:[2,39],7:[2,39],8:[2,39],9:[2,39],10:[2,39],11:[2,39],12:[2,39],13:[2,39],14:[2,39],15:[2,39],16:[2,39],17:[2,39],18:[2,39],19:[2,39],20:[2,39],21:[2,39],22:[2,39],23:[2,39],24:[2,39],25:[2,39],26:[2,39],27:[2,39],28:[2,39],29:[2,39],30:[2,39],31:[2,39],32:[2,39],33:[2,39],34:[2,39],35:[2,39],36:[2,39],37:[2,39],38:[2,39],39:[2,39],40:[2,39],41:[2,39],42:[2,39],43:[2,39],44:[2,39],45:[2,39],46:[2,39],47:[2,39],48:[2,39],49:[2,39],50:[2,39],51:[2,39],52:[2,39],53:[2,39],54:[2,39],55:[2,39],56:[2,39],57:[2,39],58:[2,39],59:[2,39],60:[2,39],61:[2,39],62:[2,39]},{4:[2,40],7:[2,40],8:[2,40],9:[2,40],10:[2,40],11:[2,40],12:[2,40],13:[2,40],14:[2,40],15:[2,40],16:[2,40],17:[2,40],18:[2,40],19:[2,40],20:[2,40],21:[2,40],22:[2,40],23:[2,40],24:[2,40],25:[2,40],26:[2,40],27:[2,40],28:[2,40],29:[2,40],30:[2,40],31:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],37:[2,40],38:[2,40],39:[2,40],40:[2,40],41:[2,40],42:[2,40],43:[2,40],44:[2,40],45:[2,40],46:[2,40],47:[2,40],48:[2,40],49:[2,40],50:[2,40],51:[2,40],52:[2,40],53:[2,40],54:[2,40],55:[2,40],56:[2,40],57:[2,40],58:[2,40],59:[2,40],60:[2,40],61:[2,40],62:[2,40]},{4:[2,41],7:[2,41],8:[2,41],9:[2,41],10:[2,41],11:[2,41],12:[2,41],13:[2,41],14:[2,41],15:[2,41],16:[2,41],17:[2,41],18:[2,41],19:[2,41],20:[2,41],21:[2,41],22:[2,41],23:[2,41],24:[2,41],25:[2,41],26:[2,41],27:[2,41],28:[2,41],29:[2,41],30:[2,41],31:[2,41],32:[2,41],33:[2,41],34:[2,41],35:[2,41],36:[2,41],37:[2,41],38:[2,41],39:[2,41],40:[2,41],41:[2,41],42:[2,41],43:[2,41],44:[2,41],45:[2,41],46:[2,41],47:[2,41],48:[2,41],49:[2,41],50:[2,41],51:[2,41],52:[2,41],53:[2,41],54:[2,41],55:[2,41],56:[2,41],57:[2,41],58:[2,41],59:[2,41],60:[2,41],61:[2,41],62:[2,41]},{4:[2,42],7:[2,42],8:[2,42],9:[2,42],10:[2,42],11:[2,42],12:[2,42],13:[2,42],14:[2,42],15:[2,42],16:[2,42],17:[2,42],18:[2,42],19:[2,42],20:[2,42],21:[2,42],22:[2,42],23:[2,42],24:[2,42],25:[2,42],26:[2,42],27:[2,42],28:[2,42],29:[2,42],30:[2,42],31:[2,42],32:[2,42],33:[2,42],34:[2,42],35:[2,42],36:[2,42],37:[2,42],38:[2,42],39:[2,42],40:[2,42],41:[2,42],42:[2,42],43:[2,42],44:[2,42],45:[2,42],46:[2,42],47:[2,42],48:[2,42],49:[2,42],50:[2,42],51:[2,42],52:[2,42],53:[2,42],54:[2,42],55:[2,42],56:[2,42],57:[2,42],58:[2,42],59:[2,42],60:[2,42],61:[2,42],62:[2,42]},{4:[2,43],7:[2,43],8:[2,43],9:[2,43],10:[2,43],11:[2,43],12:[2,43],13:[2,43],14:[2,43],15:[2,43],16:[2,43],17:[2,43],18:[2,43],19:[2,43],20:[2,43],21:[2,43],22:[2,43],23:[2,43],24:[2,43],25:[2,43],26:[2,43],27:[2,43],28:[2,43],29:[2,43],30:[2,43],31:[2,43],32:[2,43],33:[2,43],34:[2,43],35:[2,43],36:[2,43],37:[2,43],38:[2,43],39:[2,43],40:[2,43],41:[2,43],42:[2,43],43:[2,43],44:[2,43],45:[2,43],46:[2,43],47:[2,43],48:[2,43],49:[2,43],50:[2,43],51:[2,43],52:[2,43],53:[2,43],54:[2,43],55:[2,43],56:[2,43],57:[2,43],58:[2,43],59:[2,43],60:[2,43],61:[2,43],62:[2,43]},{4:[2,44],7:[2,44],8:[2,44],9:[2,44],10:[2,44],11:[2,44],12:[2,44],13:[2,44],14:[2,44],15:[2,44],16:[2,44],17:[2,44],18:[2,44],19:[2,44],20:[2,44],21:[2,44],22:[2,44],23:[2,44],24:[2,44],25:[2,44],26:[2,44],27:[2,44],28:[2,44],29:[2,44],30:[2,44],31:[2,44],32:[2,44],33:[2,44],34:[2,44],35:[2,44],36:[2,44],37:[2,44],38:[2,44],39:[2,44],40:[2,44],41:[2,44],42:[2,44],43:[2,44],44:[2,44],45:[2,44],46:[2,44],47:[2,44],48:[2,44],49:[2,44],50:[2,44],51:[2,44],52:[2,44],53:[2,44],54:[2,44],55:[2,44],56:[2,44],57:[2,44],58:[2,44],59:[2,44],60:[2,44],61:[2,44],62:[2,44]},{4:[2,45],7:[2,45],8:[2,45],9:[2,45],10:[2,45],11:[2,45],12:[2,45],13:[2,45],14:[2,45],15:[2,45],16:[2,45],17:[2,45],18:[2,45],19:[2,45],20:[2,45],21:[2,45],22:[2,45],23:[2,45],24:[2,45],25:[2,45],26:[2,45],27:[2,45],28:[2,45],29:[2,45],30:[2,45],31:[2,45],32:[2,45],33:[2,45],34:[2,45],35:[2,45],36:[2,45],37:[2,45],38:[2,45],39:[2,45],40:[2,45],41:[2,45],42:[2,45],43:[2,45],44:[2,45],45:[2,45],46:[2,45],47:[2,45],48:[2,45],49:[2,45],50:[2,45],51:[2,45],52:[2,45],53:[2,45],54:[2,45],55:[2,45],56:[2,45],57:[2,45],58:[2,45],59:[2,45],60:[2,45],61:[2,45],62:[2,45]},{4:[2,46],7:[2,46],8:[2,46],9:[2,46],10:[2,46],11:[2,46],12:[2,46],13:[2,46],14:[2,46],15:[2,46],16:[2,46],17:[2,46],18:[2,46],19:[2,46],20:[2,46],21:[2,46],22:[2,46],23:[2,46],24:[2,46],25:[2,46],26:[2,46],27:[2,46],28:[2,46],29:[2,46],30:[2,46],31:[2,46],32:[2,46],33:[2,46],34:[2,46],35:[2,46],36:[2,46],37:[2,46],38:[2,46],39:[2,46],40:[2,46],41:[2,46],42:[2,46],43:[2,46],44:[2,46],45:[2,46],46:[2,46],47:[2,46],48:[2,46],49:[2,46],50:[2,46],51:[2,46],52:[2,46],53:[2,46],54:[2,46],55:[2,46],56:[2,46],57:[2,46],58:[2,46],59:[2,46],60:[2,46],61:[2,46],62:[2,46]},{4:[2,47],7:[2,47],8:[2,47],9:[2,47],10:[2,47],11:[2,47],12:[2,47],13:[2,47],14:[2,47],15:[2,47],16:[2,47],17:[2,47],18:[2,47],19:[2,47],20:[2,47],21:[2,47],22:[2,47],23:[2,47],24:[2,47],25:[2,47],26:[2,47],27:[2,47],28:[2,47],29:[2,47],30:[2,47],31:[2,47],32:[2,47],33:[2,47],34:[2,47],35:[2,47],36:[2,47],37:[2,47],38:[2,47],39:[2,47],40:[2,47],41:[2,47],42:[2,47],43:[2,47],44:[2,47],45:[2,47],46:[2,47],47:[2,47],48:[2,47],49:[2,47],50:[2,47],51:[2,47],52:[2,47],53:[2,47],54:[2,47],55:[2,47],56:[2,47],57:[2,47],58:[2,47],59:[2,47],60:[2,47],61:[2,47],62:[2,47]},{4:[2,48],7:[2,48],8:[2,48],9:[2,48],10:[2,48],11:[2,48],12:[2,48],13:[2,48],14:[2,48],15:[2,48],16:[2,48],17:[2,48],18:[2,48],19:[2,48],20:[2,48],21:[2,48],22:[2,48],23:[2,48],24:[2,48],25:[2,48],26:[2,48],27:[2,48],28:[2,48],29:[2,48],30:[2,48],31:[2,48],32:[2,48],33:[2,48],34:[2,48],35:[2,48],36:[2,48],37:[2,48],38:[2,48],39:[2,48],40:[2,48],41:[2,48],42:[2,48],43:[2,48],44:[2,48],45:[2,48],46:[2,48],47:[2,48],48:[2,48],49:[2,48],50:[2,48],51:[2,48],52:[2,48],53:[2,48],54:[2,48],55:[2,48],56:[2,48],57:[2,48],58:[2,48],59:[2,48],60:[2,48],61:[2,48],62:[2,48]},{4:[2,49],7:[2,49],8:[2,49],9:[2,49],10:[2,49],11:[2,49],12:[2,49],13:[2,49],14:[2,49],15:[2,49],16:[2,49],17:[2,49],18:[2,49],19:[2,49],20:[2,49],21:[2,49],22:[2,49],23:[2,49],24:[2,49],25:[2,49],26:[2,49],27:[2,49],28:[2,49],29:[2,49],30:[2,49],31:[2,49],32:[2,49],33:[2,49],34:[2,49],35:[2,49],36:[2,49],37:[2,49],38:[2,49],39:[2,49],40:[2,49],41:[2,49],42:[2,49],43:[2,49],44:[2,49],45:[2,49],46:[2,49],47:[2,49],48:[2,49],49:[2,49],50:[2,49],51:[2,49],52:[2,49],53:[2,49],54:[2,49],55:[2,49],56:[2,49],57:[2,49],58:[2,49],59:[2,49],60:[2,49],61:[2,49],62:[2,49]},{4:[2,50],7:[2,50],8:[2,50],9:[2,50],10:[2,50],11:[2,50],12:[2,50],13:[2,50],14:[2,50],15:[2,50],16:[2,50],17:[2,50],18:[2,50],19:[2,50],20:[2,50],21:[2,50],22:[2,50],23:[2,50],24:[2,50],25:[2,50],26:[2,50],27:[2,50],28:[2,50],29:[2,50],30:[2,50],31:[2,50],32:[2,50],33:[2,50],34:[2,50],35:[2,50],36:[2,50],37:[2,50],38:[2,50],39:[2,50],40:[2,50],41:[2,50],42:[2,50],43:[2,50],44:[2,50],45:[2,50],46:[2,50],47:[2,50],48:[2,50],49:[2,50],50:[2,50],51:[2,50],52:[2,50],53:[2,50],54:[2,50],55:[2,50],56:[2,50],57:[2,50],58:[2,50],59:[2,50],60:[2,50],61:[2,50],62:[2,50]},{4:[2,51],7:[2,51],8:[2,51],9:[2,51],10:[2,51],11:[2,51],12:[2,51],13:[2,51],14:[2,51],15:[2,51],16:[2,51],17:[2,51],18:[2,51],19:[2,51],20:[2,51],21:[2,51],22:[2,51],23:[2,51],24:[2,51],25:[2,51],26:[2,51],27:[2,51],28:[2,51],29:[2,51],30:[2,51],31:[2,51],32:[2,51],33:[2,51],34:[2,51],35:[2,51],36:[2,51],37:[2,51],38:[2,51],39:[2,51],40:[2,51],41:[2,51],42:[2,51],43:[2,51],44:[2,51],45:[2,51],46:[2,51],47:[2,51],48:[2,51],49:[2,51],50:[2,51],51:[2,51],52:[2,51],53:[2,51],54:[2,51],55:[2,51],56:[2,51],57:[2,51],58:[2,51],59:[2,51],60:[2,51],61:[2,51],62:[2,51]},{4:[2,52],7:[2,52],8:[2,52],9:[2,52],10:[2,52],11:[2,52],12:[2,52],13:[2,52],14:[2,52],15:[2,52],16:[2,52],17:[2,52],18:[2,52],19:[2,52],20:[2,52],21:[2,52],22:[2,52],23:[2,52],24:[2,52],25:[2,52],26:[2,52],27:[2,52],28:[2,52],29:[2,52],30:[2,52],31:[2,52],32:[2,52],33:[2,52],34:[2,52],35:[2,52],36:[2,52],37:[2,52],38:[2,52],39:[2,52],40:[2,52],41:[2,52],42:[2,52],43:[2,52],44:[2,52],45:[2,52],46:[2,52],47:[2,52],48:[2,52],49:[2,52],50:[2,52],51:[2,52],52:[2,52],53:[2,52],54:[2,52],55:[2,52],56:[2,52],57:[2,52],58:[2,52],59:[2,52],60:[2,52],61:[2,52],62:[2,52]},{4:[2,53],7:[2,53],8:[2,53],9:[2,53],10:[2,53],11:[2,53],12:[2,53],13:[2,53],14:[2,53],15:[2,53],16:[2,53],17:[2,53],18:[2,53],19:[2,53],20:[2,53],21:[2,53],22:[2,53],23:[2,53],24:[2,53],25:[2,53],26:[2,53],27:[2,53],28:[2,53],29:[2,53],30:[2,53],31:[2,53],32:[2,53],33:[2,53],34:[2,53],35:[2,53],36:[2,53],37:[2,53],38:[2,53],39:[2,53],40:[2,53],41:[2,53],42:[2,53],43:[2,53],44:[2,53],45:[2,53],46:[2,53],47:[2,53],48:[2,53],49:[2,53],50:[2,53],51:[2,53],52:[2,53],53:[2,53],54:[2,53],55:[2,53],56:[2,53],57:[2,53],58:[2,53],59:[2,53],60:[2,53],61:[2,53],62:[2,53]},{4:[2,54],7:[2,54],8:[2,54],9:[2,54],10:[2,54],11:[2,54],12:[2,54],13:[2,54],14:[2,54],15:[2,54],16:[2,54],17:[2,54],18:[2,54],19:[2,54],20:[2,54],21:[2,54],22:[2,54],23:[2,54],24:[2,54],25:[2,54],26:[2,54],27:[2,54],28:[2,54],29:[2,54],30:[2,54],31:[2,54],32:[2,54],33:[2,54],34:[2,54],35:[2,54],36:[2,54],37:[2,54],38:[2,54],39:[2,54],40:[2,54],41:[2,54],42:[2,54],43:[2,54],44:[2,54],45:[2,54],46:[2,54],47:[2,54],48:[2,54],49:[2,54],50:[2,54],51:[2,54],52:[2,54],53:[2,54],54:[2,54],55:[2,54],56:[2,54],57:[2,54],58:[2,54],59:[2,54],60:[2,54],61:[2,54],62:[2,54]},{4:[2,55],7:[2,55],8:[2,55],9:[2,55],10:[2,55],11:[2,55],12:[2,55],13:[2,55],14:[2,55],15:[2,55],16:[2,55],17:[2,55],18:[2,55],19:[2,55],20:[2,55],21:[2,55],22:[2,55],23:[2,55],24:[2,55],25:[2,55],26:[2,55],27:[2,55],28:[2,55],29:[2,55],30:[2,55],31:[2,55],32:[2,55],33:[2,55],34:[2,55],35:[2,55],36:[2,55],37:[2,55],38:[2,55],39:[2,55],40:[2,55],41:[2,55],42:[2,55],43:[2,55],44:[2,55],45:[2,55],46:[2,55],47:[2,55],48:[2,55],49:[2,55],50:[2,55],51:[2,55],52:[2,55],53:[2,55],54:[2,55],55:[2,55],56:[2,55],57:[2,55],58:[2,55],59:[2,55],60:[2,55],61:[2,55],62:[2,55]},{4:[2,56],7:[2,56],8:[2,56],9:[2,56],10:[2,56],11:[2,56],12:[2,56],13:[2,56],14:[2,56],15:[2,56],16:[2,56],17:[2,56],18:[2,56],19:[2,56],20:[2,56],21:[2,56],22:[2,56],23:[2,56],24:[2,56],25:[2,56],26:[2,56],27:[2,56],28:[2,56],29:[2,56],30:[2,56],31:[2,56],32:[2,56],33:[2,56],34:[2,56],35:[2,56],36:[2,56],37:[2,56],38:[2,56],39:[2,56],40:[2,56],41:[2,56],42:[2,56],43:[2,56],44:[2,56],45:[2,56],46:[2,56],47:[2,56],48:[2,56],49:[2,56],50:[2,56],51:[2,56],52:[2,56],53:[2,56],54:[2,56],55:[2,56],56:[2,56],57:[2,56],58:[2,56],59:[2,56],60:[2,56],61:[2,56],62:[2,56]},{4:[2,57],7:[2,57],8:[2,57],9:[2,57],10:[2,57],11:[2,57],12:[2,57],13:[2,57],14:[2,57],15:[2,57],16:[2,57],17:[2,57],18:[2,57],19:[2,57],20:[2,57],21:[2,57],22:[2,57],23:[2,57],24:[2,57],25:[2,57],26:[2,57],27:[2,57],28:[2,57],29:[2,57],30:[2,57],31:[2,57],32:[2,57],33:[2,57],34:[2,57],35:[2,57],36:[2,57],37:[2,57],38:[2,57],39:[2,57],40:[2,57],41:[2,57],42:[2,57],43:[2,57],44:[2,57],45:[2,57],46:[2,57],47:[2,57],48:[2,57],49:[2,57],50:[2,57],51:[2,57],52:[2,57],53:[2,57],54:[2,57],55:[2,57],56:[2,57],57:[2,57],58:[2,57],59:[2,57],60:[2,57],61:[2,57],62:[2,57]},{4:[2,58],7:[2,58],8:[2,58],9:[2,58],10:[2,58],11:[2,58],12:[2,58],13:[2,58],14:[2,58],15:[2,58],16:[2,58],17:[2,58],18:[2,58],19:[2,58],20:[2,58],21:[2,58],22:[2,58],23:[2,58],24:[2,58],25:[2,58],26:[2,58],27:[2,58],28:[2,58],29:[2,58],30:[2,58],31:[2,58],32:[2,58],33:[2,58],34:[2,58],35:[2,58],36:[2,58],37:[2,58],38:[2,58],39:[2,58],40:[2,58],41:[2,58],42:[2,58],43:[2,58],44:[2,58],45:[2,58],46:[2,58],47:[2,58],48:[2,58],49:[2,58],50:[2,58],51:[2,58],52:[2,58],53:[2,58],54:[2,58],55:[2,58],56:[2,58],57:[2,58],58:[2,58],59:[2,58],60:[2,58],61:[2,58],62:[2,58]},{4:[2,59],7:[2,59],8:[2,59],9:[2,59],10:[2,59],11:[2,59],12:[2,59],13:[2,59],14:[2,59],15:[2,59],16:[2,59],17:[2,59],18:[2,59],19:[2,59],20:[2,59],21:[2,59],22:[2,59],23:[2,59],24:[2,59],25:[2,59],26:[2,59],27:[2,59],28:[2,59],29:[2,59],30:[2,59],31:[2,59],32:[2,59],33:[2,59],34:[2,59],35:[2,59],36:[2,59],37:[2,59],38:[2,59],39:[2,59],40:[2,59],41:[2,59],42:[2,59],43:[2,59],44:[2,59],45:[2,59],46:[2,59],47:[2,59],48:[2,59],49:[2,59],50:[2,59],51:[2,59],52:[2,59],53:[2,59],54:[2,59],55:[2,59],56:[2,59],57:[2,59],58:[2,59],59:[2,59],60:[2,59],61:[2,59],62:[2,59]},{4:[2,60],7:[2,60],8:[2,60],9:[2,60],10:[2,60],11:[2,60],12:[2,60],13:[2,60],14:[2,60],15:[2,60],16:[2,60],17:[2,60],18:[2,60],19:[2,60],20:[2,60],21:[2,60],22:[2,60],23:[2,60],24:[2,60],25:[2,60],26:[2,60],27:[2,60],28:[2,60],29:[2,60],30:[2,60],31:[2,60],32:[2,60],33:[2,60],34:[2,60],35:[2,60],36:[2,60],37:[2,60],38:[2,60],39:[2,60],40:[2,60],41:[2,60],42:[2,60],43:[2,60],44:[2,60],45:[2,60],46:[2,60],47:[2,60],48:[2,60],49:[2,60],50:[2,60],51:[2,60],52:[2,60],53:[2,60],54:[2,60],55:[2,60],56:[2,60],57:[2,60],58:[2,60],59:[2,60],60:[2,60],61:[2,60],62:[2,60]},{1:[2,2]},{4:[2,4]}], 208 | defaultActions: {2:[2,1],61:[2,2],62:[2,4]}, 209 | parseError: function parseError(str, hash) { 210 | if (hash.recoverable) { 211 | this.trace(str); 212 | } else { 213 | throw new Error(str); 214 | } 215 | }, 216 | parse: function parse(input) { 217 | var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; 218 | var args = lstack.slice.call(arguments, 1); 219 | this.lexer.setInput(input); 220 | this.lexer.yy = this.yy; 221 | this.yy.lexer = this.lexer; 222 | this.yy.parser = this; 223 | if (typeof this.lexer.yylloc == 'undefined') { 224 | this.lexer.yylloc = {}; 225 | } 226 | var yyloc = this.lexer.yylloc; 227 | lstack.push(yyloc); 228 | var ranges = this.lexer.options && this.lexer.options.ranges; 229 | if (typeof this.yy.parseError === 'function') { 230 | this.parseError = this.yy.parseError; 231 | } else { 232 | this.parseError = Object.getPrototypeOf(this).parseError; 233 | } 234 | function popStack(n) { 235 | stack.length = stack.length - 2 * n; 236 | vstack.length = vstack.length - n; 237 | lstack.length = lstack.length - n; 238 | } 239 | function lex() { 240 | var token; 241 | token = self.lexer.lex() || EOF; 242 | if (typeof token !== 'number') { 243 | token = self.symbols_[token] || token; 244 | } 245 | return token; 246 | } 247 | var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; 248 | while (true) { 249 | state = stack[stack.length - 1]; 250 | if (this.defaultActions[state]) { 251 | action = this.defaultActions[state]; 252 | } else { 253 | if (symbol === null || typeof symbol == 'undefined') { 254 | symbol = lex(); 255 | } 256 | action = table[state] && table[state][symbol]; 257 | } 258 | if (typeof action === 'undefined' || !action.length || !action[0]) { 259 | var errStr = ''; 260 | expected = []; 261 | for (p in table[state]) { 262 | if (this.terminals_[p] && p > TERROR) { 263 | expected.push('\'' + this.terminals_[p] + '\''); 264 | } 265 | } 266 | if (this.lexer.showPosition) { 267 | errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + this.lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; 268 | } else { 269 | errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); 270 | } 271 | this.parseError(errStr, { 272 | text: this.lexer.match, 273 | token: this.terminals_[symbol] || symbol, 274 | line: this.lexer.yylineno, 275 | loc: yyloc, 276 | expected: expected 277 | }); 278 | } 279 | if (action[0] instanceof Array && action.length > 1) { 280 | throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); 281 | } 282 | switch (action[0]) { 283 | case 1: 284 | stack.push(symbol); 285 | vstack.push(this.lexer.yytext); 286 | lstack.push(this.lexer.yylloc); 287 | stack.push(action[1]); 288 | symbol = null; 289 | if (!preErrorSymbol) { 290 | yyleng = this.lexer.yyleng; 291 | yytext = this.lexer.yytext; 292 | yylineno = this.lexer.yylineno; 293 | yyloc = this.lexer.yylloc; 294 | if (recovering > 0) { 295 | recovering--; 296 | } 297 | } else { 298 | symbol = preErrorSymbol; 299 | preErrorSymbol = null; 300 | } 301 | break; 302 | case 2: 303 | len = this.productions_[action[1]][1]; 304 | yyval.$ = vstack[vstack.length - len]; 305 | yyval._$ = { 306 | first_line: lstack[lstack.length - (len || 1)].first_line, 307 | last_line: lstack[lstack.length - 1].last_line, 308 | first_column: lstack[lstack.length - (len || 1)].first_column, 309 | last_column: lstack[lstack.length - 1].last_column 310 | }; 311 | if (ranges) { 312 | yyval._$.range = [ 313 | lstack[lstack.length - (len || 1)].range[0], 314 | lstack[lstack.length - 1].range[1] 315 | ]; 316 | } 317 | r = this.performAction.apply(yyval, [ 318 | yytext, 319 | yyleng, 320 | yylineno, 321 | this.yy, 322 | action[1], 323 | vstack, 324 | lstack 325 | ].concat(args)); 326 | if (typeof r !== 'undefined') { 327 | return r; 328 | } 329 | if (len) { 330 | stack = stack.slice(0, -1 * len * 2); 331 | vstack = vstack.slice(0, -1 * len); 332 | lstack = lstack.slice(0, -1 * len); 333 | } 334 | stack.push(this.productions_[action[1]][0]); 335 | vstack.push(yyval.$); 336 | lstack.push(yyval._$); 337 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; 338 | stack.push(newState); 339 | break; 340 | case 3: 341 | return true; 342 | } 343 | } 344 | return true; 345 | }}; 346 | /* generated by jison-lex 0.2.1 */ 347 | var lexer = (function(){ 348 | var lexer = { 349 | 350 | EOF:1, 351 | 352 | parseError:function parseError(str, hash) { 353 | if (this.yy.parser) { 354 | this.yy.parser.parseError(str, hash); 355 | } else { 356 | throw new Error(str); 357 | } 358 | }, 359 | 360 | // resets the lexer, sets new input 361 | setInput:function (input) { 362 | this._input = input; 363 | this._more = this._backtrack = this.done = false; 364 | this.yylineno = this.yyleng = 0; 365 | this.yytext = this.matched = this.match = ''; 366 | this.conditionStack = ['INITIAL']; 367 | this.yylloc = { 368 | first_line: 1, 369 | first_column: 0, 370 | last_line: 1, 371 | last_column: 0 372 | }; 373 | if (this.options.ranges) { 374 | this.yylloc.range = [0,0]; 375 | } 376 | this.offset = 0; 377 | return this; 378 | }, 379 | 380 | // consumes and returns one char from the input 381 | input:function () { 382 | var ch = this._input[0]; 383 | this.yytext += ch; 384 | this.yyleng++; 385 | this.offset++; 386 | this.match += ch; 387 | this.matched += ch; 388 | var lines = ch.match(/(?:\r\n?|\n).*/g); 389 | if (lines) { 390 | this.yylineno++; 391 | this.yylloc.last_line++; 392 | } else { 393 | this.yylloc.last_column++; 394 | } 395 | if (this.options.ranges) { 396 | this.yylloc.range[1]++; 397 | } 398 | 399 | this._input = this._input.slice(1); 400 | return ch; 401 | }, 402 | 403 | // unshifts one char (or a string) into the input 404 | unput:function (ch) { 405 | var len = ch.length; 406 | var lines = ch.split(/(?:\r\n?|\n)/g); 407 | 408 | this._input = ch + this._input; 409 | this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); 410 | //this.yyleng -= len; 411 | this.offset -= len; 412 | var oldLines = this.match.split(/(?:\r\n?|\n)/g); 413 | this.match = this.match.substr(0, this.match.length - 1); 414 | this.matched = this.matched.substr(0, this.matched.length - 1); 415 | 416 | if (lines.length - 1) { 417 | this.yylineno -= lines.length - 1; 418 | } 419 | var r = this.yylloc.range; 420 | 421 | this.yylloc = { 422 | first_line: this.yylloc.first_line, 423 | last_line: this.yylineno + 1, 424 | first_column: this.yylloc.first_column, 425 | last_column: lines ? 426 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) 427 | + oldLines[oldLines.length - lines.length].length - lines[0].length : 428 | this.yylloc.first_column - len 429 | }; 430 | 431 | if (this.options.ranges) { 432 | this.yylloc.range = [r[0], r[0] + this.yyleng - len]; 433 | } 434 | this.yyleng = this.yytext.length; 435 | return this; 436 | }, 437 | 438 | // When called from action, caches matched text and appends it on next action 439 | more:function () { 440 | this._more = true; 441 | return this; 442 | }, 443 | 444 | // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. 445 | reject:function () { 446 | if (this.options.backtrack_lexer) { 447 | this._backtrack = true; 448 | } else { 449 | return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { 450 | text: "", 451 | token: null, 452 | line: this.yylineno 453 | }); 454 | 455 | } 456 | return this; 457 | }, 458 | 459 | // retain first n characters of the match 460 | less:function (n) { 461 | this.unput(this.match.slice(n)); 462 | }, 463 | 464 | // displays already matched input, i.e. for error messages 465 | pastInput:function () { 466 | var past = this.matched.substr(0, this.matched.length - this.match.length); 467 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 468 | }, 469 | 470 | // displays upcoming input, i.e. for error messages 471 | upcomingInput:function () { 472 | var next = this.match; 473 | if (next.length < 20) { 474 | next += this._input.substr(0, 20-next.length); 475 | } 476 | return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); 477 | }, 478 | 479 | // displays the character position where the lexing error occurred, i.e. for error messages 480 | showPosition:function () { 481 | var pre = this.pastInput(); 482 | var c = new Array(pre.length + 1).join("-"); 483 | return pre + this.upcomingInput() + "\n" + c + "^"; 484 | }, 485 | 486 | // test the lexed token: return FALSE when not a match, otherwise return token 487 | test_match:function (match, indexed_rule) { 488 | var token, 489 | lines, 490 | backup; 491 | 492 | if (this.options.backtrack_lexer) { 493 | // save context 494 | backup = { 495 | yylineno: this.yylineno, 496 | yylloc: { 497 | first_line: this.yylloc.first_line, 498 | last_line: this.last_line, 499 | first_column: this.yylloc.first_column, 500 | last_column: this.yylloc.last_column 501 | }, 502 | yytext: this.yytext, 503 | match: this.match, 504 | matches: this.matches, 505 | matched: this.matched, 506 | yyleng: this.yyleng, 507 | offset: this.offset, 508 | _more: this._more, 509 | _input: this._input, 510 | yy: this.yy, 511 | conditionStack: this.conditionStack.slice(0), 512 | done: this.done 513 | }; 514 | if (this.options.ranges) { 515 | backup.yylloc.range = this.yylloc.range.slice(0); 516 | } 517 | } 518 | 519 | lines = match[0].match(/(?:\r\n?|\n).*/g); 520 | if (lines) { 521 | this.yylineno += lines.length; 522 | } 523 | this.yylloc = { 524 | first_line: this.yylloc.last_line, 525 | last_line: this.yylineno + 1, 526 | first_column: this.yylloc.last_column, 527 | last_column: lines ? 528 | lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : 529 | this.yylloc.last_column + match[0].length 530 | }; 531 | this.yytext += match[0]; 532 | this.match += match[0]; 533 | this.matches = match; 534 | this.yyleng = this.yytext.length; 535 | if (this.options.ranges) { 536 | this.yylloc.range = [this.offset, this.offset += this.yyleng]; 537 | } 538 | this._more = false; 539 | this._backtrack = false; 540 | this._input = this._input.slice(match[0].length); 541 | this.matched += match[0]; 542 | token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); 543 | if (this.done && this._input) { 544 | this.done = false; 545 | } 546 | if (token) { 547 | return token; 548 | } else if (this._backtrack) { 549 | // recover context 550 | for (var k in backup) { 551 | this[k] = backup[k]; 552 | } 553 | return false; // rule action called reject() implying the next rule should be tested instead. 554 | } 555 | return false; 556 | }, 557 | 558 | // return next match in input 559 | next:function () { 560 | if (this.done) { 561 | return this.EOF; 562 | } 563 | if (!this._input) { 564 | this.done = true; 565 | } 566 | 567 | var token, 568 | match, 569 | tempMatch, 570 | index; 571 | if (!this._more) { 572 | this.yytext = ''; 573 | this.match = ''; 574 | } 575 | var rules = this._currentRules(); 576 | for (var i = 0; i < rules.length; i++) { 577 | tempMatch = this._input.match(this.rules[rules[i]]); 578 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 579 | match = tempMatch; 580 | index = i; 581 | if (this.options.backtrack_lexer) { 582 | token = this.test_match(tempMatch, rules[i]); 583 | if (token !== false) { 584 | return token; 585 | } else if (this._backtrack) { 586 | match = false; 587 | continue; // rule action called reject() implying a rule MISmatch. 588 | } else { 589 | // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) 590 | return false; 591 | } 592 | } else if (!this.options.flex) { 593 | break; 594 | } 595 | } 596 | } 597 | if (match) { 598 | token = this.test_match(match, rules[index]); 599 | if (token !== false) { 600 | return token; 601 | } 602 | // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) 603 | return false; 604 | } 605 | if (this._input === "") { 606 | return this.EOF; 607 | } else { 608 | return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { 609 | text: "", 610 | token: null, 611 | line: this.yylineno 612 | }); 613 | } 614 | }, 615 | 616 | // return next match that has a token 617 | lex:function lex() { 618 | var r = this.next(); 619 | if (r) { 620 | return r; 621 | } else { 622 | return this.lex(); 623 | } 624 | }, 625 | 626 | // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) 627 | begin:function begin(condition) { 628 | this.conditionStack.push(condition); 629 | }, 630 | 631 | // pop the previously active lexer condition state off the condition stack 632 | popState:function popState() { 633 | var n = this.conditionStack.length - 1; 634 | if (n > 0) { 635 | return this.conditionStack.pop(); 636 | } else { 637 | return this.conditionStack[0]; 638 | } 639 | }, 640 | 641 | // produce the lexer rule set which is active for the currently active lexer condition state 642 | _currentRules:function _currentRules() { 643 | if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { 644 | return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; 645 | } else { 646 | return this.conditions["INITIAL"].rules; 647 | } 648 | }, 649 | 650 | // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available 651 | topState:function topState(n) { 652 | n = this.conditionStack.length - 1 - Math.abs(n || 0); 653 | if (n >= 0) { 654 | return this.conditionStack[n]; 655 | } else { 656 | return "INITIAL"; 657 | } 658 | }, 659 | 660 | // alias for begin(condition) 661 | pushState:function pushState(condition) { 662 | this.begin(condition); 663 | }, 664 | 665 | // return the number of states currently on the stack 666 | stateStackSize:function stateStackSize() { 667 | return this.conditionStack.length; 668 | }, 669 | options: {}, 670 | performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 671 | 672 | var YYSTATE=YY_START; 673 | switch($avoiding_name_collisions) { 674 | case 0: return 7; 675 | break; 676 | case 1: return 8; 677 | break; 678 | case 2: return 9; 679 | break; 680 | case 3: return 10; 681 | break; 682 | case 4: this.pushState('rp'); return yy_.yytext; 683 | break; 684 | case 5: return yy_.yytext; 685 | break; 686 | case 6: this.popState(); 687 | this.pushState('rw'); return yy_.yytext; 688 | break; 689 | case 7: this.popState(); 690 | this.pushState('r'); return 11; 691 | break; 692 | case 8: this.pushState('r'); return 11; 693 | break; 694 | case 9: return 12; 695 | break; 696 | case 10: this.pushState('b'); return yy_.yytext; 697 | break; 698 | case 11: this.popState(); return yy_.yytext; 699 | break; 700 | case 12: this.pushState('p'); return yy_.yytext; 701 | break; 702 | case 13: this.popState(); return yy_.yytext; 703 | break; 704 | case 14: this.pushState('m'); return yy_.yytext; 705 | break; 706 | case 15: this.popState(); return yy_.yytext; 707 | break; 708 | case 16: this.popState(); return 4; 709 | break; 710 | case 17: this.popState(); 711 | break; 712 | case 18: /*ignore whitespace and \n*/ 713 | break; 714 | case 19: return yy_.yytext; 715 | break; 716 | case 20: return yy_.yytext; 717 | break; 718 | case 21: return yy_.yytext; 719 | break; 720 | case 22: return 24; 721 | break; 722 | case 23: return yy_.yytext.toUpperCase(); 723 | break; 724 | case 24: return yy_.yytext; 725 | break; 726 | case 25: return yy_.yytext; 727 | break; 728 | case 26: return yy_.yytext; 729 | break; 730 | case 27: return yy_.yytext; 731 | break; 732 | case 28: return 43; 733 | break; 734 | case 29: return 44; 735 | break; 736 | case 30: return 45; 737 | break; 738 | case 31: return 46; 739 | break; 740 | case 32: this.pushState('bp'); return 47; 741 | break; 742 | case 33: this.pushState('bp'); return 48; 743 | break; 744 | case 34: this.pushState('bp'); return 49; 745 | break; 746 | case 35: return 50; 747 | break; 748 | case 36: return 51; 749 | break; 750 | case 37: this.pushState('bp'); return 52; 751 | break; 752 | case 38: this.pushState('bp'); return 53; 753 | break; 754 | case 39: this.pushState('bp'); return 54; 755 | break; 756 | case 40: return 55; 757 | break; 758 | case 41: return 56; 759 | break; 760 | case 42: this.pushState('bp'); return 57; 761 | break; 762 | case 43: this.pushState('bp'); return 58; 763 | break; 764 | case 44: this.pushState('bpm'); return 59; 765 | break; 766 | case 45: this.pushState('bpm'); return 60; 767 | break; 768 | case 46: this.pushState('bpm'); return 61; 769 | break; 770 | case 47: this.popState(); 771 | this.pushState('p'); return 14; 772 | break; 773 | case 48: this.popState(); 774 | this.pushState('pm'); return 14; 775 | break; 776 | case 49: return 11; 777 | break; 778 | case 50: return 21; 779 | break; 780 | case 51: this.popState(); return 15; 781 | break; 782 | case 52: return 62; 783 | break; 784 | case 53: return 7; 785 | break; 786 | case 54: return 4; 787 | break; 788 | } 789 | }, 790 | rules: [/^(?:(\\\$|\\#|[^\$\#])+)/,/^(?:#\[\[[\s\S]*?\]\]#)/,/^(?:##.*)/,/^(?:#\*[\s\S]*?\*#)/,/^(?:\$(?=!?\{?([a-zA-Z][a-zA-Z0-9-_]*)))/,/^(?:!)/,/^(?:\{)/,/^(?:([a-zA-Z][a-zA-Z0-9-_]*))/,/^(?:([a-zA-Z][a-zA-Z0-9-_]*))/,/^(?:\.([a-zA-Z][a-zA-Z0-9-_]*))/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:\{)/,/^(?:\})/,/^(?:$)/,/^(?:)/,/^(?:\s+)/,/^(?::)/,/^(?:,)/,/^(?:\.\.)/,/^(?:in\b)/,/^(?:true|false|null\b)/,/^(?:==|!=|>=|<=|>|<)/,/^(?:&&|\|\||!)/,/^(?:[\+\-\*\/\%])/,/^(?:=)/,/^(?:\d+\.\d+)/,/^(?:\d+)/,/^(?:"(\\"|[^\"])*")/,/^(?:'(\\'|[^\'])*')/,/^(?:#(\{set\}|set)((?=[ \t]*\()))/,/^(?:#(\{if\}|if)((?=[ \t]*\()))/,/^(?:#(\{elseif\}|elseif)((?=[ \t]*\()))/,/^(?:#(\{else\}|else(?!([a-zA-Z]))))/,/^(?:#(\{end\}|end(?!([a-zA-Z]))))/,/^(?:#(\{foreach\}|foreach)((?=[ \t]*\()))/,/^(?:#(\{include\}|include)((?=[ \t]*\()))/,/^(?:#(\{parse\}|parse)((?=[ \t]*\()))/,/^(?:#(\{stop\}|stop(?!([a-zA-Z]))))/,/^(?:#(\{break\}|break(?!([a-zA-Z]))))/,/^(?:#(\{evaluate\}|evaluate)((?=[ \t]*\()))/,/^(?:#(\{define\}|define)((?=[ \t]*\()))/,/^(?:#(\{macro\}|macro)((?=[ \t]*\()))/,/^(?:#(\{([a-zA-Z])+\}|([a-zA-Z])+)((?=[ \t]*\()))/,/^(?:#@(\{([a-zA-Z])+\}|([a-zA-Z])+)((?=[ \t]*\()))/,/^(?:[ \t]*\()/,/^(?:[ \t]*\(\s*)/,/^(?:([a-zA-Z][a-zA-Z0-9-_]*))/,/^(?:\s*,\s*)/,/^(?:\s*\))/,/^(?:\s+)/,/^(?:[\$\#])/,/^(?:$)/], 791 | conditions: {"rp":{"rules":[5,6,7],"inclusive":false},"rw":{"rules":[8,15],"inclusive":false},"r":{"rules":[9,10,12,16,17],"inclusive":false},"b":{"rules":[4,10,11,12,14,18,20,21,23,24,25,26,27,28,29,30,31],"inclusive":false},"bpm":{"rules":[48],"inclusive":false},"bp":{"rules":[47],"inclusive":false},"pm":{"rules":[4,10,12,14,23,28,29,30,31,49,50,51,52],"inclusive":false},"p":{"rules":[4,10,12,13,14,18,20,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"m":{"rules":[4,10,12,14,15,18,19,20,23,24,25,26,27,28,29,30,31],"inclusive":false},"INITIAL":{"rules":[0,1,2,3,4,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,53,54],"inclusive":true}} 792 | }; 793 | return lexer; 794 | })(); 795 | parser.lexer = lexer; 796 | function Parser () { 797 | this.yy = {}; 798 | } 799 | Parser.prototype = parser;parser.Parser = Parser; 800 | return new Parser; 801 | })(); 802 | 803 | 804 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') { 805 | exports.parser = lex; 806 | exports.Parser = lex.Parser; 807 | exports.parse = function () { return lex.parse.apply(lex, arguments); }; 808 | exports.main = function commonjsMain(args) { 809 | if (!args[1]) { 810 | console.log('Usage: '+args[0]+' FILE'); 811 | process.exit(1); 812 | } 813 | var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8"); 814 | return exports.parser.parse(source); 815 | }; 816 | if (typeof module !== 'undefined' && require.main === module) { 817 | exports.main(process.argv.slice(1)); 818 | } 819 | } -------------------------------------------------------------------------------- /lib/engine/lex.yy: -------------------------------------------------------------------------------- 1 | %right '=' 2 | %left '||' 3 | %left '&&' 4 | %left '==' '!=' 5 | %left '>=' '<=' '>' '<' 6 | %left '+' '-' 7 | %left '*' '/' '%' 8 | %right '!' 9 | 10 | %start root 11 | 12 | %% 13 | 14 | root 15 | : EOF { return []; } 16 | | statements EOF { return $1; } 17 | ; 18 | 19 | statements 20 | : statement { $$ = [$1]; } 21 | | statement statements { $$ = [$1].concat($2); } 22 | ; 23 | 24 | statement 25 | : TEXT { $$ = ['TEXT', $1]; } 26 | | BTEXT { $$ = ['BTEXT', $1]; } 27 | | COMMENT { $$ = ['COMMENT', $1]; } 28 | | BCOMMENT { $$ = ['BCOMMENT', $1]; } 29 | | ID { $$ = ['ID', $1]; } 30 | | PROP { $$ = ['PROP', $1]; } 31 | | '$' { $$ = ['$']; } 32 | | '(' { $$ = ['(']; } 33 | | ')' { $$ = [')']; } 34 | | '[' { $$ = ['[']; } 35 | | ']' { $$ = [']']; } 36 | | '{' { $$ = ['{']; } 37 | | '}' { $$ = ['}']; } 38 | | ':' { $$ = [':']; } 39 | | ',' { $$ = [',']; } 40 | | ';' { $$ = [';']; } 41 | | '..' { $$ = ['..']; } 42 | | IN { $$ = ['IN']; } 43 | | TRUE { $$ = ['TRUE']; } 44 | | FALSE { $$ = ['FALSE']; } 45 | | NULL { $$ = ['NULL']; } 46 | | '==' { $$ = [$1]; } 47 | | '!=' { $$ = [$1]; } 48 | | '>=' { $$ = [$1]; } 49 | | '<=' { $$ = [$1]; } 50 | | '>' { $$ = [$1]; } 51 | | '<' { $$ = [$1]; } 52 | | '&&' { $$ = [$1]; } 53 | | '||' { $$ = [$1]; } 54 | | '!' { $$ = [$1]; } 55 | | '+' { $$ = [$1]; } 56 | | '-' { $$ = [$1]; } 57 | | '*' { $$ = [$1]; } 58 | | '/' { $$ = [$1]; } 59 | | '%' { $$ = [$1]; } 60 | | '=' { $$ = [$1]; } 61 | | FLOAT { $$ = ['FLOAT', $1]; } 62 | | INTEGER { $$ = ['INTEGER', $1]; } 63 | | DSTRING { $$ = ['DSTRING', $1]; } 64 | | STRING { $$ = ['STRING', $1]; } 65 | | SET { $$ = ['SET', $1]; } 66 | | IF { $$ = ['IF', $1]; } 67 | | ELSEIF { $$ = ['ELSEIF', $1]; } 68 | | ELSE { $$ = ['ELSE', $1]; } 69 | | END { $$ = ['END', $1]; } 70 | | FOREACH { $$ = ['FOREACH', $1]; } 71 | | INCLUDE { $$ = ['INCLUDE', $1]; } 72 | | PARSE { $$ = ['PARSE', $1]; } 73 | | STOP { $$ = ['STOP', $1]; } 74 | | BREAK { $$ = ['BREAK', $1]; } 75 | | EVALUATE { $$ = ['EVALUATE', $1]; } 76 | | DEFINE { $$ = ['DEFINE', $1]; } 77 | | MACRO { $$ = ['MACRO', $1]; } 78 | | MACROCALL { $$ = ['MACROCALL', $1]; } 79 | | BMACROCALL { $$ = ['BMACROCALL', $1]; } 80 | | WS { $$ = ['WS']; } 81 | ; 82 | -------------------------------------------------------------------------------- /lib/engine/velocity.l: -------------------------------------------------------------------------------- 1 | /* 2 | * Start conditions 3 | * rp - reference prefix 4 | * rw - reference wrapper 5 | * r - reference 6 | * b - brackets 7 | * bpm- before left parenthesis of macro 8 | * bp - before left parenthesis 9 | * pm - parantheses of macro 10 | * p - parentheses 11 | * m - map 12 | */ 13 | 14 | %x rp rw r b bpm bp pm p m 15 | 16 | A [a-zA-Z0-9-_] 17 | ID [a-zA-Z_][a-zA-Z0-9-_]* 18 | LP (?=[ \t]*\() 19 | 20 | %% 21 | 22 | ("\$"|"\#"|[^\$\#])+ { return 'TEXT'; } 23 | "#[["[\s\S]*?"]]#" { return 'BTEXT'; } 24 | "##".* { return 'COMMENT'; } 25 | "#*"[\s\S]*?"*#" { return 'BCOMMENT'; } 26 | 27 | 28 | \$(?=\!?\{?{ID}) { this.pushState('rp'); return yytext; } 29 | "!" { return yytext; } 30 | "{" { this.popState(); 31 | this.pushState('rw'); return yytext; } 32 | {ID} { this.popState(); 33 | this.pushState('r'); return 'ID'; } 34 | {ID} { this.pushState('r'); return 'ID'; } 35 | 36 | "."{ID} { return 'PROP'; } 37 | "[" { this.pushState('b'); return yytext; } 38 | "]" { this.popState(); return yytext; } 39 | "(" { this.pushState('p'); return yytext; } 40 |

")" { this.popState(); return yytext; } 41 | "{" { this.pushState('m'); return yytext; } 42 | "}" { this.popState(); return yytext; } 43 | <> { this.popState(); return 'EOF'; } 44 | "" { this.popState(); } 45 | 46 | 47 | \s+ { /*ignore whitespace and \n*/ } 48 | ":" { return yytext; } 49 | "," { return yytext; } 50 | ".." { return yytext; } 51 |

"in" { return 'IN'; } 52 | 53 | "true"|"false"|"null" { return yytext.toUpperCase(); } 54 | "=="|"!="|">="|"<="|">"|"<" { return yytext; } 55 | "&&"|"||"|"!" { return yytext; } 56 | [\+\-\*\/\%] { return yytext; } 57 | "=" { return yytext; } 58 | 59 | \d+\.\d+ { return 'FLOAT'; } 60 | \d+ { return 'INTEGER'; } 61 | 62 | \"(\\\"|[^\"])*\" { return 'DSTRING'; } 63 | \'(\\\'|[^\'])*\' { return 'STRING'; } 64 | 65 | 66 | "#"("{set}"|"set"){LP} { this.pushState('bp'); return 'SET'; } 67 | "#"("{if}"|"if"){LP} { this.pushState('bp'); return 'IF'; } 68 | "#"("{elseif}"|"elseif"){LP} { this.pushState('bp'); return 'ELSEIF'; } 69 | "#"("{else}"|"else"(?!{A})) { return 'ELSE'; } 70 | "#"("{end}"|"end"(?!{A})) { return 'END'; } 71 | "#"("{foreach}"|"foreach"){LP} { this.pushState('bp'); return 'FOREACH';} 72 | "#"("{include}"|"include"){LP} { this.pushState('bp'); return 'INCLUDE'; } 73 | "#"("{parse}"|"parse"){LP} { this.pushState('bp'); return 'PARSE'; } 74 | "#"("{stop}"|"stop"(?!{A})) { return 'STOP'; } 75 | "#"("{break}"|"break"(?!{A})) { return 'BREAK'; } 76 | "#"("{evaluate}"|"evaluate"){LP} { this.pushState('bp'); return 'EVALUATE'; } 77 | "#"("{define}"|"define"){LP} { this.pushState('bp'); return 'DEFINE'; } 78 | "#"("{macro}"|"macro"){LP} { this.pushState('bpm'); return 'MACRO'; } 79 | "#"(\{{ID}\}|{ID}){LP} { this.pushState('bpm'); return 'MACROCALL'; } 80 | "#@"(\{{ID}\}|{ID}){LP} { this.pushState('bpm'); return 'BMACROCALL'; } 81 | 82 | [ \t]*"(" { this.popState(); 83 | this.pushState('p'); return '('; } 84 | [ \t]*"("\s* { this.popState(); 85 | this.pushState('pm'); return '(';} 86 | {ID} { return 'ID'; } 87 | \s*","\s* { return ','; } 88 | \s*")" { this.popState(); return ')'; } 89 | \s+ { return 'WS'; } 90 | 91 | [\$\#] { return 'TEXT'; } 92 | <> { return 'EOF'; } 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /lib/engine/velocity.yy: -------------------------------------------------------------------------------- 1 | %right '=' 2 | %left '||' 3 | %left '&&' 4 | %left '==' '!=' 5 | %left '>=' '<=' '>' '<' 6 | %left '+' '-' 7 | %left '*' '/' '%' 8 | %right '!' 9 | 10 | %start root 11 | 12 | %% 13 | 14 | root 15 | : EOF { return {type: 'Statements', pos: @$, body: []}; } 16 | | statements EOF { return $1; } 17 | ; 18 | 19 | 20 | statements 21 | : states { $$ = {type: 'Statements', pos: @$, body: $1}; } 22 | ; 23 | 24 | states 25 | : statement { $$ = [$1]; } 26 | | statement states { $$ = [$1].concat($2); } 27 | ; 28 | 29 | statement 30 | : TEXT { $$ = {type: 'Text', pos: @$, value: $1.replace(/\\(?=#|\$)/g, '')}; } 31 | | BTEXT { $$ = {type: 'BText', pos: @$, value: $1.replace(/^#\[\[|\]\]#/g, '')}; } 32 | | COMMENT { $$ = {type: 'Comment', pos: @$, value: $1.replace(/^##/, '')}; } 33 | | BCOMMENT { $$ = {type: 'BComment', pos: @$, value: $1.replace(/^#\*|\*#$/g, '')}; } 34 | | reference { $$ = $1; } 35 | | directive { $$ = $1; } 36 | ; 37 | 38 | reference 39 | : '$' ref { $$ = {type: 'Reference', pos: @$, object: $2}; } 40 | | '$' '!' ref { $$ = {type: 'Reference', pos: @$, object: $3, silent: true}; } 41 | | '$' '{' ref '}' { $$ = {type: 'Reference', pos: @$, object: $3, wrapped: true}; } 42 | | '$' '!' '{' ref '}' { $$ = {type: 'Reference', pos: @$, object: $4, silent: true, wrapped: true}; } 43 | ; 44 | 45 | ref 46 | : id { $$ = $1; } 47 | | property { $$ = $1; } 48 | | method { $$ = $1; } 49 | | index { $$ = $1; } 50 | ; 51 | 52 | id 53 | : ID { $$ = {type: 'Identifier', pos: @$, name: $1}; } 54 | ; 55 | 56 | prop 57 | : PROP { $$ = {type: 'Prop', pos: @$, name: $1.replace(/^\./, '')}; } 58 | ; 59 | 60 | property 61 | : id prop { $$ = {type: 'Property', pos: @$, object: $1, property: $2}; } 62 | | method prop { $$ = {type: 'Property', pos: @$, object: $1, property: $2}; } 63 | | index prop { $$ = {type: 'Property', pos: @$, object: $1, property: $2}; } 64 | | property prop { $$ = {type: 'Property', pos: @$, object: $1, property: $2}; } 65 | ; 66 | 67 | method 68 | : property '(' exprItems ')' { $$ = {type: 'Method', pos: @$, callee: $1, arguments: $3}; } 69 | | property '(' ')' { $$ = {type: 'Method', pos: @$, callee: $1, arguments: []}; } 70 | ; 71 | 72 | index 73 | : id '[' exprItem ']' { $$ = {type: 'Index', pos: @$, object: $1, property: $3}; } 74 | | method '[' exprItem ']' { $$ = {type: 'Index', pos: @$, object: $1, property: $3}; } 75 | | property '[' exprItem ']' { $$ = {type: 'Index', pos: @$, object: $1, property: $3}; } 76 | | index '[' exprItem ']' { $$ = {type: 'Index', pos: @$, object: $1, property: $3}; } 77 | ; 78 | 79 | /* Why cannot simplify the production of range: https://github.com/zaach/jison/issues/212 */ 80 | range 81 | : '[' reference '..' reference ']' { $$ = {type: 'Range', pos: @$, start: $2, end: $4}; } 82 | | '[' reference '..' integer ']' { $$ = {type: 'Range', pos: @$, start: $2, end: $4}; } 83 | | '[' integer '..' reference ']' { $$ = {type: 'Range', pos: @$, start: $2, end: $4}; } 84 | | '[' integer '..' integer ']' { $$ = {type: 'Range', pos: @$, start: $2, end: $4}; } 85 | ; 86 | 87 | list 88 | : '[' exprItems ']' { $$ = {type: 'List', pos: @$, elements: $2}; } 89 | | '[' ']' { $$ = {type: 'List', pos: @$, elements: []}; } 90 | ; 91 | 92 | map 93 | : '{' mapItems '}' { $$ = {type: 'Map', pos: @$, mapItems: $2}; } 94 | | '{' '}' { $$ = {type: 'Map', pos: @$, mapItems: []}; } 95 | ; 96 | 97 | mapItems 98 | : mapItem { $$ = [$1]; } 99 | | mapItem ',' mapItems { $$ = [$1].concat($3); } 100 | ; 101 | 102 | mapItem 103 | : exprItem ':' exprItem { $$ = {type: 'MapItem', pos: @$, property: $1, value: $3}; } 104 | ; 105 | 106 | 107 | expr 108 | : exprItem { $$ = $1; } 109 | | '(' expr ')' { $$ = $2; } 110 | | '!' expr { $$ = {type: 'UnaryExpr', pos: @$, operator: $1, argument: $2}; } 111 | | expr '*' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 112 | | expr '/' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 113 | | expr '%' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 114 | | expr '+' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 115 | | expr '-' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 116 | | expr '>=' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 117 | | expr '>' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 118 | | expr '<=' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 119 | | expr '<' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 120 | | expr '==' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 121 | | expr '!=' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 122 | | expr '&&' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 123 | | expr '||' expr { $$ = {type: 'BinaryExpr', pos: @$, operator: $2, left: $1, right: $3}; } 124 | ; 125 | 126 | assignExpr 127 | : reference '=' expr { $$ = {type: 'AssignExpr', pos: @$, left: $1, right: $3}; } 128 | ; 129 | 130 | exprItems 131 | : exprItem { $$ = [$1]; } 132 | | exprItem ',' exprItems { $$ = [$1].concat($3); } 133 | ; 134 | 135 | exprItem 136 | : reference { $$ = $1; } 137 | | integer { $$ = $1; } 138 | | float { $$ = $1; } 139 | | dstring { $$ = $1; } 140 | | string { $$ = $1; } 141 | | range { $$ = $1; } 142 | | list { $$ = $1; } 143 | | map { $$ = $1; } 144 | | TRUE { $$ = {type: 'Boolean', pos: @$, value: true}; } 145 | | FALSE { $$ = {type: 'Boolean', pos: @$, value: false}; } 146 | | NULL { $$ = {type: 'Null', pos: @$, value: null}; } 147 | ; 148 | 149 | integer 150 | : INTEGER { $$ = {type: 'Integer', pos: @$, value: parseInt($1)}; } 151 | | '-' INTEGER { $$ = {type: 'Integer', pos: @$, value: - parseInt($2)}; } 152 | ; 153 | 154 | float 155 | : FLOAT { $$ = {type: 'Float', pos: @$, value: parseFloat($1)}; } 156 | | '-' FLOAT { $$ = {type: 'Float', pos: @$, value: - parseInt($2)}; } 157 | ; 158 | 159 | dstring 160 | : DSTRING { $$ = {type: 'DString', pos: @$, value: $1.replace(/^"|"$/g, '').replace(/\\"/g, '"')}; } 161 | ; 162 | 163 | string 164 | : STRING { $$ = {type: 'String', pos: @$, value: $1.replace(/^'|'$/g, '')}; } 165 | ; 166 | 167 | directive 168 | : SET '(' assignExpr ')' { $$ = $3; } 169 | | if { $$ = $1; } 170 | | FOREACH '(' reference IN reference ')' statements END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5, body: $7}; } 171 | | FOREACH '(' reference IN reference ')' END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5}; } 172 | | FOREACH '(' reference IN range ')' statements END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5, body: $7}; } 173 | | FOREACH '(' reference IN range ')' END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5}; } 174 | | FOREACH '(' reference IN list ')' statements END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5, body: $7}; } 175 | | FOREACH '(' reference IN list ')' END { $$ = {type: 'Foreach', pos: @$, left: $3, right: $5}; } 176 | | INCLUDE '(' exprItems ')' { $$ = {type: 'Include', pos: @$, arguments: $3}; } 177 | | PARSE '(' exprItem ')' { $$ = {type: 'Parse', pos: @$, argument: $3}; } 178 | | EVALUATE '(' exprItem ')' { $$ = {type: 'Evaluate', pos: @$, argument: $3}; } 179 | | DEFINE '(' reference ')' statements END { $$ = {type: 'Define', pos: @$, name: $3, body: $5}; } 180 | | DEFINE '(' reference ')' END { $$ = {type: 'Define', pos: @$, name: $3}; } 181 | | MACRO '(' ID delim macroParams ')' statements END { $$ = {type: 'Macro', pos: @$, name: $3, arguments: $5, body: $7}; } 182 | | MACRO '(' ID ')' statements END { $$ = {type: 'Macro', pos: @$, name: $3, arguments: [], body: $5}; } 183 | | MACRO '(' ID delim macroParams ')' END { $$ = {type: 'Macro', pos: @$, name: $3, arguments: $5}; } 184 | | MACRO '(' ID ')' END { $$ = {type: 'Macro', pos: @$, name: $3, arguments: []}; } 185 | | MACROCALL '(' macroCallParams ')' { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#{?|}$/g, ''), arguments: $3}; } 186 | | MACROCALL '(' ')' { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#{?|}$/g, ''), arguments: []}; } 187 | | BMACROCALL '(' macroCallParams ')' statements END { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#@{?|}$/g, ''), arguments: $3, isBlock: true, body: $5}; } 188 | | BMACROCALL '(' ')' statements END { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#@{?|}$/g, ''), arguments: [], isBlock: true, body: $4}; } 189 | | BMACROCALL '(' macroCallParams ')' END { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#@{?|}$/g, ''), arguments: $3, isBlock: true}; } 190 | | BMACROCALL '(' ')' END { $$ = {type: 'MacroCall', pos: @$, name: $1.replace(/^#@{?|}$/g, ''), arguments: [], isBlock: true}; } 191 | | STOP { $$ = {type: 'Stop', pos: @$}; } 192 | | BREAK { $$ = {type: 'Break', pos: @$}; } 193 | ; 194 | 195 | else 196 | : ELSE { $$ = undefined; } 197 | | ELSE statements { $$ = $2; } 198 | ; 199 | 200 | elseif 201 | : ELSEIF '(' expr ')' statements { $$ = {type: 'If', pos: @$, test: $3, consequent: $5}; } 202 | | ELSEIF '(' expr ')' { $$ = {type: 'If', pos: @$, test: $3}; } 203 | | ELSEIF '(' expr ')' statements else { $$ = {type: 'If', pos: @$, test: $3, consequent: $5, alternate: $6}; } 204 | | ELSEIF '(' expr ')' else { $$ = {type: 'If', pos: @$, test: $3, alternate: $5}; } 205 | | ELSEIF '(' expr ')' statements elseif { $$ = {type: 'If', pos: @$, test: $3, consequent: $5, alternate: $6}; } 206 | | ELSEIF '(' expr ')' elseif { $$ = {type: 'If', pos: @$, test: $3, alternate: $6}; } 207 | ; 208 | 209 | if 210 | : IF '(' expr ')' statements END { $$ = {type: 'If', pos: @$, test: $3, consequent: $5}; } 211 | | IF '(' expr ')' END { $$ = {type: 'If', pos: @$, test: $3}; } 212 | | IF '(' expr ')' statements else END { $$ = {type: 'If', pos: @$, test: $3, consequent: $5, alternate: $6}; } 213 | | IF '(' expr ')' else END { $$ = {type: 'If', pos: @$, test: $3, alternate: $5}; } 214 | | IF '(' expr ')' statements elseif END { $$ = {type: 'If', pos: @$, test: $3, consequent: $5, alternate: $6}; } 215 | | IF '(' expr ')' elseif END { $$ = {type: 'If', pos: @$, test: $3, alternate: $5}; } 216 | ; 217 | 218 | macroParams 219 | : reference { $$ = [$1]; } 220 | | reference delim macroParams { $$ = [$1].concat($3); } 221 | ; 222 | 223 | macroCallParams 224 | : exprItem { $$ = [$1]; } 225 | | exprItem delim macroCallParams { $$ = [$1].concat($3); } 226 | ; 227 | 228 | delim 229 | : WS 230 | | ',' 231 | ; 232 | 233 | -------------------------------------------------------------------------------- /lib/handle-cfg.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var utilx = require('utilx') 4 | 5 | var logger = require('./logger') 6 | 7 | var defCfg = { 8 | encoding: 'utf8' 9 | } 10 | 11 | module.exports = function(cfg) { 12 | // logger.debug('Raw config', cfg) 13 | 14 | var cfgFile = './velocity-config.js' 15 | if (utilx.isExistedFile(cfgFile)) { 16 | var projCfg = require(path.resolve(cfgFile)) 17 | cfg = utilx.mix(cfg, projCfg) 18 | // logger.debug('Mix project config', cfg) 19 | } 20 | 21 | cfg = utilx.mix(cfg, defCfg) 22 | 23 | // Template 24 | if (!cfg.template) logger.error('Option template is required.') 25 | cfg.template = str2Obj(cfg.template) 26 | 27 | // Root 28 | if (cfg.root) { 29 | if (utilx.isString(cfg.root)) cfg.root = [cfg.root] 30 | cfg.root.forEach(function(p, idx){ 31 | if (!utilx.isExistedDir(p)) { 32 | logger.error('Option root <%s> is not directory or not exists.', p) 33 | } 34 | }) 35 | cfg.root = cfg.root.map(function(p) { 36 | return path.resolve(p) 37 | }) 38 | } 39 | 40 | // Macro 41 | if (cfg.macro) { 42 | if (utilx.isString(cfg.macro)) cfg.macro = [cfg.macro] 43 | cfg.macro = cfg.macro.map(function(raw) { 44 | return str2Obj(raw) 45 | }) 46 | } 47 | 48 | logger.debug('Processed config', cfg) 49 | return cfg 50 | } 51 | 52 | 53 | function str2Obj(raw) { 54 | if (utilx.isExistedFile(raw)) { 55 | return { 56 | isFile: true, 57 | raw: raw, 58 | fullPath: path.resolve(raw) 59 | } 60 | } else if (utilx.isString(raw)){ 61 | return { 62 | isFile: false, 63 | raw: raw 64 | } 65 | } else { 66 | logger.error('Value of config.value or config.template must be a file path or a velocity string.') 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Data: require('./data'), 3 | Engine: require('./engine'), 4 | parser: require('./engine/velocity'), 5 | dep: require('./dep') 6 | } -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | var colorful = require('colorful') 2 | 3 | var levels = ['debug', 'info', 'warn', 'error'] 4 | var logLevel = 'info' 5 | process.argv.forEach(function(item, idx, list) { 6 | if (item.match(/^(--debug|-[a-zA-Z]*d[a-zA-Z]*)$/)) { 7 | logLevel = 'debug' 8 | } 9 | }) 10 | 11 | module.exports = require('tracer').colorConsole({ 12 | depth: null, 13 | methods: levels, 14 | level: logLevel, 15 | 16 | format: "{{title}}: {{message}} ({{file}}: {{line}})", 17 | 18 | filters: { 19 | info: colorful.gray, 20 | warn: colorful.yellow, 21 | error: colorful.red 22 | }, 23 | 24 | transport: function(data) { 25 | var title = data.title; 26 | if (levels.indexOf(title) >= levels.indexOf(logLevel)) { 27 | if (title === 'error') { 28 | throw new Error(data.message) 29 | } else { 30 | console.log(data.output) 31 | } 32 | } 33 | } 34 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "velocity", 3 | "version": "0.7.3", 4 | "description": "A node velocity template engine.", 5 | "homepage": "https://github.com/fool2fish/velocity", 6 | "keywords": [ 7 | "node", 8 | "velocity", 9 | "template", 10 | "engine" 11 | ], 12 | "author": "fool2fish ", 13 | "scripts": { 14 | "test": "mocha -R spec -t 15000 -r should test/*.test.js", 15 | "test-cov": "istanbul cover node_modules/.bin/_mocha -- -t 15000 -r should test/*.test.js" 16 | }, 17 | "dependencies": { 18 | "colorful": "~2.1.0", 19 | "commander": "~2.3.0", 20 | "tracer": "~1.1.4", 21 | "utilx": "0.0.5" 22 | }, 23 | "devDependencies": { 24 | "autod": "*", 25 | "contributors": "*", 26 | "mocha": "*", 27 | "should": "~5.2.0", 28 | "istanbul": "*", 29 | "cov": "*", 30 | "jshint": "*" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git@github.com:fool2fish/velocity.git" 35 | }, 36 | "bin": { 37 | "velocity": "./bin/velocity" 38 | }, 39 | "config": { 40 | "alicov": { 41 | "threshold": 80 42 | } 43 | }, 44 | "preferGlobal": true, 45 | "license": "MIT" 46 | } 47 | -------------------------------------------------------------------------------- /test/engine.test.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * velocity - test/engine.test.js 3 | * 4 | * Copyright(c) fengmk2 and other contributors. 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var should = require('should') 18 | var path = require('path') 19 | var Engine = require('../').Engine 20 | var utils = require('./utils') 21 | 22 | var tpl = path.join(path.dirname(__dirname), 'examples') 23 | var fixtures = path.join(__dirname, 'fixtures') 24 | 25 | describe('engine.test.js', function () { 26 | describe('render()', function () { 27 | it('should render a simple vm', function () { 28 | var engine = new Engine({ 29 | template: path.join(tpl, 'hello', 'index.vm') 30 | }) 31 | 32 | engine.render({name: 'fengmk2'}).should.equal('Hello, fengmk2!\n') 33 | engine.render({name: ''}).should.equal('Hello, !\n') 34 | engine.render({name: null}).should.equal('Hello, ${name}!\n') 35 | engine.render({name: undefined}).should.equal('Hello, ${name}!\n') 36 | engine.render({}).should.equal('Hello, ${name}!\n') 37 | }) 38 | 39 | it('should render $!id => "" when id not exist', function () { 40 | var engine = new Engine({ 41 | template: 'ok $!id.' 42 | }) 43 | engine.render({}).should.equal('ok .') 44 | engine.render({id: null}).should.equal('ok .') 45 | engine.render({id: 123}).should.equal('ok 123.') 46 | engine.render({id: 'foo'}).should.equal('ok foo.') 47 | }) 48 | 49 | it('should render with macro', function () { 50 | var engine = new Engine({ 51 | template: path.join(tpl, 'macro', 'index.vm'), 52 | macro: path.join(tpl, 'macro', 'macro.vm'), 53 | }) 54 | 55 | engine.render({ 56 | name: 'fool2fish', 57 | github: 'https://github.com/fool2fish', 58 | favorites: ['food', 'travel', 'comic', '...'] 59 | }).should.equal(utils.string('macro-result.txt')) 60 | }) 61 | 62 | it('should throw an error when calling an undefined macro', function() { 63 | var engine = new Engine({ 64 | template: '#a()' 65 | }) 66 | engine.render.bind().should.throw() 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/fixtures/if-else-end.vm: -------------------------------------------------------------------------------- 1 | #if($user.getAge(18)) 2 | welcome board 3 | #else 4 | this is else 5 | #end 6 | -------------------------------------------------------------------------------- /test/fixtures/if-elseif-elseif-end.vm: -------------------------------------------------------------------------------- 1 | #if($a) 2 | $a 3 | #elseif($b) 4 | $b 5 | #elseif($c) 6 | $c 7 | #end 8 | -------------------------------------------------------------------------------- /test/fixtures/if-elseif-end.vm: -------------------------------------------------------------------------------- 1 | #if ($a) 2 | $a 3 | #elseif ($b) 4 | $b 5 | #end 6 | -------------------------------------------------------------------------------- /test/fixtures/if-end.vm: -------------------------------------------------------------------------------- 1 | #if($user.getAge(18)) 2 | welcome board 3 | #end 4 | -------------------------------------------------------------------------------- /test/fixtures/macro-result.txt: -------------------------------------------------------------------------------- 1 | 2 | Arguments of commonMacro: [$a, fool2fish, https://github.com/fool2fish] 3 | 4 | 5 | Arguments of commonMacro: [a, b, c] 6 | 7 | 8 | 9 | Argument of blockMacro: one 10 | Body content: 11 | Block macro is called 12 | 13 | 14 | 15 | Argument of blockMacro: two 16 | Body content: 17 | Block macro is called again 18 | 19 | 20 | 21 | 22 | 23 | Argument of local macro: $arg1, secondParam 24 | 25 | 26 | Argument of local macro: food,travel,comic,..., $arg2 27 | 28 | -------------------------------------------------------------------------------- /test/parser.test.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * velocity - test/parser.test.js 3 | * 4 | * Copyright(c) fengmk2 and other contributors. 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var should = require('should') 18 | var parser = require('../').parser 19 | var utils = require('./utils') 20 | 21 | describe('parser.test.js', function () { 22 | describe('Text', function () { 23 | it('should parse normal text', function () { 24 | var ast = parser.parse('Hello world') 25 | ast.type.should.equal('Statements') 26 | ast.body.should.length(1) 27 | ast.body[0].type.should.equal('Text') 28 | ast.body[0].value.should.equal('Hello world') 29 | }) 30 | }) 31 | 32 | describe('Reference => Identifier', function () { 33 | it('should parse a simple vm and return ast', function () { 34 | var ast = parser.parse('Hello, ${name}!\n$!foo 123') 35 | ast.body.should.length(5) 36 | 37 | ast.body[0].type.should.equal('Text') 38 | ast.body[0].value.should.equal('Hello, ') 39 | 40 | ast.body[1].type.should.equal('Reference') 41 | ast.body[1].object.type.should.equal('Identifier') 42 | ast.body[1].object.name.should.equal('name') 43 | ast.body[1].wrapped.should.equal(true) 44 | 45 | ast.body[2].type.should.equal('Text') 46 | ast.body[2].value.should.equal('!\n') 47 | 48 | ast.body[3].type.should.equal('Reference') 49 | ast.body[3].object.type.should.equal('Identifier') 50 | ast.body[3].object.name.should.equal('foo') 51 | ast.body[3].silent.should.equal(true) 52 | 53 | ast.body[4].type.should.equal('Text') 54 | ast.body[4].value.should.equal(' 123') 55 | }) 56 | 57 | it('should parse simple Reference', function () { 58 | var ast = parser.parse('$name') 59 | ast.body.should.eql([ 60 | { type: 'Reference', 61 | pos: { first_line: 1, last_line: 1, first_column: 0, last_column: 5 }, 62 | object: 63 | { type: 'Identifier', 64 | pos: { first_line: 1, last_line: 1, first_column: 1, last_column: 5 }, 65 | name: 'name' } } 66 | ]) 67 | 68 | ast = parser.parse('${name}') 69 | ast.body.should.eql([ 70 | { type: 'Reference', 71 | pos: { first_line: 1, last_line: 1, first_column: 0, last_column: 7 }, 72 | object: 73 | { type: 'Identifier', 74 | pos: { first_line: 1, last_line: 1, first_column: 2, last_column: 6 }, 75 | name: 'name' }, 76 | wrapped: true } 77 | ]) 78 | 79 | ast = parser.parse('$_ref') 80 | ast.body.should.eql([ { 81 | type: 'Reference', 82 | pos: { first_line: 1, last_line: 1, first_column: 0, last_column: 5 }, 83 | object: 84 | { type: 'Identifier', 85 | pos: { first_line: 1, last_line: 1, first_column: 1, last_column: 5 }, 86 | name: '_ref' } } 87 | ]) 88 | }) 89 | }) 90 | 91 | describe('UnaryExpr', function () { 92 | it('should parse `#set($a = !$b)`', function () { 93 | var ast = parser.parse("#set($a = !$b)") 94 | ast.body.should.length(1) 95 | 96 | ast.body[0].type.should.equal('AssignExpr') 97 | ast.body[0].should.have.property('left') 98 | ast.body[0].should.have.property('right') 99 | ast.body[0].left.type.should.equal('Reference') 100 | ast.body[0].left.object.type.should.equal('Identifier') 101 | ast.body[0].left.object.name.should.equal('a') 102 | ast.body[0].right.type.should.equal('UnaryExpr') 103 | ast.body[0].right.operator.should.equal('!') 104 | 105 | var argument = ast.body[0].right.argument 106 | argument.type.should.equal('Reference') 107 | argument.object.type.should.equal('Identifier') 108 | argument.object.name.should.equal('b') 109 | }) 110 | }) 111 | 112 | describe('AssignExpr', function () { 113 | it('should parse `#set($a = $b)`', function () { 114 | var ast = parser.parse("#set($a = $b)\nMap: { 'nick': '$map.nick' }") 115 | ast.body.should.length(4) 116 | 117 | ast.body[0].type.should.equal('AssignExpr') 118 | ast.body[0].should.have.property('left') 119 | ast.body[0].should.have.property('right') 120 | ast.body[0].left.type.should.equal('Reference') // $ 121 | ast.body[0].left.object.type.should.equal('Identifier') // $xxx 122 | ast.body[0].left.object.name.should.equal('a') 123 | ast.body[0].right.type.should.equal('Reference') 124 | ast.body[0].right.object.type.should.equal('Identifier') 125 | ast.body[0].right.object.name.should.equal('b') 126 | 127 | ast.body[1].type.should.equal('Text') 128 | ast.body[1].value.should.equal("\nMap: { 'nick': '") 129 | 130 | ast.body[2].type.should.equal('Reference') // $ 131 | ast.body[2].object.type.should.equal('Property') // $xx.yy 132 | ast.body[2].object.object.type.should.equal('Identifier') 133 | ast.body[2].object.object.name.should.equal('map') 134 | ast.body[2].object.property.type.should.equal('Prop') 135 | ast.body[2].object.property.name.should.equal('nick') 136 | 137 | ast.body[3].type.should.equal('Text') 138 | ast.body[3].value.should.equal("' }") 139 | }) 140 | 141 | it('should parse `#set($a = 1)`', function () { 142 | var ast = parser.parse("#set($a = 1)") 143 | ast.body.should.length(1) 144 | 145 | ast.body[0].type.should.equal('AssignExpr') 146 | ast.body[0].should.have.property('left') 147 | ast.body[0].should.have.property('right') 148 | ast.body[0].left.type.should.equal('Reference') // $ 149 | ast.body[0].left.object.type.should.equal('Identifier') // $xxx 150 | ast.body[0].left.object.name.should.equal('a') 151 | ast.body[0].right.type.should.equal('Integer') 152 | ast.body[0].right.value.should.equal(1) 153 | }) 154 | 155 | it('should parse `#set($a = \'\')`', function () { 156 | var ast = parser.parse("#set($a = '')") 157 | ast.body.should.length(1) 158 | 159 | ast.body[0].type.should.equal('AssignExpr') 160 | ast.body[0].should.have.property('left') 161 | ast.body[0].should.have.property('right') 162 | ast.body[0].left.type.should.equal('Reference') 163 | ast.body[0].left.object.type.should.equal('Identifier') 164 | ast.body[0].left.object.name.should.equal('a') 165 | ast.body[0].right.type.should.equal('String') 166 | ast.body[0].right.value.should.equal('') 167 | 168 | var ast = parser.parse("#set($a = 'foo')") 169 | ast.body.should.length(1) 170 | 171 | ast.body[0].type.should.equal('AssignExpr') 172 | ast.body[0].should.have.property('left') 173 | ast.body[0].should.have.property('right') 174 | ast.body[0].left.type.should.equal('Reference') 175 | ast.body[0].left.object.type.should.equal('Identifier') 176 | ast.body[0].left.object.name.should.equal('a') 177 | ast.body[0].right.type.should.equal('String') 178 | ast.body[0].right.value.should.equal('foo') 179 | 180 | var ast = parser.parse("#set($a = \"foo\")") 181 | ast.body.should.length(1) 182 | 183 | ast.body[0].type.should.equal('AssignExpr') 184 | ast.body[0].should.have.property('left') 185 | ast.body[0].should.have.property('right') 186 | ast.body[0].left.type.should.equal('Reference') 187 | ast.body[0].left.object.type.should.equal('Identifier') 188 | ast.body[0].left.object.name.should.equal('a') 189 | ast.body[0].right.type.should.equal('DString') 190 | ast.body[0].right.value.should.equal('foo') 191 | }) 192 | 193 | it('should parse `#set($a = $user.nick)`', function () { 194 | var ast = parser.parse("#set($a = $user.nick)") 195 | ast.body.should.length(1) 196 | 197 | ast.body[0].type.should.equal('AssignExpr') 198 | ast.body[0].should.have.property('left') 199 | ast.body[0].should.have.property('right') 200 | ast.body[0].left.type.should.equal('Reference') // $ 201 | ast.body[0].left.object.type.should.equal('Identifier') // $xxx 202 | ast.body[0].left.object.name.should.equal('a') 203 | 204 | ast.body[0].right.type.should.equal('Reference') 205 | ast.body[0].right.object.type.should.equal('Property') // $ 206 | ast.body[0].right.object.object.type.should.equal('Identifier') // user 207 | ast.body[0].right.object.object.name.should.equal('user') 208 | ast.body[0].right.object.property.type.should.equal('Prop') // .nick 209 | ast.body[0].right.object.property.name.should.equal('nick') 210 | }) 211 | 212 | it('should parse `#set(${a} = ${user.nick})`', function () { 213 | var ast = parser.parse("#set(${a} = ${user.nick})") 214 | ast.body.should.length(1) 215 | 216 | ast.body[0].type.should.equal('AssignExpr') 217 | ast.body[0].should.have.property('left') 218 | ast.body[0].should.have.property('right') 219 | ast.body[0].left.type.should.equal('Reference') // $ 220 | ast.body[0].left.object.type.should.equal('Identifier') // $xxx 221 | ast.body[0].left.object.name.should.equal('a') 222 | ast.body[0].left.wrapped.should.equal(true) 223 | 224 | ast.body[0].right.type.should.equal('Reference') 225 | ast.body[0].right.object.type.should.equal('Property') // $ 226 | ast.body[0].right.object.object.type.should.equal('Identifier') // user 227 | ast.body[0].right.object.object.name.should.equal('user') 228 | ast.body[0].right.object.property.type.should.equal('Prop') // .nick 229 | ast.body[0].right.object.property.name.should.equal('nick') 230 | ast.body[0].right.wrapped.should.equal(true) 231 | }) 232 | 233 | it('should parse `#set($your.name = $user.nick)`', function () { 234 | var ast = parser.parse("#set($your.name = $user.nick)") 235 | ast.body.should.length(1) 236 | 237 | ast.body[0].type.should.equal('AssignExpr') 238 | ast.body[0].should.have.property('left') 239 | ast.body[0].should.have.property('right') 240 | 241 | ast.body[0].left.type.should.equal('Reference') // $ 242 | ast.body[0].left.object.type.should.equal('Property') 243 | ast.body[0].left.object.object.type.should.equal('Identifier') // your 244 | ast.body[0].left.object.object.name.should.equal('your') 245 | ast.body[0].left.object.property.type.should.equal('Prop') // .name 246 | ast.body[0].left.object.property.name.should.equal('name') 247 | 248 | ast.body[0].right.type.should.equal('Reference') 249 | ast.body[0].right.object.type.should.equal('Property') // $ 250 | ast.body[0].right.object.object.type.should.equal('Identifier') // user 251 | ast.body[0].right.object.object.name.should.equal('user') 252 | ast.body[0].right.object.property.type.should.equal('Prop') // .nick 253 | ast.body[0].right.object.property.name.should.equal('nick') 254 | }) 255 | }) 256 | 257 | describe('DString', function () { 258 | it('shold parse #set($a = "$foo bar")', function () { 259 | var ast = parser.parse('#set($a = "$foo bar")') 260 | ast.body.should.length(1) 261 | ast.body[0].right.type.should.equal('DString') 262 | ast.body[0].right.value.should.equal('$foo bar') 263 | }) 264 | }) 265 | 266 | describe('Reference => Property', function () { 267 | it('should parse `$map.nick`', function () { 268 | var ast = parser.parse("{ 'nick': '$map.nick' }") 269 | ast.body.should.length(3) 270 | 271 | ast.body[0].type.should.equal('Text') 272 | ast.body[0].value.should.equal("{ 'nick': '") 273 | 274 | ast.body[1].type.should.equal('Reference') // $ 275 | ast.body[1].object.type.should.equal('Property') // $xx.yy 276 | ast.body[1].object.object.type.should.equal('Identifier') 277 | ast.body[1].object.object.name.should.equal('map') 278 | ast.body[1].object.property.type.should.equal('Prop') 279 | ast.body[1].object.property.name.should.equal('nick') 280 | 281 | ast.body[2].type.should.equal('Text') 282 | ast.body[2].value.should.equal("' }") 283 | }) 284 | 285 | it('should parse `${map.nick}`', function () { 286 | var ast = parser.parse("{ 'nick': '${map.nick}Haha' }") 287 | ast.body.should.length(3) 288 | 289 | ast.body[0].type.should.equal('Text') 290 | ast.body[0].value.should.equal("{ 'nick': '") 291 | 292 | ast.body[1].type.should.equal('Reference') // $ 293 | ast.body[1].object.type.should.equal('Property') // $xx.yy 294 | ast.body[1].object.object.type.should.equal('Identifier') 295 | ast.body[1].object.object.name.should.equal('map') 296 | ast.body[1].object.property.type.should.equal('Prop') 297 | ast.body[1].object.property.name.should.equal('nick') 298 | ast.body[1].wrapped.should.equal(true) 299 | 300 | ast.body[2].type.should.equal('Text') 301 | ast.body[2].value.should.equal("Haha' }") 302 | }) 303 | 304 | it('should parse `$dateHelper.isHourBefore(11)`', function () { 305 | var ast = parser.parse("$dateHelper.isHourBefore(11)") 306 | ast.body.should.length(1) 307 | var obj = ast.body[0] 308 | obj.type.should.equal('Reference') // $ 309 | obj = obj.object 310 | obj.type.should.equal('Method') // $xx.foo(arg, ...) 311 | obj.callee.type.should.equal('Property') 312 | var callee = obj.callee 313 | callee.object.type.should.equal('Identifier') 314 | callee.object.name.should.equal('dateHelper') 315 | callee.property.type.should.equal('Prop') 316 | callee.property.name.should.equal('isHourBefore') 317 | 318 | obj.arguments.should.length(1) 319 | obj.arguments[0].type.should.equal('Integer') 320 | obj.arguments[0].value.should.equal(11) 321 | }) 322 | 323 | it('should parse `$user.name()`', function () { 324 | var ast = parser.parse("$user.name()") 325 | ast.body.should.length(1) 326 | var obj = ast.body[0] 327 | obj.type.should.equal('Reference') // $ 328 | obj = obj.object 329 | obj.type.should.equal('Method') // $xx.foo(arg, ...) 330 | obj.callee.type.should.equal('Property') 331 | var callee = obj.callee 332 | callee.object.type.should.equal('Identifier') 333 | callee.object.name.should.equal('user') 334 | callee.property.type.should.equal('Prop') 335 | callee.property.name.should.equal('name') 336 | 337 | obj.arguments.should.length(0) 338 | }) 339 | }) 340 | 341 | describe('if else', function () { 342 | it('should parse inline #if-else', function () { 343 | var ast = parser.parse('#if($a)foo#else b#end') 344 | ast.body.should.length(1) 345 | var obj = ast.body[0] 346 | obj.type.should.equal('If') 347 | obj.test.type.should.equal('Reference') 348 | obj.test.object.name.should.equal('a') 349 | obj.consequent.body.should.length(1) 350 | obj.consequent.body[0].type.should.equal('Text') 351 | obj.consequent.body[0].value.should.equal('foo') 352 | obj.alternate.type.should.equal('Statements') 353 | obj.alternate.body.should.length(1) 354 | obj.alternate.body[0].type.should.equal('Text') 355 | obj.alternate.body[0].value.should.equal(' b') 356 | }) 357 | 358 | it('should parse #if($a || $b)', function () { 359 | var ast = parser.parse('#if($a || $b)\nok\n#end') 360 | ast.body.should.length(1) 361 | var obj = ast.body[0] 362 | obj.type.should.equal('If') 363 | var test = obj.test; 364 | test.type.should.equal('BinaryExpr') 365 | test.operator.should.equal('||') 366 | var left = test.left 367 | left.type.should.equal('Reference') 368 | left.object.name.should.equal('a') 369 | var right = test.right 370 | right.type.should.equal('Reference') 371 | right.object.name.should.equal('b') 372 | obj.consequent.body.should.length(1) 373 | obj.consequent.body[0].type.should.equal('Text') 374 | obj.consequent.body[0].value.should.equal('\nok\n') 375 | }) 376 | 377 | it('should parse #if($a || $b || $c)', function () { 378 | var ast = parser.parse('#if($a || $b || $c)\nok\n#end') 379 | ast.body.should.length(1) 380 | var obj = ast.body[0] 381 | obj.type.should.equal('If') 382 | var test = obj.test; 383 | test.type.should.equal('BinaryExpr') 384 | test.operator.should.equal('||') 385 | var left = test.left 386 | left.type.should.equal('BinaryExpr') 387 | left.operator.should.equal('||') 388 | left.left.type.should.equal('Reference') 389 | left.left.object.name.should.equal('a') 390 | left.right.type.should.equal('Reference') 391 | left.right.object.name.should.equal('b') 392 | 393 | var right = test.right 394 | right.type.should.equal('Reference') 395 | right.object.name.should.equal('c') 396 | obj.consequent.body.should.length(1) 397 | obj.consequent.body[0].type.should.equal('Text') 398 | obj.consequent.body[0].value.should.equal('\nok\n') 399 | }) 400 | 401 | it('should parse #if($a || $b && $c)', function () { 402 | var ast = parser.parse('#if($a || $b && $c)\nok\n#end') 403 | ast.body.should.length(1) 404 | var obj = ast.body[0] 405 | obj.type.should.equal('If') 406 | var test = obj.test; 407 | test.type.should.equal('BinaryExpr') 408 | test.operator.should.equal('||') 409 | var left = test.left 410 | left.type.should.equal('Reference') 411 | left.object.name.should.equal('a') 412 | 413 | var right = test.right 414 | right.type.should.equal('BinaryExpr') 415 | right.operator.should.equal('&&') 416 | right.left.type.should.equal('Reference') 417 | right.left.object.name.should.equal('b') 418 | right.right.type.should.equal('Reference') 419 | right.right.object.name.should.equal('c') 420 | 421 | obj.consequent.body.should.length(1) 422 | obj.consequent.body[0].type.should.equal('Text') 423 | obj.consequent.body[0].value.should.equal('\nok\n') 424 | }) 425 | 426 | it('should parse #if(($a || $b) && $c)', function () { 427 | var ast = parser.parse('#if(($a || $b) && $c)\nok\n#end') 428 | ast.body.should.length(1) 429 | var obj = ast.body[0] 430 | obj.type.should.equal('If') 431 | var test = obj.test; 432 | test.type.should.equal('BinaryExpr') 433 | test.operator.should.equal('&&') 434 | var left = test.left 435 | left.type.should.equal('BinaryExpr') 436 | left.operator.should.equal('||') 437 | left.left.type.should.equal('Reference') 438 | left.left.object.name.should.equal('a') 439 | left.right.type.should.equal('Reference') 440 | left.right.object.name.should.equal('b') 441 | 442 | var right = test.right 443 | right.type.should.equal('Reference') 444 | right.object.name.should.equal('c') 445 | 446 | obj.consequent.body.should.length(1) 447 | obj.consequent.body[0].type.should.equal('Text') 448 | obj.consequent.body[0].value.should.equal('\nok\n') 449 | }) 450 | 451 | it('should parse #if(!$a)', function () { 452 | var ast = parser.parse('#if(!$a)\nok\n#end') 453 | ast.body.should.length(1) 454 | var obj = ast.body[0] 455 | obj.type.should.equal('If') 456 | var test = obj.test; 457 | test.type.should.equal('UnaryExpr') 458 | test.operator.should.equal('!') 459 | var argument = test.argument 460 | argument.type.should.equal('Reference') 461 | argument.object.name.should.equal('a') 462 | obj.consequent.body.should.length(1) 463 | obj.consequent.body[0].type.should.equal('Text') 464 | obj.consequent.body[0].value.should.equal('\nok\n') 465 | }) 466 | 467 | it('should parse #if #end', function () { 468 | var ast = parser.parse(utils.string('if-end.vm').trim()) 469 | ast.body.should.length(1) 470 | var obj = ast.body[0] 471 | obj.type.should.equal('If') 472 | 473 | obj.test.type.should.equal('Reference') 474 | var test = obj.test.object 475 | test.type.should.equal('Method') 476 | test.callee.type.should.equal('Property') 477 | test.callee.object.name.should.equal('user') 478 | test.callee.property.name.should.equal('getAge') 479 | test.arguments.should.length(1) 480 | test.arguments[0].type.should.equal('Integer') 481 | test.arguments[0].value.should.equal(18) 482 | 483 | obj.consequent.type.should.equal('Statements') 484 | obj.consequent.body.should.length(1) 485 | obj.consequent.body[0].type.should.equal('Text') 486 | obj.consequent.body[0].value.should.equal('\n welcome board\n') 487 | }) 488 | 489 | it('should parse #if #else #end', function () { 490 | var ast = parser.parse(utils.string('if-else-end.vm').trim()) 491 | ast.body.should.length(1) 492 | var obj = ast.body[0] 493 | obj.type.should.equal('If') 494 | 495 | obj.test.type.should.equal('Reference') 496 | var test = obj.test.object 497 | test.type.should.equal('Method') 498 | test.callee.type.should.equal('Property') 499 | test.callee.object.name.should.equal('user') 500 | test.callee.property.name.should.equal('getAge') 501 | test.arguments.should.length(1) 502 | test.arguments[0].type.should.equal('Integer') 503 | test.arguments[0].value.should.equal(18) 504 | 505 | obj.consequent.type.should.equal('Statements') 506 | obj.consequent.body.should.length(1) 507 | obj.consequent.body[0].type.should.equal('Text') 508 | obj.consequent.body[0].value.should.equal('\n welcome board\n') 509 | 510 | should.exist(obj.alternate) 511 | obj.alternate.type.should.equal('Statements') 512 | obj.alternate.body.should.length(1) 513 | obj.alternate.body[0].type.should.equal('Text') 514 | obj.alternate.body[0].value.should.equal('\n this is else\n') 515 | }) 516 | 517 | it('should parse #if #elseif #end', function () { 518 | var ast = parser.parse(utils.string('if-elseif-end.vm').trim()) 519 | ast.body.should.length(1) 520 | var obj = ast.body[0] 521 | obj.type.should.equal('If') 522 | 523 | var test = obj.test; 524 | test.type.should.equal('Reference') 525 | test.object.type.should.equal('Identifier') 526 | test.object.name.should.equal('a') 527 | obj.consequent.type.should.equal('Statements') 528 | obj.consequent.body.should.length(3) 529 | 530 | obj.alternate.type.should.equal('If') 531 | var alternate = obj.alternate 532 | alternate.test.type.should.equal('Reference') 533 | alternate.test.object.type.should.equal('Identifier') 534 | alternate.test.object.name.should.equal('b') 535 | alternate.consequent.type.should.equal('Statements') 536 | alternate.consequent.body.should.length(3) 537 | 538 | should.not.exist(alternate.alternate) 539 | }) 540 | 541 | it('should parse #if #elseif #elseif #end', function () { 542 | var ast = parser.parse(utils.string('if-elseif-elseif-end.vm').trim()) 543 | ast.body.should.length(1) 544 | var obj = ast.body[0] 545 | obj.type.should.equal('If') 546 | 547 | var test = obj.test; 548 | test.type.should.equal('Reference') 549 | test.object.type.should.equal('Identifier') 550 | test.object.name.should.equal('a') 551 | obj.consequent.type.should.equal('Statements') 552 | obj.consequent.body.should.length(3) 553 | 554 | obj.alternate.type.should.equal('If') 555 | var alternate = obj.alternate 556 | alternate.test.type.should.equal('Reference') 557 | alternate.test.object.type.should.equal('Identifier') 558 | alternate.test.object.name.should.equal('b') 559 | alternate.consequent.type.should.equal('Statements') 560 | alternate.consequent.body.should.length(3) 561 | 562 | should.exist(alternate.alternate) 563 | 564 | alternate = obj.alternate.alternate 565 | alternate.test.type.should.equal('Reference') 566 | alternate.test.object.type.should.equal('Identifier') 567 | alternate.test.object.name.should.equal('c') 568 | alternate.consequent.type.should.equal('Statements') 569 | alternate.consequent.body.should.length(3) 570 | }) 571 | }) 572 | 573 | describe('Macro', function() { 574 | it('should define a macro', function() { 575 | var ast = parser.parse('#macro(a)#end') 576 | ast.body[0].type.should.equal('Macro') 577 | ast.body[0].name.should.equal('a') 578 | 579 | ast = parser.parse('#macro(_a)#end') 580 | ast.body[0].type.should.equal('Macro') 581 | ast.body[0].name.should.equal('_a') 582 | 583 | ast = parser.parse('#macro(a-)#end') 584 | ast.body[0].type.should.equal('Macro') 585 | ast.body[0].name.should.equal('a-') 586 | 587 | ast = parser.parse('#macro(a1)#end') 588 | ast.body[0].type.should.equal('Macro') 589 | ast.body[0].name.should.equal('a1') 590 | 591 | parser.parse.bind('#macro(a*)#end').should.throw() 592 | 593 | parser.parse.bind('#macro(1a)#end').should.throw() 594 | }) 595 | }) 596 | 597 | describe('MacroCall', function() { 598 | it('should call a macro', function() { 599 | var ast = parser.parse('#a()') 600 | ast.body[0].type.should.equal('MacroCall') 601 | ast.body[0].name.should.equal('a') 602 | 603 | ast = parser.parse('#1a()') 604 | ast.body[0].type.should.equal('Text') 605 | 606 | ast = parser.parse('#a*()') 607 | ast.body[0].type.should.equal('Text') 608 | 609 | ast = parser.parse('#_a()') 610 | ast.body[0].type.should.equal('MacroCall') 611 | ast.body[0].name.should.equal('_a') 612 | 613 | ast = parser.parse('#a-()') 614 | ast.body[0].type.should.equal('MacroCall') 615 | ast.body[0].name.should.equal('a-') 616 | 617 | ast = parser.parse('#a1()') 618 | ast.body[0].type.should.equal('MacroCall') 619 | ast.body[0].name.should.equal('a1') 620 | 621 | ast = parser.parse('#_if()') 622 | ast.body[0].type.should.equal('MacroCall') 623 | ast.body[0].name.should.equal('_if') 624 | 625 | ast = parser.parse('#define1()') 626 | ast.body[0].type.should.equal('MacroCall') 627 | ast.body[0].name.should.equal('define1') 628 | }) 629 | }) 630 | }) 631 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * velocity - test/utils.js 3 | * 4 | * Copyright(c) fengmk2 and other contributors. 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var fs = require('fs') 18 | var path = require('path') 19 | 20 | var fixtures = path.join(__dirname, 'fixtures') 21 | 22 | exports.string = function (name) { 23 | return fs.readFileSync(path.join(fixtures, name), 'utf8') 24 | } 25 | --------------------------------------------------------------------------------