├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lib ├── acorn │ └── dist │ │ ├── acorn.js │ │ ├── acorn_loose.js │ │ └── walk.js ├── demo.js ├── require.js ├── setup_paths.js └── treehugger │ ├── js │ ├── builtin.json │ ├── infer.js │ ├── inferUp.js │ ├── parse.js │ ├── parse_test.js │ ├── types.js │ ├── uglifyparser.js │ └── values.js │ ├── rewrite.disabled.js │ ├── traverse.js │ └── tree.js ├── package.json └── test.html /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/ajaxorg/treehugger/issues), or [recently closed](https://github.com/ajaxorg/treehugger/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/ajaxorg/treehugger/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/ajaxorg/treehugger/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 2 | software and associated documentation files (the "Software"), to deal in the Software 3 | without restriction, including without limitation the rights to use, copy, modify, 4 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 5 | permit persons to whom the Software is furnished to do so, subject to the following 6 | conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies 9 | or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | treehugger.js 2 | ============= 3 | 4 | `treehugger.js` is a Javascript library for program processing. It 5 | has generic means to represent and manipulate (analyze, transform) 6 | [abstract syntax trees (ASTs)](http://en.wikipedia.org/wiki/Abstract_syntax_tree). 7 | It consists of three parts: 8 | 9 | * A generic ASTs representation format, inspired by [ATerms](http://www.meta-environment.org/Meta-Environment/ATerms)) 10 | that can be used to represent programs written in any language (Java, Ruby, Javascript) 11 | `lib/treehugger/tree.js` 12 | * A set of generic traversals to query, manipulate and annotate these ASTs, 13 | inspired by [Stratego/XT](http://strategoxt.org) 14 | `lib/treehugger/traverse.js` 15 | * A set of analyses for specific languages. Currently: 16 | - Javascript: a ([UglifyJS](https://github.com/mishoo/UglifyJS)-based) parser 17 | and analyses reconstructing the type structure and first attempts at type-inference. 18 | `lib/treehugger/js/*.js` 19 | 20 | The project relies on [require.js](http://requirejs.org) for library loading. 21 | 22 | AST Representation 23 | ------------------ 24 | 25 | ast.js uses a few simple data structures to represent ASTs and a textual representation 26 | that makes it easy to debug and write these ASTs. The textual representation 27 | is best introduced by example. 28 | 29 | Consider a simple expresion language with expression as follows: 30 | 31 | 2 + 3 * 1 32 | 33 | A parser could turn this into the following AST: 34 | 35 | Add 36 | / \ 37 | Num Mul 38 | | / \ 39 | 2 Num Num 40 | | | 41 | 3 1 42 | 43 | Which is expressed using treehugger.js's textual representation as follows: 44 | 45 | Add(Num("2"), Mul(Num("3"), Num("1"))) 46 | 47 | Using the `treehugger.js` API this AST can be contructed as follows: 48 | 49 | var tree = require('treehugger/tree'); 50 | var node = tree.cons("Add", [tree.cons("Num", [tree.string("2")]), 51 | tree.cons("Mul", [tree.cons("Num", [tree.string("3")]), 52 | tree.cons("Num", [tree.string("1")])])]); 53 | 54 | Or, more simply: 55 | 56 | var node = tree.parse('Add(Num("2"), Mul(Num("3"), Num("1")))'); 57 | 58 | treehugger.js has three kinds of AST node types: 59 | 60 | * Strings (e.g. `"2"`, `"myVariable"`), usually representing identifiers or other 61 | textual values. 62 | * Lists (e.g. `["a", "b", None()]`) 63 | * Constructors (or simply: cons nodes) (e.g. `None()` or `Num("2")`), used to represent 64 | language constructs in the language, such as operators, loop constructs, etc. 65 | 66 | 67 | AST Analysis 68 | ------------ 69 | 70 | Treehugger.js is based on the concept of _generic traversals_. A generic traversal 71 | traverses the tree in a particular order, for instance from top to bottom or from 72 | the bottom up. At every node the traversal comes across you can apply one or transformations. 73 | Tranformations can either be AST _patterns_, _transformation functions_ or a combination of both. 74 | 75 | The `treehugger/traverse.js` library adds a number of methods to AST nodes that make traversals simpler: 76 | 77 | * `collectTopDown` (traverses the tree top to bottom until finding a match, collecting all matches and returning them as a list) 78 | * `traverseTopDown` (traverses the tree top to bottom until finding a match) 79 | * (more are coming) 80 | 81 | A _transformation_ can be either: 82 | 83 | * A textual AST pattern 84 | * A textual AST pattern followed by a transformation function that is passed a binding object 85 | * A transformation function 86 | 87 | Each transform either _matches_ or _doesn't_ match. If a transformation function matches, 88 | it returns a non-falsy value (usually a new AST node), if it doesn't, it returns `false`. 89 | A simple example: 90 | 91 | var node = tree.parse('Add(Num("2"), Mul(Num("3"), Num("1")))'); 92 | node.collectTopDown("Num(_)").debug(); 93 | 94 | This will traverse the AST and look for nodes that match the Num(_) _pattern_, 95 | where `_` (a wildcard) can be anything. The `collectTopDown` traversal traverses 96 | the AST from top to bottom, and on the way collects all matches and returns them 97 | as an `ListNode`. The result of the `collectTopDown` call in this case will be: 98 | 99 | [Num("2"), Num("3"), Num("1")] 100 | 101 | The `.debug()` call prints the result to the Javascript console (`console.log`). 102 | 103 | So, what if we want to only return the numbers, not the `Num(...)` constructors? 104 | If we follow a textual pattern by a function, we can transform the result: 105 | 106 | var node = tree.parse('Add(Num("2"), Mul(Num("3"), Num("1")))'); 107 | node.collectTopDown("Num(n)", function(b) { return b.n; }).debug(); 108 | 109 | Instead of using the placeholder `_`, we now used `n`. The function is passed a 110 | _bindings_ object whose `n` property will contain the value of the placeholder. 111 | So, the following will be printed to the Javascript console: 112 | 113 | ["2", "3", "1"] 114 | 115 | If we want to match _either_ `Num` or `Mul` nodes we can add a pattern for that 116 | to the `collectTopDown` call: 117 | 118 | var node = tree.parse('Add(Num("2"), Mul(Num("3"), Num("1")))'); 119 | node.collectTopDown( 120 | "Num(n)", function(b) { 121 | return b.n; 122 | }, 123 | "Mul(op1, op2)", function(b) { 124 | return b.op1; 125 | } 126 | ).debug(); 127 | 128 | This will print: 129 | 130 | ["2", Num("3")] 131 | 132 | Why is that? The AST is traversed top to bottom by `collectTopDown`. On its way, it will 133 | try to match every node first against the `Num(n)` pattern. If that succeeds, 134 | the function after it in the argument list is executed, if the function returns 135 | a value other than `false`, the traversal stops at that branch and the result 136 | is added to the result list. If the `"Num(n)"` pattern did _not_ match, it is 137 | matched against `"Mul(op1, op2)"`. Again, if this pattern matches, the function 138 | is executed etc. etc. The `collectTopDown` traversal will not traverse down to the 139 | `Num("3")` and `Num("1")` nodes, because the traversal stopped when matching the 140 | `Mul(..., ...)` node -- if, instead the tranformation function would have returned 141 | `false, the traversal would have proceeded down those nodes as well. 142 | 143 | License (MIT) 144 | ------------- 145 | 146 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 147 | software and associated documentation files (the "Software"), to deal in the Software 148 | without restriction, including without limitation the rights to use, copy, modify, 149 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 150 | permit persons to whom the Software is furnished to do so, subject to the following 151 | conditions: 152 | 153 | The above copyright notice and this permission notice shall be included in all copies 154 | or substantial portions of the Software. 155 | 156 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 157 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 158 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 159 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 160 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 161 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 162 | 163 | Demo 164 | ---- 165 | 166 | You can see [treehugger.js in action in this simple demo](http://ajaxorg.github.com/treehugger/test.html). -------------------------------------------------------------------------------- /lib/acorn/dist/acorn_loose.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports", "module", "./acorn"], function(require, exports, module) { 2 | 3 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).loose = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o minPrec) { 108 | var node = this.startNodeAt(start); 109 | node.left = left; 110 | node.operator = this.tok.value; 111 | this.next(); 112 | if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) { 113 | node.right = this.dummyIdent(); 114 | } else { 115 | var rightStart = this.storeCurrentPos(); 116 | node.right = this.parseExprOp(this.parseMaybeUnary(noIn), rightStart, prec, noIn, indent, line); 117 | } 118 | this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); 119 | return this.parseExprOp(node, start, minPrec, noIn, indent, line); 120 | } 121 | } 122 | return left; 123 | }; 124 | 125 | lp.parseMaybeUnary = function (noIn) { 126 | if (this.tok.type.prefix) { 127 | var node = this.startNode(), 128 | update = this.tok.type === _.tokTypes.incDec; 129 | node.operator = this.tok.value; 130 | node.prefix = true; 131 | this.next(); 132 | node.argument = this.parseMaybeUnary(noIn); 133 | if (update) node.argument = this.checkLVal(node.argument); 134 | return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); 135 | } else if (this.tok.type === _.tokTypes.ellipsis) { 136 | var node = this.startNode(); 137 | this.next(); 138 | node.argument = this.parseMaybeUnary(noIn); 139 | return this.finishNode(node, "SpreadElement"); 140 | } 141 | var start = this.storeCurrentPos(); 142 | var expr = this.parseExprSubscripts(); 143 | while (this.tok.type.postfix && !this.canInsertSemicolon()) { 144 | var node = this.startNodeAt(start); 145 | node.operator = this.tok.value; 146 | node.prefix = false; 147 | node.argument = this.checkLVal(expr); 148 | this.next(); 149 | expr = this.finishNode(node, "UpdateExpression"); 150 | } 151 | return expr; 152 | }; 153 | 154 | lp.parseExprSubscripts = function () { 155 | var start = this.storeCurrentPos(); 156 | return this.parseSubscripts(this.parseExprAtom(), start, false, this.curIndent, this.curLineStart); 157 | }; 158 | 159 | lp.parseSubscripts = function (base, start, noCalls, startIndent, line) { 160 | for (;;) { 161 | if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) { 162 | if (this.tok.type == _.tokTypes.dot && this.curIndent == startIndent) --startIndent;else return base; 163 | } 164 | 165 | if (this.eat(_.tokTypes.dot)) { 166 | var node = this.startNodeAt(start); 167 | node.object = base; 168 | if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) node.property = this.dummyIdent();else node.property = this.parsePropertyAccessor() || this.dummyIdent(); 169 | node.computed = false; 170 | base = this.finishNode(node, "MemberExpression"); 171 | } else if (this.tok.type == _.tokTypes.bracketL) { 172 | this.pushCx(); 173 | this.next(); 174 | var node = this.startNodeAt(start); 175 | node.object = base; 176 | node.property = this.parseExpression(); 177 | node.computed = true; 178 | this.popCx(); 179 | this.expect(_.tokTypes.bracketR); 180 | base = this.finishNode(node, "MemberExpression"); 181 | } else if (!noCalls && this.tok.type == _.tokTypes.parenL) { 182 | var node = this.startNodeAt(start); 183 | node.callee = base; 184 | node.arguments = this.parseExprList(_.tokTypes.parenR); 185 | base = this.finishNode(node, "CallExpression"); 186 | } else if (this.tok.type == _.tokTypes.backQuote) { 187 | var node = this.startNodeAt(start); 188 | node.tag = base; 189 | node.quasi = this.parseTemplate(); 190 | base = this.finishNode(node, "TaggedTemplateExpression"); 191 | } else { 192 | return base; 193 | } 194 | } 195 | }; 196 | 197 | lp.parseExprAtom = function () { 198 | var node = undefined; 199 | switch (this.tok.type) { 200 | case _.tokTypes._this: 201 | case _.tokTypes._super: 202 | var type = this.tok.type === _.tokTypes._this ? "ThisExpression" : "Super"; 203 | node = this.startNode(); 204 | this.next(); 205 | return this.finishNode(node, type); 206 | 207 | case _.tokTypes.name: 208 | // quick hack to allow async and await 209 | if (this.value == "async" && /^[ \t]*(function\b|\(|\w+[ \t]*=>)/.test(this.input.slice(this.tok.end))) { 210 | node = this.startNode(); 211 | this.next(); 212 | return this.parseExprAtom(); 213 | } 214 | if (this.tok.value == "await" && /^[ \t]+[\w\x1f-\uffff]/.test(this.input.slice(this.tok.end))) { 215 | node = this.startNode(); 216 | this.next(); 217 | return this.parseExprAtom(); 218 | } 219 | var start = this.storeCurrentPos(); 220 | var id = this.parseIdent(); 221 | return this.eat(_.tokTypes.arrow) ? this.parseArrowExpression(this.startNodeAt(start), [id]) : id; 222 | 223 | case _.tokTypes.regexp: 224 | node = this.startNode(); 225 | var val = this.tok.value; 226 | node.regex = { pattern: val.pattern, flags: val.flags }; 227 | node.value = val.value; 228 | node.raw = this.input.slice(this.tok.start, this.tok.end); 229 | this.next(); 230 | return this.finishNode(node, "Literal"); 231 | 232 | case _.tokTypes.num:case _.tokTypes.string: 233 | node = this.startNode(); 234 | node.value = this.tok.value; 235 | node.raw = this.input.slice(this.tok.start, this.tok.end); 236 | this.next(); 237 | return this.finishNode(node, "Literal"); 238 | 239 | case _.tokTypes._null:case _.tokTypes._true:case _.tokTypes._false: 240 | node = this.startNode(); 241 | node.value = this.tok.type === _.tokTypes._null ? null : this.tok.type === _.tokTypes._true; 242 | node.raw = this.tok.type.keyword; 243 | this.next(); 244 | return this.finishNode(node, "Literal"); 245 | 246 | case _.tokTypes.parenL: 247 | var parenStart = this.storeCurrentPos(); 248 | this.next(); 249 | var inner = this.parseExpression(); 250 | this.expect(_.tokTypes.parenR); 251 | if (this.eat(_.tokTypes.arrow)) { 252 | return this.parseArrowExpression(this.startNodeAt(parenStart), inner.expressions || (_parseutil.isDummy(inner) ? [] : [inner])); 253 | } 254 | if (this.options.preserveParens) { 255 | var par = this.startNodeAt(parenStart); 256 | par.expression = inner; 257 | inner = this.finishNode(par, "ParenthesizedExpression"); 258 | } 259 | return inner; 260 | 261 | case _.tokTypes.bracketL: 262 | node = this.startNode(); 263 | node.elements = this.parseExprList(_.tokTypes.bracketR, true); 264 | return this.finishNode(node, "ArrayExpression"); 265 | 266 | case _.tokTypes.braceL: 267 | return this.parseObj(); 268 | 269 | case _.tokTypes._class: 270 | return this.parseClass(); 271 | 272 | case _.tokTypes._function: 273 | node = this.startNode(); 274 | this.next(); 275 | return this.parseFunction(node, false); 276 | 277 | case _.tokTypes._new: 278 | return this.parseNew(); 279 | 280 | case _.tokTypes.backQuote: 281 | return this.parseTemplate(); 282 | 283 | default: 284 | return this.dummyIdent(); 285 | } 286 | }; 287 | 288 | lp.parseNew = function () { 289 | var node = this.startNode(), 290 | startIndent = this.curIndent, 291 | line = this.curLineStart; 292 | var meta = this.parseIdent(true); 293 | if (this.options.ecmaVersion >= 6 && this.eat(_.tokTypes.dot)) { 294 | node.meta = meta; 295 | node.property = this.parseIdent(true); 296 | return this.finishNode(node, "MetaProperty"); 297 | } 298 | var start = this.storeCurrentPos(); 299 | node.callee = this.parseSubscripts(this.parseExprAtom(), start, true, startIndent, line); 300 | if (this.tok.type == _.tokTypes.parenL) { 301 | node.arguments = this.parseExprList(_.tokTypes.parenR); 302 | } else { 303 | node.arguments = []; 304 | } 305 | return this.finishNode(node, "NewExpression"); 306 | }; 307 | 308 | lp.parseTemplateElement = function () { 309 | var elem = this.startNode(); 310 | elem.value = { 311 | raw: this.input.slice(this.tok.start, this.tok.end).replace(/\r\n?/g, "\n"), 312 | cooked: this.tok.value 313 | }; 314 | this.next(); 315 | elem.tail = this.tok.type === _.tokTypes.backQuote; 316 | return this.finishNode(elem, "TemplateElement"); 317 | }; 318 | 319 | lp.parseTemplate = function () { 320 | var node = this.startNode(); 321 | this.next(); 322 | node.expressions = []; 323 | var curElt = this.parseTemplateElement(); 324 | node.quasis = [curElt]; 325 | while (!curElt.tail) { 326 | this.next(); 327 | node.expressions.push(this.parseExpression()); 328 | if (this.expect(_.tokTypes.braceR)) { 329 | curElt = this.parseTemplateElement(); 330 | } else { 331 | curElt = this.startNode(); 332 | curElt.value = { cooked: "", raw: "" }; 333 | curElt.tail = true; 334 | } 335 | node.quasis.push(curElt); 336 | } 337 | this.expect(_.tokTypes.backQuote); 338 | return this.finishNode(node, "TemplateLiteral"); 339 | }; 340 | 341 | lp.parseObj = function () { 342 | var node = this.startNode(); 343 | node.properties = []; 344 | this.pushCx(); 345 | var indent = this.curIndent + 1, 346 | line = this.curLineStart; 347 | this.eat(_.tokTypes.braceL); 348 | if (this.curIndent + 1 < indent) { 349 | indent = this.curIndent;line = this.curLineStart; 350 | } 351 | while (!this.closes(_.tokTypes.braceR, indent, line)) { 352 | var prop = this.startNode(), 353 | isGenerator = undefined, 354 | start = undefined; 355 | if (this.options.ecmaVersion >= 6) { 356 | start = this.storeCurrentPos(); 357 | prop.method = false; 358 | prop.shorthand = false; 359 | isGenerator = this.eat(_.tokTypes.star); 360 | } 361 | this.parsePropertyName(prop); 362 | if (_parseutil.isDummy(prop.key)) { 363 | if (_parseutil.isDummy(this.parseMaybeAssign())) this.next();this.eat(_.tokTypes.comma);continue; 364 | } 365 | if (this.eat(_.tokTypes.colon)) { 366 | prop.kind = "init"; 367 | prop.value = this.parseMaybeAssign(); 368 | } else if (this.options.ecmaVersion >= 6 && (this.tok.type === _.tokTypes.parenL || this.tok.type === _.tokTypes.braceL)) { 369 | prop.kind = "init"; 370 | prop.method = true; 371 | prop.value = this.parseMethod(isGenerator); 372 | } else if (this.options.ecmaVersion >= 5 && prop.key.type === "Identifier" && !prop.computed && (prop.key.name === "get" || prop.key.name === "set") && (this.tok.type != _.tokTypes.comma && this.tok.type != _.tokTypes.braceR)) { 373 | prop.kind = prop.key.name; 374 | this.parsePropertyName(prop); 375 | prop.value = this.parseMethod(false); 376 | } else { 377 | prop.kind = "init"; 378 | if (this.options.ecmaVersion >= 6) { 379 | if (this.eat(_.tokTypes.eq)) { 380 | var assign = this.startNodeAt(start); 381 | assign.operator = "="; 382 | assign.left = prop.key; 383 | assign.right = this.parseMaybeAssign(); 384 | prop.value = this.finishNode(assign, "AssignmentExpression"); 385 | } else { 386 | prop.value = prop.key; 387 | } 388 | } else { 389 | prop.value = this.dummyIdent(); 390 | } 391 | prop.shorthand = true; 392 | } 393 | node.properties.push(this.finishNode(prop, "Property")); 394 | this.eat(_.tokTypes.comma); 395 | } 396 | this.popCx(); 397 | if (!this.eat(_.tokTypes.braceR)) { 398 | // If there is no closing brace, make the node span to the start 399 | // of the next token (this is useful for Tern) 400 | this.last.end = this.tok.start; 401 | if (this.options.locations) this.last.loc.end = this.tok.loc.start; 402 | } 403 | return this.finishNode(node, "ObjectExpression"); 404 | }; 405 | 406 | lp.parsePropertyName = function (prop) { 407 | if (this.options.ecmaVersion >= 6) { 408 | if (this.eat(_.tokTypes.bracketL)) { 409 | prop.computed = true; 410 | prop.key = this.parseExpression(); 411 | this.expect(_.tokTypes.bracketR); 412 | return; 413 | } else { 414 | prop.computed = false; 415 | } 416 | } 417 | var key = this.tok.type === _.tokTypes.num || this.tok.type === _.tokTypes.string ? this.parseExprAtom() : this.parseIdent(); 418 | prop.key = key || this.dummyIdent(); 419 | }; 420 | 421 | lp.parsePropertyAccessor = function () { 422 | if (this.tok.type === _.tokTypes.name || this.tok.type.keyword) return this.parseIdent(); 423 | }; 424 | 425 | lp.parseIdent = function () { 426 | var name = this.tok.type === _.tokTypes.name ? this.tok.value : this.tok.type.keyword; 427 | if (!name) return this.dummyIdent(); 428 | var node = this.startNode(); 429 | this.next(); 430 | node.name = name; 431 | return this.finishNode(node, "Identifier"); 432 | }; 433 | 434 | lp.initFunction = function (node) { 435 | node.id = null; 436 | node.params = []; 437 | if (this.options.ecmaVersion >= 6) { 438 | node.generator = false; 439 | node.expression = false; 440 | } 441 | }; 442 | 443 | // Convert existing expression atom to assignable pattern 444 | // if possible. 445 | 446 | lp.toAssignable = function (node, binding) { 447 | if (!node || node.type == "Identifier" || node.type == "MemberExpression" && !binding) {} else if (node.type == "ParenthesizedExpression") { 448 | node.expression = this.toAssignable(node.expression, binding); 449 | } else if (this.options.ecmaVersion < 6) { 450 | return this.dummyIdent(); 451 | } else if (node.type == "ObjectExpression") { 452 | node.type = "ObjectPattern"; 453 | var props = node.properties; 454 | for (var i = 0; i < props.length; i++) { 455 | props[i].value = this.toAssignable(props[i].value, binding); 456 | } 457 | } else if (node.type == "ArrayExpression") { 458 | node.type = "ArrayPattern"; 459 | this.toAssignableList(node.elements, binding); 460 | } else if (node.type == "SpreadElement") { 461 | node.type = "RestElement"; 462 | node.argument = this.toAssignable(node.argument, binding); 463 | } else if (node.type == "AssignmentExpression") { 464 | node.type = "AssignmentPattern"; 465 | delete node.operator; 466 | } else { 467 | return this.dummyIdent(); 468 | } 469 | return node; 470 | }; 471 | 472 | lp.toAssignableList = function (exprList, binding) { 473 | for (var i = 0; i < exprList.length; i++) { 474 | exprList[i] = this.toAssignable(exprList[i], binding); 475 | }return exprList; 476 | }; 477 | 478 | lp.parseFunctionParams = function (params) { 479 | params = this.parseExprList(_.tokTypes.parenR); 480 | return this.toAssignableList(params, true); 481 | }; 482 | 483 | lp.parseMethod = function (isGenerator) { 484 | var node = this.startNode(); 485 | this.initFunction(node); 486 | node.params = this.parseFunctionParams(); 487 | node.generator = isGenerator || false; 488 | node.expression = this.options.ecmaVersion >= 6 && this.tok.type !== _.tokTypes.braceL; 489 | node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock(); 490 | return this.finishNode(node, "FunctionExpression"); 491 | }; 492 | 493 | lp.parseArrowExpression = function (node, params) { 494 | this.initFunction(node); 495 | node.params = this.toAssignableList(params, true); 496 | node.expression = this.tok.type !== _.tokTypes.braceL; 497 | node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock(); 498 | return this.finishNode(node, "ArrowFunctionExpression"); 499 | }; 500 | 501 | lp.parseExprList = function (close, allowEmpty) { 502 | this.pushCx(); 503 | var indent = this.curIndent, 504 | line = this.curLineStart, 505 | elts = []; 506 | this.next(); // Opening bracket 507 | while (!this.closes(close, indent + 1, line)) { 508 | if (this.eat(_.tokTypes.comma)) { 509 | elts.push(allowEmpty ? null : this.dummyIdent()); 510 | continue; 511 | } 512 | var elt = this.parseMaybeAssign(); 513 | if (_parseutil.isDummy(elt)) { 514 | if (this.closes(close, indent, line)) break; 515 | this.next(); 516 | } else { 517 | elts.push(elt); 518 | } 519 | this.eat(_.tokTypes.comma); 520 | } 521 | this.popCx(); 522 | if (!this.eat(close)) { 523 | // If there is no closing brace, make the node span to the start 524 | // of the next token (this is useful for Tern) 525 | this.last.end = this.tok.start; 526 | if (this.options.locations) this.last.loc.end = this.tok.loc.start; 527 | } 528 | return elts; 529 | }; 530 | 531 | // Okay 532 | 533 | },{"..":"/src\\index.js","./parseutil":"/src\\loose\\parseutil.js","./state":"/src\\loose\\state.js"}],"/src\\loose\\index.js":[function(_dereq_,module,exports){ 534 | // Acorn: Loose parser 535 | // 536 | // This module provides an alternative parser (`parse_dammit`) that 537 | // exposes that same interface as `parse`, but will try to parse 538 | // anything as JavaScript, repairing syntax error the best it can. 539 | // There are circumstances in which it will raise an error and give 540 | // up, but they are very rare. The resulting AST will be a mostly 541 | // valid JavaScript AST (as per the [Mozilla parser API][api], except 542 | // that: 543 | // 544 | // - Return outside functions is allowed 545 | // 546 | // - Label consistency (no conflicts, break only to existing labels) 547 | // is not enforced. 548 | // 549 | // - Bogus Identifier nodes with a name of `"✖"` are inserted whenever 550 | // the parser got too confused to return anything meaningful. 551 | // 552 | // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API 553 | // 554 | // The expected use for this is to *first* try `acorn.parse`, and only 555 | // if that fails switch to `parse_dammit`. The loose parser might 556 | // parse badly indented code incorrectly, so **don't** use it as 557 | // your default parser. 558 | // 559 | // Quite a lot of acorn.js is duplicated here. The alternative was to 560 | // add a *lot* of extra cruft to that file, making it less readable 561 | // and slower. Copying and editing the code allowed me to make 562 | // invasive changes and simplifications without creating a complicated 563 | // tangle. 564 | 565 | "use strict"; 566 | 567 | exports.__esModule = true; 568 | exports.parse_dammit = parse_dammit; 569 | 570 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } 571 | 572 | var _ = _dereq_(".."); 573 | 574 | var acorn = _interopRequireWildcard(_); 575 | 576 | var _state = _dereq_("./state"); 577 | 578 | _dereq_("./tokenize"); 579 | 580 | _dereq_("./statement"); 581 | 582 | _dereq_("./expression"); 583 | 584 | exports.LooseParser = _state.LooseParser; 585 | exports.pluginsLoose = _state.pluginsLoose; 586 | 587 | acorn.defaultOptions.tabSize = 4; 588 | 589 | function parse_dammit(input, options) { 590 | var p = new _state.LooseParser(input, options); 591 | p.next(); 592 | return p.parseTopLevel(); 593 | } 594 | 595 | acorn.parse_dammit = parse_dammit; 596 | acorn.LooseParser = _state.LooseParser; 597 | acorn.pluginsLoose = _state.pluginsLoose; 598 | 599 | },{"..":"/src\\index.js","./expression":"/src\\loose\\expression.js","./state":"/src\\loose\\state.js","./statement":"/src\\loose\\statement.js","./tokenize":"/src\\loose\\tokenize.js"}],"/src\\loose\\parseutil.js":[function(_dereq_,module,exports){ 600 | "use strict"; 601 | 602 | exports.__esModule = true; 603 | exports.isDummy = isDummy; 604 | 605 | function isDummy(node) { 606 | return node.name == "✖"; 607 | } 608 | 609 | },{}],"/src\\loose\\state.js":[function(_dereq_,module,exports){ 610 | "use strict"; 611 | 612 | exports.__esModule = true; 613 | 614 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 615 | 616 | var _ = _dereq_(".."); 617 | 618 | // Registered plugins 619 | var pluginsLoose = {}; 620 | 621 | exports.pluginsLoose = pluginsLoose; 622 | 623 | var LooseParser = (function () { 624 | function LooseParser(input, options) { 625 | 626 | 627 | this.toks = _.tokenizer(input, options); 628 | this.options = this.toks.options; 629 | this.input = this.toks.input; 630 | this.tok = this.last = { type: _.tokTypes.eof, start: 0, end: 0 }; 631 | if (this.options.locations) { 632 | var here = this.toks.curPosition(); 633 | this.tok.loc = new _.SourceLocation(this.toks, here, here); 634 | } 635 | this.ahead = []; // Tokens ahead 636 | this.context = []; // Indentation contexted 637 | this.curIndent = 0; 638 | this.curLineStart = 0; 639 | this.nextLineStart = this.lineEnd(this.curLineStart) + 1; 640 | // Load plugins 641 | this.options.pluginsLoose = options.pluginsLoose || {}; 642 | this.loadPlugins(this.options.pluginsLoose); 643 | } 644 | 645 | LooseParser.prototype.startNode = function startNode() { 646 | return new _.Node(this.toks, this.tok.start, this.options.locations ? this.tok.loc.start : null); 647 | }; 648 | 649 | LooseParser.prototype.storeCurrentPos = function storeCurrentPos() { 650 | return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start; 651 | }; 652 | 653 | LooseParser.prototype.startNodeAt = function startNodeAt(pos) { 654 | if (this.options.locations) { 655 | return new _.Node(this.toks, pos[0], pos[1]); 656 | } else { 657 | return new _.Node(this.toks, pos); 658 | } 659 | }; 660 | 661 | LooseParser.prototype.finishNode = function finishNode(node, type) { 662 | node.type = type; 663 | node.end = this.last.end; 664 | if (this.options.locations) node.loc.end = this.last.loc.end; 665 | if (this.options.ranges) node.range[1] = this.last.end; 666 | return node; 667 | }; 668 | 669 | LooseParser.prototype.dummyNode = function dummyNode(type) { 670 | var dummy = this.startNode(); 671 | dummy.type = type; 672 | dummy.end = dummy.start; 673 | if (this.options.locations) dummy.loc.end = dummy.loc.start; 674 | if (this.options.ranges) dummy.range[1] = dummy.start; 675 | this.last = { type: _.tokTypes.name, start: dummy.start, end: dummy.start, loc: dummy.loc }; 676 | return dummy; 677 | }; 678 | 679 | LooseParser.prototype.dummyIdent = function dummyIdent() { 680 | var dummy = this.dummyNode("Identifier"); 681 | dummy.name = "✖"; 682 | return dummy; 683 | }; 684 | 685 | LooseParser.prototype.dummyString = function dummyString() { 686 | var dummy = this.dummyNode("Literal"); 687 | dummy.value = dummy.raw = "✖"; 688 | return dummy; 689 | }; 690 | 691 | LooseParser.prototype.eat = function eat(type) { 692 | if (this.tok.type === type) { 693 | this.next(); 694 | return true; 695 | } else { 696 | return false; 697 | } 698 | }; 699 | 700 | LooseParser.prototype.isContextual = function isContextual(name) { 701 | return this.tok.type === _.tokTypes.name && this.tok.value === name; 702 | }; 703 | 704 | LooseParser.prototype.eatContextual = function eatContextual(name) { 705 | return this.tok.value === name && this.eat(_.tokTypes.name); 706 | }; 707 | 708 | LooseParser.prototype.canInsertSemicolon = function canInsertSemicolon() { 709 | return this.tok.type === _.tokTypes.eof || this.tok.type === _.tokTypes.braceR || _.lineBreak.test(this.input.slice(this.last.end, this.tok.start)); 710 | }; 711 | 712 | LooseParser.prototype.semicolon = function semicolon() { 713 | return this.eat(_.tokTypes.semi); 714 | }; 715 | 716 | LooseParser.prototype.expect = function expect(type) { 717 | if (this.eat(type)) return true; 718 | for (var i = 1; i <= 2; i++) { 719 | if (this.lookAhead(i).type == type) { 720 | for (var j = 0; j < i; j++) { 721 | this.next(); 722 | }return true; 723 | } 724 | } 725 | }; 726 | 727 | LooseParser.prototype.pushCx = function pushCx() { 728 | this.context.push(this.curIndent); 729 | }; 730 | 731 | LooseParser.prototype.popCx = function popCx() { 732 | this.curIndent = this.context.pop(); 733 | }; 734 | 735 | LooseParser.prototype.lineEnd = function lineEnd(pos) { 736 | while (pos < this.input.length && !_.isNewLine(this.input.charCodeAt(pos))) ++pos; 737 | return pos; 738 | }; 739 | 740 | LooseParser.prototype.indentationAfter = function indentationAfter(pos) { 741 | for (var count = 0;; ++pos) { 742 | var ch = this.input.charCodeAt(pos); 743 | if (ch === 32) ++count;else if (ch === 9) count += this.options.tabSize;else return count; 744 | } 745 | }; 746 | 747 | LooseParser.prototype.closes = function closes(closeTok, indent, line, blockHeuristic) { 748 | if (this.tok.type === closeTok || this.tok.type === _.tokTypes.eof) return true 749 | /* TODO reenable indent based recovery 750 | return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() && 751 | (!blockHeuristic || this.nextLineStart >= this.input.length || 752 | this.indentationAfter(this.nextLineStart) < indent)*/ 753 | ; 754 | }; 755 | 756 | LooseParser.prototype.tokenStartsLine = function tokenStartsLine() { 757 | for (var p = this.tok.start - 1; p >= this.curLineStart; --p) { 758 | var ch = this.input.charCodeAt(p); 759 | if (ch !== 9 && ch !== 32) return false; 760 | } 761 | return true; 762 | }; 763 | 764 | LooseParser.prototype.extend = function extend(name, f) { 765 | this[name] = f(this[name]); 766 | }; 767 | 768 | LooseParser.prototype.loadPlugins = function loadPlugins(pluginConfigs) { 769 | for (var _name in pluginConfigs) { 770 | var plugin = pluginsLoose[_name]; 771 | if (!plugin) throw new Error("Plugin '" + _name + "' not found"); 772 | plugin(this, pluginConfigs[_name]); 773 | } 774 | }; 775 | 776 | return LooseParser; 777 | })(); 778 | 779 | exports.LooseParser = LooseParser; 780 | 781 | },{"..":"/src\\index.js"}],"/src\\loose\\statement.js":[function(_dereq_,module,exports){ 782 | "use strict"; 783 | 784 | var _state = _dereq_("./state"); 785 | 786 | var _parseutil = _dereq_("./parseutil"); 787 | 788 | var _ = _dereq_(".."); 789 | 790 | var lp = _state.LooseParser.prototype; 791 | 792 | lp.parseTopLevel = function () { 793 | var node = this.startNodeAt(this.options.locations ? [0, _.getLineInfo(this.input, 0)] : 0); 794 | node.body = []; 795 | while (this.tok.type !== _.tokTypes.eof) node.body.push(this.parseStatement()); 796 | this.last = this.tok; 797 | if (this.options.ecmaVersion >= 6) { 798 | node.sourceType = this.options.sourceType; 799 | } 800 | return this.finishNode(node, "Program"); 801 | }; 802 | 803 | lp.parseStatement = function () { 804 | var starttype = this.tok.type, 805 | node = this.startNode(), 806 | kind = undefined; 807 | 808 | if (this.toks.isLet()) { 809 | starttype = _.tokTypes._var; 810 | kind = "let"; 811 | } 812 | 813 | switch (starttype) { 814 | case _.tokTypes._break:case _.tokTypes._continue: 815 | this.next(); 816 | var isBreak = starttype === _.tokTypes._break; 817 | if (this.semicolon() || this.canInsertSemicolon()) { 818 | node.label = null; 819 | } else { 820 | node.label = this.tok.type === _.tokTypes.name ? this.parseIdent() : null; 821 | this.semicolon(); 822 | } 823 | return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); 824 | 825 | case _.tokTypes._debugger: 826 | this.next(); 827 | this.semicolon(); 828 | return this.finishNode(node, "DebuggerStatement"); 829 | 830 | case _.tokTypes._do: 831 | this.next(); 832 | node.body = this.parseStatement(); 833 | node.test = this.eat(_.tokTypes._while) ? this.parseParenExpression() : this.dummyIdent(); 834 | this.semicolon(); 835 | return this.finishNode(node, "DoWhileStatement"); 836 | 837 | case _.tokTypes._for: 838 | this.next(); 839 | this.pushCx(); 840 | this.expect(_.tokTypes.parenL); 841 | if (this.tok.type === _.tokTypes.semi) return this.parseFor(node, null); 842 | var isLet = this.toks.isLet(); 843 | if (isLet || this.tok.type === _.tokTypes._var || this.tok.type === _.tokTypes._const) { 844 | var _init = this.parseVar(true, isLet ? "let" : this.tok.value); 845 | if (_init.declarations.length === 1 && (this.tok.type === _.tokTypes._in || this.isContextual("of"))) { 846 | return this.parseForIn(node, _init); 847 | } 848 | return this.parseFor(node, _init); 849 | } 850 | var init = this.parseExpression(true); 851 | if (this.tok.type === _.tokTypes._in || this.isContextual("of")) return this.parseForIn(node, this.toAssignable(init)); 852 | return this.parseFor(node, init); 853 | 854 | case _.tokTypes._function: 855 | this.next(); 856 | return this.parseFunction(node, true); 857 | 858 | case _.tokTypes._if: 859 | this.next(); 860 | node.test = this.parseParenExpression(); 861 | node.consequent = this.parseStatement(); 862 | node.alternate = this.eat(_.tokTypes._else) ? this.parseStatement() : null; 863 | return this.finishNode(node, "IfStatement"); 864 | 865 | case _.tokTypes._return: 866 | this.next(); 867 | if (this.eat(_.tokTypes.semi) || this.canInsertSemicolon()) node.argument = null;else { 868 | node.argument = this.parseExpression();this.semicolon(); 869 | } 870 | return this.finishNode(node, "ReturnStatement"); 871 | 872 | case _.tokTypes._switch: 873 | var blockIndent = this.curIndent, 874 | line = this.curLineStart; 875 | this.next(); 876 | node.discriminant = this.parseParenExpression(); 877 | node.cases = []; 878 | this.pushCx(); 879 | this.expect(_.tokTypes.braceL); 880 | 881 | var cur = undefined; 882 | while (!this.closes(_.tokTypes.braceR, blockIndent, line, true)) { 883 | if (this.tok.type === _.tokTypes._case || this.tok.type === _.tokTypes._default) { 884 | var isCase = this.tok.type === _.tokTypes._case; 885 | if (cur) this.finishNode(cur, "SwitchCase"); 886 | node.cases.push(cur = this.startNode()); 887 | cur.consequent = []; 888 | this.next(); 889 | if (isCase) cur.test = this.parseExpression();else cur.test = null; 890 | this.expect(_.tokTypes.colon); 891 | } else { 892 | if (!cur) { 893 | node.cases.push(cur = this.startNode()); 894 | cur.consequent = []; 895 | cur.test = null; 896 | } 897 | cur.consequent.push(this.parseStatement()); 898 | } 899 | } 900 | if (cur) this.finishNode(cur, "SwitchCase"); 901 | this.popCx(); 902 | this.eat(_.tokTypes.braceR); 903 | return this.finishNode(node, "SwitchStatement"); 904 | 905 | case _.tokTypes._throw: 906 | this.next(); 907 | node.argument = this.parseExpression(); 908 | this.semicolon(); 909 | return this.finishNode(node, "ThrowStatement"); 910 | 911 | case _.tokTypes._try: 912 | this.next(); 913 | node.block = this.parseBlock(); 914 | node.handler = null; 915 | if (this.tok.type === _.tokTypes._catch) { 916 | var clause = this.startNode(); 917 | this.next(); 918 | this.expect(_.tokTypes.parenL); 919 | clause.param = this.toAssignable(this.parseExprAtom(), true); 920 | this.expect(_.tokTypes.parenR); 921 | clause.body = this.parseBlock(); 922 | node.handler = this.finishNode(clause, "CatchClause"); 923 | } 924 | node.finalizer = this.eat(_.tokTypes._finally) ? this.parseBlock() : null; 925 | if (!node.handler && !node.finalizer) return node.block; 926 | return this.finishNode(node, "TryStatement"); 927 | 928 | case _.tokTypes._var: 929 | case _.tokTypes._const: 930 | return this.parseVar(false, kind || this.tok.value); 931 | 932 | case _.tokTypes._while: 933 | this.next(); 934 | node.test = this.parseParenExpression(); 935 | node.body = this.parseStatement(); 936 | return this.finishNode(node, "WhileStatement"); 937 | 938 | case _.tokTypes._with: 939 | this.next(); 940 | node.object = this.parseParenExpression(); 941 | node.body = this.parseStatement(); 942 | return this.finishNode(node, "WithStatement"); 943 | 944 | case _.tokTypes.braceL: 945 | return this.parseBlock(); 946 | 947 | case _.tokTypes.semi: 948 | this.next(); 949 | return this.finishNode(node, "EmptyStatement"); 950 | 951 | case _.tokTypes._class: 952 | return this.parseClass(true); 953 | 954 | case _.tokTypes._import: 955 | return this.parseImport(); 956 | 957 | case _.tokTypes._export: 958 | return this.parseExport(); 959 | 960 | default: 961 | var expr = this.parseExpression(); 962 | if (_parseutil.isDummy(expr)) { 963 | this.next(); 964 | if (this.tok.type === _.tokTypes.eof) return this.finishNode(node, "EmptyStatement"); 965 | return this.parseStatement(); 966 | } else if (starttype === _.tokTypes.name && expr.type === "Identifier" && this.eat(_.tokTypes.colon)) { 967 | node.body = this.parseStatement(); 968 | node.label = expr; 969 | return this.finishNode(node, "LabeledStatement"); 970 | } else { 971 | node.expression = expr; 972 | this.semicolon(); 973 | return this.finishNode(node, "ExpressionStatement"); 974 | } 975 | } 976 | }; 977 | 978 | lp.parseBlock = function () { 979 | var node = this.startNode(); 980 | this.pushCx(); 981 | this.expect(_.tokTypes.braceL); 982 | var blockIndent = this.curIndent, 983 | line = this.curLineStart; 984 | node.body = []; 985 | while (!this.closes(_.tokTypes.braceR, blockIndent, line, true)) node.body.push(this.parseStatement()); 986 | this.popCx(); 987 | this.eat(_.tokTypes.braceR); 988 | return this.finishNode(node, "BlockStatement"); 989 | }; 990 | 991 | lp.parseFor = function (node, init) { 992 | node.init = init; 993 | node.test = node.update = null; 994 | if (this.eat(_.tokTypes.semi) && this.tok.type !== _.tokTypes.semi) node.test = this.parseExpression(); 995 | if (this.eat(_.tokTypes.semi) && this.tok.type !== _.tokTypes.parenR) node.update = this.parseExpression(); 996 | this.popCx(); 997 | this.expect(_.tokTypes.parenR); 998 | node.body = this.parseStatement(); 999 | return this.finishNode(node, "ForStatement"); 1000 | }; 1001 | 1002 | lp.parseForIn = function (node, init) { 1003 | var type = this.tok.type === _.tokTypes._in ? "ForInStatement" : "ForOfStatement"; 1004 | this.next(); 1005 | node.left = init; 1006 | node.right = this.parseExpression(); 1007 | this.popCx(); 1008 | this.expect(_.tokTypes.parenR); 1009 | node.body = this.parseStatement(); 1010 | return this.finishNode(node, type); 1011 | }; 1012 | 1013 | lp.parseVar = function (noIn, kind) { 1014 | var node = this.startNode(); 1015 | node.kind = kind; 1016 | this.next(); 1017 | node.declarations = []; 1018 | do { 1019 | var decl = this.startNode(); 1020 | decl.id = this.options.ecmaVersion >= 6 ? this.toAssignable(this.parseExprAtom(), true) : this.parseIdent(); 1021 | decl.init = this.eat(_.tokTypes.eq) ? this.parseMaybeAssign(noIn) : null; 1022 | node.declarations.push(this.finishNode(decl, "VariableDeclarator")); 1023 | } while (this.eat(_.tokTypes.comma)); 1024 | if (!node.declarations.length) { 1025 | var decl = this.startNode(); 1026 | decl.id = this.dummyIdent(); 1027 | node.declarations.push(this.finishNode(decl, "VariableDeclarator")); 1028 | } 1029 | if (!noIn) this.semicolon(); 1030 | return this.finishNode(node, "VariableDeclaration"); 1031 | }; 1032 | 1033 | lp.parseClass = function (isStatement) { 1034 | var node = this.startNode(); 1035 | this.next(); 1036 | if (this.tok.type === _.tokTypes.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent();else node.id = null; 1037 | node.superClass = this.eat(_.tokTypes._extends) ? this.parseExpression() : null; 1038 | node.body = this.startNode(); 1039 | node.body.body = []; 1040 | this.pushCx(); 1041 | var indent = this.curIndent + 1, 1042 | line = this.curLineStart; 1043 | this.eat(_.tokTypes.braceL); 1044 | if (this.curIndent + 1 < indent) { 1045 | indent = this.curIndent;line = this.curLineStart; 1046 | } 1047 | while (!this.closes(_.tokTypes.braceR, indent, line)) { 1048 | if (this.semicolon()) continue; 1049 | var method = this.startNode(), 1050 | isGenerator = undefined; 1051 | if (this.options.ecmaVersion >= 6) { 1052 | method["static"] = false; 1053 | isGenerator = this.eat(_.tokTypes.star); 1054 | } 1055 | this.parsePropertyName(method); 1056 | if (_parseutil.isDummy(method.key)) { 1057 | if (_parseutil.isDummy(this.parseMaybeAssign())) this.next();this.eat(_.tokTypes.comma);continue; 1058 | } 1059 | if (method.key.type === "Identifier" && !method.computed && method.key.name === "static" && (this.tok.type != _.tokTypes.parenL && this.tok.type != _.tokTypes.braceL)) { 1060 | method["static"] = true; 1061 | isGenerator = this.eat(_.tokTypes.star); 1062 | this.parsePropertyName(method); 1063 | } else { 1064 | method["static"] = false; 1065 | } 1066 | if (this.options.ecmaVersion >= 5 && method.key.type === "Identifier" && !method.computed && (method.key.name === "get" || method.key.name === "set") && this.tok.type !== _.tokTypes.parenL && this.tok.type !== _.tokTypes.braceL) { 1067 | method.kind = method.key.name; 1068 | this.parsePropertyName(method); 1069 | method.value = this.parseMethod(false); 1070 | } else { 1071 | if (!method.computed && !method["static"] && !isGenerator && (method.key.type === "Identifier" && method.key.name === "constructor" || method.key.type === "Literal" && method.key.value === "constructor")) { 1072 | method.kind = "constructor"; 1073 | } else { 1074 | method.kind = "method"; 1075 | } 1076 | method.value = this.parseMethod(isGenerator); 1077 | } 1078 | node.body.body.push(this.finishNode(method, "MethodDefinition")); 1079 | } 1080 | this.popCx(); 1081 | if (!this.eat(_.tokTypes.braceR)) { 1082 | // If there is no closing brace, make the node span to the start 1083 | // of the next token (this is useful for Tern) 1084 | this.last.end = this.tok.start; 1085 | if (this.options.locations) this.last.loc.end = this.tok.loc.start; 1086 | } 1087 | this.semicolon(); 1088 | this.finishNode(node.body, "ClassBody"); 1089 | return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); 1090 | }; 1091 | 1092 | lp.parseFunction = function (node, isStatement) { 1093 | this.initFunction(node); 1094 | if (this.options.ecmaVersion >= 6) { 1095 | node.generator = this.eat(_.tokTypes.star); 1096 | } 1097 | if (this.tok.type === _.tokTypes.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent(); 1098 | node.params = this.parseFunctionParams(); 1099 | node.body = this.parseBlock(); 1100 | return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); 1101 | }; 1102 | 1103 | lp.parseExport = function () { 1104 | var node = this.startNode(); 1105 | this.next(); 1106 | if (this.eat(_.tokTypes.star)) { 1107 | node.source = this.eatContextual("from") ? this.parseExprAtom() : null; 1108 | return this.finishNode(node, "ExportAllDeclaration"); 1109 | } 1110 | if (this.eat(_.tokTypes._default)) { 1111 | var expr = this.parseMaybeAssign(); 1112 | if (expr.id) { 1113 | switch (expr.type) { 1114 | case "FunctionExpression": 1115 | expr.type = "FunctionDeclaration";break; 1116 | case "ClassExpression": 1117 | expr.type = "ClassDeclaration";break; 1118 | } 1119 | } 1120 | node.declaration = expr; 1121 | this.semicolon(); 1122 | return this.finishNode(node, "ExportDefaultDeclaration"); 1123 | } 1124 | if (this.tok.type.keyword || this.toks.isLet()) { 1125 | node.declaration = this.parseStatement(); 1126 | node.specifiers = []; 1127 | node.source = null; 1128 | } else { 1129 | node.declaration = null; 1130 | node.specifiers = this.parseExportSpecifierList(); 1131 | node.source = this.eatContextual("from") ? this.parseExprAtom() : null; 1132 | this.semicolon(); 1133 | } 1134 | return this.finishNode(node, "ExportNamedDeclaration"); 1135 | }; 1136 | 1137 | lp.parseImport = function () { 1138 | var node = this.startNode(); 1139 | this.next(); 1140 | if (this.tok.type === _.tokTypes.string) { 1141 | node.specifiers = []; 1142 | node.source = this.parseExprAtom(); 1143 | node.kind = ""; 1144 | } else { 1145 | var elt = undefined; 1146 | if (this.tok.type === _.tokTypes.name && this.tok.value !== "from") { 1147 | elt = this.startNode(); 1148 | elt.local = this.parseIdent(); 1149 | this.finishNode(elt, "ImportDefaultSpecifier"); 1150 | this.eat(_.tokTypes.comma); 1151 | } 1152 | node.specifiers = this.parseImportSpecifierList(); 1153 | node.source = this.eatContextual("from") && this.tok.type == _.tokTypes.string ? this.parseExprAtom() : this.dummyString(); 1154 | if (elt) node.specifiers.unshift(elt); 1155 | } 1156 | this.semicolon(); 1157 | return this.finishNode(node, "ImportDeclaration"); 1158 | }; 1159 | 1160 | lp.parseImportSpecifierList = function () { 1161 | var elts = []; 1162 | if (this.tok.type === _.tokTypes.star) { 1163 | var elt = this.startNode(); 1164 | this.next(); 1165 | if (this.eatContextual("as")) elt.local = this.parseIdent(); 1166 | elts.push(this.finishNode(elt, "ImportNamespaceSpecifier")); 1167 | } else { 1168 | var indent = this.curIndent, 1169 | line = this.curLineStart, 1170 | continuedLine = this.nextLineStart; 1171 | this.pushCx(); 1172 | this.eat(_.tokTypes.braceL); 1173 | if (this.curLineStart > continuedLine) continuedLine = this.curLineStart; 1174 | while (!this.closes(_.tokTypes.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) { 1175 | var elt = this.startNode(); 1176 | if (this.eat(_.tokTypes.star)) { 1177 | elt.local = this.eatContextual("as") ? this.parseIdent() : this.dummyIdent(); 1178 | this.finishNode(elt, "ImportNamespaceSpecifier"); 1179 | } else { 1180 | if (this.isContextual("from")) break; 1181 | elt.imported = this.parseIdent(); 1182 | if (_parseutil.isDummy(elt.imported)) break; 1183 | elt.local = this.eatContextual("as") ? this.parseIdent() : elt.imported; 1184 | this.finishNode(elt, "ImportSpecifier"); 1185 | } 1186 | elts.push(elt); 1187 | this.eat(_.tokTypes.comma); 1188 | } 1189 | this.eat(_.tokTypes.braceR); 1190 | this.popCx(); 1191 | } 1192 | return elts; 1193 | }; 1194 | 1195 | lp.parseExportSpecifierList = function () { 1196 | var elts = []; 1197 | var indent = this.curIndent, 1198 | line = this.curLineStart, 1199 | continuedLine = this.nextLineStart; 1200 | this.pushCx(); 1201 | this.eat(_.tokTypes.braceL); 1202 | if (this.curLineStart > continuedLine) continuedLine = this.curLineStart; 1203 | while (!this.closes(_.tokTypes.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) { 1204 | if (this.isContextual("from")) break; 1205 | var elt = this.startNode(); 1206 | elt.local = this.parseIdent(); 1207 | if (_parseutil.isDummy(elt.local)) break; 1208 | elt.exported = this.eatContextual("as") ? this.parseIdent() : elt.local; 1209 | this.finishNode(elt, "ExportSpecifier"); 1210 | elts.push(elt); 1211 | this.eat(_.tokTypes.comma); 1212 | } 1213 | this.eat(_.tokTypes.braceR); 1214 | this.popCx(); 1215 | return elts; 1216 | }; 1217 | 1218 | },{"..":"/src\\index.js","./parseutil":"/src\\loose\\parseutil.js","./state":"/src\\loose\\state.js"}],"/src\\loose\\tokenize.js":[function(_dereq_,module,exports){ 1219 | "use strict"; 1220 | 1221 | var _ = _dereq_(".."); 1222 | 1223 | var _state = _dereq_("./state"); 1224 | 1225 | var lp = _state.LooseParser.prototype; 1226 | 1227 | function isSpace(ch) { 1228 | return ch < 14 && ch > 8 || ch === 32 || ch === 160 || _.isNewLine(ch); 1229 | } 1230 | 1231 | lp.next = function () { 1232 | this.last = this.tok; 1233 | if (this.ahead.length) this.tok = this.ahead.shift();else this.tok = this.readToken(); 1234 | 1235 | if (this.tok.start >= this.nextLineStart) { 1236 | while (this.tok.start >= this.nextLineStart) { 1237 | this.curLineStart = this.nextLineStart; 1238 | this.nextLineStart = this.lineEnd(this.curLineStart) + 1; 1239 | } 1240 | this.curIndent = this.indentationAfter(this.curLineStart); 1241 | } 1242 | }; 1243 | 1244 | lp.readToken = function () { 1245 | for (;;) { 1246 | try { 1247 | this.toks.next(); 1248 | if (this.toks.type === _.tokTypes.dot && this.input.substr(this.toks.end, 1) === "." && this.options.ecmaVersion >= 6) { 1249 | this.toks.end++; 1250 | this.toks.type = _.tokTypes.ellipsis; 1251 | } 1252 | return new _.Token(this.toks); 1253 | } catch (e) { 1254 | if (!(e instanceof SyntaxError)) throw e; 1255 | 1256 | // Try to skip some text, based on the error message, and then continue 1257 | var msg = e.message, 1258 | pos = e.raisedAt, 1259 | replace = true; 1260 | if (/unterminated/i.test(msg)) { 1261 | pos = this.lineEnd(e.pos + 1); 1262 | if (/string/.test(msg)) { 1263 | replace = { start: e.pos, end: pos, type: _.tokTypes.string, value: this.input.slice(e.pos + 1, pos) }; 1264 | } else if (/regular expr/i.test(msg)) { 1265 | var re = this.input.slice(e.pos, pos); 1266 | try { 1267 | re = new RegExp(re); 1268 | } catch (e) {} 1269 | replace = { start: e.pos, end: pos, type: _.tokTypes.regexp, value: re }; 1270 | } else if (/template/.test(msg)) { 1271 | replace = { start: e.pos, end: pos, 1272 | type: _.tokTypes.template, 1273 | value: this.input.slice(e.pos, pos) }; 1274 | } else { 1275 | replace = false; 1276 | } 1277 | } else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number|expected number in radix/i.test(msg)) { 1278 | while (pos < this.input.length && !isSpace(this.input.charCodeAt(pos))) ++pos; 1279 | } else if (/character escape|expected hexadecimal/i.test(msg)) { 1280 | while (pos < this.input.length) { 1281 | var ch = this.input.charCodeAt(pos++); 1282 | if (ch === 34 || ch === 39 || _.isNewLine(ch)) break; 1283 | } 1284 | } else if (/unexpected character/i.test(msg)) { 1285 | pos++; 1286 | replace = false; 1287 | } else if (/regular expression/i.test(msg)) { 1288 | replace = true; 1289 | } else { 1290 | throw e; 1291 | } 1292 | this.resetTo(pos); 1293 | if (replace === true) replace = { start: pos, end: pos, type: _.tokTypes.name, value: "✖" }; 1294 | if (replace) { 1295 | if (this.options.locations) replace.loc = new _.SourceLocation(this.toks, _.getLineInfo(this.input, replace.start), _.getLineInfo(this.input, replace.end)); 1296 | return replace; 1297 | } 1298 | } 1299 | } 1300 | }; 1301 | 1302 | lp.resetTo = function (pos) { 1303 | this.toks.pos = pos; 1304 | var ch = this.input.charAt(pos - 1); 1305 | this.toks.exprAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) || /[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(this.input.slice(pos - 10, pos)); 1306 | 1307 | if (this.options.locations) { 1308 | this.toks.curLine = 0; 1309 | this.toks.lineStart = _.lineBreakG.lastIndex = 0; 1310 | var match = undefined; 1311 | while ((match = _.lineBreakG.exec(this.input)) && match.index < pos) { 1312 | ++this.toks.curLine; 1313 | this.toks.lineStart = match.index + match[0].length; 1314 | } 1315 | } 1316 | }; 1317 | 1318 | lp.lookAhead = function (n) { 1319 | while (n > this.ahead.length) this.ahead.push(this.readToken()); 1320 | return this.ahead[n - 1]; 1321 | }; 1322 | 1323 | },{"..":"/src\\index.js","./state":"/src\\loose\\state.js"}]},{},["/src\\loose\\index.js"])("/src\\loose\\index.js") 1324 | }); 1325 | }); -------------------------------------------------------------------------------- /lib/acorn/dist/walk.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports", "module", "./acorn"], function(require, exports, module) { 2 | 3 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).walk = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= end)) base[type](node, st, c); 99 | if ((start == null || node.start == start) && (end == null || node.end == end) && test(type, node)) throw new Found(node, st); 100 | })(node, state); 101 | } catch (e) { 102 | if (e instanceof Found) return e; 103 | throw e; 104 | } 105 | } 106 | 107 | // Find the innermost node of a given type that contains the given 108 | // position. Interface similar to findNodeAt. 109 | 110 | function findNodeAround(node, pos, test, base, state) { 111 | test = makeTest(test); 112 | if (!base) base = exports.base; 113 | try { 114 | ;(function c(node, st, override) { 115 | var type = override || node.type; 116 | if (node.start > pos || node.end < pos) return; 117 | base[type](node, st, c); 118 | if (test(type, node)) throw new Found(node, st); 119 | })(node, state); 120 | } catch (e) { 121 | if (e instanceof Found) return e; 122 | throw e; 123 | } 124 | } 125 | 126 | // Find the outermost matching node after a given position. 127 | 128 | function findNodeAfter(node, pos, test, base, state) { 129 | test = makeTest(test); 130 | if (!base) base = exports.base; 131 | try { 132 | ;(function c(node, st, override) { 133 | if (node.end < pos) return; 134 | var type = override || node.type; 135 | if (node.start >= pos && test(type, node)) throw new Found(node, st); 136 | base[type](node, st, c); 137 | })(node, state); 138 | } catch (e) { 139 | if (e instanceof Found) return e; 140 | throw e; 141 | } 142 | } 143 | 144 | // Find the outermost matching node before a given position. 145 | 146 | function findNodeBefore(node, pos, test, base, state) { 147 | test = makeTest(test); 148 | if (!base) base = exports.base; 149 | var max = undefined;(function c(node, st, override) { 150 | if (node.start > pos) return; 151 | var type = override || node.type; 152 | if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node)) max = new Found(node, st); 153 | base[type](node, st, c); 154 | })(node, state); 155 | return max; 156 | } 157 | 158 | // Used to create a custom walker. Will fill in all missing node 159 | // type properties with the defaults. 160 | 161 | function make(funcs, base) { 162 | if (!base) base = exports.base; 163 | var visitor = {}; 164 | for (var type in base) visitor[type] = base[type]; 165 | for (var type in funcs) visitor[type] = funcs[type]; 166 | return visitor; 167 | } 168 | 169 | function skipThrough(node, st, c) { 170 | c(node, st); 171 | } 172 | function ignore(_node, _st, _c) {} 173 | 174 | // Node walkers. 175 | 176 | var base = {}; 177 | 178 | exports.base = base; 179 | base.Program = base.BlockStatement = function (node, st, c) { 180 | for (var i = 0; i < node.body.length; ++i) { 181 | c(node.body[i], st, "Statement"); 182 | } 183 | }; 184 | base.Statement = skipThrough; 185 | base.EmptyStatement = ignore; 186 | base.ExpressionStatement = base.ParenthesizedExpression = function (node, st, c) { 187 | return c(node.expression, st, "Expression"); 188 | }; 189 | base.IfStatement = function (node, st, c) { 190 | c(node.test, st, "Expression"); 191 | c(node.consequent, st, "Statement"); 192 | if (node.alternate) c(node.alternate, st, "Statement"); 193 | }; 194 | base.LabeledStatement = function (node, st, c) { 195 | return c(node.body, st, "Statement"); 196 | }; 197 | base.BreakStatement = base.ContinueStatement = ignore; 198 | base.WithStatement = function (node, st, c) { 199 | c(node.object, st, "Expression"); 200 | c(node.body, st, "Statement"); 201 | }; 202 | base.SwitchStatement = function (node, st, c) { 203 | c(node.discriminant, st, "Expression"); 204 | for (var i = 0; i < node.cases.length; ++i) { 205 | var cs = node.cases[i]; 206 | if (cs.test) c(cs.test, st, "Expression"); 207 | for (var j = 0; j < cs.consequent.length; ++j) { 208 | c(cs.consequent[j], st, "Statement"); 209 | } 210 | } 211 | }; 212 | base.ReturnStatement = base.YieldExpression = function (node, st, c) { 213 | if (node.argument) c(node.argument, st, "Expression"); 214 | }; 215 | base.ThrowStatement = base.SpreadElement = function (node, st, c) { 216 | return c(node.argument, st, "Expression"); 217 | }; 218 | base.TryStatement = function (node, st, c) { 219 | c(node.block, st, "Statement"); 220 | if (node.handler) { 221 | c(node.handler.param, st, "Pattern"); 222 | c(node.handler.body, st, "ScopeBody"); 223 | } 224 | if (node.finalizer) c(node.finalizer, st, "Statement"); 225 | }; 226 | base.WhileStatement = base.DoWhileStatement = function (node, st, c) { 227 | c(node.test, st, "Expression"); 228 | c(node.body, st, "Statement"); 229 | }; 230 | base.ForStatement = function (node, st, c) { 231 | if (node.init) c(node.init, st, "ForInit"); 232 | if (node.test) c(node.test, st, "Expression"); 233 | if (node.update) c(node.update, st, "Expression"); 234 | c(node.body, st, "Statement"); 235 | }; 236 | base.ForInStatement = base.ForOfStatement = function (node, st, c) { 237 | c(node.left, st, "ForInit"); 238 | c(node.right, st, "Expression"); 239 | c(node.body, st, "Statement"); 240 | }; 241 | base.ForInit = function (node, st, c) { 242 | if (node.type == "VariableDeclaration") c(node, st);else c(node, st, "Expression"); 243 | }; 244 | base.DebuggerStatement = ignore; 245 | 246 | base.FunctionDeclaration = function (node, st, c) { 247 | return c(node, st, "Function"); 248 | }; 249 | base.VariableDeclaration = function (node, st, c) { 250 | for (var i = 0; i < node.declarations.length; ++i) { 251 | c(node.declarations[i], st); 252 | } 253 | }; 254 | base.VariableDeclarator = function (node, st, c) { 255 | c(node.id, st, "Pattern"); 256 | if (node.init) c(node.init, st, "Expression"); 257 | }; 258 | 259 | base.Function = function (node, st, c) { 260 | if (node.id) c(node.id, st, "Pattern"); 261 | for (var i = 0; i < node.params.length; i++) { 262 | c(node.params[i], st, "Pattern"); 263 | }c(node.body, st, node.expression ? "ScopeExpression" : "ScopeBody"); 264 | }; 265 | // FIXME drop these node types in next major version 266 | // (They are awkward, and in ES6 every block can be a scope.) 267 | base.ScopeBody = function (node, st, c) { 268 | return c(node, st, "Statement"); 269 | }; 270 | base.ScopeExpression = function (node, st, c) { 271 | return c(node, st, "Expression"); 272 | }; 273 | 274 | base.Pattern = function (node, st, c) { 275 | if (node.type == "Identifier") c(node, st, "VariablePattern");else if (node.type == "MemberExpression") c(node, st, "MemberPattern");else c(node, st); 276 | }; 277 | base.VariablePattern = ignore; 278 | base.MemberPattern = skipThrough; 279 | base.RestElement = function (node, st, c) { 280 | return c(node.argument, st, "Pattern"); 281 | }; 282 | base.ArrayPattern = function (node, st, c) { 283 | for (var i = 0; i < node.elements.length; ++i) { 284 | var elt = node.elements[i]; 285 | if (elt) c(elt, st, "Pattern"); 286 | } 287 | }; 288 | base.ObjectPattern = function (node, st, c) { 289 | for (var i = 0; i < node.properties.length; ++i) { 290 | c(node.properties[i].value, st, "Pattern"); 291 | } 292 | }; 293 | 294 | base.Expression = skipThrough; 295 | base.ThisExpression = base.Super = base.MetaProperty = ignore; 296 | base.ArrayExpression = function (node, st, c) { 297 | for (var i = 0; i < node.elements.length; ++i) { 298 | var elt = node.elements[i]; 299 | if (elt) c(elt, st, "Expression"); 300 | } 301 | }; 302 | base.ObjectExpression = function (node, st, c) { 303 | for (var i = 0; i < node.properties.length; ++i) { 304 | c(node.properties[i], st); 305 | } 306 | }; 307 | base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration; 308 | base.SequenceExpression = base.TemplateLiteral = function (node, st, c) { 309 | for (var i = 0; i < node.expressions.length; ++i) { 310 | c(node.expressions[i], st, "Expression"); 311 | } 312 | }; 313 | base.UnaryExpression = base.UpdateExpression = function (node, st, c) { 314 | c(node.argument, st, "Expression"); 315 | }; 316 | base.BinaryExpression = base.LogicalExpression = function (node, st, c) { 317 | c(node.left, st, "Expression"); 318 | c(node.right, st, "Expression"); 319 | }; 320 | base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) { 321 | c(node.left, st, "Pattern"); 322 | c(node.right, st, "Expression"); 323 | }; 324 | base.ConditionalExpression = function (node, st, c) { 325 | c(node.test, st, "Expression"); 326 | c(node.consequent, st, "Expression"); 327 | c(node.alternate, st, "Expression"); 328 | }; 329 | base.NewExpression = base.CallExpression = function (node, st, c) { 330 | c(node.callee, st, "Expression"); 331 | if (node.arguments) for (var i = 0; i < node.arguments.length; ++i) { 332 | c(node.arguments[i], st, "Expression"); 333 | } 334 | }; 335 | base.MemberExpression = function (node, st, c) { 336 | c(node.object, st, "Expression"); 337 | if (node.computed) c(node.property, st, "Expression"); 338 | }; 339 | base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) { 340 | if (node.declaration) c(node.declaration, st, node.type == "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression"); 341 | if (node.source) c(node.source, st, "Expression"); 342 | }; 343 | base.ExportAllDeclaration = function (node, st, c) { 344 | c(node.source, st, "Expression"); 345 | }; 346 | base.ImportDeclaration = function (node, st, c) { 347 | for (var i = 0; i < node.specifiers.length; i++) { 348 | c(node.specifiers[i], st); 349 | }c(node.source, st, "Expression"); 350 | }; 351 | base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.Literal = ignore; 352 | 353 | base.TaggedTemplateExpression = function (node, st, c) { 354 | c(node.tag, st, "Expression"); 355 | c(node.quasi, st); 356 | }; 357 | base.ClassDeclaration = base.ClassExpression = function (node, st, c) { 358 | return c(node, st, "Class"); 359 | }; 360 | base.Class = function (node, st, c) { 361 | if (node.id) c(node.id, st, "Pattern"); 362 | if (node.superClass) c(node.superClass, st, "Expression"); 363 | for (var i = 0; i < node.body.body.length; i++) { 364 | c(node.body.body[i], st); 365 | } 366 | }; 367 | base.MethodDefinition = base.Property = function (node, st, c) { 368 | if (node.computed) c(node.key, st, "Expression"); 369 | c(node.value, st, "Expression"); 370 | }; 371 | base.ComprehensionExpression = function (node, st, c) { 372 | for (var i = 0; i < node.blocks.length; i++) { 373 | c(node.blocks[i].right, st, "Expression"); 374 | }c(node.body, st, "Expression"); 375 | }; 376 | 377 | },{}]},{},["/src\\walk\\index.js"])("/src\\walk\\index.js") 378 | }); 379 | }); -------------------------------------------------------------------------------- /lib/demo.js: -------------------------------------------------------------------------------- 1 | require({ 2 | baseUrl: "lib" 3 | }, ["treehugger/tree", "treehugger/traverse", "treehugger/js/parse", 4 | "acorn/dist/acorn", "acorn/dist/acorn_loose", "acorn/dist/walk" 5 | ], function(tree, traverse, parsejs, acorn, acorn_loose) { 6 | var $ = document.querySelector.bind(document); 7 | window.acorn_loose = acorn_loose; 8 | 9 | if (localStorage.trehuggerJsVal) 10 | $("#code").value = localStorage.trehuggerJsVal; 11 | if (localStorage.trehuggerAnalysisVal) 12 | $("#analysis").value = localStorage.trehuggerAnalysisVal; 13 | window.onbeforeunload = function() { 14 | localStorage.trehuggerJsVal = $("#code").value; 15 | localStorage.trehuggerAnalysisVal = $("#analysis").value; 16 | }; 17 | 18 | function log(message) { 19 | $("#output").value = $("#output").value + message + "\n"; 20 | } 21 | 22 | function exec() { 23 | var js = $("#code").value; 24 | var analysisJs = $("#analysis").value; 25 | $("#output").value = ""; 26 | 27 | try { 28 | var t = performance.now(); 29 | var ast = parsejs.parse(js); 30 | t -= performance.now(); 31 | $("#ast").value = t + "\n" + ast.toPrettyString(); 32 | eval(analysisJs); 33 | } catch(e) { 34 | $("#output").value = "JS Error \n\t" + (e.stack || e.message); 35 | console.log(e) 36 | } 37 | } 38 | 39 | tree.Node.prototype.log = function() { 40 | $("#output").value = this.toPrettyString(); 41 | } 42 | 43 | 44 | $("#code").addEventListener("input", exec); 45 | $("#runbutton").addEventListener("click", exec); 46 | exec(); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /lib/setup_paths.js: -------------------------------------------------------------------------------- 1 | var module = require("module"); 2 | 3 | var oldResolve = module._resolveFilename; 4 | var extraPaths = [ 5 | __dirname 6 | ]; 7 | 8 | module._resolveFilename = function(request, paths) { 9 | // Add the extra paths 10 | extraPaths.forEach(function(p) { 11 | if(paths.paths.indexOf(p) === -1) 12 | paths.paths.push(p); 13 | }); 14 | return oldResolve(request, paths); 15 | }; -------------------------------------------------------------------------------- /lib/treehugger/js/builtin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": { 3 | "guid": "es5:Object", 4 | "properties": { 5 | "_prototype": [{ 6 | "properties": { 7 | "_toString": [{ 8 | "guid": "es5:Object/prototype/toString", 9 | "returnValue": "es5:String" 10 | }], 11 | "_hasOwnProperty": [{ 12 | "guid": "es5:Object/prototype/hasOwnProperty", 13 | "returnValue": "es5:Boolean" 14 | }], 15 | "_isPrototypeOf": [{ 16 | "guid": "es5:Object/prototype/isPrototypeOf", 17 | "returnValue": "es5:Boolean" 18 | }] 19 | } 20 | }], 21 | "_create": [{ 22 | "guid": "es5:Object/create", 23 | "returnValue": null 24 | }] 25 | } 26 | }, 27 | "String": { 28 | "guid": "es5:String", 29 | "doc": "EcmaScript String constructor", 30 | "properties": { 31 | "_prototype": [{ 32 | "guid": "es5:String/prototype", 33 | "doc": "EcmaScript String object", 34 | "properties": { 35 | "_length": ["es5:Number"], 36 | "___proto__": ["es5:Object"] 37 | } 38 | }] 39 | } 40 | }, 41 | "Number": { 42 | "guid": "es5:Number", 43 | "doc": "EcmaScript Number constructor", 44 | "properties": { 45 | "_prototype": [{ 46 | "guid": "es5:Number/prototype", 47 | "doc": "EcmaScript Number object", 48 | "properties": { 49 | "___proto__": ["es5:Object"], 50 | "_toExponential": [{ 51 | "guid": "es5:Number/prototype/toExponential", 52 | "returnValue": "es5:String" 53 | }], 54 | "_toFixed": [{ 55 | "guid": "es5:Number/prototype/toFixed", 56 | "returnValue": "es5:String" 57 | }], 58 | "_toLocaleString": [{ 59 | "guid": "es5:Number/prototype/toLocaleString", 60 | "returnValue": "es5:String" 61 | }] 62 | } 63 | }] 64 | } 65 | }, 66 | "Boolean": { 67 | "guid": "es5:Boolean", 68 | "properties": { 69 | "_prototype": [{ 70 | "properties": { 71 | "___proto__": ["es5:Object"] 72 | } 73 | }] 74 | } 75 | }, 76 | "Function": { 77 | "guid": "es5:Function", 78 | "properties": { 79 | "_prototype": [{ 80 | "properties": { 81 | "_call": [{ 82 | "guid": "es5:Function/prototype/call", 83 | "returnValue": null 84 | }], 85 | "_apply": [{ 86 | "guid": "es5:Function/prototype/apply", 87 | "returnValue": null 88 | }], 89 | "_toSource": [{ 90 | "guid": "es5:Function/prototype/toSource", 91 | "returnValue": null 92 | }], 93 | "___proto__": ["es5:Object"] 94 | } 95 | }] 96 | } 97 | }, 98 | "Array": { 99 | "guid": "es5:Array", 100 | "properties": { 101 | "_prototype": [{ 102 | "properties": { 103 | "_push": [{ 104 | "guid": "es5:Array/prototype/push", 105 | "returnValue": null 106 | }], 107 | "_concat": [{ 108 | "guid": "es5:Array/prototype/concat", 109 | "returnValue": null 110 | }], 111 | "_slice": [{ 112 | "guid": "es5:Array/prototype/slice", 113 | "returnValue": null 114 | }], 115 | "_splice": [{ 116 | "guid": "es5:Array/prototype/splice", 117 | "returnValue": null 118 | }] 119 | } 120 | }] 121 | } 122 | }, 123 | "console": { 124 | "guid": "es5:console", 125 | "doc": "Console object, for debugging", 126 | "properties": { 127 | "_log": [{ 128 | "guid": "es5:console/log", 129 | "doc": "Prints its arguments to the debug console", 130 | "returnValue": null 131 | }] 132 | } 133 | }, 134 | "JSON": { 135 | "guid": "es5:JSON", 136 | "doc": "JSON API", 137 | "properties": { 138 | "_parse": [{ 139 | "guid": "es5:JSON/parse", 140 | "doc": "Parses a JSON string and turns it into a Javascript object", 141 | "returnValue": "es5:Object" 142 | }], 143 | "_stringify": [{ 144 | "guid": "es5:JSON/stringify", 145 | "doc": "Serializes a Javascript object to a JSON-encoded string", 146 | "returnValue": "es5:String" 147 | }] 148 | } 149 | }, 150 | "document": { 151 | "guid": "es5:document", 152 | "doc": "Window's document object", 153 | "properties": { 154 | "_createElement": [{ 155 | "guid": "es5:document/createElement", 156 | "doc": "Parses a JSON string and turns it into a Javascript object", 157 | "returnValue": "es5:Object" 158 | }] 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /lib/treehugger/js/infer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module that implements basic value inference. Type inference in Javascript 3 | * doesn't make a whole lot of sense because it is so dynamic. Therefore, this 4 | * analysis semi-evaluates the Javascript AST and attempts to do simple predictions 5 | * of the values an expression, function or variable may contain. 6 | */ 7 | 8 | define(function(require, exports, module) { 9 | 10 | var tree = require('treehugger/tree'), 11 | Value = require('treehugger/js/values').Value, 12 | FunctionValue = require('treehugger/js/values').FunctionValue, 13 | instantiate = require('treehugger/js/values').instantiate, 14 | valueFromJSON = require('treehugger/js/values').fromJSON, 15 | lookupValue = require('treehugger/js/values').lookupValue; 16 | 17 | require('treehugger/traverse'); 18 | 19 | /** 20 | * Implements Javascript's scoping mechanism using a hashmap with parent 21 | * pointers. 22 | */ 23 | function Scope(parent) { 24 | this.parent = parent; 25 | this.vars = {}; 26 | } 27 | 28 | /** 29 | * Declare a variable in the current scope 30 | */ 31 | Scope.prototype.declare = function(name, initValue) { 32 | if(!this.vars['_'+name]) { 33 | this.vars['_'+name] = initValue ? [initValue] : []; 34 | } 35 | }; 36 | 37 | /** 38 | * Get possible values of a variable 39 | * @param name name of variable 40 | * @return array of values 41 | */ 42 | Scope.prototype.get = function(name) { 43 | if(this.vars['_'+name]) { 44 | return this.vars['_'+name]; 45 | } else if(this.parent) { 46 | return this.parent.get(name); 47 | } 48 | }; 49 | 50 | /** 51 | * Hints at what the value of a variable may be 52 | * @param variable name 53 | * @param val AST node of expression 54 | */ 55 | Scope.prototype.hint = function(name, val) { 56 | var analysis = this.get(name); 57 | if(analysis) { 58 | analysis.push(val); 59 | } 60 | }; 61 | 62 | /** 63 | * Attempts to infer the value, of possible values of expression `e` 64 | * @param e AST node repersenting an expression 65 | * @return an array of possible values 66 | */ 67 | Scope.prototype.inferValues = function(e) { 68 | var scope = this; 69 | var values = []; 70 | e.rewrite( 71 | "String(_)", function() { 72 | values = [instantiate(lookupValue("es5:String"))]; 73 | return this; 74 | }, 75 | "Num(_)", function() { 76 | values = [instantiate(lookupValue("es5:Number"))]; 77 | return this; 78 | }, 79 | "True()", function() { 80 | values = [instantiate(lookupValue("es5:Boolean"))]; 81 | return this; 82 | }, 83 | "False()", function() { 84 | values = [instantiate(lookupValue("es5:Boolean"))]; 85 | return this; 86 | }, 87 | "Array(_)", function() { 88 | values = [instantiate(lookupValue("es5:Array"))]; 89 | return this; 90 | }, 91 | "Var(nm)", function(b) { 92 | var v = this.getAnnotation("scope") ? this.getAnnotation("scope").get(b.nm.value) : scope.get(b.nm.value); 93 | if(!v) { 94 | return false; 95 | } 96 | values = v.slice(0); 97 | return this; 98 | }, 99 | "ObjectInit(inits)", function(b) { 100 | var v = instantiate(lookupValue("es5:Object")); 101 | b.inits.filter('PropertyInit(prop, e)', function(b) { 102 | scope.inferValues(b.e).forEach(function(val) { 103 | v.fieldHint(b.prop.value, val); 104 | }); 105 | }); 106 | values = [v]; 107 | }, 108 | "New(e, args)", function(b) { 109 | var vs = scope.inferValues(b.e); 110 | vs.forEach(function(fn) { 111 | var value = instantiate(fn); 112 | var fargs = fn.getFargs(); 113 | var funScope = handleFunction(fn, scope, value); 114 | for(var i = 0; i < b.args.length; i++) { 115 | scope.inferValues(b.args[i]).forEach(function(v) { 116 | funScope.hint(fargs[i].value, v); 117 | }); 118 | } 119 | inferAllTypes(funScope, fn.getBody()); 120 | values.push(value); 121 | }); 122 | return this; 123 | }, 124 | /*"Op(op, e1, e2)", function(b) { 125 | values = values.concat(coercevalues(scope.inferValues(b.e1), scope.inferValues(b.e2))); 126 | return this; 127 | },*/ 128 | "This()", function() { 129 | values = this.getAnnotation("scope") ? this.getAnnotation("scope").get('__this') : scope.get('__this'); 130 | return this; 131 | }, 132 | "Call(PropAccess(e, method), args)", function(b) { 133 | //var propValues = scope.inferValues(this[0]); 134 | var objectValues = scope.inferValues(b.e); 135 | //console.log("Call: ", this.toString(), this[0], objectValues); 136 | objectValues.forEach(function(objectValue) { 137 | var methods = objectValue.get(b.method.value); 138 | if(methods.length > 0) { 139 | methods.forEach(function(fn) { 140 | if(fn instanceof FunctionValue) { 141 | var funScope = handleFunction(fn, scope, new Value()); //objectValue); 142 | var fargs = fn.getFargs(); 143 | for(var i = 0; i < b.args.length, i < fargs.length; i++) { 144 | scope.inferValues(b.args[i]).forEach(function(v) { 145 | // TODO: create a link rather than a copy 146 | funScope.hint(fargs[i].value, v); 147 | }); 148 | } 149 | inferAllTypes(funScope, fn.getBody()); 150 | values = values.concat(funScope.get('__return')); 151 | } 152 | }); 153 | } 154 | }); 155 | return this; 156 | }, 157 | "Call(e, args)", function(b) { 158 | var vs = scope.inferValues(b.e); 159 | vs.forEach(function(fn) { 160 | if(fn instanceof FunctionValue) { 161 | var funScope = handleFunction(fn, scope, new Value()); 162 | var fargs = fn.getFargs(); 163 | for(var i = 0; i < b.args.length, i < fargs.length; i++) { 164 | scope.inferValues(b.args[i]).forEach(function(v) { 165 | funScope.hint(fargs[i].value, v); 166 | }); 167 | } 168 | inferAllTypes(funScope, fn.getBody()); 169 | values = values.concat(funScope.get('__return')); 170 | } 171 | }); 172 | return this; 173 | }, 174 | "PropAccess(e, prop)", function(b) { 175 | var vs = scope.inferValues(b.e); 176 | vs.forEach(function(val) { 177 | var vs = val.get(b.prop.value); 178 | values = values.concat(vs); 179 | }); 180 | return this; 181 | }, 182 | "Function(name, fargs, _)", function(b) { 183 | var val = new FunctionValue(this); 184 | lookupValue("es5:Function").get('prototype').forEach(function(v) { 185 | val.fieldHint('__proto__', v); 186 | }); 187 | values = [val]; 188 | return this; 189 | } 190 | ); 191 | return values; 192 | }; 193 | 194 | /** 195 | * Functions get two implicit variables: a __this variable and 196 | * a __return variable. 197 | */ 198 | function handleFunction(fn, scope, thisVal) { 199 | var localScope = fn.node && fn.node.getAnnotation("scope") ? fn.node.getAnnotation("scope") : new Scope(scope); 200 | localScope.declare('__return'); 201 | if(fn.node) { 202 | fn.node.setAnnotation("scope", localScope); 203 | fn.node.rewrite('Function(name, fargs, body)', function(b) { 204 | b.fargs.forEach(function(farg) { 205 | localScope.declare(farg.value); 206 | localScope.hint(farg.value, new Value()); 207 | }); 208 | localScope.declare('__this'); 209 | localScope.hint('__this', thisVal); 210 | inferAllTypes(localScope, b.body); 211 | return this; 212 | }); 213 | } else if(fn.returnValue) { 214 | localScope.hint('__return', fn.returnValue); 215 | } 216 | return localScope; 217 | } 218 | 219 | /** 220 | * Returns an array of transformations that pin-point points in the 221 | * Javascript program where values are assigned to variables and stores 222 | * them in the current scope. Var() nodes are attached their current scope 223 | * for later inference. 224 | */ 225 | function evalRules(scope) { 226 | return [// "_", function() { console.log(this.toString()); }, 227 | "Function(name, fargs, body)", function(b) { 228 | var val = new FunctionValue(this); 229 | lookupValue("es5:Function").get('prototype').forEach(function(v) { 230 | val.fieldHint('__proto__', v); 231 | }); 232 | val.fieldHint('prototype', new Value()); 233 | if(b.name.value) { 234 | scope.declare(b.name.value, val); 235 | } 236 | handleFunction(val, scope, new Value()); 237 | return this; 238 | }, 239 | "VarDecls(vardecs)", function(b) { 240 | b.vardecs.each( 241 | "VarDeclInit(name, e)", function(b) { 242 | scope.declare(b.name.value); 243 | inferAllTypes(scope, b.e); 244 | var values = scope.inferValues(b.e); 245 | values.forEach(function(v) { 246 | scope.hint(b.name.value, v); 247 | }); 248 | }, 249 | "VarDecl(name)", function(b) { 250 | scope.declare(b.name.value); 251 | } 252 | ); 253 | return this; 254 | }, 255 | "Assign(PropAccess(e1, prop), e2)", function(b) { 256 | inferAllTypes(scope, b.e1); 257 | var vs = scope.inferValues(b.e1); 258 | inferAllTypes(scope,b.e2); 259 | var vs2 = scope.inferValues(b.e2); 260 | vs.forEach(function(v) { 261 | vs2.forEach(function(v2) { 262 | v.fieldHint(b.prop.value, v2); 263 | }); 264 | }); 265 | return this; 266 | }, 267 | "Assign(Var(name), e)", function(b) { 268 | inferAllTypes(scope,b.e); 269 | var vs = scope.inferValues(b.e); 270 | vs.forEach(function(v) { 271 | scope.hint(b.name.value, v); 272 | }); 273 | return this; 274 | }, 275 | "PropAccess(e, prop)", function(b) { 276 | inferAllTypes(scope, b.e); 277 | var vs = scope.inferValues(this); 278 | if(vs.length > 0) { 279 | return; // property is defined 280 | } 281 | // Apparently there's a property used in the code that 282 | // is defined elsewhere (or by some other means) 283 | // let's add it to the object 284 | vs = scope.inferValues(b.e); 285 | vs.forEach(function(v) { 286 | v.fieldHint(b.prop.value, new Value()); 287 | }); 288 | return this; 289 | }, 290 | "Call(PropAccess(e, prop), args)", function(b) { 291 | // property access is called as a function, let's hint that 292 | inferAllTypes(scope, b.e); 293 | inferAllTypes(scope, b.args); 294 | var vs = scope.inferValues(b.e); 295 | vs.forEach(function(v) { 296 | v.fieldHint(b.prop.value, instantiate(lookupValue("es5:Function"))); 297 | }); 298 | return this; 299 | }, 300 | "Return(e)", function(b) { 301 | inferAllTypes(scope, b.e); 302 | var vs = scope.inferValues(b.e); 303 | vs.forEach(function(v) { 304 | scope.hint('__return', v); 305 | }); 306 | return this; 307 | }, 308 | "Var(name)", function(b) { 309 | this.setAnnotation("scope", scope); 310 | var vs = scope.get(b.name.value); 311 | if(!vs) { 312 | scope.declare(b.name.value); 313 | scope.hint(b.name.value, new Value()); 314 | } 315 | return this; 316 | }, 317 | "This()", function() { 318 | this.setAnnotation("scope", scope); 319 | // NOTE: Probably something went wrong here, or using this pointer outside of function? 320 | var vs = scope.get('__this'); 321 | if(!vs) { 322 | scope.declare('__this'); 323 | scope.hint('__this', new Value()); 324 | } 325 | return this; 326 | } 327 | ]; 328 | } 329 | 330 | function getAllProperties(scope, node) { 331 | var properties = {}; 332 | function handleProto(v) { 333 | for(var p in v.fields) { 334 | if(v.fields.hasOwnProperty(p)) { 335 | properties[p] = true; 336 | } 337 | } 338 | v.get('__proto__').forEach(function(v) { 339 | handleProto(v); 340 | }); 341 | } 342 | scope.inferValues(node).forEach(function(v) { 343 | for(var p in v.fields) { 344 | if(v.fields.hasOwnProperty(p)) { 345 | properties[p] = true; 346 | } 347 | } 348 | v.get('__proto__').forEach(function(v) { 349 | handleProto(v); 350 | }); 351 | }); 352 | var ar = []; 353 | for(var p in properties) { 354 | if(properties.hasOwnProperty(p)) { 355 | ar.push(p.substring(1)); 356 | } 357 | } 358 | return ar; 359 | } 360 | 361 | function retrieveValueInfo(value) { 362 | if(value.guid) { 363 | return { 364 | guid: value.guid, 365 | doc: value.doc 366 | }; 367 | } 368 | 369 | var info; 370 | value.get('__proto__').forEach(function(v) { 371 | if(v.guid) { 372 | info = { 373 | guid: v.guid, 374 | doc: v.doc 375 | }; 376 | } 377 | }); 378 | return info; 379 | } 380 | 381 | /** 382 | * Invoke the actual traversal applying the eval rules 383 | */ 384 | function inferAllTypes(scope, node) { 385 | node.traverseTopDown(evalRules(scope)); 386 | return scope; 387 | } 388 | 389 | function createRootScope(builtinsJSON) { 390 | var scope = new Scope(); 391 | for (var TypeName in builtinsJSON) { 392 | scope.declare(TypeName); 393 | var value = valueFromJSON(builtinsJSON[TypeName]); 394 | scope.hint(TypeName, value); 395 | } 396 | return scope; 397 | } 398 | 399 | exports.inferAllTypes = inferAllTypes; 400 | exports.getAllProperties = getAllProperties; 401 | exports.Scope = Scope; 402 | exports.createRootScope = createRootScope; 403 | exports.retrieveValueInfo = retrieveValueInfo; 404 | 405 | }); -------------------------------------------------------------------------------- /lib/treehugger/js/inferUp.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | require('treehugger/traverse'); 4 | 5 | var STRING_TYPE = 'string'; 6 | var NUMBER_TYPE = 'number'; 7 | 8 | function VarTracker() { 9 | this.vars = {}; 10 | } 11 | 12 | VarTracker.prototype.track = function(name) { 13 | if(!this.vars[name]) { 14 | this.vars[name] = {types: [], exps: []}; 15 | } 16 | }; 17 | 18 | function coerceTypes(t1, t2) { 19 | if(t1.length !== 1 || t2.length !== 1) { 20 | return t1.concat(t2); 21 | } 22 | t1 = t1[0]; 23 | t2 = t2[0]; 24 | if(t1 === t2) { 25 | return [t1]; 26 | } else if(t1 === STRING_TYPE || t2 === STRING_TYPE) { 27 | return [STRING_TYPE]; 28 | } 29 | return [t1, t2]; 30 | } 31 | 32 | VarTracker.prototype.getTypes = function(e) { 33 | var tracker = this; 34 | var types = []; 35 | e.rewrite("String(_)", function() { types.push(STRING_TYPE); }, 36 | "Number(_)", function() { types.push(NUMBER_TYPE); }, 37 | "Var(nm)", function(b) { 38 | var analysis = tracker.vars[b.nm.value]; 39 | if(analysis.types.length > 0) { 40 | types = types.concat(analysis.types); 41 | } else { 42 | analysis.exps.forEach(function(e) { 43 | types = types.concat(tracker.getTypes(e)); 44 | }); 45 | } 46 | }, 47 | "New(te, args)", function(b) { 48 | types.push(b.te); 49 | }, 50 | "Op(op, e1, e2)", function(b) { 51 | types = types.concat(coerceTypes(tracker.getTypes(b.e1), tracker.getTypes(b.e2))); 52 | }); 53 | return types; 54 | }; 55 | 56 | VarTracker.prototype.hint = function(name, e) { 57 | if(!this.vars[name]) return; // Don't care 58 | var tracker = this; 59 | var analysis = this.vars[name]; 60 | e.traverseTopDown("Var(nm)", function(b) { 61 | tracker.track(b.nm.value); 62 | }); 63 | analysis.exps.push(e); 64 | }; 65 | 66 | function inferType(node) { 67 | var tracker = new VarTracker(); 68 | var name; 69 | node.rewrite("Var(nm)", function(b) { 70 | name = b.nm.value; 71 | tracker.track(name); 72 | this.traverseUp("VarDecls(vardecs)", function(b) { 73 | b.vardecs.filter("VarDeclInit(name, e)", function(b) { 74 | tracker.hint(b.name.value, b.e); 75 | }); 76 | }, 77 | "ExpStat(Assign(Var(name), e))", function(b) { 78 | tracker.hint(b.name.value, b.e); 79 | }); 80 | }); 81 | console.log(tracker, name, tracker.getTypes(node)); 82 | //return tracker.getTypes(name); 83 | } 84 | 85 | exports.inferType = inferType; 86 | 87 | }); -------------------------------------------------------------------------------- /lib/treehugger/js/parse.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var parser = require("acorn/dist/acorn_loose"); 4 | var tree = require('treehugger/tree'); 5 | 6 | // var REV = 0; // for debugging 7 | 8 | exports.parse = function(s) { 9 | // REV++; 10 | var result = parser.parse_dammit( 11 | s, 12 | { 13 | locations: true, 14 | ecmaVersion: 6, 15 | allowReturnOutsideFunction: true 16 | } 17 | ); 18 | var node = exports.transform(result); 19 | if(result.error) 20 | node.setAnnotation("error", result.error); 21 | return node; 22 | }; 23 | 24 | 25 | function setIdPos(n, resultNode) { 26 | if(n.loc) { 27 | resultNode.setAnnotation("pos", { 28 | sl: n.loc.start.line, sc: n.loc.start.column, 29 | el: n.loc.end.line, ec: n.loc.end.column 30 | }); 31 | } 32 | return resultNode; 33 | } 34 | function id(n, val) { 35 | var s = tree.string(val || (n && n.name) || ""); 36 | s.$pos = n && n.loc; 37 | // s.REV = REV; 38 | return s; 39 | } 40 | 41 | exports.transform = function transform(n) { 42 | if (!n) { 43 | return tree.cons("None", []); 44 | } 45 | if (Array.isArray(n)) { 46 | return tree.list(n.map(transform)); 47 | } 48 | var nodeName = n.type; 49 | 50 | var resultNode; 51 | 52 | switch(nodeName) { 53 | case "Program": 54 | resultNode = tree.list(n.body.map(transform)); 55 | break; 56 | case "VariableDeclaration": 57 | if (n.kind === "var") { 58 | var VarDecls = "VarDecls", VarDeclInit = "VarDeclInit", VarDecl = "VarDecl"; 59 | } else if (n.kind === "let") { 60 | var VarDecls = "LetDecls", VarDeclInit = "LetDeclInit", VarDecl = "LetDecl"; 61 | } else if (n.kind === "const") { 62 | var VarDecls = "ConstDecls", VarDeclInit = "ConstDeclInit", VarDecl = "ConstDecl"; 63 | } 64 | resultNode = tree.cons(VarDecls, [tree.list(n.declarations.map(function(varNode) { 65 | var idNode = id(varNode.id); 66 | if(varNode.init) 67 | return tree.cons(VarDeclInit, [idNode, transform(varNode.init)]); 68 | else 69 | return tree.cons(VarDecl, [idNode]); 70 | }))]); 71 | break; 72 | case "ExpressionStatement": 73 | return transform(n.expression); 74 | case "CallExpression": 75 | resultNode = tree.cons("Call", [transform(n.callee), tree.list(n.arguments.map(transform))]); 76 | break; 77 | case "ReturnStatement": 78 | resultNode = tree.cons("Return", [transform(n.argument)]); 79 | break; 80 | case "NewExpression": 81 | resultNode = tree.cons("New", [transform(n.callee), tree.list(n.arguments.map(transform))]); 82 | break; 83 | case "ObjectExpression": 84 | resultNode = tree.cons("ObjectInit", [tree.list(n.properties.map(function(propInit) { 85 | var key = propInit.key; 86 | var result = tree.cons("PropertyInit", [id(key, key.name || key.value), transform(propInit.value)]); 87 | result.kind = propInit.kind; 88 | return result; 89 | }))]); 90 | break; 91 | case "ArrayExpression": 92 | resultNode = tree.cons("Array", [tree.list(n.elements.map(transform))]); 93 | break; 94 | case "ConditionalExpression": 95 | resultNode = tree.cons("TernaryIf", [transform(n.test), transform(n.consequent), transform(n.alternate)]); 96 | break; 97 | case "LabeledStatement": 98 | resultNode = tree.cons("Label", [id(n.label), transform(n.body)]); 99 | break; 100 | case "AssignmentExpression": 101 | if(n.operator != "=") { 102 | resultNode = tree.cons("OpAssign", [tree.string(n.operator[0]), transform(n.left), transform(n.right)]); 103 | } else { 104 | resultNode = tree.cons("Assign", [transform(n.left), transform(n.right)]); 105 | } 106 | break; 107 | case "MemberExpression": 108 | resultNode = n.computed 109 | ? tree.cons("Index", [transform(n.object), transform(n.property)]) 110 | : tree.cons("PropAccess", [transform(n.object), id(n.property)]); 111 | break; 112 | case "Identifier": 113 | resultNode = tree.cons("Var", [id(n)]); 114 | break; 115 | case "ThisExpression": 116 | resultNode = tree.cons("Var", [tree.string("this")]); 117 | break; 118 | case "FunctionDeclaration": 119 | // todo this doesn't handle error in id.name, but old parser doen't handle it as well 120 | resultNode = tree.cons("Function", [id(n.id), tree.list(n.params.map(function(arg) { 121 | return setIdPos(arg, tree.cons("FArg", [id(arg)])); 122 | })), tree.list(n.body.body.map(transform))]); 123 | break; 124 | case "FunctionExpression": 125 | var funName = id(n.id); 126 | var fargs = tree.list(n.params.map(function(arg) { 127 | return setIdPos(arg, tree.cons("FArg", [id(arg)])); 128 | })); 129 | resultNode = tree.cons("Function", [funName, fargs, tree.list(n.body.body.map(transform))]); 130 | break; 131 | case "LogicalExpression": 132 | case "BinaryExpression": 133 | resultNode = tree.cons("Op", [tree.string(n.operator), transform(n.left), transform(n.right)]); 134 | break; 135 | case "UpdateExpression": 136 | case "UnaryExpression": 137 | resultNode = tree.cons(n.prefix ? "PrefixOp" : "PostfixOp", [tree.string(n.operator), transform(n.argument)]); 138 | break; 139 | case "sub": 140 | resultNode = tree.cons("Index", [transform(n[1]), transform(n[2])]); 141 | break; 142 | case "ForStatement": 143 | resultNode = tree.cons("For", [transform(n.init), transform(n.test), transform(n.update), transform(n.body)]); 144 | break; 145 | case "ForInStatement": 146 | resultNode = tree.cons("ForIn", [transform(n.left), transform(n.right), transform(n.body)]); 147 | break; 148 | case "ForOfStatement": 149 | resultNode = tree.cons("ForOf", [transform(n.left), transform(n.right), transform(n.body)]); 150 | break; 151 | case "WhileStatement": 152 | resultNode = tree.cons("While", [transform(n.test), transform(n.body)]); 153 | break; 154 | case "DoWhileStatement": 155 | resultNode = tree.cons("Do", [transform(n.body), transform(n.test)]); 156 | break; 157 | case "SwitchStatement": 158 | resultNode = tree.cons("Switch", [transform(n.discriminant), tree.list(n.cases.map(function(opt) { 159 | return tree.cons("Case", [transform(opt.test), tree.list(opt.consequent.map(transform))]); 160 | }))]); 161 | break; 162 | case "ContinueStatement": 163 | resultNode = tree.cons("Continue", [id(n.label)]); 164 | break; 165 | case "BreakStatement": 166 | resultNode = tree.cons("Break", [id(n.label)]); 167 | break; 168 | case "SequenceExpression": // todo can we get rid of nesting? 169 | resultNode = n.expressions.reduceRight(function(a, b) { 170 | return a ? tree.cons("Seq", [transform(b), a]) : transform(b); 171 | }, ""); 172 | break; 173 | case "IfStatement": 174 | resultNode = tree.cons("If", [transform(n.test), transform(n.consequent), transform(n.alternate)]); 175 | break; 176 | case "EmptyStatement": 177 | case "BlockStatement": 178 | resultNode = tree.cons("Block", [tree.list(n.body ? n.body.map(transform) : [])]); 179 | break; 180 | case "ThrowStatement": 181 | resultNode = tree.cons("Throw", [transform(n.argument)]); 182 | break; 183 | case "DebuggerStatement": 184 | resultNode = tree.cons("Debugger", [transform(n.argument)]); 185 | break; 186 | case "TryStatement": 187 | resultNode = tree.cons("Try", [tree.list(n.block.body.map(transform)), 188 | tree.list(n.handler ? [tree.cons("Catch", [ 189 | id(n.handler.param), tree.list(n.handler.body.body.map(transform)) 190 | ])] : []), 191 | n.finalizer ? tree.list(n.finalizer.body.map(transform)) : tree.cons("None", []) 192 | ]); 193 | break; 194 | case "WithStatement": 195 | resultNode = tree.cons("With", [transform(n.object), tree.list((n.body.body||[]).map(transform))]); 196 | break; 197 | case "Literal": 198 | var litType = typeof n.value; 199 | if (litType == "number") { 200 | resultNode = tree.cons("Num", [id(n, n.raw)]); 201 | } else if (litType == "string") { 202 | resultNode = tree.cons("String", [id(n, n.value)]); 203 | } else { 204 | var val = n.raw; 205 | if (val[0] == "/") { 206 | var i = val.lastIndexOf("/"); 207 | resultNode = tree.cons("RegExp", [tree.string(val.slice(1, i)), tree.string(val.substr(i + 1))]); 208 | } else { 209 | resultNode = tree.cons("Var", [tree.string(n.value + "")]); 210 | } 211 | } 212 | break; 213 | case "ERROR": 214 | resultNode = tree.cons("ERROR", []); 215 | break; 216 | case "ArrowFunctionExpression": 217 | resultNode = tree.cons("Arrow", [tree.list(n.params.map(function(arg) { 218 | return setIdPos(arg, tree.cons("FArg", [id(arg)])); 219 | })), tree.list(n.body.body ? n.body.body.map(transform) : transform(n.body))]); 220 | break; 221 | case "YieldExpression": 222 | resultNode = tree.cons("Yield", [transform(n.argument)]); 223 | break; 224 | case "ImportDeclaration": 225 | resultNode = tree.cons("ImportDecls", [ 226 | tree.list(n.specifiers.map(transform)), 227 | transform(n.source) 228 | ]); 229 | break; 230 | case "ImportSpecifier": 231 | resultNode = tree.cons("ImportDecl", [transform(n.id), transform(n.name)]); 232 | break; 233 | case "ExportSpecifier": 234 | resultNode = tree.cons("ExporDecl", [transform(n.id), transform(n.name)]); 235 | break; 236 | case "ImportBatchSpecifier": 237 | resultNode = tree.cons("ImportBatchDecl", [transform(n.name)]); 238 | break; 239 | case "ExportDeclaration": 240 | resultNode = tree.cons("ExportDecl", [ 241 | n.default ? tree.list([tree.cons("Default", [])]) : tree.list([]), 242 | n.declaration ? transform(n.declaration) : transform(n.specifiers), 243 | transform(n.source) 244 | ]); 245 | break; 246 | case "SpreadElement": 247 | resultNode = tree.cons("Spread", [transform(n.argument)]); 248 | break; 249 | case "ArrayPattern": 250 | resultNode = tree.cons("ArrayPattern", transform(n.elements)); 251 | break; 252 | case "ClassDeclaration": 253 | resultNode = tree.cons("Class", [id(n.id), id(n.superClass), transform(n.body)]); 254 | break; 255 | case "ClassBody": 256 | resultNode = tree.list(transform(n.body)); 257 | break; 258 | case "MethodDefinition": 259 | resultNode = tree.cons("Method", [id(n.key), transform(n.value)]); 260 | break; 261 | case "ComprehensionBlock": 262 | case "ComprehensionExpression": 263 | default: 264 | console.log("Not yet supported: " + nodeName); 265 | // console.log("Current node: "+ JSON.stringify(n)); 266 | resultNode = tree.cons(tree.string(nodeName), [tree.string(JSON.stringify(n, function(key, val) { 267 | if (key !== "loc") 268 | return val; 269 | }, 4))]); 270 | } 271 | 272 | resultNode.setAnnotation("origin", n); 273 | /*if(n.loc) { 274 | resultNode.setAnnotation("pos", { 275 | sl: n.loc.start.line, sc: n.loc.start.column, 276 | el: n.loc.end.line, ec: n.loc.end.column 277 | }); 278 | }*/ 279 | resultNode.$pos = n.loc; 280 | return resultNode; 281 | }; 282 | }); 283 | -------------------------------------------------------------------------------- /lib/treehugger/js/parse_test.js: -------------------------------------------------------------------------------- 1 | if (typeof process !== "undefined") { 2 | require("amd-loader"); 3 | require("../../setup_paths"); 4 | } 5 | 6 | var parser = require("treehugger/js/parse"); 7 | require('treehugger/traverse'); 8 | var assert = require("assert"); 9 | //var microtime = require('microtime'); 10 | 11 | module.exports = { 12 | "test basic parsing" : function() { 13 | assert.equal(parser.parse("hello()").toString(), '[Call(Var("hello"),[])]'); 14 | assert.equal(parser.parse("if(true) a = 8;").toString(), '[If(Var("true"),Assign(Var("a"),Num("8")),None())]'); 15 | var node = parser.parse("log(); var b = true; b"); 16 | node.traverseTopDown('VarDeclInit(x, _)', function(b) { 17 | var pos = b.x.getPos(); 18 | assert.equal(pos.sc, 11); 19 | assert.equal(pos.ec, 12); 20 | pos = this.getPos(); 21 | assert.equal(pos.sc, 11); 22 | assert.equal(pos.ec, 19); 23 | }); 24 | node = parser.parse("function hello(a, b) { }"); 25 | node.traverseTopDown('Function(x, fargs, body)', function(b) { 26 | var pos = b.x.getPos(); 27 | assert.equal(pos.sc, 9); 28 | assert.equal(pos.ec, 14); 29 | pos = b.fargs[0].getPos(); 30 | assert.equal(pos.sc, 15); 31 | assert.equal(pos.ec, 16); 32 | pos = b.fargs[1].getPos(); 33 | assert.equal(pos.sc, 18); 34 | assert.equal(pos.ec, 19); 35 | pos = b.fargs.getPos(); 36 | assert.equal(pos.sc, 15); 37 | }); 38 | assert.equal(parser.parse("with(a) { console.log(b); }").toString(), "[With(Var(\"a\"),[Call(PropAccess(Var(\"console\"),\"log\"),[Var(\"b\")])])]"); 39 | assert.equal(parser.parse("let b = true;").toString(), "[LetDecls([LetDeclInit(\"b\",Var(\"true\"))])]"); 40 | assert.equal(parser.parse("let b = true, a, c;").toString(), "[LetDecls([LetDeclInit(\"b\",Var(\"true\")),LetDecl(\"a\"),LetDecl(\"c\")])]"); 41 | }, 42 | "test parse jquery": function() { 43 | var code = require('fs').readFileSync(__dirname+'/../../jquery.js', 'ascii'); 44 | //var now = microtime.now(); 45 | parser.parse(code); 46 | //console.log("Parsing jQuery took: " + (microtime.now() - now)/1000 + "ms"); 47 | }, 48 | "test parse treehugger": function() { 49 | var code = require('fs').readFileSync(__dirname+'/../traverse.js', 'ascii'); 50 | code += require('fs').readFileSync(__dirname+'/../tree.js', 'ascii'); 51 | code += require('fs').readFileSync(__dirname+'/../js/uglifyparser.js', 'ascii'); 52 | code += require('fs').readFileSync(__dirname+'/../js/uglifyparser.js', 'ascii'); 53 | code += require('fs').readFileSync(__dirname+'/../js/infer.js', 'ascii'); 54 | //var now = microtime.now(); 55 | parser.parse(code); 56 | //console.log("Parsing jQuery took: " + (microtime.now() - now)/1000 + "ms"); 57 | }, 58 | "test error recovery" : function() { 59 | assert.equal(parser.parse("hello.;").toString(), '[PropAccess(Var("hello"),"✖")]'); 60 | assert.equal(parser.parse("hello.").toString(), '[PropAccess(Var("hello"),"✖")]'); 61 | assert.equal(parser.parse("if(hello.) { }").toString(), '[If(PropAccess(Var("hello"),"✖"),Block([]),None())]'); 62 | assert.equal(parser.parse("while(hello.) { }").toString(), '[While(PropAccess(Var("hello"),"✖"),Block([]))]'); 63 | // for variants 64 | assert.equal(parser.parse("for(var i = 0; i.) { }").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),PropAccess(Var("i"),"✖"),None(),Block([]))]'); 65 | assert.equal(parser.parse("for(var i = 0; i.)").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),PropAccess(Var("i"),"✖"),None(),Block([]))]'); 66 | // This produces a funky AST, have to deal with it 67 | assert.equal(parser.parse("for(var i = 0; i.\nif(true) {}").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),PropAccess(Var("i"),"✖"),None(),If(Var("true"),Block([]),None()))]'); 68 | assert.equal(parser.parse("for(var i = 0; i < array.) { }").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),Op("<",Var("i"),PropAccess(Var("array"),"✖")),None(),Block([]))]'); 69 | assert.equal(parser.parse("for(var i = 0; i < array.)").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),Op("<",Var("i"),PropAccess(Var("array"),"✖")),None(),Block([]))]'); 70 | assert.equal(parser.parse("for(var i = 0; i < array.length; i.)").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),Op("<",Var("i"),PropAccess(Var("array"),"length")),PropAccess(Var("i"),"✖"),Block([]))]'); 71 | assert.equal(parser.parse("for(var i = 0; i < array.length; i.);\nalert('hello');").toString(), '[For(VarDecls([VarDeclInit("i",Num("0"))]),Op("<",Var("i"),PropAccess(Var("array"),"length")),PropAccess(Var("i"),"✖"),Block([])),Call(Var("alert"),[String("hello")])]'); 72 | // for in 73 | assert.equal(parser.parse("for(var p in something.) bla()").toString(), '[ForIn(VarDecls([VarDecl("p")]),PropAccess(Var("something"),"✖"),Call(Var("bla"),[]))]'); 74 | assert.equal(parser.parse("for(var p in something.) bla()").toString(), '[ForIn(VarDecls([VarDecl("p")]),PropAccess(Var("something"),"✖"),Call(Var("bla"),[]))]'); 75 | 76 | assert.equal(parser.parse("if(hello.").toString(), '[If(PropAccess(Var("hello"),"✖"),Block([]),None())]'); 77 | assert.equal(parser.parse("if(hello.after()").toString(), '[If(Call(PropAccess(Var("hello"),"after"),[]),Block([]),None())]'); 78 | // this produces a funky AST, but we'll have to deal with it 79 | assert.equal(parser.parse("if(hello.\nafter()").toString(), '[If(PropAccess(Var(\"hello\"),\"✖\"),Block([]),None())]'); 80 | assert.equal(parser.parse("while(hello.").toString(), '[While(PropAccess(Var("hello"),"✖"),Block([]))]'); 81 | assert.equal(parser.parse("if(hello.)").toString(), '[If(PropAccess(Var("hello"),"✖"),Block([]),None())]'); 82 | assert.equal(parser.parse("while(hello.)").toString(), '[While(PropAccess(Var("hello"),"✖"),Block([]))]'); 83 | 84 | assert.equal(parser.parse("switch(hello.)").toString(), '[Switch(PropAccess(Var("hello"),"✖"),[])]'); 85 | assert.equal(parser.parse("switch(hello.)\nhello();if(true)").toString(), '[Switch(PropAccess(Var("hello"),"✖"),[Case(None(),[Call(Var("hello"),[]),If(Var("true"),Block([]),None())])])]'); 86 | assert.equal(parser.parse("(function() { hello()").toString(), '[Function("",[],[Call(Var("hello"),[])])]'); 87 | assert.equal(parser.parse("(function() { hello.").toString(), '[Function("",[],[PropAccess(Var("hello"),"✖")])]'); 88 | assert.equal(parser.parse("(function() { hello.})();").toString(), '[Call(Function("",[],[PropAccess(Var("hello"),"✖")]),[])]'); 89 | 90 | assert.equal(parser.parse("bla(start.);").toString(), '[Call(Var("bla"),[PropAccess(Var("start"),"✖")])]'); 91 | assert.equal(parser.parse("var Editor = function() { start. }; hello();").toString(), '[VarDecls([VarDeclInit("Editor",Function("",[],[PropAccess(Var("start"),"✖")]))]),Call(Var("hello"),[])]'); 92 | 93 | // keywords 94 | assert.equal(parser.parse("function").toString(), '[Function("✖",[],[])]'); 95 | assert.equal(parser.parse("while").toString(), '[While(Var("✖"),Block([]))]'); 96 | assert.equal(parser.parse("if").toString(), '[If(Var("✖"),Block([]),None())]'); 97 | assert.equal(parser.parse("for").toString(), '[For(Var("✖"),None(),None(),Block([]))]'); 98 | assert.equal(parser.parse("do").toString(), '[Do(Block([]),Var("✖"))]'); 99 | assert.equal(parser.parse("if(").toString(), '[If(Var("✖"),Block([]),None())]'); 100 | // todo should this be broken if and a function outside? 101 | assert.equal(parser.parse("if(hello.\nfunction hello() { return 0; }").toString(), '[If(PropAccess(Var("hello"),"✖"),Function("hello",[],[Return(Num("0"))]),None())]'); 102 | // assert.equal(parser.parse("var\nfunction hello() {}").toString(), '[VarDecls([]),Function("hello",[],[])]'); 103 | }, 104 | "test parse literals": function() { 105 | assert.equal(parser.parse("true").toString(), '[Var("true")]'); 106 | assert.equal(parser.parse("15").toString(), '[Num("15")]'); 107 | assert.equal(parser.parse("15.5").toString(), '[Num("15.5")]'); 108 | }, 109 | "test es7": function() { 110 | assert.equal(parser.parse("async function a(x) {await y()}").toString(), '[Function("a",[FArg("x")],[Call(Var("y"),[])])]'); 111 | } 112 | }; 113 | 114 | if (typeof module !== "undefined" && module === require.main) { 115 | require("asyncjs").test.testcase(module.exports).exec(); 116 | } -------------------------------------------------------------------------------- /lib/treehugger/js/types.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var tree = require('treehugger/tree'); 4 | require('treehugger/traverse'); 5 | 6 | function startsWithCapital(value) { 7 | if(value.length === 0) { 8 | return false; 9 | } 10 | return value[0].toUpperCase() === value[0]; 11 | } 12 | 13 | function lhsToString(n) { 14 | return n.rewrite("Var(nm)", function(b) { return b.nm.value; }, 15 | "PropAccess(e, p)", function(b) { return lhsToString(b.e) + "." + b.p.value; }); 16 | } 17 | 18 | function lhsName(n) { 19 | return n.rewrite("Var(nm)", function(b) { return b.nm.value; }, 20 | "PropAccess(e, p)", function(b) { return b.p.value; }); 21 | } 22 | 23 | exports.typeAnalysisPrototype = function(repository) { 24 | repository.types = repository.types || {}; 25 | var types = repository.types; 26 | 27 | function findProperties(type, n) { 28 | return n.collectTopDown('PropAccess(This(), _)', 'Function(_, _, _)', 'Call(PropAccess(This(), _), _)') 29 | .filter('PropAccess(This(), p)', function(bindings) { 30 | type.properties[bindings.p.value] = { 31 | name: bindings.p.value, 32 | meta: this.meta.pos 33 | }; 34 | }); 35 | } 36 | return [ 37 | 'Function(nm, fargs, body)', function(bindings) { 38 | if(startsWithCapital(bindings.nm.value)) { 39 | types[bindings.nm.value] = { 40 | name: bindings.nm.value, 41 | constructorArgs: bindings.fargs.map(function(n) { return this.value; }).toArray(), 42 | methods: {}, 43 | staticMethods: {}, 44 | properties: {}, 45 | meta: this.meta 46 | }; 47 | findProperties(types[bindings.nm.value], bindings.body); 48 | return this; 49 | } 50 | return false; 51 | }, 52 | 'Assign(lhs, Function(nm, fargs, body))', function(bindings) { 53 | var nm = lhsName(bindings.lhs); 54 | var qid = lhsToString(bindings.lhs); 55 | if(startsWithCapital(nm)) { 56 | types[qid] = { 57 | name: nm, 58 | constructorArgs: bindings.fargs.map(function(n) { return this.value; }).toArray(), 59 | methods: {}, 60 | staticMethods: {}, 61 | properties: {}, 62 | meta: this[1].meta 63 | }; 64 | findProperties(types[qid], bindings.body); 65 | return this; 66 | } 67 | return false; 68 | }, 69 | 'Assign(PropAccess(PropAccess(t, "prototype"), method), Function(nm, fargs, body))', function(bindings) { 70 | var qid = lhsToString(bindings.t); 71 | var t = types[qid]; 72 | if(!t) return false; 73 | t.methods[bindings.method.value] = { 74 | name: bindings.method.value, 75 | args: bindings.fargs.map(function(n) { return this.value; }).toArray(), 76 | meta: this[1].meta 77 | }; 78 | findProperties(types[qid], bindings.body); 79 | return this; 80 | }, 81 | 'Assign(PropAccess(t, method), Function(nm, fargs, body))', function(bindings) { 82 | var name = lhsName(bindings.t); 83 | var qid = lhsToString(bindings.t); 84 | if(startsWithCapital(name)) { 85 | var t = types[qid]; 86 | if(!t) return false; 87 | t.staticMethods[bindings.method.value] = { 88 | name: bindings.method.value, 89 | args: bindings.fargs.map(function(n) { return this.value; }).toArray(), 90 | meta: this[1].meta 91 | }; 92 | findProperties(types[qid], bindings.body); 93 | return this; 94 | } 95 | return false; 96 | }, 97 | 'Assign(PropAccess(t, "prototype"), ObjectInit(props))', function(bindings) { 98 | var qid = lhsToString(bindings.t); 99 | var t = types[qid]; 100 | if(!t) return false; 101 | bindings.props.filter('PropertyInit(method, Function(nm2, fargs, body))', function(bindings) { 102 | t.methods[bindings.method.value] = { 103 | name: bindings.method.value, 104 | args: bindings.fargs.map(function(n) { return this.value; }).toArray(), 105 | meta: this[1].meta 106 | }; 107 | findProperties(types[qid], bindings.body); 108 | }); 109 | return this; 110 | } 111 | ]; 112 | }; 113 | 114 | exports.functionAnalysis = function(repository) { 115 | repository.functions = repository.functions || {}; 116 | var functions = repository.functions; 117 | return [ 118 | 'Function(nm, fargs, body)', function(bindings) { 119 | if(bindings.nm.value && !startsWithCapital(bindings.nm.value)) { 120 | functions[bindings.nm.value] = { 121 | name: bindings.nm.value, 122 | args: bindings.fargs.map(function(n) { return this.value; }).toArray(), 123 | meta: this.meta.pos 124 | }; 125 | return this; 126 | } 127 | return false; 128 | }, 129 | 'Assign(lhs, Function(nm, fargs, body))', function(bindings) { 130 | var nm = lhsName(bindings.lhs); 131 | var qid = lhsToString(bindings.lhs); 132 | if(!startsWithCapital(nm)) { 133 | functions[qid] = { 134 | name: nm, 135 | args: bindings.fargs.map(function(n) { return this.value; }).toArray(), 136 | meta: this.meta.pos 137 | }; 138 | return this; 139 | } 140 | return false; 141 | } 142 | ]; 143 | }; 144 | 145 | exports.analyze = function(n) { 146 | var repository = {}; 147 | n.collectTopDown(exports.typeAnalysisPrototype(repository) 148 | .concat(exports.functionAnalysis(repository))); 149 | return repository; 150 | }; 151 | 152 | }); -------------------------------------------------------------------------------- /lib/treehugger/js/uglifyparser.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | 5 | This version is suitable for Node.js. With minimal changes (the 6 | exports stuff) it should work on any JS platform. 7 | 8 | This file contains the tokenizer/parser. It is a port to JavaScript 9 | of parse-js [1], a JavaScript parser library written in Common Lisp 10 | by Marijn Haverbeke. Thank you Marijn! 11 | 12 | [1] http://marijn.haverbeke.nl/parse-js/ 13 | 14 | Exported functions: 15 | 16 | - tokenizer(code) -- returns a function. Call the returned 17 | function to fetch the next token. 18 | 19 | - parse(code) -- returns an AST of the given JavaScript code. 20 | 21 | ---------------------------------------------------------------------- 22 | 23 | This is an updated version of UglifyJS, adapted by zef@c9.io with error 24 | recovery, to keep parsing code when errors are encountered 25 | 26 | -------------------------------- (C) --------------------------------- 27 | 28 | Author: Mihai Bazon 29 | 30 | http://mihai.bazon.net/blog 31 | 32 | Distributed under the BSD license: 33 | 34 | Copyright 2010 (c) Mihai Bazon 35 | Based on parse-js (http://marijn.haverbeke.nl/parse-js/). 36 | 37 | Redistribution and use in source and binary forms, with or without 38 | modification, are permitted provided that the following conditions 39 | are met: 40 | 41 | * Redistributions of source code must retain the above 42 | copyright notice, this list of conditions and the following 43 | disclaimer. 44 | 45 | * Redistributions in binary form must reproduce the above 46 | copyright notice, this list of conditions and the following 47 | disclaimer in the documentation and/or other materials 48 | provided with the distribution. 49 | 50 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 51 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 52 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 53 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 54 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 55 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 58 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 59 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 60 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 61 | SUCH DAMAGE. 62 | 63 | ***********************************************************************/ 64 | 65 | define(function(require, exports, module) { 66 | 67 | /* -----[ Tokenizer (constants) ]----- */ 68 | 69 | var KEYWORDS = array_to_hash([ 70 | "break", 71 | "case", 72 | "catch", 73 | "const", 74 | "continue", 75 | "default", 76 | "delete", 77 | "do", 78 | "else", 79 | "finally", 80 | "for", 81 | "function", 82 | "if", 83 | "in", 84 | "instanceof", 85 | "new", 86 | "return", 87 | "switch", 88 | "throw", 89 | "try", 90 | "typeof", 91 | "var", 92 | "void", 93 | "while", 94 | "with", 95 | "let" 96 | ]); 97 | 98 | var RESERVED_WORDS = array_to_hash([ 99 | "abstract", 100 | "boolean", 101 | "byte", 102 | "char", 103 | "class", 104 | "debugger", 105 | "double", 106 | "enum", 107 | "export", 108 | "extends", 109 | "final", 110 | "float", 111 | "goto", 112 | "implements", 113 | "import", 114 | "int", 115 | "interface", 116 | "long", 117 | "native", 118 | "package", 119 | "private", 120 | "protected", 121 | "public", 122 | "short", 123 | "static", 124 | "super", 125 | "synchronized", 126 | "throws", 127 | "transient", 128 | "volatile" 129 | ]); 130 | 131 | var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ 132 | "return", 133 | "new", 134 | "delete", 135 | "throw", 136 | "else", 137 | "case" 138 | ]); 139 | 140 | var KEYWORDS_ATOM = array_to_hash([ 141 | "false", 142 | "null", 143 | "true", 144 | "undefined" 145 | ]); 146 | 147 | var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); 148 | 149 | var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; 150 | var RE_OCT_NUMBER = /^0[0-7]+$/; 151 | var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; 152 | 153 | var OPERATORS = array_to_hash([ 154 | "in", 155 | "instanceof", 156 | "typeof", 157 | "new", 158 | "void", 159 | "delete", 160 | "++", 161 | "--", 162 | "+", 163 | "-", 164 | "!", 165 | "~", 166 | "&", 167 | "|", 168 | "^", 169 | "*", 170 | "/", 171 | "%", 172 | ">>", 173 | "<<", 174 | ">>>", 175 | "<", 176 | ">", 177 | "<=", 178 | ">=", 179 | "==", 180 | "===", 181 | "!=", 182 | "!==", 183 | "?", 184 | "=", 185 | "+=", 186 | "-=", 187 | "/=", 188 | "*=", 189 | "%=", 190 | ">>=", 191 | "<<=", 192 | ">>>=", 193 | "|=", 194 | "^=", 195 | "&=", 196 | "&&", 197 | "||" 198 | ]); 199 | 200 | var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000")); 201 | 202 | var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); 203 | 204 | var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); 205 | 206 | var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); 207 | 208 | /* -----[ Tokenizer ]----- */ 209 | 210 | // regexps adapted from http://xregexp.com/plugins/#unicode 211 | var UNICODE = { 212 | letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), 213 | non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"), 214 | space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"), 215 | connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") 216 | }; 217 | 218 | function is_letter(ch) { 219 | return UNICODE.letter.test(ch); 220 | } 221 | 222 | function is_digit(ch) { 223 | ch = ch.charCodeAt(0); 224 | return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9 225 | } 226 | 227 | function is_alphanumeric_char(ch) { 228 | return is_digit(ch) || is_letter(ch); 229 | } 230 | 231 | function is_unicode_combining_mark(ch) { 232 | return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); 233 | } 234 | 235 | function is_unicode_connector_punctuation(ch) { 236 | return UNICODE.connector_punctuation.test(ch); 237 | } 238 | 239 | function is_identifier_start(ch) { 240 | return ch == "$" || ch == "_" || is_letter(ch); 241 | } 242 | 243 | function is_identifier_char(ch) { 244 | return is_identifier_start(ch) 245 | || is_unicode_combining_mark(ch) 246 | || is_digit(ch) 247 | || is_unicode_connector_punctuation(ch) 248 | || ch == "\u200c" // zero-width non-joiner 249 | || ch == "\u200d"; // zero-width joiner (in my ECMA-262 PDF, this is also 200c) 250 | } 251 | 252 | function parse_js_number(num) { 253 | if (RE_HEX_NUMBER.test(num)) { 254 | return parseInt(num.substr(2), 16); 255 | } 256 | else if (RE_OCT_NUMBER.test(num)) { 257 | return parseInt(num.substr(1), 8); 258 | } 259 | else if (RE_DEC_NUMBER.test(num)) { 260 | return parseFloat(num); 261 | } 262 | } 263 | 264 | function JS_Parse_Error(message, line, col, pos) { 265 | this.message = message; 266 | this.line = line; 267 | this.col = col; 268 | this.pos = pos; 269 | this.stack = new Error().stack; 270 | } 271 | 272 | JS_Parse_Error.prototype.toString = function() { 273 | return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; 274 | } 275 | 276 | function js_error(message, line, col, pos) { 277 | throw new JS_Parse_Error(message, line, col, pos); 278 | } 279 | 280 | function is_token(token, type, val) { 281 | return token.type == type && (val == null || token.value == val); 282 | } 283 | 284 | var EX_EOF = {}; 285 | 286 | function tokenizer($TEXT) { 287 | 288 | var S = { 289 | text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), 290 | pos : 0, 291 | tokpos : 0, 292 | line : 0, 293 | tokline : 0, 294 | col : 0, 295 | tokcol : 0, 296 | newline_before : false, 297 | regex_allowed : false, 298 | comments_before : [] 299 | }; 300 | 301 | function peek() { 302 | return S.text.charAt(S.pos); 303 | } 304 | 305 | function next(signal_eof) { 306 | var ch = S.text.charAt(S.pos++); 307 | if (signal_eof && !ch) throw EX_EOF; 308 | if (ch == "\n") { 309 | S.newline_before = true; 310 | ++S.line; 311 | S.col = 0; 312 | } 313 | else { 314 | ++S.col; 315 | } 316 | return ch; 317 | } 318 | 319 | function eof() { 320 | return !S.peek(); 321 | } 322 | 323 | function find(what, signal_eof) { 324 | var pos = S.text.indexOf(what, S.pos); 325 | if (signal_eof && pos == -1) throw EX_EOF; 326 | return pos; 327 | } 328 | 329 | function start_token() { 330 | S.tokline = S.line; 331 | S.tokcol = S.col; 332 | S.tokpos = S.pos; 333 | } 334 | 335 | function token(type, value, is_comment) { 336 | S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || 337 | (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || 338 | (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); 339 | var ret = { 340 | type : type, 341 | value : value, 342 | line : S.tokline, 343 | col : S.tokcol, 344 | pos : S.tokpos, 345 | nlb : S.newline_before 346 | }; 347 | if (!is_comment) { 348 | ret.comments_before = S.comments_before; 349 | S.comments_before = []; 350 | } 351 | S.newline_before = false; 352 | return ret; 353 | } 354 | 355 | function skip_whitespace() { 356 | while (HOP(WHITESPACE_CHARS, peek())) 357 | next(); 358 | } 359 | 360 | function read_while(pred) { 361 | var ret = "", ch = peek(), i = 0; 362 | while (ch && pred(ch, i++)) { 363 | ret += next(); 364 | ch = peek(); 365 | } 366 | return ret; 367 | } 368 | 369 | function parse_error(err) { 370 | js_error(err, S.tokline, S.tokcol, S.tokpos); 371 | } 372 | 373 | function read_num(prefix) { 374 | var has_e = false, 375 | after_e = false, 376 | has_x = false, 377 | has_dot = prefix == "."; 378 | var num = read_while(function(ch, i) { 379 | if (ch == "x" || ch == "X") { 380 | if (has_x) return false; 381 | return has_x = true; 382 | } 383 | if (!has_x && (ch == "E" || ch == "e")) { 384 | if (has_e) return false; 385 | return has_e = after_e = true; 386 | } 387 | if (ch == "-") { 388 | if (after_e || (i == 0 && !prefix)) return true; 389 | return false; 390 | } 391 | if (ch == "+") return after_e; 392 | after_e = false; 393 | if (ch == ".") { 394 | if (!has_dot && !has_x) return has_dot = true; 395 | return false; 396 | } 397 | return is_alphanumeric_char(ch); 398 | }); 399 | if (prefix) num = prefix + num; 400 | var valid = parse_js_number(num); 401 | return token("num", num); 402 | } 403 | 404 | function read_escaped_char() { 405 | var ch = next(true); 406 | switch (ch) { 407 | case "n": 408 | return "\n"; 409 | case "r": 410 | return "\r"; 411 | case "t": 412 | return "\t"; 413 | case "b": 414 | return "\b"; 415 | case "v": 416 | return "\u000b"; 417 | case "f": 418 | return "\f"; 419 | case "0": 420 | return "\0"; 421 | case "x": 422 | return String.fromCharCode(hex_bytes(2)); 423 | case "u": 424 | return String.fromCharCode(hex_bytes(4)); 425 | case "\n": 426 | return ""; 427 | default: 428 | return ch; 429 | } 430 | } 431 | 432 | function hex_bytes(n) { 433 | var num = 0; 434 | for (; n > 0; --n) { 435 | var digit = parseInt(next(true), 16); 436 | if (isNaN(digit)) parse_error("Invalid hex-character pattern in string"); 437 | num = (num << 4) | digit; 438 | } 439 | return num; 440 | } 441 | 442 | function read_string() { 443 | return with_eof_error("Unterminated string constant", function(){ 444 | var quote = next(), ret = ""; 445 | for (;;) { 446 | var ch = next(false); // RECOVERY 447 | if (ch == "\\") { 448 | // read OctalEscapeSequence (XXX: deprecated if "strict mode") 449 | // https://github.com/mishoo/UglifyJS/issues/178 450 | var octal_len = 0, first = null; 451 | ch = read_while(function(ch){ 452 | if (ch >= "0" && ch <= "7") { 453 | if (!first) { 454 | first = ch; 455 | return ++octal_len; 456 | } 457 | else if (first <= "3" && octal_len <= 2) return ++octal_len; 458 | else if (first >= "4" && octal_len <= 1) return ++octal_len; 459 | } 460 | return false; 461 | }); 462 | if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); 463 | else ch = read_escaped_char(); 464 | } 465 | else if (ch == quote) break; 466 | // RECOVERY 467 | else if (ch == "\n" || ch == "\r" || !ch) { 468 | var result = token("string", ret); 469 | register_error("Unterminated string literal", result); 470 | return result; 471 | } 472 | 473 | ret += ch; 474 | } 475 | return token("string", ret); 476 | }); 477 | } 478 | 479 | // RECOVERY 480 | function register_error(message, token) { 481 | token.error = message; 482 | } 483 | 484 | function read_line_comment() { 485 | next(); 486 | var i = find("\n"), ret; 487 | if (i == -1) { 488 | ret = S.text.substr(S.pos); 489 | S.pos = S.text.length; 490 | } else { 491 | ret = S.text.substring(S.pos, i); 492 | S.pos = i; 493 | } 494 | return token("comment1", ret, true); 495 | } 496 | 497 | function read_multiline_comment() { 498 | next(); 499 | return with_eof_error("Unterminated multiline comment", function() { 500 | var i = find("*/", true), 501 | text = S.text.substring(S.pos, i), 502 | tok = token("comment2", text, true); 503 | S.pos = i + 2; 504 | S.line += text.split("\n").length - 1; 505 | S.newline_before = text.indexOf("\n") >= 0; 506 | // https://github.com/mishoo/UglifyJS/issues/#issue/100 507 | if (/^@cc_on/i.test(text)) { 508 | warn("WARNING: at line " + S.line); 509 | warn("*** Found \"conditional comment\": " + text); 510 | warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); 511 | } 512 | return tok; 513 | }); 514 | } 515 | 516 | function read_name() { 517 | var backslash = false, 518 | name = "", 519 | ch; 520 | while ((ch = peek()) != null) { 521 | if (!backslash) { 522 | if (ch == "\\") backslash = true, next(); 523 | else if (is_identifier_char(ch)) name += next(); 524 | else break; 525 | } 526 | else { 527 | if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); 528 | ch = read_escaped_char(); 529 | if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); 530 | name += ch; 531 | backslash = false; 532 | } 533 | } 534 | return name; 535 | } 536 | 537 | function read_regexp(regexp) { 538 | return with_eof_error("Unterminated regular expression", function() { 539 | var prev_backslash = false, 540 | ch, in_class = false; 541 | while ((ch = next(true))) if (prev_backslash) { 542 | regexp += "\\" + ch; 543 | prev_backslash = false; 544 | } 545 | else if (ch == "[") { 546 | in_class = true; 547 | regexp += ch; 548 | } 549 | else if (ch == "]" && in_class) { 550 | in_class = false; 551 | regexp += ch; 552 | } 553 | else if (ch == "/" && !in_class) { 554 | break; 555 | } 556 | else if (ch == "\\") { 557 | prev_backslash = true; 558 | } 559 | else { 560 | regexp += ch; 561 | } 562 | var mods = read_name(); 563 | return token("regexp", [regexp, mods]); 564 | }); 565 | } 566 | 567 | function read_operator(prefix) { 568 | function grow(op) { 569 | if (!peek()) return op; 570 | var bigger = op + peek(); 571 | if (HOP(OPERATORS, bigger)) { 572 | next(); 573 | return grow(bigger); 574 | } 575 | else { 576 | return op; 577 | } 578 | }; 579 | return token("operator", grow(prefix || next())); 580 | } 581 | 582 | function handle_slash() { 583 | next(); 584 | var regex_allowed = S.regex_allowed; 585 | switch (peek()) { 586 | case "/": 587 | S.comments_before.push(read_line_comment()); 588 | S.regex_allowed = regex_allowed; 589 | return next_token(); 590 | case "*": 591 | S.comments_before.push(read_multiline_comment()); 592 | S.regex_allowed = regex_allowed; 593 | return next_token(); 594 | } 595 | return S.regex_allowed ? read_regexp("") : read_operator("/"); 596 | } 597 | 598 | function handle_dot() { 599 | next(); 600 | return is_digit(peek()) ? read_num(".") : token("punc", "."); 601 | } 602 | 603 | function read_word() { 604 | var word = read_name(); 605 | return !HOP(KEYWORDS, word) 606 | ? token("name", word) 607 | : HOP(OPERATORS, word) 608 | ? token("operator", word) 609 | : HOP(KEYWORDS_ATOM, word) 610 | ? token("atom", word) 611 | : token("keyword", word); 612 | }; 613 | 614 | function with_eof_error(eof_error, cont) { 615 | try { 616 | return cont(); 617 | } 618 | catch (ex) { 619 | if (ex === EX_EOF) parse_error(eof_error); 620 | else throw ex; 621 | } 622 | } 623 | 624 | function next_token(force_regexp) { 625 | if (force_regexp != null) return read_regexp(force_regexp); 626 | skip_whitespace(); 627 | start_token(); 628 | var ch = peek(); 629 | if (!ch) return token("eof"); 630 | if (is_digit(ch)) return read_num(); 631 | if (ch == '"' || ch == "'") return read_string(); 632 | if (HOP(PUNC_CHARS, ch)) return token("punc", next()); 633 | if (ch == ".") return handle_dot(); 634 | if (ch == "/") return handle_slash(); 635 | if (HOP(OPERATOR_CHARS, ch)) return read_operator(); 636 | if (ch == "\\" || is_identifier_start(ch)) return read_word(); 637 | 638 | // RECOVERY 639 | register_error("Unexpected character '" + ch + "'", token("ignore")); 640 | next(true); 641 | return next_token(force_regexp); 642 | } 643 | 644 | next_token.context = function(nc) { 645 | if (nc) S = nc; 646 | return S; 647 | }; 648 | 649 | return next_token; 650 | } 651 | 652 | /* -----[ Parser (constants) ]----- */ 653 | 654 | var UNARY_PREFIX = array_to_hash([ 655 | "typeof", 656 | "void", 657 | "delete", 658 | "--", 659 | "++", 660 | "!", 661 | "~", 662 | "-", 663 | "+" 664 | ]); 665 | 666 | var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); 667 | 668 | var ASSIGNMENT = (function(a, ret, i) { 669 | while (i < a.length) { 670 | ret[a[i]] = a[i].substr(0, a[i].length - 1); 671 | i++; 672 | } 673 | return ret; 674 | })(["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="], { 675 | "=": true 676 | }, 0); 677 | 678 | var PRECEDENCE = (function(a, ret) { 679 | for (var i = 0, n = 1; i < a.length; ++i, ++n) { 680 | var b = a[i]; 681 | for (var j = 0; j < b.length; ++j) { 682 | ret[b[j]] = n; 683 | } 684 | } 685 | return ret; 686 | })([ 687 | ["||"], 688 | ["&&"], 689 | ["|"], 690 | ["^"], 691 | ["&"], 692 | ["==", "===", "!=", "!=="], 693 | ["<", ">", "<=", ">=", "in", "instanceof"], 694 | [">>", "<<", ">>>"], 695 | ["+", "-"], 696 | ["*", "/", "%"] 697 | ], {}); 698 | 699 | var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); 700 | var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); 701 | 702 | /* -----[ Parser ]----- */ 703 | 704 | function NodeWithToken(str, start, end) { 705 | this.name = str; 706 | this.start = start; 707 | this.end = end; 708 | } 709 | 710 | NodeWithToken.prototype.toString = function() { 711 | return this.name; 712 | }; 713 | 714 | function parse($TEXT, exigent_mode, embed_tokens) { 715 | var S = { 716 | input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT, 717 | token : null, 718 | prev : null, 719 | peeked : null, 720 | in_function : 0, 721 | in_loop : 0, 722 | labels : [], 723 | line : 0, 724 | col : 0, 725 | error : null 726 | }; 727 | 728 | S.token = next(); 729 | 730 | function is(type, value) { 731 | return is_token(S.token, type, value); 732 | } 733 | 734 | function peek() { 735 | return S.peeked || (S.peeked = S.input()); 736 | } 737 | 738 | function register_error(message, token) { 739 | if (!token) 740 | token = S.token; 741 | if(!S.error) 742 | S.error = {line: token.line, col: token.col, message: message}; 743 | } 744 | 745 | function next() { 746 | S.prev = S.token; 747 | var context = S.input.context(); 748 | S.line = context.line; 749 | S.col = context.col; 750 | if (S.peeked) { 751 | S.token = S.peeked; 752 | S.peeked = null; 753 | } 754 | else { 755 | S.token = S.input(); 756 | } 757 | return S.token; 758 | } 759 | 760 | function prev() { 761 | return S.prev; 762 | } 763 | 764 | function croak(msg, line, col, pos) { 765 | var ctx = S.input.context(); 766 | js_error(msg, 767 | line != null ? line : ctx.tokline, 768 | col != null ? col : ctx.tokcol, 769 | pos != null ? pos : ctx.tokpos); 770 | } 771 | 772 | function token_error(token, msg) { 773 | // RECOVERY 774 | register_error(msg, token); 775 | // croak(msg, token.line, token.col); 776 | } 777 | 778 | function unexpected(token) { 779 | // RECOVERY 780 | if (token == null) 781 | token = S.token; 782 | register_error("Unexpected token: " + token.type + " (" + token.value + ")", token); 783 | /*if (token == null) 784 | token = S.token; 785 | token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");*/ 786 | } 787 | 788 | function expect_token(type, val) { 789 | if (is(type, val)) { 790 | return next(); 791 | } 792 | register_error("Unexpected token " + S.token.type + ", expected " + type, S.token); 793 | } 794 | 795 | function expect(punc) { 796 | // RECOVER 797 | if(is("punc", punc)) 798 | return next(); 799 | 800 | register_error("Expected: " + punc); 801 | // return expect_token("punc", punc); 802 | } 803 | 804 | function can_insert_semicolon() { 805 | return !exigent_mode && (S.token.nlb || is("eof") || is("punc", "}")); 806 | } 807 | 808 | function semicolon() { 809 | // RECOVER 810 | if (is("punc", ";")) 811 | next(); 812 | else if (!can_insert_semicolon()) 813 | register_error("Semicolon expected"); 814 | } 815 | 816 | function as() { 817 | return slice(arguments); 818 | } 819 | 820 | function parenthesised() { 821 | if(!is("punc", "(")) { 822 | // RECOVER 823 | register_error("Expected: ("); 824 | return as("ERROR"); 825 | } 826 | expect("("); 827 | var ex = expression(); 828 | expect(")"); 829 | return ex; 830 | } 831 | 832 | function add_tokens(str, start, end) { 833 | return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); 834 | } 835 | 836 | function maybe_embed_tokens(parser) { 837 | if (embed_tokens) return function() { 838 | var start = S.token; 839 | var ast = parser.apply(this, arguments); 840 | if (!ast) { 841 | register_error("Parse error"); 842 | return ["ERROR"]; 843 | } 844 | ast[0] = add_tokens(ast[0], start, start === S.token ? start : prev()); 845 | return ast; 846 | }; 847 | else return parser; 848 | } 849 | 850 | var statement = maybe_embed_tokens(function() { 851 | if (is("operator", "/") || is("operator", "/=")) { 852 | S.peeked = null; 853 | S.token = S.input(S.token.value.substr(1)); // force regexp 854 | } 855 | switch (S.token.type) { 856 | case "num": 857 | case "string": 858 | case "regexp": 859 | case "operator": 860 | case "atom": 861 | return simple_statement(); 862 | 863 | case "name": 864 | return is_token(peek(), "punc", ":") 865 | ? labeled_statement(prog1(S.token.value, next, next)) 866 | : simple_statement(); 867 | 868 | case "punc": 869 | switch (S.token.value) { 870 | case "{": 871 | return as("block", block_()); 872 | case "[": 873 | case "(": 874 | return simple_statement(); 875 | case ";": 876 | next(); 877 | return as("block"); 878 | default: 879 | // RECOVER 880 | next(); 881 | register_error("Bracket expected."); 882 | return as("ERROR"); 883 | //unexpected(); 884 | } 885 | 886 | case "keyword": 887 | switch (prog1(S.token.value, next)) { 888 | case "break": 889 | return break_cont("break"); 890 | 891 | case "continue": 892 | return break_cont("continue"); 893 | 894 | case "debugger": 895 | semicolon(); 896 | return as("debugger"); 897 | 898 | case "do": 899 | return (function(body) { 900 | // RECOVER 901 | if (is("keyword", "while")) { 902 | expect_token("keyword", "while"); 903 | return as("do", prog1(parenthesised, semicolon), body); 904 | } 905 | else { 906 | register_error("Invalid do statement."); 907 | return as("do", as("ERROR"), as("ERROR")); 908 | } 909 | })(in_loop(statement)); 910 | case "for": 911 | return for_(); 912 | 913 | case "function": 914 | return function_(true); 915 | 916 | case "if": 917 | return if_(); 918 | 919 | case "return": 920 | // RECOVERY 921 | if (S.in_function == 0) 922 | register_error("'return' outside of function"); 923 | return as("return", 924 | is("punc", ";") 925 | ? (next(), null) 926 | : can_insert_semicolon() 927 | ? null 928 | : prog1(expression, semicolon)); 929 | 930 | case "switch": 931 | return as("switch", parenthesised(), switch_block_()); 932 | 933 | case "throw": 934 | if (S.token.nlb) 935 | croak("Illegal newline after 'throw'"); 936 | return as("throw", prog1(expression, semicolon)); 937 | 938 | case "try": 939 | return try_(); 940 | 941 | case "var": 942 | return prog1(var_, semicolon); 943 | 944 | case "let": 945 | return prog1(let_, semicolon); 946 | 947 | case "const": 948 | return prog1(const_, semicolon); 949 | 950 | case "while": 951 | return as("while", parenthesised(), in_loop(statement)); 952 | 953 | case "with": 954 | return as("with", parenthesised(), statement()); 955 | 956 | default: 957 | unexpected(); 958 | } 959 | } 960 | }); 961 | 962 | function labeled_statement(label) { 963 | S.labels.push(label); 964 | var start = S.token, 965 | stat = statement(); 966 | if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) unexpected(start); 967 | S.labels.pop(); 968 | return as("label", label, stat); 969 | } 970 | 971 | function simple_statement() { 972 | return as("stat", prog1(expression, semicolon)); 973 | } 974 | 975 | function break_cont(type) { 976 | var name; 977 | if (!can_insert_semicolon()) { 978 | name = is("name") ? S.token.value : null; 979 | } 980 | if (name != null) { 981 | next(); 982 | if (!member(name, S.labels)) croak("Label " + name + " without matching loop or statement"); 983 | } 984 | else if (S.in_loop == 0) croak(type + " not inside a loop or switch"); 985 | semicolon(); 986 | return as(type, name); 987 | } 988 | 989 | function for_() { 990 | // RECOVER 991 | if(!is("punc", "(")) { 992 | register_error("Expected: ("); 993 | return as("for", as("ERROR"), as("ERROR"), as("ERROR"), as("ERROR")); 994 | } else { 995 | expect("("); 996 | var init = null; 997 | if (!is("punc", ";")) { 998 | if(is("keyword", "var")) 999 | init = (next(), var_(true)); 1000 | else if(is("keyword", "let")) 1001 | init = (next(), let_(true)); 1002 | else 1003 | init = expression(true, true); 1004 | if (is("operator", "in")) 1005 | return for_in(init); 1006 | } 1007 | return regular_for(init); 1008 | } 1009 | } 1010 | 1011 | function regular_for(init) { 1012 | expect(";"); 1013 | var test = is("punc", ";") ? null : expression(); 1014 | // RECOVER 1015 | if(is("punc", ";")) { 1016 | expect(";"); 1017 | var step = is("punc", ")") ? null : expression(); 1018 | expect(")"); 1019 | } 1020 | else { 1021 | register_error("Expected: ;"); 1022 | } 1023 | return as("for", init, test, step, in_loop(statement)); 1024 | }; 1025 | 1026 | function for_in(init) { 1027 | var lhs; 1028 | if(init[0] == "var") 1029 | lhs = as("name", init[1][0]); 1030 | else if(init[0] == "let") 1031 | lhs = as("name", init[1][0]); 1032 | else 1033 | lhs = init; 1034 | next(); 1035 | var obj = expression(); 1036 | expect(")"); 1037 | return as("for-in", init, lhs, obj, in_loop(statement)); 1038 | } 1039 | 1040 | var function_ = maybe_embed_tokens(function(in_statement) { 1041 | var prev = S.token; 1042 | var name = is("name") ? prog1(S.token.value, next) : null; 1043 | name = add_tokens(name, prev, S.token); 1044 | if (in_statement && !name) { 1045 | // RECOVER 1046 | register_error("Invalid function definition"); 1047 | return as("function", "", [], []); 1048 | } 1049 | expect("("); 1050 | return as(in_statement ? "defun" : "function", name, 1051 | // arguments 1052 | (function(first, a) { 1053 | while (!is("punc", ")") && !is("eof")) { 1054 | if (first) first = false; 1055 | else expect(","); 1056 | if (!is("name")) unexpected(); 1057 | a.push(add_tokens(S.token.value, S.token)); 1058 | next(); 1059 | } 1060 | next(); 1061 | return a; 1062 | })(true, []), 1063 | // body 1064 | (function() { 1065 | ++S.in_function; 1066 | var loop = S.in_loop; 1067 | S.in_loop = 0; 1068 | var a = block_(); 1069 | --S.in_function; 1070 | S.in_loop = loop; 1071 | return a; 1072 | })()); 1073 | }); 1074 | 1075 | function if_() { 1076 | var cond = parenthesised(), 1077 | body = statement(), 1078 | belse; 1079 | if (is("keyword", "else")) { 1080 | next(); 1081 | belse = statement(); 1082 | } 1083 | return as("if", cond, body, belse); 1084 | }; 1085 | 1086 | function block_() { 1087 | expect("{"); 1088 | var a = []; 1089 | while (!is("punc", "}")) { 1090 | if (is("eof")) { 1091 | // RECOVERY 1092 | //unexpected(); 1093 | return a; 1094 | } 1095 | a.push(statement()); 1096 | } 1097 | next(); 1098 | return a; 1099 | }; 1100 | 1101 | var switch_block_ = curry(in_loop, function() { 1102 | // RECOVER 1103 | if (is("punc", "{")) expect("{"); 1104 | var a = [], 1105 | cur = null; 1106 | while (!is("punc", "}")) { 1107 | if (is("eof")) { 1108 | // RECOVER 1109 | break; 1110 | //unexpected(); 1111 | } 1112 | if (is("keyword", "case")) { 1113 | next(); 1114 | cur = []; 1115 | a.push([expression(), cur]); 1116 | expect(":"); 1117 | } 1118 | else if (is("keyword", "default")) { 1119 | next(); 1120 | expect(":"); 1121 | cur = []; 1122 | a.push([null, cur]); 1123 | } 1124 | else { 1125 | if (!cur) { 1126 | // RECOVER 1127 | //unexpected(); 1128 | return a; 1129 | } 1130 | cur.push(statement()); 1131 | } 1132 | } 1133 | next(); 1134 | return a; 1135 | }); 1136 | 1137 | function try_() { 1138 | var body = block_(), 1139 | bcatch, bfinally; 1140 | if (is("keyword", "catch")) { 1141 | next(); 1142 | expect("("); 1143 | if (!is("name")) croak("Name expected"); 1144 | var name = S.token.value; 1145 | next(); 1146 | expect(")"); 1147 | bcatch = [name, block_()]; 1148 | } 1149 | if (is("keyword", "finally")) { 1150 | next(); 1151 | bfinally = block_(); 1152 | } 1153 | if (!bcatch && !bfinally) croak("Missing catch/finally blocks"); 1154 | return as("try", body, bcatch, bfinally); 1155 | } 1156 | 1157 | function vardefs(no_in) { 1158 | var a = []; 1159 | for (;;) { 1160 | if (!is("name")) unexpected(); 1161 | var prev = S.token; 1162 | var name = S.token.value; 1163 | next(); 1164 | name = add_tokens(name, prev, S.token); 1165 | if (is("operator", "=")) { 1166 | next(); 1167 | a.push([name, expression(false, no_in)]); 1168 | } 1169 | else { 1170 | a.push([name]); 1171 | } 1172 | if (!is("punc", ",")) break; 1173 | next(); 1174 | } 1175 | return a; 1176 | } 1177 | 1178 | function var_(no_in) { 1179 | return as("var", vardefs(no_in)); 1180 | } 1181 | 1182 | function let_(no_in) { 1183 | return as("let", vardefs(no_in)); 1184 | } 1185 | 1186 | function const_() { 1187 | return as("const", vardefs()); 1188 | } 1189 | 1190 | function new_() { 1191 | var newexp = expr_atom(false), 1192 | args; 1193 | if (is("punc", "(")) { 1194 | next(); 1195 | args = expr_list(")"); 1196 | } 1197 | else { 1198 | args = []; 1199 | } 1200 | return subscripts(as("new", newexp, args), true); 1201 | } 1202 | 1203 | var expr_atom = maybe_embed_tokens(function(allow_calls) { 1204 | if (is("operator", "new")) { 1205 | next(); 1206 | return new_(); 1207 | } 1208 | if (is("punc")) { 1209 | switch (S.token.value) { 1210 | case "(": 1211 | next(); 1212 | return subscripts(prog1(expression, function() { 1213 | // RECOVER 1214 | // curry(expect, ")") 1215 | expect(")"); 1216 | }), allow_calls); 1217 | case "[": 1218 | next(); 1219 | return subscripts(array_(), allow_calls); 1220 | case "{": 1221 | next(); 1222 | return subscripts(object_(), allow_calls); 1223 | } 1224 | // RECOVER 1225 | register_error("Bracket expected."); 1226 | return as("ERROR"); 1227 | //unexpected(); 1228 | } 1229 | if (is("keyword", "function")) { 1230 | next(); 1231 | return subscripts(function_(false), allow_calls); 1232 | } 1233 | if (HOP(ATOMIC_START_TOKEN, S.token.type)) { 1234 | var atom = S.token.type == "regexp" 1235 | ? as("regexp", S.token.value[0], S.token.value[1]) 1236 | : as(S.token.type, S.token.value); 1237 | return subscripts(prog1(atom, next), allow_calls); 1238 | } 1239 | unexpected(); 1240 | }); 1241 | 1242 | function expr_list(closing, allow_trailing_comma, allow_empty) { 1243 | var first = true, 1244 | a = []; 1245 | while (!is("punc", closing) && !is("eof")) { 1246 | if (first) first = false; 1247 | else expect(","); 1248 | if (allow_trailing_comma && is("punc", closing)) break; 1249 | if (is("punc", ",") && allow_empty) { 1250 | a.push(["atom", "undefined"]); 1251 | } 1252 | else { 1253 | a.push(expression(false)); 1254 | } 1255 | } 1256 | next(); 1257 | return a; 1258 | } 1259 | 1260 | function array_() { 1261 | return as("array", expr_list("]", !exigent_mode, true)); 1262 | } 1263 | 1264 | function object_() { 1265 | var first = true, 1266 | a = []; 1267 | while (!is("punc", "}") && !is("eof")) { 1268 | if (first) first = false; 1269 | else expect(","); 1270 | if (!exigent_mode && is("punc", "}")) 1271 | // allow trailing comma 1272 | break; 1273 | var type = S.token.type; 1274 | 1275 | var name = as_property_name(); 1276 | if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) { 1277 | a.push([as_name(), function_(false), name]); 1278 | } 1279 | else { 1280 | expect(":"); 1281 | a.push([name, expression(false)]); 1282 | } 1283 | } 1284 | next(); 1285 | return as("object", a); 1286 | } 1287 | 1288 | function as_property_name() { 1289 | switch (S.token.type) { 1290 | case "num": 1291 | case "string": 1292 | return prog1(S.token.value, next); 1293 | } 1294 | return as_name(); 1295 | }; 1296 | 1297 | function as_name() { 1298 | switch (S.token.type) { 1299 | case "name": 1300 | case "operator": 1301 | case "keyword": 1302 | case "atom": 1303 | return prog1(S.token.value, next); 1304 | default: 1305 | //unexpected(); 1306 | // RECOVER: Return empty token 1307 | register_error("Name, operator, keyword or atom expected."); 1308 | return ""; // prog1("", next); 1309 | } 1310 | }; 1311 | 1312 | function insert_token(ast, prev) { 1313 | ast[0] = add_tokens(ast[0], prev, S.token); 1314 | return ast; 1315 | } 1316 | 1317 | function subscripts(expr, allow_calls) { 1318 | var p = S.prev; 1319 | if(!(expr[0] instanceof NodeWithToken)) { 1320 | expr = insert_token(expr, p); 1321 | } 1322 | if (is("punc", ".")) { 1323 | next(); 1324 | return subscripts(insert_token(as("dot", expr, as_name()), p), allow_calls); 1325 | } 1326 | if (is("punc", "[")) { 1327 | next(); 1328 | return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); 1329 | } 1330 | if (allow_calls && is("punc", "(")) { 1331 | next(); 1332 | return subscripts(as("call", expr, expr_list(")")), true); 1333 | } 1334 | return expr; 1335 | } 1336 | 1337 | function maybe_unary(allow_calls) { 1338 | if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) { 1339 | return make_unary("unary-prefix", prog1(S.token.value, next), maybe_unary(allow_calls)); 1340 | } 1341 | var val = expr_atom(allow_calls); 1342 | while (is("operator") && HOP(UNARY_POSTFIX, S.token.value) && !S.token.nlb) { 1343 | val = make_unary("unary-postfix", S.token.value, val); 1344 | next(); 1345 | } 1346 | return val; 1347 | } 1348 | 1349 | function make_unary(tag, op, expr) { 1350 | if ((op == "++" || op == "--") && !is_assignable(expr)) croak("Invalid use of " + op + " operator"); 1351 | return as(tag, op, expr); 1352 | } 1353 | 1354 | function expr_op(left, min_prec, no_in) { 1355 | var op = is("operator") ? S.token.value : null; 1356 | if (op && op == "in" && no_in) op = null; 1357 | var prec = op != null ? PRECEDENCE[op] : null; 1358 | if (prec != null && prec > min_prec) { 1359 | next(); 1360 | var right = expr_op(maybe_unary(true), prec, no_in); 1361 | return expr_op(as("binary", op, left, right), min_prec, no_in); 1362 | } 1363 | return left; 1364 | } 1365 | 1366 | function expr_ops(no_in) { 1367 | return expr_op(maybe_unary(true), 0, no_in); 1368 | } 1369 | 1370 | function maybe_conditional(no_in) { 1371 | var expr = expr_ops(no_in); 1372 | if (is("operator", "?")) { 1373 | next(); 1374 | var yes = expression(false); 1375 | expect(":"); 1376 | return as("conditional", expr, yes, expression(false, no_in)); 1377 | } 1378 | return expr; 1379 | } 1380 | 1381 | function is_assignable(expr) { 1382 | if (!exigent_mode) return true; 1383 | switch (expr[0] + "") { 1384 | case "dot": 1385 | case "sub": 1386 | case "new": 1387 | case "call": 1388 | return true; 1389 | case "name": 1390 | return expr[1] != "this"; 1391 | } 1392 | } 1393 | 1394 | function maybe_assign(no_in) { 1395 | var left = maybe_conditional(no_in), 1396 | val = S.token.value; 1397 | if (is("operator") && HOP(ASSIGNMENT, val)) { 1398 | if (is_assignable(left)) { 1399 | next(); 1400 | return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in)); 1401 | } 1402 | croak("Invalid assignment"); 1403 | } 1404 | return left; 1405 | } 1406 | 1407 | var expression = maybe_embed_tokens(function(commas, no_in) { 1408 | if (arguments.length == 0) commas = true; 1409 | // RECOVER: prevent infinite loops 1410 | var oldLine = S.line; 1411 | var oldCol = S.col; 1412 | // 1413 | var expr = maybe_assign(no_in); 1414 | if (commas && is("punc", ",")) { 1415 | next(); 1416 | return as("seq", expr, expression(true, no_in)); 1417 | } 1418 | if(S.col === oldCol && S.line === oldLine) { 1419 | // RECOVER: Preventing infinite loops here 1420 | next(); 1421 | } 1422 | return expr; 1423 | }); 1424 | 1425 | function in_loop(cont) { 1426 | try { 1427 | ++S.in_loop; 1428 | return cont(); 1429 | } 1430 | finally { 1431 | --S.in_loop; 1432 | } 1433 | } 1434 | 1435 | return { 1436 | ast: as("toplevel", (function(a) { 1437 | while (!is("eof")) 1438 | a.push(statement()); 1439 | return a; 1440 | })([])), 1441 | error: S.error 1442 | }; 1443 | }; 1444 | 1445 | /* -----[ Utilities ]----- */ 1446 | 1447 | function curry(f) { 1448 | var args = slice(arguments, 1); 1449 | return function() { 1450 | return f.apply(this, args.concat(slice(arguments))); 1451 | }; 1452 | } 1453 | 1454 | function prog1(ret) { 1455 | if (ret instanceof Function) ret = ret(); 1456 | for (var i = 1, n = arguments.length; --n > 0; ++i) 1457 | arguments[i](); 1458 | return ret; 1459 | } 1460 | 1461 | function array_to_hash(a) { 1462 | var ret = {}; 1463 | for (var i = 0; i < a.length; ++i) 1464 | ret[a[i]] = true; 1465 | return ret; 1466 | } 1467 | 1468 | function slice(a, start) { 1469 | return Array.prototype.slice.call(a, start || 0); 1470 | } 1471 | 1472 | function characters(str) { 1473 | return str.split(""); 1474 | } 1475 | 1476 | function member(name, array) { 1477 | for (var i = array.length; --i >= 0;) 1478 | if (array[i] === name) return true; 1479 | return false; 1480 | } 1481 | 1482 | function HOP(obj, prop) { 1483 | return Object.prototype.hasOwnProperty.call(obj, prop); 1484 | } 1485 | 1486 | var warn = function() {}; 1487 | 1488 | /* -----[ Exports ]----- */ 1489 | 1490 | exports.tokenizer = tokenizer; 1491 | exports.parse = parse; 1492 | exports.slice = slice; 1493 | exports.curry = curry; 1494 | exports.member = member; 1495 | exports.array_to_hash = array_to_hash; 1496 | exports.PRECEDENCE = PRECEDENCE; 1497 | exports.KEYWORDS_ATOM = KEYWORDS_ATOM; 1498 | exports.RESERVED_WORDS = RESERVED_WORDS; 1499 | exports.KEYWORDS = KEYWORDS; 1500 | exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; 1501 | exports.OPERATORS = OPERATORS; 1502 | exports.is_alphanumeric_char = is_alphanumeric_char; 1503 | exports.set_logger = function(logger) { 1504 | warn = logger; 1505 | }; 1506 | 1507 | }); 1508 | -------------------------------------------------------------------------------- /lib/treehugger/js/values.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var tree = require('treehugger/tree'); 4 | 5 | var valueRegistry = {}; 6 | window.valueRegistry = valueRegistry; 7 | 8 | function Value() { 9 | this.init(); 10 | } 11 | 12 | Value.prototype.init = function() { 13 | this.fields = {}; 14 | this.doc = null; 15 | this.guid = null; 16 | }; 17 | 18 | Value.prototype.get = function(name) { 19 | var rv; 20 | if (this.fields['_' + name]) { 21 | rv = this.fields['_' + name]; 22 | } 23 | else { 24 | rv = []; 25 | } 26 | if (name !== '__proto__') { 27 | this.get('__proto__').forEach(function(p) { 28 | rv = rv.concat(p.get(name)); 29 | }); 30 | } 31 | // Dereference values, if necessary 32 | for (var i = 0; i < rv.length; i++) 33 | if(typeof rv[i] === 'string') 34 | rv[i] = valueRegistry[rv[i]]; 35 | 36 | return rv; 37 | }; 38 | 39 | Value.prototype.toJSON = function() { 40 | return { 41 | guid: this.guid, 42 | doc: this.doc, 43 | properties: this.fields 44 | }; 45 | }; 46 | 47 | Value.prototype.fieldHint = function(name, v) { 48 | if (!this.fields['_' + name]) { 49 | this.fields['_' + name] = [v]; 50 | } 51 | else { 52 | this.fields['_' + name].push(v); 53 | } 54 | }; 55 | 56 | function FunctionValue(node, returnValue) { 57 | this.init(); 58 | this.node = node; 59 | this.returnValue = returnValue; 60 | } 61 | 62 | FunctionValue.prototype = new Value(); 63 | 64 | FunctionValue.prototype.getFargs = function() { 65 | return this.node ? this.node[1] : []; 66 | }; 67 | 68 | FunctionValue.prototype.getBody = function() { 69 | return this.node ? this.node[2] : tree.cons('None', []); 70 | }; 71 | 72 | FunctionValue.prototype.toJSON = function() { 73 | var json = Value.prototype.toJSON.call(this); 74 | json.returnValue = this.returnValue; 75 | return json; 76 | }; 77 | 78 | function instantiate(fn, initVal) { 79 | var value = initVal || new Value(); 80 | fn.get('prototype').forEach(function(p) { 81 | value.fieldHint('__proto__', p); 82 | }); 83 | value.fieldHint('constructor', fn); 84 | return value; 85 | } 86 | 87 | function lookupValue(guid) { 88 | return valueRegistry[guid]; 89 | } 90 | 91 | function fromJSON(json) { 92 | if(typeof json === "string") 93 | return json; 94 | 95 | var value = json.returnValue !== undefined ? new FunctionValue(json.node, json.returnValue) : new Value(); 96 | 97 | var properties = json.properties || {}; 98 | for(var p in properties) { 99 | properties[p].forEach(function(v) { 100 | value.fieldHint(p.substr(1), fromJSON(v)); 101 | }); 102 | } 103 | 104 | if(json.guid) { 105 | valueRegistry[json.guid] = value; 106 | } 107 | value.guid = json.guid; 108 | value.doc = json.doc; 109 | 110 | return value; 111 | } 112 | 113 | exports.Value = Value; 114 | exports.FunctionValue = FunctionValue; 115 | exports.instantiate = instantiate; 116 | exports.fromJSON = fromJSON; 117 | exports.lookupValue = lookupValue; 118 | 119 | }); -------------------------------------------------------------------------------- /lib/treehugger/rewrite.disabled.js: -------------------------------------------------------------------------------- 1 | // DEPRECATED, OUT OF DATE 2 | define(function(require, exports, module) { 3 | 4 | var ast = require('ast'), 5 | Node = ast.Node; 6 | 7 | if (!Function.prototype.curry) { 8 | Function.prototype.curry = function () { 9 | var fn = this, args = Array.prototype.slice.call(arguments); 10 | return function () { 11 | return fn.apply(this, args.concat(Array.prototype.slice.call(arguments))); 12 | }; 13 | }; 14 | } 15 | 16 | function normalizeArgs(args) { 17 | if(args.length === 1 && args[0].apply) { // basic, one function, shortcut! 18 | return args[0]; 19 | } 20 | args = Array.prototype.slice.call(args, 0); 21 | if(args[0] && Object.prototype.toString.call(args[0]) === '[object Array]') { 22 | args = args[0]; 23 | } 24 | return function() { 25 | var result; 26 | for(var i = 0; i < args.length; i++) { 27 | if(typeof args[i] === 'string') { 28 | var parsedPattern = ast.parse(args[i]); 29 | var bindings = parsedPattern.match(this); 30 | if(bindings) { 31 | if(args[i+1] && args[i+1].apply) { 32 | result = args[i+1].call(this, bindings); 33 | i++; 34 | } else { 35 | result = this; 36 | } 37 | if(result) { 38 | return result; 39 | } 40 | } else if(args[i+1] && args[i+1].apply) { 41 | i++; 42 | } 43 | } else if(args[i].apply) { 44 | result = args[i].call(this); 45 | if(result) { 46 | return result; 47 | } 48 | } else { 49 | throw Error("Invalid argument: ", args[i]); 50 | } 51 | } 52 | return false; 53 | }; 54 | } 55 | 56 | exports.all = function(fn) { 57 | var newChildren, result, i; 58 | fn = normalizeArgs(arguments); 59 | if(this instanceof ast.ConsNode) { 60 | newChildren = []; 61 | for (i = 0; i < this.length; i++) { 62 | result = fn.call(this[i]); 63 | if (result) { 64 | newChildren.push(result); 65 | } else { 66 | return false; 67 | } 68 | } 69 | return ast.cons(this.cons, newChildren); 70 | } else if(this instanceof ast.ListNode) { 71 | newChildren = []; 72 | for (i = 0; i < this.length; i++) { 73 | result = fn.call(this[i]); 74 | if (result) { 75 | newChildren.push(result); 76 | } else { 77 | return false; 78 | } 79 | } 80 | return ast.list(newChildren); 81 | } else { 82 | return this; 83 | } 84 | }; 85 | 86 | exports.one = function(fn) { 87 | var newChildren, result, i, oneSucceeded; 88 | fn = normalizeArgs(arguments); 89 | 90 | if(this instanceof ast.ConsNode) { 91 | newChildren = []; 92 | oneSucceeded = false; 93 | for (i = 0; i < this.length; i++) { 94 | result = fn.call(this[i]); 95 | if (result) { 96 | newChildren.push(result); 97 | oneSucceeded = true; 98 | } else { 99 | newChildren.push(this[i]); 100 | } 101 | } 102 | if (oneSucceeded) { 103 | return ast.cons(this.cons, newChildren); 104 | } else { 105 | return false; 106 | } 107 | } else if(this instanceof ast.ListNode) { 108 | newChildren = []; 109 | oneSucceeded = false; 110 | for (i = 0; i < this.length; i++) { 111 | result = fn.call(this[i]); 112 | if (result) { 113 | newChildren.push(result); 114 | oneSucceeded = true; 115 | } else { 116 | newChildren.push(this[i]); 117 | } 118 | } 119 | if (oneSucceeded) { 120 | return ast.list(this.cons, newChildren); 121 | } else { 122 | return false; 123 | } 124 | } else { 125 | return this; 126 | } 127 | }; 128 | 129 | /** 130 | * Sequential application last argument is term 131 | */ 132 | exports.seq = function() { 133 | var fn; 134 | var t = this; 135 | for ( var i = 0; i < arguments.length; i++) { 136 | fn = arguments[i]; 137 | t = fn.call(t); 138 | if (!t) { 139 | return false; 140 | } 141 | } 142 | return this; 143 | }; 144 | 145 | /** 146 | * Left-choice (<+) application 147 | */ 148 | exports.leftChoice = function() { 149 | var t = this; 150 | var fn, result; 151 | for ( var i = 0; i < arguments.length; i++) { 152 | fn = arguments[i]; 153 | result = fn.call(t); 154 | if (result) { 155 | return result; 156 | } 157 | } 158 | return false; 159 | }; 160 | 161 | // Try 162 | exports.attempt = function(fn) { 163 | fn = normalizeArgs(arguments); 164 | var result = fn.call(this); 165 | return !result ? this : result; 166 | }; 167 | 168 | exports.debug = function(pretty) { 169 | console.log(pretty ? this.toPrettyString("") : this.toString()); 170 | return this; 171 | }; 172 | 173 | exports.map = function(fn) { 174 | fn = normalizeArgs(arguments); 175 | return this.all(fn); 176 | }; 177 | 178 | // fn return boolean 179 | exports.filter = function(fn) { 180 | var matching = []; 181 | fn = normalizeArgs(arguments); 182 | this.forEach(function(el) { 183 | var result = fn.call(el); 184 | if(result) { 185 | matching.push(result); 186 | } 187 | }); 188 | return ast.list(matching); 189 | }; 190 | 191 | exports.alltd = function(fn) { 192 | fn = normalizeArgs(arguments); 193 | return this.leftChoice(fn, exports.all.curry(exports.alltd.curry(fn))); 194 | }; 195 | 196 | exports.topdown = function(fn) { 197 | fn = normalizeArgs(arguments); 198 | return this.seq(fn, exports.all.curry(exports.topdown.curry(fn))); 199 | }; 200 | 201 | exports.bottomup = function(fn) { 202 | fn = normalizeArgs(arguments); 203 | return this.seq(exports.all.curry(exports.bottomup.curry(fn)), fn); 204 | }; 205 | 206 | exports.innermost = function(fn) { 207 | fn = normalizeArgs(arguments); 208 | return this.bottomup(exports.attempt.curry(exports.seq.curry(fn, exports.innermost.curry(fn)))); 209 | }; 210 | 211 | exports.collect = function(fn) { 212 | fn = normalizeArgs(arguments); 213 | var results = []; 214 | this.alltd(function() { 215 | var r = fn.call(this); 216 | if(r) { 217 | results.push(r); 218 | } 219 | return r; 220 | }); 221 | return ast.list(results); 222 | }; 223 | 224 | exports.apply = function(fn) { 225 | fn = normalizeArgs(arguments); 226 | return fn.call(this); 227 | }; 228 | 229 | exports.removeDuplicates = function() { 230 | var newList = []; 231 | lbl: for(var i = 0; i < this.length; i++) { 232 | for(var j = 0; j < newList.length; j++) { 233 | if(newList[j].match(this[i])) { 234 | continue lbl; 235 | } 236 | } 237 | newList.push(this[i]); 238 | } 239 | return new ast.list(newList); 240 | }; 241 | 242 | Node.prototype.rewrite = {}; 243 | 244 | for(var p in exports) { 245 | if(exports.hasOwnProperty(p)) { 246 | Node.prototype.rewrite[p] = exports[p]; 247 | } 248 | } 249 | 250 | }); -------------------------------------------------------------------------------- /lib/treehugger/traverse.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | var tree = require('treehugger/tree'); 4 | 5 | if (!Function.prototype.curry) { 6 | Function.prototype.curry = function() { 7 | var fn = this, 8 | args = Array.prototype.slice.call(arguments); 9 | return function() { 10 | return fn.apply(this, args.concat(Array.prototype.slice.call(arguments))); 11 | }; 12 | }; 13 | } 14 | 15 | function normalizeArgs(args) { 16 | if (args.length === 1 && args[0].apply) { // basic, one function, shortcut! 17 | return args[0]; 18 | } 19 | args = Array.prototype.slice.call(args, 0); 20 | if (args[0] && Object.prototype.toString.call(args[0]) === '[object Array]') { 21 | args = args[0]; 22 | } 23 | return function normalizeArgsHelper() { 24 | var result; 25 | for (var i = 0; i < args.length; i++) { 26 | if (typeof args[i] === 'string') { 27 | var parsedPattern = tree.parse(args[i]); 28 | var bindings = parsedPattern.match(this); 29 | if (bindings) { 30 | while (args[i + 1]) { 31 | if (args[i + 1].apply) 32 | break; 33 | i++; 34 | } 35 | if (args[i + 1] && args[i + 1].apply) { 36 | result = args[i + 1].call(this, bindings, this); 37 | i++; 38 | } 39 | else 40 | result = this; 41 | if (result) 42 | return result; 43 | } 44 | else if (args[i + 1] && args[i + 1].apply) 45 | i++; 46 | } 47 | else if (args[i].apply) { 48 | result = args[i].call(this, this); 49 | if (result) 50 | return result; 51 | } 52 | else 53 | throw Error("Invalid argument: ", args[i]); 54 | } 55 | return false; 56 | }; 57 | } 58 | 59 | exports.traverseAll = function(fn) { 60 | var result, i; 61 | fn = normalizeArgs(arguments); 62 | if (this instanceof tree.ConsNode || this instanceof tree.ListNode) { 63 | for (i = 0; i < this.length; i++) { 64 | result = fn.call(this[i]); 65 | if (!result) 66 | return false; 67 | } 68 | } 69 | return this; 70 | }; 71 | 72 | /** 73 | * Sequential application last argument is term 74 | */ 75 | function seq() { 76 | var fn; 77 | var t = this; 78 | for (var i = 0; i < arguments.length; i++) { 79 | fn = arguments[i]; 80 | t = fn.call(t); 81 | if (!t) 82 | return false; 83 | } 84 | return this; 85 | } 86 | // Try 87 | exports.attempt = function(fn) { 88 | fn = normalizeArgs(arguments); 89 | var result = fn.call(this); 90 | return !result ? this : result; 91 | }; 92 | 93 | exports.debug = function(pretty) { 94 | console.log(pretty ? this.toPrettyString("") : this.toString()); 95 | return this; 96 | }; 97 | 98 | // A somewhat optimized version of the "clean" topdown traversal 99 | function traverseTopDown(fn) { 100 | var result, i; 101 | result = fn.call(this); 102 | if(result) 103 | return result; 104 | if (this instanceof tree.ConsNode || this instanceof tree.ListNode) { 105 | for (i = 0; i < this.length; i++) { 106 | traverseTopDown.call(this[i], fn); 107 | } 108 | } 109 | return this; 110 | } 111 | 112 | exports.traverseTopDown = function(fn) { 113 | fn = normalizeArgs(arguments); 114 | return traverseTopDown.call(this, fn); 115 | //exports.rewrite.call(this, fn, exports.traverseAll.curry(exports.traverseTopDown.curry(fn))); 116 | //return this; 117 | }; 118 | 119 | /** 120 | * Traverse up the tree (using parent pointers) and return the first matching node 121 | * Doesn't only traverse parents, but also upward siblings 122 | */ 123 | exports.traverseUp = function(fn) { 124 | fn = normalizeArgs(arguments); 125 | var result = fn.call(this); 126 | if(result) 127 | return result; 128 | if (!this.parent) 129 | return false; 130 | return this.parent.traverseUp(fn); 131 | }; 132 | 133 | exports.collectTopDown = function(fn) { 134 | fn = normalizeArgs(arguments); 135 | var results = []; 136 | this.traverseTopDown(function() { 137 | var r = fn.call(this); 138 | if (r) { 139 | results.push(r); 140 | } 141 | return r; 142 | }); 143 | return tree.list(results); 144 | }; 145 | 146 | exports.map = function(fn) { 147 | fn = normalizeArgs(arguments); 148 | var result, results = []; 149 | for (var i = 0; i < this.length; i++) { 150 | result = fn.call(this[i], this[i]); 151 | if (result) { 152 | results.push(result); 153 | } 154 | else { 155 | throw Error("Mapping failed: ", this[i]); 156 | } 157 | } 158 | return tree.list(results); 159 | }; 160 | 161 | exports.each = function(fn) { 162 | fn = normalizeArgs(arguments); 163 | for (var i = 0; i < this.length; i++) { 164 | fn.call(this[i], this[i]); 165 | } 166 | }; 167 | 168 | // fn return boolean 169 | exports.filter = function(fn) { 170 | fn = normalizeArgs(arguments); 171 | var matching = []; 172 | this.forEach(function(el) { 173 | var result = fn.call(el); 174 | if (result) { 175 | matching.push(result); 176 | } 177 | }); 178 | return tree.list(matching); 179 | }; 180 | 181 | exports.rewrite = function(fn) { 182 | fn = normalizeArgs(arguments); 183 | return fn.call(this); 184 | }; 185 | 186 | exports.isMatch = function(pattern) { 187 | return !!this.rewrite(pattern); 188 | }; 189 | 190 | // Add above methods to all tree nodes 191 | for (var p in exports) { 192 | if (exports.hasOwnProperty(p)) { 193 | tree.Node.prototype[p] = exports[p]; 194 | } 195 | } 196 | 197 | exports.addParentPointers = function(node) { 198 | return node.traverseTopDown(function() { 199 | var that = this; 200 | this.traverseAll(function() { 201 | this.parent = that; 202 | return this; 203 | }); 204 | }); 205 | }; 206 | 207 | }); -------------------------------------------------------------------------------- /lib/treehugger/tree.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | function inRange(p, pos, exclusive) { 4 | if(p && p.sl <= pos.line && pos.line <= p.el) { 5 | if(p.sl < pos.line && pos.line < p.el) 6 | return true; 7 | else if(p.sl == pos.line && pos.line < p.el) 8 | return p.sc <= pos.col; 9 | else if(p.sl == pos.line && p.el === pos.line) 10 | return p.sc <= pos.col && pos.col <= p.ec + (exclusive ? 1 : 0); 11 | else if(p.sl < pos.line && p.el === pos.line) 12 | return pos.col <= p.ec + (exclusive ? 1 : 0); 13 | } 14 | } 15 | 16 | /** 17 | * Base 'class' of every tree node 18 | */ 19 | function Node() { 20 | } 21 | 22 | Node.prototype.toPrettyString = function(prefix) { 23 | prefix = prefix || ""; 24 | return prefix + this.toString(); 25 | }; 26 | 27 | Node.prototype.setAnnotation = function(name, value) { 28 | this.annos = this.annos || {}; 29 | this.annos[name] = value; 30 | }; 31 | 32 | Node.prototype.getAnnotation = function(name) { 33 | return this.annos ? this.annos[name] : undefined; 34 | }; 35 | 36 | Node.prototype.$pos = null; 37 | Node.prototype.getPos = function() { 38 | if(this.annos && this.annos.pos) { 39 | return this.annos.pos; 40 | } else { 41 | var p = this.$pos; 42 | return p && { 43 | sl : p.start.line, sc : p.start.column, 44 | el : p.end.line, ec : p.end.column 45 | }; 46 | } 47 | }; 48 | 49 | Node.prototype.findNode = function(pos) { 50 | var p = this.getPos(); 51 | if(inRange(p, pos)) { 52 | return this; 53 | } else { 54 | return null; 55 | } 56 | }; 57 | 58 | /** 59 | * Represents a constructor node 60 | * 61 | * Example: Add(Num("1"), Num("2")) is constucted 62 | * using new ConsNode(new ConsNode("Num", [new StringNode("1")]), 63 | * new ConsNode("Num", [new StringNode("2")])) 64 | * or, more briefly: 65 | * tree.cons("Add", [tree.cons("Num", [ast.string("1"), ast.string("2")])]) 66 | */ 67 | function ConsNode(cons, children) { 68 | this.cons = cons; 69 | for(var i = 0; i < children.length; i++) { 70 | this[i] = children[i]; 71 | } 72 | this.length = children.length; 73 | } 74 | 75 | ConsNode.prototype = new Node(); 76 | 77 | /** 78 | * Simple human-readable string representation (no indentation) 79 | */ 80 | ConsNode.prototype.toString = function(prefix) { 81 | try { 82 | var s = this.cons + "("; 83 | for ( var i = 0; i < this.length; i++) { 84 | s += this[i].toString() + ","; 85 | } 86 | if (this.length > 0) { 87 | s = s.substring(0, s.length - 1); 88 | } 89 | return s + ")"; 90 | } catch(e) { 91 | console.error("Something went wrong: ", this, e); 92 | } 93 | }; 94 | 95 | /** 96 | * Human-readable string representation (indentented) 97 | * @param prefix is for internal use 98 | */ 99 | ConsNode.prototype.toPrettyString = function(prefix) { 100 | prefix = prefix || ""; 101 | try { 102 | if(this.length === 0) { 103 | return prefix + this.cons + "()"; 104 | } 105 | if(this.length === 1 && (this[0] instanceof StringNode || this[0] instanceof NumNode)) { 106 | return prefix + this.cons + "(" + this[0].toString() + ")"; 107 | } 108 | var s = prefix + this.cons + "(\n"; 109 | for ( var i = 0; i < this.length; i++) { 110 | s += this[i].toPrettyString(prefix + " ") + ",\n"; 111 | } 112 | s = s.substring(0, s.length - 2); 113 | s += "\n"; 114 | return s + prefix + ")"; 115 | } catch(e) { 116 | console.error("Something went wrong: ", this, e); 117 | } 118 | }; 119 | 120 | /** 121 | * Matches the current term against `t`, writing matching placeholder values to `matches` 122 | * @param t the node to match against 123 | * @param matches the object to write placeholder values to 124 | * @returns the `matches` object if it matches, false otherwise 125 | */ 126 | ConsNode.prototype.match = function(t, matches) { 127 | matches = matches || {}; 128 | if (t instanceof ConsNode) { 129 | if (this.cons === t.cons) { 130 | if (this.length === t.length) { 131 | for ( var i = 0; i < this.length; i++) { 132 | if (!this[i].match(t[i], matches)) { 133 | return false; 134 | } 135 | } 136 | return matches; 137 | } 138 | } 139 | } 140 | return false; 141 | }; 142 | 143 | /** 144 | * Builds a node, based on values (similar to `matches` object), 145 | * replacing placeholder nodes with values from `values` 146 | * @returns resulting cons node 147 | */ 148 | ConsNode.prototype.build = function(values) { 149 | var children = []; 150 | for ( var i = 0; i < this.length; i++) { 151 | children.push(this[i].build(values)); 152 | } 153 | return new ConsNode(this.cons, children); 154 | }; 155 | 156 | /** 157 | * Prettier JSON representation of constructor node. 158 | */ 159 | ConsNode.prototype.toJSON = function() { 160 | var l = []; 161 | for(var i = 0; i < this.length; i++) { 162 | l.push(this[i]); 163 | } 164 | return {cons: this.cons, children: l}; 165 | }; 166 | 167 | ConsNode.prototype.getPos = function() { 168 | // Below is a pretty horribly convoluted way of getting a position, 169 | // which initially was to help reliability of uglify positions, 170 | // and now still helps with missing positions while maintaining 171 | // backward compatibility. 172 | if (this.$pos && this.$pos.start && this.$pos.end) { 173 | var p = this.$pos; 174 | return {sl : p.start.line, sc : p.start.column, 175 | el : p.end.line, ec : p.end.column}; 176 | } 177 | 178 | var nodePos = this.getAnnotation("pos"); 179 | var result = nodePos 180 | ? {sl : nodePos.sl, sc : nodePos.sc, el : nodePos.el, ec : nodePos.ec} 181 | : {sl : Number.MAX_VALUE, sc : Number.MAX_VALUE, el : 0, ec : 0}; 182 | 183 | var hasSl = false; 184 | var hasSc = false; 185 | 186 | for (var i = 0; i < this.length; i++) { 187 | var p = this[i].getPos(); 188 | 189 | if (p) { 190 | if (p.sl < Number.MAX_VALUE && !hasSl) { 191 | result.sl = p.sl; 192 | hasSl = true; 193 | } 194 | if (p.sc < Number.MAX_VALUE && !hasSc) { 195 | result.sc = p.sc; 196 | hasSc = true; 197 | } 198 | result.el = p.el || result.el; 199 | result.ec = p.ec || result.ec; 200 | } 201 | } 202 | 203 | return result; 204 | }; 205 | 206 | ConsNode.prototype.$pos = null; 207 | 208 | ConsNode.prototype.findNode = function(pos) { 209 | var p = this.getPos(); 210 | 211 | if(inRange(p, pos)) { 212 | for(var i = 0; i < this.length; i++) { 213 | var p2 = this[i].getPos(); 214 | if(inRange(p2, pos)) { 215 | var node = this[i].findNode(pos); 216 | if(node) 217 | return node instanceof StringNode ? this : node; 218 | else 219 | return this[i]; 220 | } 221 | } 222 | } else { 223 | return null; 224 | } 225 | }; 226 | 227 | /** 228 | * Constructor node factory. 229 | */ 230 | exports.cons = function(name, children) { 231 | return new ConsNode(name, children); 232 | }; 233 | 234 | /** 235 | * AST node representing a list 236 | * e.g. for constructors with variable number of arguments, e.g. in 237 | * Call(Var("alert"), [Num("10"), Num("11")]) 238 | * 239 | */ 240 | function ListNode (children) { 241 | for(var i = 0; i < children.length; i++) 242 | this[i] = children[i]; 243 | this.length = children.length; 244 | } 245 | 246 | ListNode.prototype = new Node(); 247 | 248 | ListNode.prototype.toString = function() { 249 | var s = "["; 250 | for (var i = 0; i < this.length; i++) 251 | s += this[i].toString() + ","; 252 | if (this.length > 0) 253 | s = s.substring(0, s.length - 1); 254 | return s + "]"; 255 | }; 256 | 257 | ListNode.prototype.toPrettyString = function(prefix) { 258 | prefix = prefix || ""; 259 | try { 260 | if(this.length === 0) 261 | return prefix + "[]"; 262 | var s = prefix + "[\n"; 263 | for ( var i = 0; i < this.length; i++) 264 | s += this[i].toPrettyString(prefix + " ") + ",\n"; 265 | s = s.substring(0, s.length - 2); 266 | s += "\n"; 267 | return s + prefix + "]"; 268 | } catch(e) { 269 | console.error("Something went wrong: ", this); 270 | } 271 | }; 272 | 273 | ListNode.prototype.match = function(t, matches) { 274 | matches = matches || {}; 275 | if (t instanceof ListNode) { 276 | if (this.length === t.length) { 277 | for ( var i = 0; i < this.length; i++) 278 | if (!this[i].match(t[i], matches)) 279 | return false; 280 | return matches; 281 | } 282 | else 283 | return false; 284 | } 285 | else 286 | return false; 287 | }; 288 | 289 | ListNode.prototype.build = function(values) { 290 | var children = []; 291 | for (var i = 0; i < this.length; i++) 292 | children.push(this[i].build(values)); 293 | return new ListNode(children); 294 | }; 295 | 296 | ListNode.prototype.getPos = ConsNode.prototype.getPos; 297 | ListNode.prototype.findNode = ConsNode.prototype.findNode; 298 | 299 | /** 300 | * forEach implementation, similar to Array.prototype.forEach 301 | */ 302 | ListNode.prototype.forEach = function(fn) { 303 | for(var i = 0; i < this.length; i++) { 304 | fn.call(this[i], this[i], i); 305 | } 306 | }; 307 | 308 | /** 309 | * Whether the list is empty (0 elements) 310 | */ 311 | ListNode.prototype.isEmpty = function() { 312 | return this.length === 0; 313 | }; 314 | 315 | /** 316 | * Performs linear search, performing a match 317 | * with each element in the list 318 | * @param el the element to search for 319 | * @returns true if found, false if not 320 | */ 321 | ListNode.prototype.contains = function(el) { 322 | for(var i = 0; i < this.length; i++) 323 | if(el.match(this[i])) 324 | return true; 325 | return false; 326 | }; 327 | 328 | /** 329 | * Concatenates list with another list, similar to Array.prototype.concat 330 | */ 331 | ListNode.prototype.concat = function(l) { 332 | var ar = []; 333 | for(var i = 0; i < this.length; i++) 334 | ar.push(this[i]); 335 | for(i = 0; i < l.length; i++) 336 | ar.push(l[i]); 337 | return exports.list(ar); 338 | }; 339 | 340 | ListNode.prototype.toJSON = function() { 341 | var l = []; 342 | for(var i = 0; i < this.length; i++) 343 | l.push(this[i]); 344 | return l; 345 | }; 346 | 347 | /** 348 | * Returns a new list node, with all duplicates removed 349 | * Note: cubic complexity algorithm used 350 | */ 351 | ListNode.prototype.removeDuplicates = function() { 352 | var newList = []; 353 | lbl: for(var i = 0; i < this.length; i++) { 354 | for(var j = 0; j < newList.length; j++) 355 | if(newList[j].match(this[i])) 356 | continue lbl; 357 | newList.push(this[i]); 358 | } 359 | return new exports.list(newList); 360 | }; 361 | 362 | ListNode.prototype.toArray = ListNode.prototype.toJSON; 363 | 364 | /** 365 | * ListNode factory 366 | */ 367 | exports.list = function(elements) { 368 | return new ListNode(elements); 369 | }; 370 | 371 | function NumNode (value) { 372 | this.value = value; 373 | } 374 | 375 | NumNode.prototype = new Node(); 376 | 377 | NumNode.prototype.toString = function() { 378 | return ""+this.value; 379 | }; 380 | 381 | NumNode.prototype.match = function(t, matches) { 382 | matches = matches || {}; 383 | if (t instanceof NumNode) 384 | return this.value === t.value ? matches : false; 385 | else 386 | return false; 387 | }; 388 | 389 | NumNode.prototype.build = function(values) { 390 | return this; 391 | }; 392 | 393 | exports.num = function(value) { 394 | return new NumNode(value); 395 | }; 396 | 397 | function StringNode (value) { 398 | this.value = value; 399 | } 400 | 401 | StringNode.prototype = new Node(); 402 | 403 | StringNode.prototype.toString = function() { 404 | return '"' + this.value + '"'; 405 | }; 406 | 407 | StringNode.prototype.match = function(t, matches) { 408 | matches = matches || {}; 409 | if (t instanceof StringNode) 410 | return this.value === t.value ? matches : false; 411 | else 412 | return false; 413 | }; 414 | 415 | StringNode.prototype.build = function(values) { 416 | return this; 417 | }; 418 | 419 | exports.string = function(value) { 420 | return new StringNode(value); 421 | }; 422 | 423 | function PlaceholderNode(id) { 424 | this.id = id; 425 | } 426 | 427 | PlaceholderNode.prototype = new Node(); 428 | 429 | PlaceholderNode.prototype.toString = function() { 430 | return this.id; 431 | }; 432 | 433 | PlaceholderNode.prototype.match = function(t, matches) { 434 | matches = matches || {}; 435 | if(this.id === '_') 436 | return matches; 437 | if(matches[this.id]) // already bound 438 | return matches[this.id].match(t); 439 | else { 440 | matches[this.id] = t; 441 | return matches; 442 | } 443 | }; 444 | 445 | PlaceholderNode.prototype.build = function(values) { 446 | return values[this.id]; 447 | }; 448 | 449 | exports.placeholder = function(n) { 450 | return new PlaceholderNode(n); 451 | }; 452 | 453 | 454 | function parse (s) { 455 | var idx = 0; 456 | function accept (str) { 457 | for ( var i = 0; i < str.length && idx + i < s.length; i++) { 458 | if (str[i] != s[idx + i]) { 459 | return false; 460 | } 461 | } 462 | return i == str.length; 463 | } 464 | function lookAheadLetter() { 465 | return s[idx] >= 'a' && s[idx] <= 'z' || s[idx] >= 'A' && s[idx] <= 'Z' || s[idx] === '_' || s[idx] >= '0' && s[idx] <= '9'; 466 | } 467 | function skipWhitespace () { 468 | while (idx < s.length && (s[idx] === " " || s[idx] === "\n" || s[idx] === "\r" || s[idx] === "\t")) { 469 | idx++; 470 | } 471 | } 472 | function parseInt () { 473 | var pos = idx; 474 | if (s[idx] >= '0' && s[idx] <= '9') { 475 | var ns = s[idx]; 476 | idx++; 477 | while (idx < s.length && s[idx] >= '0' && s[idx] <= '9') { 478 | ns += s[idx]; 479 | idx++; 480 | } 481 | skipWhitespace(); 482 | return new NumNode(+ns, pos); 483 | } else { 484 | return null; 485 | } 486 | } 487 | function parseString () { 488 | var pos = idx; 489 | if (accept('"')) { 490 | var ns = ""; 491 | idx++; 492 | while (!accept('"') || (accept('"') && s[idx - 1] == '\\')) { 493 | ns += s[idx]; 494 | idx++; 495 | } 496 | var ns2 = ''; 497 | for ( var i = 0; i < ns.length; i++) { 498 | if (ns[i] == "\\") { 499 | i++; 500 | switch (ns[i]) { 501 | case 'n': 502 | ns2 += "\n"; 503 | break; 504 | case 't': 505 | ns2 += "\t"; 506 | break; 507 | default: 508 | ns2 += ns[i]; 509 | } 510 | } else { 511 | ns2 += ns[i]; 512 | } 513 | } 514 | idx++; 515 | skipWhitespace(); 516 | return new StringNode(ns2, pos); 517 | } else { 518 | return null; 519 | } 520 | } 521 | function parsePlaceholder() { 522 | var pos = idx; 523 | if (lookAheadLetter() && s[idx].toLowerCase() === s[idx]) { 524 | var ns = ""; 525 | while (lookAheadLetter() && idx < s.length) { 526 | ns += s[idx]; 527 | idx++; 528 | } 529 | skipWhitespace(); 530 | return new PlaceholderNode(ns, pos); 531 | } 532 | else { 533 | return null; 534 | } 535 | } 536 | function parseList() { 537 | var pos = idx; 538 | if (accept('[')) { 539 | var items = []; 540 | idx++; 541 | skipWhitespace(); 542 | while (!accept(']') && idx < s.length) { 543 | items.push(parseExp()); 544 | if (accept(',')) { 545 | idx++; // skip comma 546 | skipWhitespace(); 547 | } 548 | } 549 | idx++; 550 | skipWhitespace(); 551 | return new ListNode(items, pos); 552 | } 553 | else { 554 | return null; 555 | } 556 | } 557 | function parseCons () { 558 | var pos = idx; 559 | // assumption: it's an appl 560 | var ns = ""; 561 | while (!accept('(')) { 562 | ns += s[idx]; 563 | idx++; 564 | } 565 | idx++; // skip ( 566 | var items = []; 567 | while (!accept(')') && idx < s.length) { 568 | items.push(parseExp()); 569 | if (accept(',')) { 570 | idx++; // skip comma 571 | skipWhitespace(); 572 | } 573 | } 574 | idx++; 575 | skipWhitespace(); 576 | return new ConsNode(ns, items, pos); 577 | } 578 | 579 | function parseExp() { 580 | var r = parseInt(); 581 | if (r) return r; 582 | r = parseString(); 583 | if (r) return r; 584 | r = parseList(); 585 | if (r) return r; 586 | r = parsePlaceholder(); 587 | if (r) return r; 588 | return parseCons(); 589 | } 590 | return parseExp(); 591 | } 592 | 593 | var parseCache = {}; 594 | function parseCached (s) { 595 | if(typeof s !== 'string') { 596 | return null; 597 | } 598 | if(s.length > 200) { 599 | return parse(); 600 | } 601 | return parseCache[s] || (parseCache[s] = parse(s)); 602 | } 603 | 604 | exports.Node = Node; 605 | exports.ConsNode = ConsNode; 606 | exports.ListNode = ListNode; 607 | exports.NumNode = NumNode; 608 | exports.StringNode = StringNode; 609 | exports.PlaceholderNode = PlaceholderNode; 610 | exports.parse = parseCached; 611 | exports.inRange = inRange; 612 | 613 | }); 614 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "treehugger", 3 | "version" : "0.0.2", 4 | "description" : "treehugger is a Javascript library for program processing", 5 | "author": "ajax.org B.V. ", 6 | "license": "MIT", 7 | "contributors": [ 8 | { "name": "Zef Hemel", "email": "zef@c9.io" }, 9 | { "name": "Lennart Kats", "email": "lennart@c9.io" }, 10 | { "name": "Harutyun Amirjanyan", "email": "harutyun@c9.io" } 11 | ], 12 | "repository" : { 13 | "type" : "git", 14 | "url" : "http://github.com/ajaxorg/treehugger.git" 15 | }, 16 | "engines" : { 17 | "node" : ">= 0.4.0" 18 | }, 19 | "scripts": { 20 | "test": "node lib/treehugger/js/parse_test.js", 21 | "acorn": "node ../acorn/bin/build-acorn.js ; mv ../acorn/dist/* lib/acorn/dist" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | treehugger.js demo 5 | 6 | 18 | 19 | 20 |

Treehugger.js playground

21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 51 | 52 | 53 |
JavascriptAST
Analysis code Output
54 | 55 | 56 | --------------------------------------------------------------------------------