├── Coding Beyond Logic └── quine.js ├── Functional JavaScript └── oddweb.js ├── Progress ├── elements-r1.js ├── elements-r2.js ├── elements-r3.js ├── elements-results.txt └── elements-test-suite.js ├── README.md └── eval and Domain-Specific Languages ├── binMatch.html ├── binMatch.js └── compile.js /Coding Beyond Logic/quine.js: -------------------------------------------------------------------------------- 1 | var a = []; a[0] = 'var a = []; '; a[1] = 'a['; 2 | a[2] = '] = '; 3 | a[3] = '\''; 4 | a[4] = '\\'; 5 | a[5] = ';'; 6 | a[6] = ''; 7 | a[7] = 'for(var i = 0; i < a.length; i++) console.log((i == 0 ? a[0] : a[6]) + a[1] + i + a[2] + a[3] + ((i == 3 || i == 4) ? a[4] : a[6]) + a[i] + a[3] + a[5] + (i == 7 ? a[7] : a[6]))';for(var i = 0; i < a.length; i++) console.log((i==0?a[0]:a[6])+a[1]+i+a[2]+a[3]+((i==3||i==4)?a[4]:a[6])+a[i]+a[3]+a[5]+(i==7?a[7]:a[6])) -------------------------------------------------------------------------------- /Functional JavaScript/oddweb.js: -------------------------------------------------------------------------------- 1 | "use script" 2 | 3 | // This is a commented version of oddweb, a static site generator 4 | // that was mentioned in Beautiful JavaScript (O'Reilly 2015) 5 | // 6 | // For the most up-to-date version of this file and its test suite, 7 | // please refer to its original repository: https://github.com/valueof/oddweb 8 | 9 | var path = require("path") 10 | var sh = require("shelljs") 11 | var fs = require("fs") 12 | var mime = require("mime") 13 | var handlebars = require("handlebars") 14 | var markdown = require("markdown").markdown 15 | var moment = require("moment") 16 | 17 | handlebars.registerHelper("date", function (date, format) { 18 | return moment(date).format(format) 19 | }) 20 | 21 | // Our main focus of interest are the following three 22 | // functions: read, build, and write. Each one of them 23 | // takes an object as an argument, modifies the object 24 | // in place and returns the modified object. 25 | // 26 | // The object's signature: 27 | // { 28 | // src: string, 29 | // dev: boolean, 30 | // pages: array, 31 | // cache: array, 32 | // resources: array 33 | // } 34 | 35 | // Here we read all the necessary information from 36 | // the disk: templates, supporting files, and so on. 37 | function read(site) { 38 | var dir = site.dir 39 | var uid = 0 40 | 41 | // All pages are in the directory named 'pages'. Each 42 | // page may contain meta information before the template 43 | // so we check to see if the file starts with an opening 44 | // brace. If it does, we parse it as JSON, extract the 45 | // meta data and make necessary modifications. 46 | site.pages = sh.ls("-R", path.join(dir, "pages")) 47 | site.pages = site.pages.reduce(function (acc, file) { 48 | var ap = path.join(dir, "pages", file) 49 | 50 | if (sh.test("-d", ap)) 51 | return acc 52 | 53 | var data = sh.cat(ap) 54 | var meta = {} 55 | 56 | if (data.trim()[0] === "{") { 57 | data = data.split("\n\n") 58 | meta = JSON.parse(data[0]) 59 | data = data.slice(1).join("\n\n") 60 | } 61 | 62 | // Add runtime data to the meta object. 63 | meta.id = uid++ 64 | meta.type = path.extname(file).slice(1) 65 | meta.path = meta.type === "md" ? file.replace(/\.\S+$/, ".html") : file 66 | 67 | // Each page can declare alternative URL (useful for 68 | // legacy links) so we should respect it. 69 | if (meta.altUrl) { 70 | meta.altPath = normalize(meta.altUrl, meta.path) 71 | 72 | if (path.extname(meta.altPath) === "") 73 | meta.altPath = path.join(meta.altPath, "index.html") 74 | } 75 | 76 | if (meta.url) 77 | meta.path = normalize(meta.url, meta.path) 78 | else 79 | meta.url = "/" + meta.path 80 | 81 | if (path.extname(meta.path) === "") 82 | meta.path = path.join(meta.path, "index.html") 83 | 84 | if (meta.template && path.extname(meta.template) === "") 85 | meta.template = meta.template + ".html" 86 | 87 | return acc.concat({ meta: meta, data: data }) 88 | }, []) 89 | 90 | // We want to read templates once and then access them 91 | // from memory in any of the functions that might follow. 92 | site.cache = sh.ls("-R", path.join(dir, "templates")) 93 | site.cache = site.cache.reduce(function (acc, file) { 94 | var ap = path.join(dir, "templates", file) 95 | 96 | if (sh.test("-d", ap) || path.extname(ap) !== ".html") 97 | return acc 98 | 99 | acc[file] = sh.cat(ap) 100 | return acc 101 | }, {}) 102 | 103 | // The same logic applies to supporting files: functions 104 | // that might follow should have quick access to them without 105 | // having to know where on the disk these files are located. 106 | site.resources = sh.ls("-R", path.join(dir, "res")) 107 | site.resources = site.resources.reduce(function (acc, file) { 108 | var ap = path.join(dir, "res", file) 109 | 110 | if (sh.test("-d", ap)) 111 | return acc 112 | 113 | var meta = { path: file, binary: !/^text\//.test(mime.lookup(file)) } 114 | var data = meta.binary ? fs.readFileSync(ap, { encoding: "binary" }) : sh.cat(ap) 115 | 116 | return acc.concat({ meta: meta, data: data }) 117 | }, []) 118 | 119 | return site 120 | } 121 | 122 | // This function builds the site: it compiles all templates, 123 | // executes plugins, and so on. 124 | function build(site) { 125 | var src = path.resolve(site.src) 126 | var config = path.join(src, "package.json") 127 | var plugins = require(config).oddwebPlugins || [] 128 | 129 | // Since we're working with a state that is represented 130 | // by a simple object, the following nine lines of code 131 | // are everything we need for a working plugin system. 132 | // Each plugin mirrors this function: it takes an object, 133 | // modifies it, and returns it back to us. 134 | // 135 | // For example, here's a plugin that replaces word 'cloud' 136 | // with 'cat' on all pages: 137 | // 138 | // module.exports = function (site, handlebars) { 139 | // site.pages = site.pages.map(function (page) { 140 | // page.data = page.data.replace(/cloud/g/, "cat") 141 | // return page 142 | // }) 143 | // 144 | // return site 145 | // } 146 | site = plugins.reduce(function (acc, plugin) { 147 | if (/^core\//.test(plugin)) 148 | return require(path.join(path.dirname(module.filename), plugin) + ".js")(acc, handlebars) 149 | 150 | if (path.extname(plugin) === ".js") 151 | return require(path.join(src, plugin))(acc, handlebars) 152 | 153 | return require(path.join(src, "node_modules", plugin))(acc, handlebars) 154 | }, site) 155 | 156 | // This is 'the core' of oddweb where we compile templates 157 | // and Markdown files. 158 | site.pages = site.pages.map(function (page) { 159 | if (page.meta.skip) 160 | return page 161 | 162 | switch (page.meta.type) { 163 | case "xml": 164 | case "html": 165 | page.data = handlebars.compile(page.data)({ page: page.meta, site: site }) 166 | break 167 | case "md": 168 | var html = [] 169 | var tmp = page.data.split("\n\n").map(function (block) { 170 | if (block.trim()[0] === "<") 171 | return "$" + (html.push(block) - 1) + "$" 172 | return block 173 | }).join("\n\n") 174 | 175 | page.data = markdown.toHTML(tmp).split("\n\n").map(function (block) { 176 | if (/^

\$\d+\$<\/p>$/.test(block)) 177 | return html[block.slice(4, block.length - 5)] 178 | return block 179 | }).join("\n\n") 180 | 181 | if (!page.meta.url) 182 | page.meta.path = page.meta.path.replace(/\.md$/, ".html") 183 | } 184 | 185 | if (page.meta.template) { 186 | page.data = handlebars.compile(site.cache[page.meta.template])({ 187 | content: new handlebars.SafeString(page.data), 188 | page: page.meta, 189 | site: site 190 | }) 191 | } 192 | 193 | return page 194 | }) 195 | 196 | return site 197 | } 198 | 199 | // Finally, a function to write our generated site 200 | // to disk. It's pretty straightforward. We're generating 201 | // a static site so its file hierarchy reflects its URL 202 | // hierarchy. 203 | function write(site) { 204 | function prep(root, p) { 205 | var dir = path.join(root, path.dirname(p)) 206 | 207 | if (!sh.test("-e", dir)) 208 | sh.mkdir("-p", dir) 209 | 210 | return path.join(root, p) 211 | } 212 | 213 | function wrt(list, root) { 214 | list.forEach(function (item) { 215 | var ap = prep(root, item.meta.path) 216 | 217 | if (item.meta.binary) 218 | fs.writeFileSync(ap, item.data, { encoding: "binary" }) 219 | else 220 | item.data.to(ap) 221 | 222 | if (item.meta.altPath) 223 | ("").to(prep(root, item.meta.altPath)) 224 | }) 225 | } 226 | 227 | wrt(site.pages, path.resolve(path.join(site.src, "site"))) 228 | wrt(site.resources, path.resolve(path.join(site.src, "site", "res"))) 229 | 230 | return site 231 | } 232 | 233 | module.exports = { 234 | read: read, 235 | build: build, 236 | write: write 237 | } 238 | 239 | // Usage example: 240 | // 241 | // var oddweb = require('oddweb') 242 | // var site = { dir: '/path/to/dir', dev: false } 243 | // 244 | // oddweb.write(oddweb.build(oddweb.read(site))) 245 | -------------------------------------------------------------------------------- /Progress/elements-r1.js: -------------------------------------------------------------------------------- 1 | function Elements(selector, context) { 2 | var elems, elem, k; 3 | 4 | selector = selector || ""; 5 | 6 | this.context = context || document; 7 | 8 | if (Array.isArray(selector) || selector instanceof Elements) { 9 | elems = selector; 10 | } else { 11 | try { 12 | elems = this.context.querySelectorAll(selector); 13 | } catch (e) { 14 | elems = []; 15 | } 16 | } 17 | 18 | if (!elems) { 19 | // elems is either: 20 | // - undefined because the selector was invalid 21 | // resulting in a thrown exception 22 | // - null because the querySelectorAll returns 23 | // null instead of an empty object when no 24 | // matching elements are found. 25 | elems = [] 26 | } 27 | 28 | if (elems.length) { 29 | k = -1; 30 | while (elem = elems[++k]) { 31 | this[k] = elem; 32 | } 33 | } 34 | 35 | this.length = elems.length; 36 | } 37 | 38 | Elements.prototype = { 39 | constructor: Elements, 40 | addClass: function(value) { 41 | this.forEach(function(elem) { 42 | elem.classList.add(value); 43 | }); 44 | 45 | return this; 46 | }, 47 | attr: function(key, value) { 48 | if (typeof value !== "undefined") { 49 | this.forEach(function(elem) { 50 | elem.setAttribute(key, value); 51 | }); 52 | 53 | return this; 54 | } else { 55 | return this[0] && this[0].getAttribute(key); 56 | } 57 | }, 58 | css: function(key, value) { 59 | if (typeof value !== "undefined") { 60 | this.forEach(function(elem) { 61 | elem.style[key] = value; 62 | }); 63 | 64 | return this; 65 | } else { 66 | return this[0] && this[0].style[key]; 67 | } 68 | }, 69 | html: function(html) { 70 | if (typeof html !== "undefined") { 71 | this.forEach(function(elem) { 72 | elem.innerHTML = html; 73 | }); 74 | return this; 75 | } else { 76 | return this[0] && this[0].innerHTML; 77 | } 78 | }, 79 | filter: function() { 80 | return new Elements([].filter.apply(this, arguments)); 81 | }, 82 | forEach: function() { 83 | [].forEach.apply(this, arguments); 84 | return this; 85 | }, 86 | indexOf: function() { 87 | return [].indexOf.apply(this, arguments); 88 | }, 89 | push: function() { 90 | [].push.apply(this, arguments); 91 | return this; 92 | }, 93 | slice: function() { 94 | return new Elements([].slice.apply(this, arguments)); 95 | }, 96 | sort: function() { 97 | return [].sort.apply(this, arguments); 98 | } 99 | }; -------------------------------------------------------------------------------- /Progress/elements-r2.js: -------------------------------------------------------------------------------- 1 | function Elements(selector, context) { 2 | Array.call(this); 3 | 4 | var elems; 5 | 6 | this.context = context || document; 7 | 8 | if (Array.isArray(selector) || selector instanceof Elements) { 9 | elems = selector; 10 | } else { 11 | try { 12 | elems = this.context.querySelectorAll(selector || ""); 13 | } catch (e) { 14 | elems = []; 15 | } 16 | } 17 | 18 | if (!elems) { 19 | // elems is either: 20 | // - undefined because the selector was invalid 21 | // resulting in a thrown exception 22 | // - null because the querySelectorAll returns 23 | // null instead of an empty object when no 24 | // matching elements are found. 25 | elems = [] 26 | } 27 | 28 | this.push.apply(this, elems); 29 | } 30 | 31 | Elements.prototype = Object.create(Array.prototype); 32 | Elements.prototype.constructor = Elements; 33 | 34 | Elements.prototype.addClass = function(value) { 35 | this.forEach(function(elem) { 36 | elem.classList.add(value); 37 | }); 38 | 39 | return this; 40 | }; 41 | Elements.prototype.attr = function(key, value) { 42 | if (typeof value !== "undefined") { 43 | this.forEach(function(elem) { 44 | elem.setAttribute(key, value); 45 | }); 46 | 47 | return this; 48 | } else { 49 | return this[0] && this[0].getAttribute(key); 50 | } 51 | }; 52 | Elements.prototype.css = function(key, value) { 53 | if (typeof value !== "undefined") { 54 | this.forEach(function(elem) { 55 | elem.style[key] = value; 56 | }); 57 | 58 | return this; 59 | } else { 60 | return this[0] && this[0].style[key]; 61 | } 62 | }; 63 | Elements.prototype.html = function(html) { 64 | if (typeof html !== "undefined") { 65 | this.forEach(function(elem) { 66 | elem.innerHTML = html; 67 | }); 68 | return this; 69 | } else { 70 | return this[0] && this[0].innerHTML; 71 | } 72 | }; 73 | Elements.prototype.filter = function() { 74 | return new Elements([].filter.apply(this, arguments)); 75 | }; 76 | Elements.prototype.slice = function() { 77 | return new Elements([].slice.apply(this, arguments)); 78 | }; 79 | Elements.prototype.push = function() { 80 | [].push.apply(this, arguments); 81 | return this; 82 | }; -------------------------------------------------------------------------------- /Progress/elements-r3.js: -------------------------------------------------------------------------------- 1 | class Elements extends Array { 2 | constructor(selector = "", context = document) { 3 | super(); 4 | 5 | let elems; 6 | 7 | this.context = context; 8 | 9 | if (Array.isArray(selector) || 10 | selector instanceof Elements) { 11 | elems = selector; 12 | } else { 13 | try { 14 | elems = this.context.querySelectorAll(selector); 15 | } catch (e) { 16 | // Thrown Exceptions caused by invalid selectors 17 | // is a nuisance. 18 | } 19 | } 20 | 21 | if (!elems) { 22 | // elems is either: 23 | // - undefined because the selector was invalid 24 | // resulting in a thrown exception 25 | // - null because the querySelectorAll returns 26 | // null instead of an empty object when no 27 | // matching elements are found. 28 | elems = [] 29 | } 30 | 31 | this.push(...elems); 32 | } 33 | addClass(value) { 34 | return this.forEach(elem => elem.classList.add(value)); 35 | } 36 | attr(key, value) { 37 | if (typeof value !== "undefined") { 38 | return this.forEach(elem => elem.setAttribute(key, value)); 39 | } else { 40 | return this[0] && this[0].getAttribute(key); 41 | } 42 | } 43 | css(key, value) { 44 | if (typeof value !== "undefined") { 45 | return this.forEach(elem => elem.style[key] = value); 46 | } else { 47 | return this[0] && this[0].style[key]; 48 | } 49 | } 50 | html(html) { 51 | if (typeof html !== "undefined") { 52 | return this.forEach(elem => elem.innerHTML = html); 53 | } else { 54 | return this[0] && this[0].innerHTML; 55 | } 56 | } 57 | filter(callback) { 58 | return new Elements(super.filter(callback, this)); 59 | } 60 | slice(...args) { 61 | return new Elements(super.slice(...args)); 62 | } 63 | forEach(callback) { 64 | super.forEach(callback, this); 65 | return this; 66 | } 67 | push(...elems) { 68 | super.push(...elems); 69 | return this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Progress/elements-results.txt: -------------------------------------------------------------------------------- 1 | [[Result]] The Elements class prototype 2 | [[Result]] Zero length instance 3 | [[Result]] Elements from Elements 4 | [[Result]] One match will have a length of 1 (no context) 5 | [[Result]] One match will have a length of 1 6 | [[Result]] Two matches will have a length of 2 7 | [[Result]] Add a class 8 | [[Result]] Set and get an attribute 9 | [[Result]] Set and get a css style property 10 | [[Result]] Set and get some html 11 | [[Result]] Filtering produces a new instance 12 | [[Result]] Filter with a dummy predicate 13 | [[Result]] Filter with a predicate 14 | [[Result]] Invocation forEach item in the list 15 | [[Result]] Find the indexOf an element 16 | [[Result]] Push an element onto the list 17 | [[Result]] Push returns the instance, not the length 18 | [[Result]] Slicing produces a new instance 19 | [[Result]] Slice a list of elements 20 | [[Result]] Sort a list of elements by nodeName -------------------------------------------------------------------------------- /Progress/elements-test-suite.js: -------------------------------------------------------------------------------- 1 | function assert(expr) { 2 | return !!expr; 3 | } 4 | 5 | function fixture() { 6 | var div = document.createElement("div"); 7 | var li = document.createElement("li"); 8 | var ul = document.createElement("ul"); 9 | 10 | div.appendChild(document.createElement("span")); 11 | div.firstElementChild.textContent = "test text content"; 12 | 13 | div.appendChild(ul.cloneNode()); 14 | div.lastElementChild.appendChild(li.cloneNode()); 15 | 16 | div.appendChild(ul.cloneNode()); 17 | div.lastElementChild.appendChild(li.cloneNode()); 18 | 19 | return div; 20 | } 21 | 22 | var tests = { 23 | "The Elements class prototype": function() { 24 | var elems = new Elements(); 25 | var methods = [ 26 | "addClass", "attr", "css", "html", 27 | "filter", "forEach", "indexOf", "push", "slice", "sort" 28 | ]; 29 | 30 | return methods.reduce(function(result, method) { 31 | return typeof Elements.prototype[method] === "function"; 32 | }, true); 33 | }, 34 | "Zero length instance": function() { 35 | var elems = new Elements(); 36 | 37 | return assert(elems.length === 0); 38 | }, 39 | "Elements from Elements": function(fixture) { 40 | var elems = new Elements(new Elements([fixture])); 41 | 42 | return assert(elems.length === 1); 43 | }, 44 | "One match will have a length of 1 (no context)": function() { 45 | var elems = new Elements("body"); 46 | 47 | return assert(elems.length === 1); 48 | }, 49 | "One match will have a length of 1": function(fixture) { 50 | var elems = new Elements("span", fixture); 51 | 52 | return assert(elems.length === 1); 53 | }, 54 | "Two matches will have a length of 2": function(fixture) { 55 | var elems = new Elements("ul", fixture); 56 | 57 | return assert(elems.length === 2); 58 | }, 59 | "Add a class": function(fixture) { 60 | var elems = new Elements("span", fixture); 61 | 62 | elems.addClass("foo"); 63 | 64 | return assert(elems[0].className === "foo"); 65 | }, 66 | "Set and get an attribute": function(fixture) { 67 | var elems = new Elements("span", fixture); 68 | 69 | elems.attr("id", "foo"); 70 | 71 | return assert(elems.attr("id") === "foo"); 72 | }, 73 | "Set and get a css style property": function(fixture) { 74 | var elems = new Elements("span", fixture); 75 | 76 | elems.css("color", "red"); 77 | 78 | return assert(elems.css("color") === "red"); 79 | }, 80 | "Set and get some html": function(fixture) { 81 | var elems = new Elements("span", fixture); 82 | var html = "hi!"; 83 | 84 | elems.html(html); 85 | 86 | return assert(elems.html() === html); 87 | }, 88 | "Filtering produces a new instance": function(fixture) { 89 | var elems = new Elements("*", fixture); 90 | var filtered = elems.filter(Boolean); 91 | 92 | return assert(filtered instanceof Elements) && 93 | assert(filtered !== elems); 94 | }, 95 | "Filter with a dummy predicate": function(fixture) { 96 | var elems = new Elements("*", fixture); 97 | var filtered = elems.filter(Boolean); 98 | 99 | return assert(filtered.length === elems.length) && 100 | assert(filtered instanceof Elements) && 101 | assert(filtered !== elems); 102 | }, 103 | "Filter with a predicate": function(fixture) { 104 | var elems = new Elements("*", fixture); 105 | var filtered = elems.filter(function(elem) { 106 | return elem.nodeName.toLowerCase() === "span"; 107 | }); 108 | 109 | return assert(filtered.length === 1); 110 | }, 111 | "Invocation forEach item in the list": function(fixture) { 112 | var elems = new Elements("*", fixture); 113 | var calls = 0; 114 | elems.forEach(function(elem) { 115 | calls++; 116 | }); 117 | 118 | return assert(calls === 5); 119 | }, 120 | "Find the indexOf an element": function(fixture) { 121 | var elems = new Elements("*", fixture); 122 | 123 | return assert(elems.indexOf(fixture.firstElementChild) === 0); 124 | }, 125 | "Push an element onto the list": function(fixture) { 126 | var elems = new Elements("*", fixture); 127 | elems.push(fixture.firstElementChild); 128 | 129 | return assert(elems.length === 6); 130 | }, 131 | "Push returns the instance, not the length": function(fixture) { 132 | var elems = new Elements("*", fixture); 133 | elems.push(fixture.firstElementChild); 134 | 135 | return assert(elems.push(fixture.firstElementChild) === elems); 136 | }, 137 | "Slicing produces a new instance": function(fixture) { 138 | var elems = new Elements("*", fixture); 139 | var sliced = elems.slice(1); 140 | 141 | return assert(sliced instanceof Elements) && 142 | assert(sliced !== elems); 143 | }, 144 | "Slice a list of elements": function(fixture) { 145 | var elems = new Elements("*", fixture); 146 | var sliced = elems.slice(1); 147 | 148 | return assert(sliced.length === 4); 149 | }, 150 | "Sort a list of elements by nodeName": function(fixture) { 151 | var elems = new Elements("*", fixture); 152 | elems.sort(function(a, b) { 153 | if (a.nodeName < b.nodeName) { 154 | return -1; 155 | } 156 | if (a.nodeName > b.nodeName) { 157 | return 1; 158 | } 159 | return 0; 160 | }); 161 | 162 | return assert(elems[0].nodeName === "LI") && 163 | assert(elems[2].nodeName === "SPAN") && 164 | assert(elems[3].nodeName === "UL"); 165 | } 166 | }; 167 | 168 | Object.keys(tests).forEach(function(message) { 169 | var result = tests[message](fixture()); 170 | var response = (result ? "Pass" : "Fail") + ": " + message; 171 | 172 | console.log(response); 173 | }); 174 | 175 | console.log("tests complete"); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Beautiful JavaScript 2 | ========== 3 | 4 | This is the example code that accompanies Beautiful JavaScript by Anton Kovalyov (9781449370756). 5 | 6 | Click the Download Zip button to the right to download example code. 7 | 8 | Visit the catalog page [here](http://shop.oreilly.com/product/0636920030706.do). 9 | 10 | See an error? Report it [here](http://oreilly.com/catalog/errata.csp?isbn=0636920030706), or simply fork and send us a pull request. 11 | 12 | -------------------------------------------------------------------------------- /eval and Domain-Specific Languages/binMatch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /eval and Domain-Specific Languages/binMatch.js: -------------------------------------------------------------------------------- 1 | function binMatch(spec) { 2 | var totalSize = 0, code = "", match; 3 | while (match = /^([^:]+):(\w+)(\d+)\s*/.exec(spec)) { 4 | spec = spec.slice(match[0].length); 5 | var pattern = match[1], type = match[2], size = Number(match[3]); 6 | totalSize += size; 7 | 8 | if (pattern == "_") { 9 | code += "pos += " + size + ";"; 10 | } else if (/^[\w$]+$/.test(pattern)) { 11 | code += "out." + pattern + " = " + binMatch.read[type](size) + ";"; 12 | } else { 13 | code += "if (" + binMatch.read[type](size) + " !== " + 14 | pattern + ") return null;"; 15 | } 16 | } 17 | 18 | code = "if (input.length - pos < " + totalSize + ") return null;" + 19 | "var out = {end: pos + " + totalSize + "};" + code + "return out;"; 20 | return new Function("input, pos", code); 21 | } 22 | 23 | binMatch.read = { 24 | uint: function(size) { 25 | for(var exprs=[],i=1;i<=size;++i) 26 | exprs.push("input[pos++] * " + Math.pow(256, size - i)); 27 | return exprs.join(" + "); 28 | }, 29 | str: function(size) { 30 | for(var exprs=[],i=0;i