Created
October 18, 2012 13:53
-
-
Save rvanvelzen/3911967 to your computer and use it in GitHub Desktop.
https://github.com/marijnh/acorn ported to php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Acorn was written by Marijn Haverbeke and released under an MIT | |
// license. The Unicode regexps (for identifiers and whitespace) were | |
// taken from [Esprima](http://esprima.org) by Ariya Hidayat. | |
// | |
// This port was done by Richard van Velzen | |
namespace JavaScript; | |
class Exception extends \Exception { } | |
class Parser { | |
const VERSION = '0.0.1'; | |
const NON_ASCII_WHITESPACE = '~[\x{1680}\x{180e}\x{2000}-\x{200a}\x{202f}\x{205f}\x{3000}\x{feff}]~u'; | |
const NON_ASCII_IDENTIFIER_START = '~[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\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\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\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-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\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-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\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-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\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]~u'; | |
const NON_ASCII_IDENTIFIER = '~[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\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\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\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-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\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-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\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-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\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\x{0371}-\x{0374}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{0620}-\x{0649}\x{0672}-\x{06d3}\x{06e7}-\x{06e8}\x{06fb}-\x{06fc}\x{0730}-\x{074a}\x{0800}-\x{0814}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0840}-\x{0857}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09d7}\x{09df}-\x{09e0}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}-\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b5f}-\x{0b60}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}-\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}-\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d46}-\x{0d48}\x{0d57}\x{0d62}-\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e34}-\x{0e3a}\x{0e40}-\x{0e45}\x{0e50}-\x{0e59}\x{0eb4}-\x{0eb9}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f41}-\x{0f47}\x{0f71}-\x{0f84}\x{0f86}-\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{1000}-\x{1029}\x{1040}-\x{1049}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{170e}-\x{1710}\x{1720}-\x{1730}\x{1740}-\x{1750}\x{1772}\x{1773}\x{1780}-\x{17b2}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1951}-\x{196d}\x{19b0}-\x{19c0}\x{19c8}-\x{19c9}\x{19d0}-\x{19d9}\x{1a00}-\x{1a15}\x{1a20}-\x{1a53}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b46}-\x{1b4b}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c00}-\x{1c22}\x{1c40}-\x{1c49}\x{1c5b}-\x{1c7d}\x{1cd0}-\x{1cd2}\x{1d00}-\x{1dbe}\x{1e01}-\x{1f15}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2d81}-\x{2d96}\x{2de0}-\x{2dff}\x{3021}-\x{3028}\x{3099}\x{309a}\x{a640}-\x{a66d}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}-\x{a6f1}\x{a7f8}-\x{a800}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}-\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8f3}-\x{a8f7}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a930}-\x{a945}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{aa00}-\x{aa27}\x{aa40}-\x{aa41}\x{aa4c}-\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aae0}-\x{aae9}\x{aaf2}-\x{aaf3}\x{abc0}-\x{abe1}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb20}-\x{fb28}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]~u'; | |
const NEWLINE = '~[\r\n\x{2028}\x{2029}]~u'; | |
const LINEBREAK = '~\r\n|[\n\r\x{2028}\x{2029}]~u'; | |
protected $options = array(); | |
protected $input = ''; | |
protected $inputLen = 0; | |
protected $sourceFile; | |
protected static $defaultOptions = array( | |
// `ecmaVersion` indicates the ECMAScript version to parse. Must | |
// be either 3 or 5. This | |
// influences support for strict mode, the set of reserved words, and | |
// support for getters and setter. | |
'ecmaVersion' => 5, | |
// Turn on `strictSemicolons` to prevent the parser from doing | |
// automatic semicolon insertion. | |
'strictSemicolons' => false, | |
// When `allowTrailingCommas` is false, the parser will not allow | |
// trailing commas in array and object literals. | |
'allowTrailingCommas' => true, | |
// By default, reserved words are not enforced. Enable | |
// `forbidReserved` to enforce them. | |
'forbidReserved' => false, | |
// When `trackComments` is turned on, the parser will attach | |
// `commentsBefore` and `commentsAfter` properties to AST nodes | |
// holding arrays of strings. A single comment may appear in both | |
// a `commentsBefore` and `commentsAfter` array (of the nodes | |
// after and before it), but never twice in the before (or after) | |
// array of different nodes. | |
'trackComments' => false, | |
// When `locations` is on, `loc` properties holding objects with | |
// `start` and `end` properties in `{line, column}` form (with | |
// line being 1-based and column 0-based) will be attached to the | |
// nodes. | |
'locations' => false, | |
// Nodes have their start and end characters offsets recorded in | |
// `start` and `end` properties (directly on the node, rather than | |
// the `loc` object, which holds line/column data. To also add a | |
// [semi-standardized][range] `range` property holding a `[start, | |
// end]` array with the same numbers, set the `ranges` option to | |
// `true`. | |
// | |
// [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 | |
'ranges' => false, | |
// It is possible to parse multiple files into a single AST by | |
// passing the tree produced by parsing the first file as | |
// `program` option in subsequent parses. This will add the | |
// toplevel forms of the parsed file to the `Program` (top) node | |
// of an existing parse tree. | |
'program' => null, | |
// When `location` is on, you can pass this to record the source | |
// file in every node's `loc` object. | |
'sourceFile' => null, | |
// When `strict` is true, the entire program will be parsed as if | |
// strict mode was on | |
'strict' => false | |
); | |
protected $tokPos = 0; | |
protected $tokStart, $tokEnd; | |
protected $tokStartLoc, $tokEndLoc; | |
protected $tokType, $tokVal; | |
protected $tokCommentsBefore, $tokCommentsAfter; | |
protected $tokRegexpAllowed, $tokComments; | |
protected $tokCurLine, $tokLineStart, $tokLineStartNext; | |
protected $lastStart, $lastEnd, $lastEndLoc; | |
protected $inFunction = false, $labels = array(), $strict = false; | |
protected $_num = array('type' => 'num'); | |
protected $_regexp = array('type' => 'regexp'); | |
protected $_string = array('type' => 'string'); | |
protected $_name = array('type' => 'name'); | |
protected $_eof = array('type' => 'eof'); | |
protected $_break = array('keyword' => 'break'); | |
protected $_case = array('keyword' => 'case', 'beforeExpr' => true); | |
protected $_catch = array('keyword' => 'catch'); | |
protected $_continue = array('keyword' => 'continue'); | |
protected $_debugger = array('keyword' => 'debugger'); | |
protected $_default = array('keyword' => 'default'); | |
protected $_do = array('keyword' => 'do', 'isLoop' => true); | |
protected $_else = array('keyword' => 'else', 'beforeExpr' => true); | |
protected $_finally = array('keyword' => 'finally'); | |
protected $_for = array('keyword' => 'for', 'isLoop' => true); | |
protected $_function = array('keyword' => 'function'); | |
protected $_if = array('keyword' => 'if'); | |
protected $_return = array('keyword' => 'return', 'beforeExpr' => true); | |
protected $_switch = array('keyword' => 'switch'); | |
protected $_throw = array('keyword' => 'throw', 'beforeExpr' => true); | |
protected $_try = array('keyword' => 'try'); | |
protected $_var = array('keyword' => 'var'); | |
protected $_while = array('keyword' => 'while', 'isLoop' => true); | |
protected $_this = array('keyword' => 'this'); | |
protected $_with = array('keyword' => 'with'); | |
protected $_new = array('keyword' => 'new', 'beforeExpr' => true); | |
protected $_null = array('keyword' => 'null', 'atomValue' => null); | |
protected $_true = array('keyword' => 'true', 'atomValue' => true); | |
protected $_false = array('keyword' => 'false', 'atomValue' => false); | |
protected $_in = array('keyword' => 'in', 'binop' => 7, 'beforeExpr' => true); | |
protected $keywordTypes = array( | |
'break' => array('keyword' => 'break'), | |
'case' => array('keyword' => 'case', 'beforeExpr' => true), | |
'catch' => array('keyword' => 'catch'), | |
'continue' => array('keyword' => 'continue'), | |
'debugger' => array('keyword' => 'debugger'), | |
'default' => array('keyword' => 'default'), | |
'do' => array('keyword' => 'do', 'isLoop' => true), | |
'else' => array('keyword' => 'else', 'beforeExpr' => true), | |
'finally' => array('keyword' => 'finally'), | |
'for' => array('keyword' => 'for', 'isLoop' => true), | |
'function' => array('keyword' => 'function'), | |
'if' => array('keyword' => 'if'), | |
'return' => array('keyword' => 'return', 'beforeExpr' => true), | |
'switch' => array('keyword' => 'switch'), | |
'throw' => array('keyword' => 'throw', 'beforeExpr' => true), | |
'try' => array('keyword' => 'try'), | |
'var' => array('keyword' => 'var'), | |
'while' => array('keyword' => 'while', 'isLoop' => true), | |
'with' => array('keyword' => 'with'), | |
'null' => array('keyword' => 'null', 'atomValue' => null), | |
'true' => array('keyword' => 'true', 'atomValue' => true), | |
'false' => array('keyword' => 'false', 'atomValue' => false), | |
'new' => array('keyword' => 'new', 'beforeExpr' => true), | |
'in' => array('keyword' => 'in', 'binop' => 7, 'beforeExpr' => true), | |
'instanceof' => array('keyword' => 'instanceof', 'binop' => 7), | |
'this' => array('keyword' => 'this'), | |
'typeof' => array('keyword' => 'typeof', 'prefix' => true), | |
'void' => array('keyword' => 'void', 'prefix' => true), | |
'delete' => array('keyword' => 'delete', 'prefix' => true) | |
); | |
protected $_bracketL = array('type' => '[', 'beforeExpr' => true); | |
protected $_bracketR = array('type' => ']', 'beforeExpr' => true); | |
protected $_braceL = array('type' => '{', 'beforeExpr' => true); | |
protected $_braceR = array('type' => '}'); | |
protected $_parenL = array('type' => '(', 'beforeExpr' => true); | |
protected $_parenR = array('type' => ')'); | |
protected $_comma = array('type' => ',', 'beforeExpr' => true); | |
protected $_semi = array('type' => ';', 'beforeExpr' => true); | |
protected $_colon = array('type' => ':', 'beforeExpr' => true); | |
protected $_dot = array('type' => '.'); | |
protected $_question = array('type' => '?', 'beforeExpr' => true); | |
protected $_slash = array('type' => '/', 'binop' => 10, 'beforeExpr' => true); | |
protected $_eq = array('type' => '=', 'isAssign' => true, 'beforeExpr' => true); | |
protected $_assign = array('type' => 'assign', 'isAssign' => true, 'beforeExpr' => true); | |
protected $_plusmin = array('binop' => 9, 'prefix' => true, 'beforeExpr' => true); | |
protected $_incdec = array('postfix' => true, 'prefix' => true, 'isUpdate' => true); | |
protected $_prefix = array('type' => 'prefix', 'prefix' => true, 'beforeExpr' => true); | |
protected $_bin1 = array('binop' => 1, 'beforeExpr' => true); | |
protected $_bin2 = array('binop' => 2, 'beforeExpr' => true); | |
protected $_bin3 = array('binop' => 3, 'beforeExpr' => true); | |
protected $_bin4 = array('binop' => 4, 'beforeExpr' => true); | |
protected $_bin5 = array('binop' => 5, 'beforeExpr' => true); | |
protected $_bin6 = array('binop' => 6, 'beforeExpr' => true); | |
protected $_bin7 = array('binop' => 7, 'beforeExpr' => true); | |
protected $_bin8 = array('binop' => 8, 'beforeExpr' => true); | |
protected $_bin10 = array('binop' => 10, 'beforeExpr' => true); | |
protected $reservedWord3 = array( | |
'abstract' => true, 'boolean' => true, 'byte' => true, 'char' => true, 'class' => true, | |
'double' => true, 'enum' => true, 'export' => true, 'extends' => true, 'final' => true, | |
'float' => true, 'goto' => true, 'implements' => true, 'import' => true, 'int' => true, | |
'interface' => true, 'long' => true, 'native' => true, 'package' => true, 'private' => true, | |
'protected' => true, 'public' => true, 'short' => true, 'static' => true, 'super' => true, | |
'synchronized' => true, 'throws' => true, 'transient' => true, 'volatile' => true | |
); | |
protected function isReservedWord3($str) { | |
return isset($this->reservedWord3[$str]); | |
} | |
protected $reservedWord5 = array( | |
'class' => true, 'enum' => true, 'extends' => true, | |
'super' => true, 'const' => true, 'export' => true, 'import' => true | |
); | |
protected function isReservedWord5($str) { | |
return isset($this->reservedWord5[$str]); | |
} | |
protected $strictReservedWord = array( | |
'implements' => true, 'interface' => true, 'let' => true, 'package' => true, | |
'private' => true, 'protected' => true, 'public' => true, 'static' => true, | |
'yield' => true | |
); | |
protected function isStrictReservedWord($str) { | |
return isset($this->strictReservedWord[$str]); | |
} | |
protected $strictBadWord = array('eval' => true, 'arguments' => true); | |
protected function isStrictBadWord($str) { | |
return isset($this->strictBadWord[$str]); | |
} | |
protected $keyword = array( | |
'break' => true, 'case' => true, 'catch' => true, 'continue' => true, 'debugger' => true, | |
'default' => true, 'do' => true, 'else' => true, 'finally' => true, 'for' => true, | |
'function' => true, 'if' => true, 'return' => true, 'switch' => true, 'throw' => true, | |
'try' => true, 'var' => true, 'while' => true, 'with' => true, 'null' => true, | |
'true' => true, 'false' => true, 'instanceof' => true, 'typeof' => true, | |
'void' => true, 'delete' => true, 'new' => true, 'in' => true, 'this' => true | |
); | |
protected function isKeyword($str) { | |
return isset($this->keyword[$str]); | |
} | |
protected $containsEsc; | |
protected $lastFinishedNode; | |
protected $loopLabel; | |
protected $switchLabel; | |
public function parse($input, array $options = array()) { | |
$this->loopLabel = \JavaScript\AST\Node::get(array('kind' => 'loop')); | |
$this->switchLabel = \JavaScript\AST\Node::get(array('kind' => 'switch')); | |
$this->input = (string)$input; | |
$this->inputLen = strlen($this->input); | |
$this->options = $options; | |
foreach(self::$defaultOptions as $key => $value) { | |
if (!array_key_exists($key, $this->options)) { | |
$this->options[$key] = $value; | |
} | |
} | |
$this->strict = $this->options['strict']; | |
$this->sourceFile = $this->options['sourceFile']; | |
return $this->parseTopLevel($this->options['program']); | |
} | |
protected function getLineInfo($input, $offset) { | |
$piece = substr($input, 0, $offset); | |
$m = null; | |
$line = preg_match_all(self::LINEBREAK, $piece, $m) + 1; | |
$offset = strrpos($piece, "\n"); | |
$column = strlen($piece) - ($offset === false ? 0 : $offset + 1); | |
return array( | |
'line' => $line, | |
'column' => $column | |
); | |
} | |
protected function raise($pos, $message) { | |
if (ctype_digit((string)$pos)) { | |
$pos = $this->getLineInfo($this->input, $pos); | |
} | |
$message .= ' (' . $pos['line'] . ':' . $pos['column'] . ')'; | |
throw new Exception($message); | |
} | |
protected function nextLineStart() { | |
$m = null; | |
preg_match(self::LINEBREAK, $this->input, $m, PREG_OFFSET_CAPTURE, $this->tokLineStart); | |
if ($m && $m[0][1] === $this->tokLineStart) { | |
return $m[0][1] + strlen($m[0][0]); | |
} | |
return strlen($this->input) + 1; | |
} | |
protected function curLineLoc() { | |
while ($this->tokLineStartNext <= $this->tokPos) { | |
++$this->tokCurLine; | |
$this->tokLineStart = $this->tokLineStartNext; | |
$this->tokLineStartNext = $this->nextLineStart(); | |
} | |
return array( | |
'line' => $this->tokCurLine, | |
'column' => ($this->tokPos - $this->tokLineStart) | |
); | |
} | |
protected function initTokenState() { | |
$this->tokCurLine = 1; | |
$this->tokPos = $this->tokLineStart = 0; | |
$this->tokLineStartNext = $this->nextLineStart(); | |
$this->tokRegexpAllowed = true; | |
$this->tokComments = null; | |
$this->skipSpace(); | |
} | |
protected function finishToken($type, $val = null) { | |
$this->tokEnd = $this->tokPos; | |
if (isset($this->options['locations'])) { | |
$this->tokEndLoc = $this->curLineLoc(); | |
} | |
$this->tokType = $type; | |
$this->skipSpace(); | |
$this->tokVal = $val; | |
$this->tokCommentsAfter = $this->tokComments; | |
$this->tokRegexpAllowed = isset($type['beforeExpr']); | |
} | |
protected function skipSpace() { | |
while ($this->tokPos < $this->inputLen) { | |
$ch = $this->input[$this->tokPos]; | |
if ($ch === '/') { | |
$nextCh = $this->input[$this->tokPos + 1]; | |
if ($nextCh === '*') { | |
$end = strpos($this->input, '*/', $this->tokPos += 2); | |
if ($end === false) { | |
$this->raise($this->tokPos - 2, 'Unterminated comment'); | |
} | |
if ($this->options['trackComments']) { | |
if (!$this->tokComments) { | |
$this->tokComments = array(); | |
} | |
$this->tokComments[] = substr($this->input, $this->tokPos, $end - $this->tokPos); | |
} | |
$this->tokPos = $end + 2; | |
} elseif ($nextCh === '/') { | |
$start = $this->tokPos; | |
$this->tokPos += 2; | |
while ($this->tokPos < $this->inputLen && $this->input[$this->tokPos] !== "\n") { | |
++$this->tokPos; | |
} | |
if ($this->options['trackComments']) { | |
if (!$this->tokComments) { | |
$this->tokComments = array(); | |
} | |
$this->tokComments[] = substr($this->input, $start, $this->tokPos - $start); | |
} | |
} else { | |
break; | |
} | |
} elseif ($ch === ' ' || $ch === "\t" || $ch === "\n" || $ch === "\r" || $ch === "\f" || | |
$ch === "\xa0" || $ch === "\x0b") { | |
++$this->tokPos; | |
} else { | |
break; | |
} | |
} | |
} | |
protected function readToken($forceRegexp = false) { | |
$this->tokStart = $this->tokPos; | |
$this->tokCommentsBefore = $this->tokComments; | |
if ($this->options['locations']) { | |
$this->tokStartLoc = $this->curLineLoc(); | |
} | |
if ($forceRegexp) { | |
return $this->readRegexp(); | |
} | |
if ($this->tokPos >= $this->inputLen) { | |
return $this->finishToken($this->_eof, 'EOF'); | |
} | |
$code = ord($char = $this->input[$this->tokPos]); | |
if (($code >= 65 && $code <= 90) || ($code >= 97 && $code <= 122) || | |
$code === 36 || $code === 95 || $code === 92 || | |
($code >= 0xaa && preg_match(self::NON_ASCII_IDENTIFIER_START, $char))) { | |
return $this->readWord(); | |
} | |
$next = $this->tokPos + 1 < $this->inputLen ? ord($this->input[$this->tokPos + 1]) : null; | |
switch ($code) { | |
case 46: // . | |
if ($next >= 48 && $next <= 57) { | |
return $this->readNumber($char); | |
} | |
++$this->tokPos; | |
return $this->finishToken($this->_dot, '.'); | |
case 40: ++$this->tokPos; return $this->finishToken($this->_parenL, '('); | |
case 41: ++$this->tokPos; return $this->finishToken($this->_parenR, ')'); | |
case 59: ++$this->tokPos; return $this->finishToken($this->_semi, ';'); | |
case 44: ++$this->tokPos; return $this->finishToken($this->_comma, ','); | |
case 91: ++$this->tokPos; return $this->finishToken($this->_bracketL, '['); | |
case 93: ++$this->tokPos; return $this->finishToken($this->_bracketR, ']'); | |
case 123: ++$this->tokPos; return $this->finishToken($this->_braceL, '{'); | |
case 125: ++$this->tokPos; return $this->finishToken($this->_braceR, '}'); | |
case 58: ++$this->tokPos; return $this->finishToken($this->_colon, ':'); | |
case 63: ++$this->tokPos; return $this->finishToken($this->_question, '?'); | |
case 48: | |
if ($next === 120 || $next === 88) { | |
return $this->readHexNumber(); | |
} | |
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: | |
return $this->readNumber($char); | |
case 34: case 39: | |
return $this->readString($char); | |
case 47: // / | |
if ($this->tokRegexpAllowed) { | |
++$this->tokPos; | |
return $this->readRegexp(); | |
} | |
if ($next === 61) { | |
return $this->finishOp($this->_assign, 2); | |
} | |
return $this->finishOp($this->_slash, 1); | |
case 37: case 42: | |
if ($next === 61) { | |
return $this->finishOp($this->_assign, 2); | |
} | |
return $this->finishOp($this->_bin10, 1); | |
case 124: case 38: | |
if ($next === $code) { | |
return $this->finishOp($code === 124 ? $this->_bin1 : $this->_bin2, 2); | |
} elseif ($next === 61) { | |
return $this->finishOp($this->_assign, 2); | |
} | |
return $this->finishOp($code === 124 ? $this->_bin3 : $this->_bin5, 1); | |
case 94: | |
if ($next === 61) { | |
return $this->finishOp($this->_assign, 2); | |
} | |
return $this->finishOp($this->_bin4, 1); | |
case 43: case 45: | |
if ($next === $code) { | |
return $this->finishOp($this->_incdec, 2); | |
} elseif ($next === 61) { | |
return $this->finishOp($this->_assign, 2); | |
} | |
return $this->finishOp($this->_plusmin, 1); | |
case 60: case 62: // '<>' | |
$size = 1; | |
if ($next === $code) { | |
$size = $code === 62 && ord($this->input[$this->tokPos + 2]) === 62 ? 3 : 2; | |
if (ord($this->input[$this->tokPos + $size]) === 61) { | |
return $this->finishOp($this->_assign, $size + 1); | |
} | |
return $this->finishOp($this->_bin8, $size); | |
} | |
if ($next === 61) { | |
$size = $this->input[$this->tokPos + 2] === '=' ? 3 : 2; | |
} | |
return $this->finishOp($this->_bin8, $size); | |
case 61: case 33: | |
if ($next === 61) { | |
return $this->finishOp($this->_bin6, ord($this->input[$this->tokPos + 2]) === 61 ? 3 : 2); | |
} | |
return $this->finishOp($code === 61 ? $this->_eq : $this->_prefix, 1); | |
case 126: | |
return $this->finishOp($this->_prefix, 1); | |
} | |
if ($char === '\\' || preg_match(self::NON_ASCII_IDENTIFIER_START, $char)) { | |
return $this->readWord(); | |
} | |
$this->raise($this->tokPos, 'Unexpected character "' . $char . '"'); | |
} | |
protected function finishOp($type, $size) { | |
$str = substr($this->input, $this->tokPos, $size); | |
$this->tokPos += $size; | |
$this->finishToken($type, $str); | |
} | |
protected function readRegexp() { | |
$start = $this->tokPos; | |
$inClass = false; | |
$escaped = false; | |
for (;;) { | |
if ($this->tokPos >= $this->inputLen) { | |
$this->raise($start, 'Unterminated regular expression'); | |
} | |
$ch = $this->input[$this->tokPos]; | |
if (preg_match(self::NEWLINE, $ch)) { | |
$this->raise($start, 'Unterminated regular expression'); | |
} | |
if (!$escaped) { | |
if ($ch === '[') { | |
$inClass = true; | |
} elseif ($inClass && $ch === ']') { | |
$inClass = false; | |
} elseif ($ch === '/' && !$inClass) { | |
break; | |
} | |
$escaped = $ch === '\\'; | |
} else { | |
$escaped = false; | |
} | |
++$this->tokPos; | |
} | |
$content = substr($this->input, $start, $this->tokPos - $start); | |
++$this->tokPos; | |
$mods = $this->readWord1(); | |
if ($mods && !preg_match('~^[gmsiy]+$~', $mods)) { | |
$this->raise($start, 'Invalid regexp flag'); | |
} | |
return $this->finishToken($this->_regexp, '/' . $content . '/' . $mods); | |
} | |
protected function readInt($radix = 10, $len = null) { | |
$start = $this->tokPos; | |
$total = 0; | |
while ($this->tokPos < $this->inputLen) { | |
$code = ord($this->input[$this->tokPos]); | |
if ($code >= 97) { | |
$val = $code - 97 + 10; // A | |
} elseif ($code >= 65) { | |
$val = $code - 65 + 10; // A | |
} elseif ($code >= 48 && $code <= 57) { | |
$val = $code - 48; | |
} else { | |
$val = INF; | |
} | |
if ($val >= $radix) { | |
break; | |
} | |
++$this->tokPos; | |
$total = $total * $radix + $val; | |
} | |
if ($this->tokPos === $start || $len !== null && $this->tokPos - $start !== $len) { | |
return null; | |
} | |
return $total; | |
} | |
protected function readHexNumber() { | |
$this->tokPos += 2; // 0x | |
$val = $this->readInt(16); | |
if ($val === null) { | |
$this->raise($this->tokStart + 2, 'Expected hexadecimal number'); | |
} | |
$ch = $this->input[$this->tokPos]; | |
if ((($ch >= 'a' && $ch <= 'z') || ($ch >= 'A' && $ch <= 'Z') || | |
$ch === '$' || $ch === '_') || | |
($ch >= "\xaa" && preg_match(self::NON_ASCII_IDENTIFIER_START, $ch))) { | |
$this->raise($this->tokPos, 'Identifier directly after number'); | |
} | |
return $this->finishToken($this->_num, $val); | |
} | |
protected function readNumber($ch) { | |
$start = $this->tokPos; | |
$isFloat = $ch === '.'; | |
if (!$isFloat && $this->readInt(10) === null) { | |
$this->raise($start, 'Invalid number'); | |
} | |
if ($this->tokPos < $this->inputLen) { | |
if ($isFloat || $this->input[$this->tokPos] === '.') { | |
$next = $this->input[++$this->tokPos]; | |
if ($next === '-' || $next === '+') { | |
++$this->tokPos; | |
} | |
if ($this->readInt(10) === null && $ch === '.') { | |
$this->raise($start, 'Invalid number'); | |
} | |
$isFloat = true; | |
} | |
if (\strcasecmp($this->input[$this->tokPos], 'e') === 0) { | |
$next = $this->input[++$this->tokPos]; | |
if ($next === '-' || $next === '+') { | |
++$this->tokPos; | |
} | |
if ($this->readInt(10) === null) { | |
$this->raise($start, 'Invalid number'); | |
} | |
$isFloat = true; | |
} | |
$ch = $this->input[$this->tokPos]; | |
if ((($ch >= 'a' && $ch <= 'z') || ($ch >= 'A' && $ch <= 'Z') || | |
$ch === '$' || $ch === '_') || | |
($ch >= "\xaa" && preg_match(self::NON_ASCII_IDENTIFIER_START, $ch))) { | |
$this->raise($this->tokPos, 'Identifier directly after number'); | |
} | |
} | |
$str = \substr($this->input, $start, $this->tokPos - $start); | |
if ($isFloat) { | |
$val = eval('return ' . $str . ';'); | |
} elseif ($ch !== '0' || strlen($str) === 1) { | |
$val = eval('return ' . $str . ';'); | |
} elseif (\strtok($str, '89')) { | |
$this->raise($start, 'Invalid number'); | |
} else { | |
$val = eval('return ' . $str . ';'); | |
} | |
return $this->finishToken($this->_num, $val); | |
} | |
protected function readString($quote) { | |
++$this->tokPos; | |
$str = ''; | |
for (;;) { | |
if ($this->tokPos >= $this->inputLen) { | |
$this->raise($this->tokStart, 'Unterminated string constant'); | |
} | |
$ch = $this->input[$this->tokPos]; | |
if ($ch === $quote) { | |
++$this->tokPos; | |
return $this->finishToken($this->_string, $str); | |
} | |
if ($ch === '\\') { | |
$ch = $this->input[++$this->tokPos]; | |
$m = null; | |
$octal = \preg_match('~^[0-7]+~', \substr($this->input, $this->tokPos, 3), $m); | |
if ($octal) { | |
$octal = $octal[0]; | |
} | |
while ($octal && eval('return ' . $octal . ';') > 255) { | |
$octal = \substr($octal, 0, -1); | |
} | |
if ($octal === '0') { | |
$octal = null; | |
} | |
++$this->tokPos; | |
if ($octal) { | |
if ($this->strict) { | |
$this->raise($this->tokPos - 2, 'Octal literal in strict mode'); | |
} | |
$str .= \chr(eval('return ' . $octal . ';')); | |
$this->tokPos += \strlen($octal) - 1; | |
} elseif ($ch === 'x') { | |
$str .= $this->readHexChar(2); | |
} elseif ($ch === 'u') { | |
$str .= $this->readHexChar(4); | |
} elseif ($ch === 'U') { | |
$str .= $this->readHexChar(8); | |
} else { | |
switch ($ch) { | |
case 'n': $str .= "\x0a"; break; | |
case 'r': $str .= "\x0d"; break; | |
case 't': $str .= "\x09"; break; | |
case 'b': $str .= "\x08"; break; | |
case 'v': $str .= "\x0b"; break; | |
case 'f': $str .= "\x0c"; break; | |
case '0': $str .= "\x00"; break; | |
case "\r": | |
if ($this->input[$this->tokPos] === "\n") { | |
++$this->tokPos; | |
} | |
case "\n": | |
break; | |
default: | |
$str .= $ch; | |
} | |
} | |
} else { | |
if (\preg_match(self::NEWLINE, $ch)) { | |
$this->raise($this->tokStart, 'Unterminated string constant'); | |
} | |
$str .= $ch; | |
++$this->tokPos; | |
} | |
} | |
} | |
protected function readHexChar($len) { | |
$n = $this->readInt(16, $len); | |
if ($n === null) { | |
$this->raise($this->tokStart, 'Bad character escape sequence'); | |
} | |
return \chr($n); | |
} | |
protected function readWord1() { | |
$this->containsEsc = false; | |
$first = true; | |
$start = $this->tokPos; | |
$word = ''; | |
while ($this->tokPos < $this->inputLen) { | |
$ch = $this->input[$this->tokPos]; | |
if ((($ch >= 'a' && $ch <= 'z') || ($ch >= 'A' && $ch <= 'Z') || | |
($ch >= '0' && $ch <= '9') || $ch === '$' || $ch === '_') || | |
($ch >= "\xaa" && preg_match(self::NON_ASCII_IDENTIFIER, $ch))) { | |
if ($this->containsEsc) { | |
$word .= $ch; | |
} | |
++$this->tokPos; | |
} elseif ($ch === '\\') { | |
if (!$this->containsEsc) { | |
$word = \substr($this->input, $start, $this->tokPos - $start); | |
} | |
$this->containsEsc = true; | |
if ($this->input[$this->tokPos + 1] !== 'u') { | |
$this->raise($this->tokPos, 'Expecting Unicode escape sequence \\uXXXX'); | |
} | |
++$this->tokPos; | |
$esc = $this->readHexChar(4); | |
if (!$esc) { | |
$this->raise($this->tokPos - 1, 'Invalid Unicode escape'); | |
} | |
if (!((($esc >= 'a' && $esc <= 'z') || ($esc >= 'A' && $esc <= 'Z') || | |
$esc === '$' || $esc === '_') || (!$first && $esc >= '0' && $esc <= '9') || | |
($esc >= "\xaa" && preg_match($first ? self::NON_ASCII_IDENTIFIER_START : self::NON_ASCII_IDENTIFIER, $esc)))) { | |
$this->raise($this->tokPos, 'Invalid Unicode escape'); | |
} | |
$word .= $esc; | |
} else { | |
break; | |
} | |
$first = false; | |
} | |
return $this->containsEsc ? $word : \substr($this->input, $start, $this->tokPos - $start); | |
} | |
public function readWord() { | |
$word = $this->readWord1(); | |
$type = $this->_name; | |
if (!$this->containsEsc) { | |
if ($this->isKeyword($word)) { | |
$type = $this->keywordTypes[$word]; | |
} elseif ($this->options['forbidReserved'] && | |
($this->options['ecmaVersion'] === 3 ? $this->isReservedWord3($word) : $this->isReservedWord5($word)) || | |
$this->strict && $this->isStrictReservedWord($word)) { | |
$this->raise($this->tokStart, 'The keyword "' . $word . '" is reserved'); | |
} | |
} | |
return $this->finishToken($type, $word); | |
} | |
// | |
// PARSER | |
// | |
protected function next() { | |
$this->lastStart = $this->tokStart; | |
$this->lastEnd = $this->tokEnd; | |
$this->lastEndLoc = $this->tokEndLoc; | |
$this->readToken(); | |
} | |
protected function setStrict($strict) { | |
$this->strict = $strict; | |
$this->tokPos = $this->lastEnd; | |
$this->skipSpace(); | |
$this->readToken(); | |
} | |
protected function nodeFor($type, $options = array()) { | |
if ($type !== null && class_exists($class = '\\JavaScript\\AST\\Node\\' . $type)) { | |
$result = call_user_func(array($class, 'get'), $options); | |
} else { | |
$result = \JavaScript\AST\Node::get($options); | |
$result->type = $type; | |
} | |
return $result; | |
} | |
protected function startNode($type = null) { | |
if ($type instanceof \JavaScript\AST\Node) { | |
$node = $type; | |
$node->start = $this->tokStart; | |
} else { | |
$node = $this->nodeFor($type, array('start' => $this->tokStart)); | |
} | |
if ($this->options['trackComments'] && $this->tokCommentsBefore) { | |
$node->commentsBefore = $this->tokCommentsBefore; | |
$this->tokCommentsBefore = null; | |
} | |
if ($this->options['locations']) { | |
$node->loc = array('start' => $this->tokStartLoc, 'end' => null, 'source' => $this->sourceFile); | |
} | |
if ($this->options['ranges']) { | |
$node->range = array($this->tokStart, 0); | |
} | |
return $node; | |
} | |
protected function startNodeFrom(\JavaScript\AST\Node $other, $type = null) { | |
if ($type instanceof \JavaScript\AST\Node) { | |
$node = $type; | |
$node->start = $other->start; | |
} else { | |
$node = $this->nodeFor($type, array('start' => $other->start)); | |
} | |
if ($other->commentsBefore) { | |
$node->commentsBefore = $other->tokCommentsBefore; | |
$other->tokCommentsBefore = null; | |
} | |
if ($this->options['locations']) { | |
$node->loc = array('start' => $other->loc['start'], 'end' => null, 'source' => $other->loc['source']); | |
} | |
if ($this->options['ranges']) { | |
$node->range = array($other->range[0], 0); | |
} | |
return $node; | |
} | |
protected function finishNode($node, $type = null) { | |
if ($type !== null) { | |
if (class_exists($class = '\\JavaScript\\AST\\Node\\' . $type)) { | |
$node = call_user_func(array($class, 'get'), \get_object_vars($node)); | |
} | |
$node->type = $type; | |
} | |
$node->end = $this->lastEnd; | |
if ($this->options['trackComments']) { | |
if ($this->tokCommentsAfter) { | |
$node->commentsAfter = $this->tokCommentsAfter; | |
$this->tokCommentsAfter = null; | |
} elseif ($this->lastFinishedNode && $this->lastFinishedNode->end === $this->lastEnd) { | |
$node->commentsAfter = $this->lastFinishedNode->commentsAfter; | |
$this->lastFinishedNode->commentsAfter = null; | |
} | |
$this->lastFinishedNode = $node; | |
} | |
if ($this->options['locations']) { | |
$node->loc['end'] = $this->lastEndLoc; | |
} | |
if ($this->options['ranges']) { | |
$node->range[1] = $this->lastEnd; | |
} | |
return $node; | |
} | |
protected function isUseStrict($stmt) { | |
return $this->options['ecmaVersion'] >= 5 && $stmt->type === 'ExpressionStatement' && | |
$stmt->expression->type === 'Literal' && $stmt->expression->value === 'use strict'; | |
} | |
protected function eat($type) { | |
if ($this->tokType === $type) { | |
$this->next(); | |
return true; | |
} | |
} | |
protected function canInsertSemicolon() { | |
return $this->tokType === $this->_eof || $this->tokType === $this->_braceR || | |
!$this->options['strictSemicolons'] && | |
preg_match(self::NEWLINE, substr($this->input, $this->lastEnd, $this->tokStart - $this->lastEnd)); | |
} | |
protected function semicolon() { | |
if (!$this->eat($this->_semi) && !$this->canInsertSemicolon()) { | |
$this->unexpected(); | |
} | |
} | |
protected function expect($type) { | |
if ($this->tokType === $type) { | |
$this->next(); | |
} else { | |
$this->unexpected(); | |
} | |
} | |
protected function unexpected() { | |
$this->raise($this->tokStart, 'Unexpected token "' . $this->tokVal . '"'); | |
} | |
protected function checkLVal($expr) { | |
if ($expr->type !== 'Identifier' && $expr->type !== 'MemberExpression') { | |
$this->raise($expr->start, 'Assigning to rvalue'); | |
} | |
if ($this->strict && $expr->type === 'Identifier' && $this->isStrictBadIdWord($expr->name)) { | |
$this->raise($expr->start, 'Assigning to ' . $expr->name . ' in strict mode'); | |
} | |
} | |
// | |
// Statement parsing | |
// | |
protected function parseTopLevel($program) { | |
$this->initTokenState(); | |
$this->lastStart = $this->lastEnd = $this->tokPos; | |
if ($this->options['locations']) { | |
$this->lastEndLoc = $this->curLineLoc(); | |
} | |
$this->inFunction = $this->strict = null; | |
$this->labels = array(); | |
$this->readToken(); | |
$node = $program ?: $this->startNode(new \JavaScript\AST\Node\Program); | |
$first = true; | |
while ($this->tokType !== $this->_eof) { | |
$node->body[] = $stmt = $this->parseStatement(); | |
if ($first && $this->isUseStrict($stmt)) { | |
$this->setStrict(true); | |
} | |
$first = false; | |
} | |
return $this->finishNode($node); | |
} | |
protected function parseStatement() { | |
if ($this->tokType === $this->_slash) { | |
$this->readToken(true); | |
} | |
$starttype = $this->tokType; | |
switch ($starttype) { | |
case $this->_break: case $this->_continue: | |
$this->next(); | |
$isBreak = $starttype === $this->_break; | |
$node = $this->startNode($isBreak ? 'BreakStatement' : 'ContinueStatement'); | |
if ($this->eat($this->_semi) || $this->canInsertSemicolon()) { | |
$node->label = null; | |
} elseif ($this->tokType !== $this->_name) { | |
$this->unexpected(); | |
} else { | |
$node->label = $this->parseIdent(); | |
$this->semicolon(); | |
} | |
for ($i = 0; $i < count($this->labels); ++$i) { | |
$lab = $this->labels[$i]; | |
if ($node->label === null || $lab->name === $node->label->name) { | |
if ($lab->kind !== null && ($isBreak || $lab->kind === 'loop')) { | |
break; | |
} | |
if ($node->label && $isBreak) { | |
break; | |
} | |
} | |
} | |
if ($i === count($this->labels)) { | |
$this->raise($node->start, 'Unsyntactic ' . $starttype['keyword']); | |
} | |
return $this->finishNode($node); | |
case $this->_debugger: | |
$this->next(); | |
return $this->finishNode($this->startNode('DebuggerStatement')); | |
case $this->_do: | |
$this->next(); | |
$this->labels[] = $this->loopLabel; | |
$node = $this->startNode('DoWhileStatement'); | |
$node->body = $this->parseStatement(); | |
array_pop($this->labels); | |
$this->expect($this->_while); | |
$node->test = $this->parseParenExpression(); | |
$this->semicolon(); | |
return $this->finishNode($node); | |
case $this->_for: | |
$this->next(); | |
$this->labels[] = $this->loopLabel; | |
$this->expect($this->_parenL); | |
if ($this->tokType === $this->_semi) { | |
return $this->parseFor($this->startNode('ForStatement'), null); | |
} | |
if ($this->tokType === $this->_var) { | |
$init = $this->startNode('VariableDeclaration'); | |
$this->next(); | |
$this->parseVar($init, true); | |
if (count($init->declarations) === 1 && $this->eat($this->_in)) { | |
return $this->parseForIn($this->startNode('ForInStatement'), $init); | |
} | |
return $this->parseFor($this->startNode('ForStatement'), $init); | |
} | |
$init = $this->parseExpression(false, true); | |
if ($this->eat($this->_in)) { | |
$this->checkLVal($init); | |
return $this->parseForIn($this->startNode('ForInStatement'), $init); | |
} | |
return $this->parseFor($this->startNode('ForStatement'), $init); | |
case $this->_function: | |
$this->next(); | |
return $this->parseFunction($this->startNode(new \JavaScript\AST\Node\FunctionDeclaration), true); | |
case $this->_if: | |
$this->next(); | |
$node = $this->startNode('IfStatement'); | |
$node->test = $this->parseParenExpression(); | |
$node->consequent = $this->parseStatement(); | |
$node->alternate = $this->eat($this->_else) ? $this->parseStatement() : null; | |
return $this->finishNode($node); | |
case $this->_return: | |
if (!$this->inFunction) { | |
$this->raise('"return" outside of function'); | |
} | |
$this->next(); | |
$node = $this->startNode('ReturnStatement'); | |
if ($this->eat($this->_semi) || $this->canInsertSemicolon()) { | |
$node->argument = null; | |
} else { | |
$node->argument = $this->parseExpression(); | |
$this->semicolon(); | |
} | |
return $this->finishNode($node); | |
case $this->_switch: | |
$this->next(); | |
$node = $this->startNode('SwitchStatement'); | |
$node->discriminant = $this->parseParenExpression(); | |
$node->cases = array(); | |
$this->expect($this->_braceL); | |
$this->labels[] = $this->switchLabel; | |
for ($cur = null, $sawDefault = false; $this->tokType !== $this->_braceR;) { | |
if ($this->tokType === $this->_case || $this->tokType === $this->_default) { | |
$isCase = $this->tokType === $this->_case; | |
if ($cur) { | |
$this->finishNode($cur); | |
} | |
$node->cases[] = $cur = $this->startNode('SwitchCase'); | |
$cur->consequent = array(); | |
$this->next(); | |
if ($isCase) { | |
$cur->test = $this->parseExpression(); | |
} else { | |
if ($sawDefault) { | |
$this->raise($this->lastStart, 'Multiple default clauses'); | |
} | |
$sawDefault = true; | |
$cur->test = null; | |
} | |
$this->expect($this->_colon); | |
} else { | |
if (!$cur) { | |
$this->unexpected(); | |
} | |
$cur->consequent[] = $this->parseStatement(); | |
} | |
} | |
if ($cur) { | |
$this->finishNode($cur); | |
} | |
$this->next(); | |
array_pop($this->labels); | |
return $this->finishNode($node); | |
case $this->_throw: | |
$this->next(); | |
$node = $this->startNode('ThrowStatement'); | |
if ($this->canInsertSemicolon()) { | |
$this->raise($this->lastEndLoc, 'Invalid newline after throw'); | |
} | |
$node->argument = $this->parseExpression(); | |
return $this->finishNode($node); | |
case $this->_try: | |
$this->next(); | |
$node = $this->startNode('TryStatement'); | |
$node->block = $this->parseBlock(); | |
$node->handlers = array(); | |
while ($this->tokType === $this->_catch) { | |
$clause = $this->startNode('CatchClause'); | |
$this->next(); | |
$this->expect($this->_parenL); | |
$clause->param = $this->parseIdent(); | |
if ($this->strict && $this->isStrictBadWord($clause->param->name)) { | |
$this->raise($clause->param->start, 'Binding ' . $clause->param->name . ' in strict mode'); | |
} | |
$this->expect($this->_parenR); | |
$clause->guard = null; | |
$clause->body = $this->parseBlock(); | |
$node->handlers[] = $this->finishNode($clause); | |
} | |
$node->finalizer = $this->eat($this->_finally) ? $this->parseBlock() : null; | |
if (!$node->handlers && !$node->finalizer) { | |
$this->raise($node->start, 'Missing catch or finally clause'); | |
} | |
return $this->finishNode($node); | |
case $this->_var: | |
$this->next(); | |
$node = $this->parseVar($this->startNode('VariableDeclaration')); | |
$this->semicolon(); | |
return $node; | |
case $this->_while: | |
$this->next(); | |
$node = $this->startNode('WhileStatement'); | |
$node->test = $this->parseParenExpression(); | |
$this->labels[] = $this->loopLabel; | |
$node->body = $this->parseStatement(); | |
array_pop($this->labels); | |
return $this->finishNode($node); | |
case $this->_with: | |
if ($this->strict) { | |
$this->raise($this->tokStart, '"with" in strict mode'); | |
} | |
$this->next(); | |
$node = $this->startNode('WithStatement'); | |
$node->object = $this->parseParenExpression(); | |
$node->body = $this->parseStatement(); | |
return $this->finishNode($node); | |
case $this->_braceL: | |
return $this->parseBlock(); | |
case $this->_semi: | |
$this->next(); | |
return $this->finishNode($this->startNode('EmptyStatement')); | |
default: | |
$maybeName = $this->tokVal; | |
$expr = $this->parseExpression(); | |
if ($starttype === $this->_name && $expr->type === 'Identifier' && $this->eat($this->_colon)) { | |
for ($i = 0; $i < count($this->labels); ++$i) { | |
if ($this->labels[$i]->name === $maybeName) { | |
$this->raise($expr->start, 'Label "' . $maybeName . '" is already declared'); | |
} | |
} | |
$kind = $this->tokType['isLoop'] ? 'loop' : ($this->tokType === $this->_switch ? 'switch' : null); | |
$this->labels[] = \JavaScript\AST\Node::get(array('name' => $maybeName, 'kind' => $kind)); | |
$node = $this->startNode('LabeledStatement'); | |
$node->body = $this->parseStatement(); | |
$node->label = $expr; | |
return $this->finishNode($node); | |
} | |
$node = $this->startNode('ExpressionStatement'); | |
$node->expression = $expr; | |
$this->semicolon(); | |
return $this->finishNode($node); | |
} | |
} | |
protected function parseParenExpression() { | |
$this->expect($this->_parenL); | |
$val = $this->parseExpression(); | |
$this->expect($this->_parenR); | |
return $val; | |
} | |
protected function parseVar(\JavaScript\AST\Node $node, $noIn = false) { | |
$node->declarations = array(); | |
$node->kind = 'var'; | |
for (;;) { | |
$decl = $this->startNode(new \JavaScript\AST\Node\VariableDeclarator); | |
$decl->id = $this->parseIdent(); | |
if ($this->strict) { | |
} | |
if ($this->eat($this->_eq)) { | |
$node->decl = $this->parseExpression(true, $noIn); | |
} else { | |
$node->decl = null; | |
} | |
$node->declarations[] = $this->finishNode($decl); | |
if (!$this->eat($this->_comma)) { | |
break; | |
} | |
} | |
return $this->finishNode($node); | |
} | |
protected function parseBlock($allowStrict = false) { | |
$node = $this->startNode(); | |
$first = true; | |
$strict = false; | |
$oldStrict = null; | |
$node->body = array(); | |
$this->expect($this->_braceL); | |
while (!$this->eat($this->_braceR)) { | |
$stmt = $this->parseStatement(); | |
$node->body[] = $stmt; | |
if ($first && $allowStrict && $this->isUseStrict($stmt)) { | |
$oldStrict = $strict; | |
$this->setStrict($strict = true); | |
} | |
$first = false; | |
} | |
if ($strict && !$oldStrict) { | |
$this->setStrict(false); | |
} | |
return $this->finishNode($node, 'BlockStatement'); | |
} | |
protected function parseFor(\JavaScript\AST\Node $node, \JavaScript\AST\Node $init = null) { | |
$node->init = $init; | |
$this->expect($this->_semi); | |
$node->test = $this->tokType === $this->_semi ? null : $this->parseExpression(); | |
$this->expect($this->_semi); | |
$node->test = $this->tokType === $this->_parenR ? null : $this->parseExpression(); | |
$this->expect($this->_parenR); | |
$node->body = $this->parseStatement(); | |
array_pop($this->labels); | |
return $this->finishNode($node); | |
} | |
protected function parseForIn(\JavaScript\AST\Node $node, \JavaScript\AST\Node $init) { | |
$node->left = $init; | |
$node->right = $this->parseExpression(); | |
$this->expect($this->_parenR); | |
$node->body = $this->parseStatement(); | |
array_pop($this->labels); | |
return $this->finishNode($node); | |
} | |
protected function parseExpression($noComma = false, $noIn = false) { | |
$expr = $this->parseMaybeAssign($noIn); | |
if (!$noComma && $this->tokType === $this->_comma) { | |
$node = $this->startNodeFrom($expr, new \JavaScript\AST\Node\SequenceExpression()); | |
$node->expressions = array($expr); | |
while ($this->eat($this->_comma)) { | |
$node->expressions[] = $this->parseMaybeAssign($noIn); | |
} | |
return $this->finishNode($node); | |
} | |
return $expr; | |
} | |
public function parseMaybeAssign($noIn = false) { | |
$left = $this->parseMaybeConditional($noIn); | |
if (isset($this->tokType['isAssign'])) { | |
$node = $this->startNodeFrom($left, 'AssignmentExpression'); | |
$node->operator = $this->tokVal; | |
$node->left = $left; | |
$this->next(); | |
$node->right = $this->parseMaybeAssign($noIn); | |
$this->checkLVal($left); | |
return $this->finishNode($node); | |
} | |
return $left; | |
} | |
public function parseMaybeConditional($noIn) { | |
$expr = $this->parseExprOps($noIn); | |
if ($this->eat($this->_question)) { | |
$node = $this->startNodeFrom($expr, 'ConditionalExpression'); | |
$node->test = $expr; | |
$node->consequent = $this->parseExpression(true); | |
$this->expect($this->_colon); | |
$node->alternate = $this->parseExpression(true, $noIn); | |
return $this->finishNode($node); | |
} | |
return $expr; | |
} | |
public function parseExprOps($noIn) { | |
return $this->parseExprOp($this->parseMaybeUnary($noIn), -1, $noIn); | |
} | |
protected function parseExprOp(\JavaScript\AST\Node $left, $minPrec, $noIn) { | |
$prec = isset($this->tokType['binop']) ? $this->tokType['binop'] : null; | |
if ($prec !== null && (!$noIn || $this->tokType !== $this->_in)) { | |
if ($prec > $minPrec) { | |
$node = $this->startNodeFrom( | |
$left, | |
$this->tokVal === '&&' || $this->tokVal === '||' ? 'LogicalExpression' : 'BinaryExpression' | |
); | |
$node->left = $left; | |
$node->operator = $this->tokVal; | |
$this->next(); | |
$node->right = $this->parseExprOp($this->parseMaybeUnary($noIn), $prec, $noIn); | |
$node1 = $this->finishNode($node); | |
return $this->parseExprOp($node1, $minPrec, $noIn); | |
} | |
} | |
return $left; | |
} | |
protected function parseMaybeUnary($noIn) { | |
if (isset($this->tokType['prefix'])) { | |
$update = isset($this->tokType['isUpdate']); | |
$node = $this->startNode($update ? 'UpdateExpression' : 'UnaryExpression'); | |
$node->operator = $this->tokVal; | |
$node->prefix = true; | |
$this->next(); | |
$node->argument = $this->parseMaybeUnary($noIn); | |
if ($update) { | |
$this->checkLVal($node->argument); | |
} elseif ($this->strict && $node->operator === 'delete' && | |
$node->argument->type === 'Identifier') { | |
$this->raise($node->start, 'Deleting local variable in strict mode'); | |
} | |
return $this->finishNode($node); | |
} | |
$expr = $this->parseExprSubscripts(); | |
while (isset($this->tokType['postfix']) && !$this->canInsertSemicolon()) { | |
$node = $this->startNodeFrom($expr, 'UpdateExpression'); | |
$node->operator = $this->tokVal; | |
$node->prefix = false; | |
$node->argument = $expr; | |
$this->checkLVal($expr); | |
$this->next(); | |
$expr = $this->finishNode($node); | |
} | |
return $expr; | |
} | |
protected function parseExprSubscripts() { | |
return $this->parseSubscripts($this->parseExprAtom()); | |
} | |
protected function parseSubscripts(\JavaScript\AST\Node $base, $noCalls = false) { | |
if ($this->eat($this->_dot)) { | |
$node = $this->startNodeFrom($base, 'MemberExpression'); | |
$node->object = $base; | |
$node->property = $this->parseIdent(true); | |
$node->computed = false; | |
return $this->parseSubscripts($this->finishNode($node), $noCalls); | |
} | |
if ($this->eat($this->_bracketL)) { | |
$node = $this->startNodeFrom($base, 'MemberExpression'); | |
$node->object = $base; | |
$node->property = $this->parseExpression(); | |
$node->computed = true; | |
$this->expect($this->_bracketR); | |
return $this->parseSubscripts($this->finishNode($node), $noCalls); | |
} | |
if (!$noCalls && $this->eat($this->_parenL)) { | |
$node = $this->startNodeFrom($base, 'CallExpression'); | |
$node->callee = $base; | |
$node->arguments = $this->parseExprList($this->_parenR, false); | |
return $this->parseSubscripts($this->finishNode($node), $noCalls); | |
} | |
return $base; | |
} | |
protected function parseExprAtom() { | |
switch ($this->tokType) { | |
case $this->_this: | |
$node = $this->startNode(new \JavaScript\AST\Node\ThisExpression); | |
$this->next(); | |
return $this->finishNode($node); | |
case $this->_name: | |
return $this->parseIdent(); | |
case $this->_num: case $this->_string: case $this->_regexp: | |
$node = $this->startNode(new \JavaScript\AST\Node\Literal); | |
$node->value = $this->tokVal; | |
$this->next(); | |
return $this->finishNode($node); | |
case $this->_null: case $this->_true: case $this->_false: | |
$node = $this->startNode(new \JavaScript\AST\Node\Literal); | |
$node->value = $this->tokType['atomValue']; | |
$this->next(); | |
return $this->finishNode($node); | |
case $this->_parenL: | |
$this->next(); | |
$val = $this->parseExpression(); | |
$this->expect($this->_parenR); | |
return $val; | |
case $this->_bracketL: | |
$node = $this->startNode(new \JavaScript\AST\Node\ArrayExpression); | |
$this->next(); | |
$node->elements = $this->parseExprList($this->_bracketR, true, true); | |
return $this->finishNode($node); | |
case $this->_braceL: | |
return $this->parseObj(); | |
case $this->_function: | |
$node = $this->startNode(new \JavaScript\AST\Node\FunctionExpression); | |
$this->next(); | |
return $this->parseFunction($node, false); | |
case $this->_new: | |
return $this->parseNew(); | |
default: | |
$this->unexpected(); | |
} | |
} | |
protected function parseNew() { | |
$node = $this->startNode(new \JavaScript\AST\Node\NewExpression); | |
$this->next(); | |
$node->callee = $this->parseSubscripts($this->parseExprAtom(false), true); | |
if ($this->eat($this->_parenL)) { | |
$node->arguments = $this->parseExprList($this->_parenR, false); | |
} else { | |
$node->arguments = array(); | |
} | |
return $this->finishNode($node); | |
} | |
protected function parseObj() { | |
$node = $this->startNode(new \JavaScript\AST\Node\ObjectExpression); | |
$first = true; | |
$sawGetSet = false; | |
$node->properties = array(); | |
$this->next(); | |
while (!$this->eat($this->_braceR)) { | |
if (!$first) { | |
$this->expect($this->_comma); | |
if ($this->options['allowTrailingCommas'] && $this->eat($this->_braceR)) { | |
break; | |
} | |
} else { | |
$first = false; | |
} | |
$prop = \JavaScript\AST\Node::get(array('key' => $this->parsePropertyName())); | |
$isGetSet = false; | |
if ($this->eat($this->_colon)) { | |
$prop->value = $this->parseExpression(true); | |
$prop->kind = 'init'; | |
} elseif ($this->options['ecmaVersion'] >= 5 && $prop->key->type === 'Identifier' && | |
($prop->key->name === 'get' || $prop->key->name === 'set')) { | |
$isGetSet = $sawSetGet = true; | |
$prop->kind = $prop->key->name; | |
$prop->key = $this->parsePropertyName(); | |
//if (!($this->tokType === $this->_parenL)) { | |
// $this->unexpected(); | |
//} | |
$prop->value = $this->parseFunction($this->startNode(new \JavaScript\AST\Node\FunctionExpression), false); | |
} else { | |
$this->unexpected(); | |
} | |
if ($prop->key->type === 'Identifier' && ($this->strict || $sawGetSet)) { | |
foreach ($node->properties as $other) { | |
if ($other->key->name === $prop->key->name) { | |
$conflict = $prop->kind === $other->kind || $isGetSet && $other->kind === 'init' || | |
$prop->kind === 'init' && ($other->kind === 'init' || $other->kind === 'set'); | |
if ($conflict && !$this->strict && $prop->kind === 'init' && $other->kind === 'init') { | |
$conflict = false; | |
} | |
if ($conflict) { | |
$this->raise($prop->key->start, 'Redefinition of property'); | |
} | |
} | |
} | |
} | |
$node->properties[] = $prop; | |
} | |
return $this->finishNode($node); | |
} | |
protected function parsePropertyName() { | |
if ($this->tokType === $this->_num || $this->tokType === $this->_string) { | |
return $this->parseExprAtom(); | |
} | |
return $this->parseIdent(true); | |
} | |
protected function parseFunction(\JavaScript\AST\Node $node, $isStatement = false) { | |
if ($this->tokType === $this->_name) { | |
$node->id = $this->parseIdent(); | |
} elseif ($isStatement) { | |
$this->unexpected(); | |
} else { | |
$node->id = null; | |
} | |
$node->params = array(); | |
$first = true; | |
$this->expect($this->_parenL); | |
while (!$this->eat($this->_parenR)) { | |
if (!$first) { | |
$this->expect($this->_comma); | |
} else { | |
$first = false; | |
} | |
$node->params[] = $this->parseIdent(); | |
} | |
$oldInFunc = $this->inFunction; | |
$oldLabels = $this->labels; | |
$this->inFunction = true; | |
$this->labels = array(); | |
$node->body = $this->parseBlock(true); | |
$this->inFunction = $oldInFunc; | |
$this->labels = $oldLabels; | |
if ($this->strict || $node->body->body && $this->isUseStrict($node->body->body[0])) { | |
for ($i = $node->id ? -1 : 0; $i < count($node->params); ++$i) { | |
$id = $i < 0 ? $node->id : $node->params[$i]; | |
if ($this->isStrictReservedWord($id->name) || $this->isStrictBadWord($id->name)) { | |
$this->raise($id->start, 'Defining "' . $id->name . '" in strict mode'); | |
} | |
if ($i >= 0) { | |
for ($j = 0; $j < $i; ++$j) { | |
if ($id->name === $node->params[$j]->name) { | |
$this->raise($id->start, 'Argument name clash in strict mode'); | |
} | |
} | |
} | |
} | |
} | |
return $this->finishNode($node); | |
} | |
protected function parseExprList($close, $allowTrailingComma = false, $allowEmpty = false) { | |
$elts = array(); | |
$first = true; | |
while (!$this->eat($close)) { | |
if (!$first) { | |
$this->expect($this->_comma); | |
if ($allowTrailingComma && $this->options['allowTrailingCommas'] && $this->eat($close)) { | |
break; | |
} | |
} else { | |
$first = false; | |
} | |
if ($allowEmpty && $this->tokType === $this->_comma) { | |
$elts[] = null; | |
} else { | |
$elts[] = $this->parseExpression(true); | |
} | |
} | |
return $elts; | |
} | |
protected function parseIdent($liberal = false) { | |
$node = $this->startNode(new \JavaScript\AST\Node\Identifier); | |
if ($this->tokType === $this->_name) { | |
$node->name = $this->tokVal; | |
} elseif ($liberal && isset($this->tokType['keyword'])) { | |
$node->name = $this->tokType['keyword']; | |
} else { | |
$this->unexpected(); | |
} | |
$this->next(); | |
return $this->finishNode($node); | |
} | |
} | |
namespace JavaScript\AST; | |
class Node implements \JavaScript\AST\Tree\Node { | |
public $type; | |
public $start = 0; | |
public $end; | |
public static function get(array $def = array()) { | |
$node = new static; | |
foreach($def as $k => $v) { | |
$node->$k = $v; | |
} | |
return $node; | |
} | |
public function __get($key) { | |
isset($key); | |
return null; | |
} | |
} | |
namespace JavaScript\AST\Tree; | |
interface Node { } | |
interface Pattern extends Node { } | |
interface Program extends Node { } | |
interface Lambda extends Node { } | |
interface Statement extends Node { } | |
interface EmptyStatement extends Statement { } | |
interface BlockStatement extends Statement { } | |
interface ExpressionStatement extends Statement { } | |
interface IfStatement extends Statement { } | |
interface LabeledStatement extends Statement { } | |
interface BreakStatement extends Statement { } | |
interface ContinueStatement extends Statement { } | |
interface WithStatement extends Statement { } | |
interface SwitchStatement extends Statement { } | |
interface ReturnStatement extends Statement { } | |
interface ThrowStatement extends Statement { } | |
interface TryStatement extends Statement { } | |
interface WhileStatement extends Statement { } | |
interface DoWhileStatement extends Statement { } | |
interface ForStatement extends Statement { } | |
interface ForInStatement extends Statement { } | |
interface DebuggerStatement extends Statement { } | |
interface Declaration extends Statement { } | |
interface FunctionDeclaration extends Lambda, Declaration { } | |
interface VariableDeclaration extends Declaration { } | |
interface VariableDeclarator extends Node { } | |
interface Expression extends Node, Pattern { } | |
interface ThisExpression extends Expression { } | |
interface ArrayExpression extends Expression { } | |
interface ObjectExpression extends Expression { } | |
interface FunctionExpression extends Lambda, Expression { } | |
interface SequenceExpression extends Expression { } | |
interface UnaryExpression extends Expression { } | |
interface BinaryExpression extends Expression { } | |
interface AssignmentExpression extends Expression { } | |
interface UpdateExpression extends Expression { } | |
interface LogicalExpression extends Expression { } | |
interface ConditionalExpression extends Expression { } | |
interface NewExpression extends Expression { } | |
interface CallExpression extends Expression { } | |
interface MemberExpression extends Expression { } | |
interface ObjectPattern extends Pattern { } | |
interface ArrayPattern extends Pattern { } | |
interface SwitchCase extends Node { } | |
interface CatchClause extends Node { } | |
interface Identifier extends Node, Expression { } | |
interface Literal extends Node, Expression { } | |
namespace JavaScript\AST\Node; | |
class Program extends \JavaScript\AST\Node implements \JavaScript\AST\Tree\Program { | |
public $type = 'Program'; | |
public $body = array(); | |
} | |
class Identifier extends \JavaScript\AST\Node implements \JavaScript\AST\Tree\Identifier { | |
public $type = 'Identifier'; | |
public $name; | |
} | |
class Expression extends \JavaScript\AST\Node implements \JavaScript\AST\Tree\Expression { | |
} | |
class ThisExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\ThisExpression { | |
public $type = 'ThisExpression'; | |
} | |
class Literal extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\Literal { | |
public $type = 'Literal'; | |
public $value; | |
} | |
class NewExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\NewExpression { | |
public $type = 'NewExpression'; | |
public $callee; | |
public $arguments = array(); | |
} | |
class ObjectExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\ObjectExpression { | |
public $type = 'ObjectExpression'; | |
public $properties = array(); | |
} | |
class ArrayExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\ArrayExpression { | |
public $type = 'ArrayExpression'; | |
public $elements = array(); | |
} | |
class SequenceExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\SequenceExpression { | |
public $type = 'SequenceExpression'; | |
public $expressions = array(); | |
} | |
class VariableDeclarator extends \JavaScript\AST\Node implements \JavaScript\AST\Tree\VariableDeclarator { | |
public $type = 'VariableDeclarator'; | |
public $id; | |
public $init; | |
} | |
class FunctionExpression extends \JavaScript\AST\Node\Expression implements \JavaScript\AST\Tree\FunctionExpression { | |
public $type = 'FunctionExpression'; | |
public $id; | |
public $params = array(); | |
public $body; | |
} | |
class Statement extends \JavaScript\AST\Node implements \JavaScript\AST\Tree\Statement { | |
} | |
class FunctionDeclaration extends \JavaScript\AST\Node\Statement implements \JavaScript\AST\Tree\FunctionDeclaration { | |
public $type = 'FunctionExpression'; | |
public $id; | |
public $params = array(); | |
public $body; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment