├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── example.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generate-function 2 | 3 | Module that helps you write generated functions in Node 4 | 5 | ``` 6 | npm install generate-function 7 | ``` 8 | 9 | ## Disclamer 10 | 11 | Writing code that generates code is hard. 12 | You should only use this if you really, really, really need this for performance reasons (like schema validators / parsers etc). 13 | 14 | ## Usage 15 | 16 | ``` js 17 | const genfun = require('generate-function') 18 | const { d } = genfun.formats 19 | 20 | function addNumber (val) { 21 | const gen = genfun() 22 | 23 | gen(` 24 | function add (n) { 25 | return n + ${d(val)} // supports format strings to insert values 26 | } 27 | `) 28 | 29 | return gen.toFunction() // will compile the function 30 | } 31 | 32 | const add2 = addNumber(2) 33 | 34 | console.log('1 + 2 =', add2(1)) 35 | console.log(add2.toString()) // prints the generated function 36 | ``` 37 | 38 | If you need to close over variables in your generated function pass them to `toFunction(scope)` 39 | 40 | ``` js 41 | function multiply (a, b) { 42 | return a * b 43 | } 44 | 45 | function addAndMultiplyNumber (val) { 46 | const gen = genfun() 47 | 48 | gen(` 49 | function (n) { 50 | if (typeof n !== 'number') { 51 | throw new Error('argument should be a number') 52 | } 53 | const result = multiply(${d(val)}, n + ${d(val)}) 54 | return result 55 | } 56 | `) 57 | 58 | // use gen.toString() if you want to see the generated source 59 | 60 | return gen.toFunction({multiply}) 61 | } 62 | 63 | const addAndMultiply2 = addAndMultiplyNumber(2) 64 | 65 | console.log(addAndMultiply2.toString()) 66 | console.log('(3 + 2) * 2 =', addAndMultiply2(3)) 67 | ``` 68 | 69 | You can call `gen(src)` as many times as you want to append more source code to the function. 70 | 71 | ## Variables 72 | 73 | If you need a unique safe identifier for the scope of the generated function call `str = gen.sym('friendlyName')`. 74 | These are safe to use for variable names etc. 75 | 76 | ## Object properties 77 | 78 | If you need to access an object property use the `str = gen.property('objectName', 'propertyName')`. 79 | 80 | This returns `'objectName.propertyName'` if `propertyName` is safe to use as a variable. Otherwise 81 | it returns `objectName[propertyNameAsString]`. 82 | 83 | If you only pass `gen.property('propertyName')` it will only return the `propertyName` part safely 84 | 85 | ## License 86 | 87 | MIT 88 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const genfun = require('./') 2 | const { d } = genfun.formats 3 | 4 | function multiply (a, b) { 5 | return a * b 6 | } 7 | 8 | function addAndMultiplyNumber (val) { 9 | const fn = genfun(` 10 | function (n) { 11 | if (typeof n !== 'number') { 12 | throw new Error('argument should be a number') 13 | } 14 | const result = multiply(${d(val)}, n + ${d(val)}) 15 | return result 16 | } 17 | `) 18 | 19 | // use fn.toString() if you want to see the generated source 20 | 21 | return fn.toFunction({multiply}) 22 | } 23 | 24 | const addAndMultiply2 = addAndMultiplyNumber(2) 25 | 26 | console.log(addAndMultiply2.toString()) 27 | console.log('(3 + 2) * 2 =', addAndMultiply2(3)) 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var isProperty = require('is-property') 3 | 4 | var INDENT_START = /[\{\[]/ 5 | var INDENT_END = /[\}\]]/ 6 | 7 | // from https://mathiasbynens.be/notes/reserved-keywords 8 | var RESERVED = [ 9 | 'do', 10 | 'if', 11 | 'in', 12 | 'for', 13 | 'let', 14 | 'new', 15 | 'try', 16 | 'var', 17 | 'case', 18 | 'else', 19 | 'enum', 20 | 'eval', 21 | 'null', 22 | 'this', 23 | 'true', 24 | 'void', 25 | 'with', 26 | 'await', 27 | 'break', 28 | 'catch', 29 | 'class', 30 | 'const', 31 | 'false', 32 | 'super', 33 | 'throw', 34 | 'while', 35 | 'yield', 36 | 'delete', 37 | 'export', 38 | 'import', 39 | 'public', 40 | 'return', 41 | 'static', 42 | 'switch', 43 | 'typeof', 44 | 'default', 45 | 'extends', 46 | 'finally', 47 | 'package', 48 | 'private', 49 | 'continue', 50 | 'debugger', 51 | 'function', 52 | 'arguments', 53 | 'interface', 54 | 'protected', 55 | 'implements', 56 | 'instanceof', 57 | 'NaN', 58 | 'undefined' 59 | ] 60 | 61 | var RESERVED_MAP = {} 62 | 63 | for (var i = 0; i < RESERVED.length; i++) { 64 | RESERVED_MAP[RESERVED[i]] = true 65 | } 66 | 67 | var isVariable = function (name) { 68 | return isProperty(name) && !RESERVED_MAP.hasOwnProperty(name) 69 | } 70 | 71 | var formats = { 72 | s: function(s) { 73 | return '' + s 74 | }, 75 | d: function(d) { 76 | return '' + Number(d) 77 | }, 78 | o: function(o) { 79 | return JSON.stringify(o) 80 | } 81 | } 82 | 83 | var genfun = function() { 84 | var lines = [] 85 | var indent = 0 86 | var vars = {} 87 | 88 | var push = function(str) { 89 | var spaces = '' 90 | while (spaces.length < indent*2) spaces += ' ' 91 | lines.push(spaces+str) 92 | } 93 | 94 | var pushLine = function(line) { 95 | if (INDENT_END.test(line.trim()[0]) && INDENT_START.test(line[line.length-1])) { 96 | indent-- 97 | push(line) 98 | indent++ 99 | return 100 | } 101 | if (INDENT_START.test(line[line.length-1])) { 102 | push(line) 103 | indent++ 104 | return 105 | } 106 | if (INDENT_END.test(line.trim()[0])) { 107 | indent-- 108 | push(line) 109 | return 110 | } 111 | 112 | push(line) 113 | } 114 | 115 | var line = function(fmt) { 116 | if (!fmt) return line 117 | 118 | if (arguments.length === 1 && fmt.indexOf('\n') > -1) { 119 | var lines = fmt.trim().split('\n') 120 | for (var i = 0; i < lines.length; i++) { 121 | pushLine(lines[i].trim()) 122 | } 123 | } else { 124 | pushLine(util.format.apply(util, arguments)) 125 | } 126 | 127 | return line 128 | } 129 | 130 | line.scope = {} 131 | line.formats = formats 132 | 133 | line.sym = function(name) { 134 | if (!name || !isVariable(name)) name = 'tmp' 135 | if (!vars[name]) vars[name] = 0 136 | return name + (vars[name]++ || '') 137 | } 138 | 139 | line.property = function(obj, name) { 140 | if (arguments.length === 1) { 141 | name = obj 142 | obj = '' 143 | } 144 | 145 | name = name + '' 146 | 147 | if (isProperty(name)) return (obj ? obj + '.' + name : name) 148 | return obj ? obj + '[' + JSON.stringify(name) + ']' : JSON.stringify(name) 149 | } 150 | 151 | line.toString = function() { 152 | return lines.join('\n') 153 | } 154 | 155 | line.toFunction = function(scope) { 156 | if (!scope) scope = {} 157 | 158 | var src = 'return ('+line.toString()+')' 159 | 160 | Object.keys(line.scope).forEach(function (key) { 161 | if (!scope[key]) scope[key] = line.scope[key] 162 | }) 163 | 164 | var keys = Object.keys(scope).map(function(key) { 165 | return key 166 | }) 167 | 168 | var vals = keys.map(function(key) { 169 | return scope[key] 170 | }) 171 | 172 | return Function.apply(null, keys.concat(src)).apply(null, vals) 173 | } 174 | 175 | if (arguments.length) line.apply(null, arguments) 176 | 177 | return line 178 | } 179 | 180 | genfun.formats = formats 181 | module.exports = genfun 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generate-function", 3 | "version": "2.3.1", 4 | "description": "Module that helps you write generated functions in Node", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mafintosh/generate-function" 12 | }, 13 | "keywords": [ 14 | "generate", 15 | "code", 16 | "generation", 17 | "function", 18 | "performance" 19 | ], 20 | "author": "Mathias Buus", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/mafintosh/generate-function/issues" 24 | }, 25 | "homepage": "https://github.com/mafintosh/generate-function", 26 | "devDependencies": { 27 | "tape": "^4.9.1" 28 | }, 29 | "dependencies": { 30 | "is-property": "^1.0.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var genfun = require('./') 3 | 4 | tape('generate add function', function(t) { 5 | var fn = genfun() 6 | ('function add(n) {') 7 | ('return n + %d', 42) 8 | ('}') 9 | 10 | t.same(fn.toString(), 'function add(n) {\n return n + 42\n}', 'code is indented') 11 | t.same(fn.toFunction()(10), 52, 'function works') 12 | t.end() 13 | }) 14 | 15 | tape('generate function + closed variables', function(t) { 16 | var fn = genfun() 17 | ('function add(n) {') 18 | ('return n + %d + number', 42) 19 | ('}') 20 | 21 | var notGood = fn.toFunction() 22 | var good = fn.toFunction({number:10}) 23 | 24 | try { 25 | notGood(10) 26 | t.ok(false, 'function should not work') 27 | } catch (err) { 28 | t.same(err.message, 'number is not defined', 'throws reference error') 29 | } 30 | 31 | t.same(good(11), 63, 'function with closed var works') 32 | t.end() 33 | }) 34 | 35 | tape('generate property', function(t) { 36 | var gen = genfun() 37 | 38 | t.same(gen.property('a'), 'a') 39 | t.same(gen.property('42'), '"42"') 40 | t.same(gen.property('b', 'a'), 'b.a') 41 | t.same(gen.property('b', '42'), 'b["42"]') 42 | t.same(gen.sym(42), 'tmp') 43 | t.same(gen.sym('a'), 'a') 44 | t.same(gen.sym('a'), 'a1') 45 | t.same(gen.sym(42), 'tmp1') 46 | t.same(gen.sym('const'), 'tmp2') 47 | 48 | t.end() 49 | }) 50 | --------------------------------------------------------------------------------