Merge branch 'devel' into minifier_js_update

This commit is contained in:
Brian Mulhall
2020-04-03 22:48:23 -05:00
19 changed files with 36 additions and 2450 deletions

View File

@@ -102,7 +102,7 @@ Pull requests that contain `dev_bundle` changes will be noted by repo collaborat
The Meteor core is best documented within the code itself, however, many components also have a `README.md` in their respective directories.
Some compartmentalized portions of Meteor are broken into packages ([see a list of packages](packages/)) and they almost all have a `README.md` within their directory. For example, [`ddp`](packages/ddp/README.md), [`ecmascript`](packages/ecmascript/README.md) and [`tinytest`](packages/tinytest/README.md).
Some compartmentalized portions of Meteor are broken into packages ([see a list of packages](packages/)) and almost all of them have a `README.md` within their directory. For example, [`ddp`](packages/ddp/README.md), [`ecmascript`](packages/ecmascript/README.md) and [`tinytest`](packages/tinytest/README.md).
For the rest, try looking nearby for a `README.md`. For example, [`isobuild`](tools/isobuild/README.md) or [`cordova`](tools/cordova/README.md).
@@ -110,10 +110,10 @@ For the rest, try looking nearby for a `README.md`. For example, [`isobuild`](t
### Test against the local meteor copy
When running any of tests, be sure run them against the checked-out copy of Meteor instead of
When running any tests, be sure to run them against the checked-out copy of Meteor instead of
the globally-installed version. This means ensuring that the command is `path-to-meteor-checkout/meteor` and not just `meteor`.
This is important so that tests are run against the version in development and not the stable (installed) Meteor release.
This is important so that tests are run against your local development version and not the stable (installed) Meteor release.
### Running tests on Meteor core
@@ -142,7 +142,7 @@ While TinyTest and the `test-packages` command can be used to test internal Mete
#### Listing available tests
To see a list of the tests which are included in the self-test system, list them with the `--list` option:
To see a list of tests included in the self-test system, use the `--list` option:
./meteor self-test --list

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2011 - 2019 Meteor Software Ltd.
Copyright (c) 2011 - present Meteor Software Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -3,7 +3,7 @@
***
This package is the heart of Meteor's Hot Code Push functionality. It has a
client component and a server component component. The client component uses a
client component and a server component. The client component uses a
DDP API provided by the server to subscribe to the version ID of the most recent
build of the app's client. When it sees that a new version is available, it uses
the [reload](https://atmospherejs.com/meteor/reload) package (if included in the

View File

@@ -15,7 +15,7 @@
// version available on the server.
//
// `Autoupdate.newClientAvailable` is a reactive data source which
// becomes `true` if there is a new version of the client is available on
// becomes `true` if a new version of the client is available on
// the server.
//
// This package doesn't implement a soft code reload process itself,

View File

@@ -2,4 +2,6 @@
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/jsparse) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/jsparse)
***
This is an internal Meteor package.
This internal Meteor package is now unnecessary and has been deprecated. To
continue to use a working version of this package, please pin your package
version to 1.0.10 (e.g. meteor add jsparse@=1.0.10)

View File

@@ -0,0 +1,6 @@
console.warn(
'The "jsparse" package is now unnecessary and has been deprecated.\n'
+ '\n'
+ 'To continue to use a working version of this package, please pin your\n'
+ 'package version to 1.0.10 (e.g. meteor add jsparse@=1.0.10).\n'
);

View File

@@ -0,0 +1,8 @@
Package.describe({
summary: "(Deprecated) Full-featured JavaScript parser",
version: "2.0.0"
});
Package.onUse(function (api) {
api.addFiles('deprecation_notice.js', 'server');
});

View File

@@ -2,4 +2,6 @@
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/meyerweb-reset) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/meyerweb-reset)
***
This is an internal Meteor package.
This internal Meteor package is now unnecessary and has been deprecated. To
continue to use a working version of this package, please pin your package
version to 1.0.7 (e.g. meteor add meyerweb-reset@=1.0.7)

View File

@@ -0,0 +1,6 @@
console.warn(
'The "meyerweb-reset" package is now unnecessary and has been deprecated.\n',
'\n',
'To continue to use a working version of this package, please pin your\n',
'package version to 1.0.7 (e.g. meteor add meyerweb-reset@=1.0.7).\n'
);

View File

@@ -2,10 +2,10 @@
// files. I've marked it internal because I'm not sure if we want to
// encourage this pattern. Maybe another solution would be better.
Package.describe({
summary: "reset.css v2.0 from http://meyerweb.com/eric/tools/css/reset/",
version: "1.0.7"
summary: "(Deprecated) reset.css v2.0 from http://meyerweb.com/eric/tools/css/reset/",
version: "2.0.0"
});
Package.onUse(function (api) {
api.addFiles("reset.css", "client");
api.addFiles('deprecation_notice.js', 'server');
});

View File

@@ -1,420 +0,0 @@
var regexEscape = function (str) {
return str.replace(/[\][^$\\.*+?(){}|]/g, '\\$&');
};
// Adapted from source code of http://xregexp.com/plugins/#unicode
var unicodeCategories = {
Ll: "0061-007A00B500DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02AF037103730377037B-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F05210523052505270561-05871D00-1D2B1D6B-1D771D79-1D9A1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF7210A210E210F2113212F21342139213C213D2146-2149214E21842C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7B2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2CF32D00-2D252D272D2DA641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA661A663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76FA771-A778A77AA77CA77FA781A783A785A787A78CA78EA791A793A7A1A7A3A7A5A7A7A7A9A7FAFB00-FB06FB13-FB17FF41-FF5A",
Lm: "02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D6A1D781D9B-1DBF2071207F2090-209C2C7C2C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A7F8A7F9A9CFAA70AADDAAF3AAF4FF70FF9EFF9F",
Lo: "00AA00BA01BB01C0-01C3029405D0-05EA05F0-05F20620-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150840-085808A008A2-08AC0904-0939093D09500958-09610972-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E450E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10D0-10FA10FD-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317DC1820-18421844-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C771CE9-1CEC1CEE-1CF11CF51CF62135-21382D30-2D672D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE3006303C3041-3096309F30A1-30FA30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A014A016-A48CA4D0-A4F7A500-A60BA610-A61FA62AA62BA66EA6A0-A6E5A7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2AA00-AA28AA40-AA42AA44-AA4BAA60-AA6FAA71-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADBAADCAAE0-AAEAAAF2AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF66-FF6FFF71-FF9DFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",
Lt: "01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC",
Lu: "0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E05200522052405260531-055610A0-10C510C710CD1E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F214521832C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CED2CF2A640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA660A662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BA78DA790A792A7A0A7A2A7A4A7A6A7A8A7AAFF21-FF3A",
Mc: "0903093B093E-09400949-094C094E094F0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A1B1A551A571A611A631A641A6D-1A721B041B351B3B1B3D-1B411B431B441B821BA11BA61BA71BAA1BAC1BAD1BE71BEA-1BEC1BEE1BF21BF31C24-1C2B1C341C351CE11CF21CF3302E302FA823A824A827A880A881A8B4-A8C3A952A953A983A9B4A9B5A9BAA9BBA9BD-A9C0AA2FAA30AA33AA34AA4DAA7BAAEBAAEEAAEFAAF5ABE3ABE4ABE6ABE7ABE9ABEAABEC",
Mn: "0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065F067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0859-085B08E4-08FE0900-0902093A093C0941-0948094D0951-095709620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630DCA0DD2-0DD40DD60E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F71-0F7E0F80-0F840F860F870F8D-0F970F99-0FBC0FC6102D-10301032-10371039103A103D103E10581059105E-10601071-1074108210851086108D109D135D-135F1712-17141732-1734175217531772177317B417B517B7-17BD17C617C9-17D317DD180B-180D18A91920-19221927192819321939-193B1A171A181A561A58-1A5E1A601A621A65-1A6C1A73-1A7C1A7F1B00-1B031B341B36-1B3A1B3C1B421B6B-1B731B801B811BA2-1BA51BA81BA91BAB1BE61BE81BE91BED1BEF-1BF11C2C-1C331C361C371CD0-1CD21CD4-1CE01CE2-1CE81CED1CF41DC0-1DE61DFC-1DFF20D0-20DC20E120E5-20F02CEF-2CF12D7F2DE0-2DFF302A-302D3099309AA66FA674-A67DA69FA6F0A6F1A802A806A80BA825A826A8C4A8E0-A8F1A926-A92DA947-A951A980-A982A9B3A9B6-A9B9A9BCAA29-AA2EAA31AA32AA35AA36AA43AA4CAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1AAECAAEDAAF6ABE5ABE8ABEDFB1EFE00-FE0FFE20-FE26",
Nd: "0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19D91A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",
Nl: "16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF",
Pc: "005F203F20402054FE33FE34FE4D-FE4FFF3F"
};
var unicodeClass = function (abbrev) {
return '[' +
unicodeCategories[abbrev].replace(/[0-9A-F]{4}/ig, "\\u$&") + ']';
};
// See ECMA-262 spec, 3rd edition, section 7
// Section 7.2
// Match one or more characters of whitespace, excluding line terminators.
// Do this by matching reluctantly, stopping at a non-dot (line terminator
// or end of string) or a non-whitespace.
// We are taking advantage of the fact that we are parsing JS from JS in
// regexes like this by "passing through" the spec's definition of whitespace,
// which is the same in regexes and the lexical grammar.
var rWhiteSpace = /[^\S\u000A\u000D\u2028\u2029]+/g;
// Section 7.3
// Match one line terminator. Same as (?!.)[\s\S] but more explicit.
var rLineTerminator = /[\u000A\u000D\u2028\u2029]/g;
// Section 7.4
// Match one multi-line comment.
// [\s\S] is shorthand for any character, including newlines.
// The *? reluctant qualifier makes this easy.
var rMultiLineComment = /\/\*[\s\S]*?\*\//g;
// Match one single-line comment, not including the line terminator.
var rSingleLineComment = /\/\/.*/g;
// Section 7.6
// Match one or more characters that can start an identifier.
// This is IdentifierStart+.
var rIdentifierPrefix = new RegExp(
"([a-zA-Z$_]+|\\\\u[0-9a-fA-F]{4}|" +
[unicodeClass('Lu'), unicodeClass('Ll'), unicodeClass('Lt'),
unicodeClass('Lm'), unicodeClass('Lo'), unicodeClass('Nl')].join('|') +
")+", 'g');
// Match one or more characters that can continue an identifier.
// This is (IdentifierPart and not IdentifierStart)+.
// To match a full identifier, match rIdentifierPrefix, then
// match rIdentifierMiddle followed by rIdentifierPrefix until they both fail.
var rIdentifierMiddle = new RegExp(
"([0-9]|" + [unicodeClass('Mn'), unicodeClass('Mc'), unicodeClass('Nd'),
unicodeClass('Pc')].join('|') + ")+", 'g');
// Section 7.7
// Match one punctuator (except for division punctuators).
var rPunctuator = new RegExp(
regexEscape("{ } ( ) [ ] . ; , < > <= >= == != === !== + - * % ++ -- << >> "+
">>> & | ^ ! ~ && || ? : = += -= *= %= <<= >>= >>>= &= |= ^=")
// sort from longest to shortest so that we don't match '==' for '===' and
// '*' for '*=', etc.
.split(' ').sort(function (a,b) { return b.length - a.length; })
.join('|'), 'g');
var rDivPunctuator = /\/=?/g;
// Section 7.8.3
var rHexLiteral = /0[xX][0-9a-fA-F]+(?!\w)/g;
var rOctLiteral = /0[0-7]+(?!\w)/g; // deprecated
var rDecLiteral =
/(((0|[1-9][0-9]*)(\.[0-9]*)?)|\.[0-9]+)([Ee][+-]?[0-9]+)?(?!\w)/g;
// Section 7.8.4
var rStringQuote = /["']/g;
// Match one or more characters besides quotes, backslashes, or line ends
var rStringMiddle = /(?=.)[^"'\\]+?((?!.)|(?=["'\\]))/g;
// Match one escape sequence, including the backslash.
var rEscapeSequence =
/\\(['"\\bfnrtv]|0(?![0-9])|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|(?=.)[^ux0-9])/g;
// Match one ES5 line continuation
var rLineContinuation =
/\\(\r\n|[\u000A\u000D\u2028\u2029])/g;
// Section 7.8.5
// Match one regex literal, including slashes, not including flags.
// Support unescaped '/' in character classes, per 5th ed.
// For example: `/[/]/` will match the string `"/"`.
//
// Explanation of regex:
// - Match `/` not followed by `/` or `*`
// - Match one or more of any of these:
// - Backslash followed by one non-newline
// - One non-newline, not `[` or `\` or `/`
// - A character class, beginning with `[` and ending with `]`.
// In the middle is zero or more of any of these:
// - Backslash followed by one non-newline
// - One non-newline, not `]` or `\`
// - Match closing `/`
var rRegexLiteral =
/\/(?![*\/])(\\.|(?=.)[^\[\/\\]|\[(\\.|(?=.)[^\]\\])*\])+\//g;
var rRegexFlags = /[a-zA-Z]*/g;
var rDecider =
/((?=.)\s)|(\/[\/\*]?)|([\][{}();,<>=!+*%&|^~?:-]|\.(?![0-9]))|([\d.])|(["'])|(.)|([\S\s])/g;
var keywordLookup = {
' break': 'KEYWORD',
' case': 'KEYWORD',
' catch': 'KEYWORD',
' continue': 'KEYWORD',
' debugger': 'KEYWORD',
' default': 'KEYWORD',
' delete': 'KEYWORD',
' do': 'KEYWORD',
' else': 'KEYWORD',
' finally': 'KEYWORD',
' for': 'KEYWORD',
' function': 'KEYWORD',
' if': 'KEYWORD',
' in': 'KEYWORD',
' instanceof': 'KEYWORD',
' new': 'KEYWORD',
' return': 'KEYWORD',
' switch': 'KEYWORD',
' this': 'KEYWORD',
' throw': 'KEYWORD',
' try': 'KEYWORD',
' typeof': 'KEYWORD',
' var': 'KEYWORD',
' void': 'KEYWORD',
' while': 'KEYWORD',
' with': 'KEYWORD',
' false': 'BOOLEAN',
' true': 'BOOLEAN',
' null': 'NULL'
};
var makeSet = function (array) {
var s = {};
for (var i = 0, N = array.length; i < N; i++)
s[array[i]] = true;
return s;
};
var nonTokenTypes = makeSet('WHITESPACE COMMENT NEWLINE EOF ERROR'.split(' '));
var punctuationBeforeDivision = makeSet('] ) } ++ --'.split(' '));
var keywordsBeforeDivision = makeSet('this'.split(' '));
var guessIsDivisionPermittedAfterToken = function (tok) {
// Figure out if a '/' character should be interpreted as division
// rather than the start of a regular expression when it follows the
// token, which must be a token lexeme per isToken().
// The beginning of section 7 of the spec briefly
// explains what's going on; basically the lexical grammar can't
// distinguish, for example, `e/f/g` (division) from `e=/f/g`
// (assignment of a regular expression), among many other variations.
//
// THIS IS ONLY A HEURISTIC, though it will rarely fail.
// Here are the two cases I know of where help from the parser is needed:
// - if (foo)
// /ba/.test("banana") && console.log("matches");
// (Close paren of a control structure before a statement starting with
// a regex literal. Starting a statement with a regex literal is
// unusual, of course, because it's hard to have a side effect.)
// - ++ /foo/.abc
// (Prefix `++` or `--` before an expression starting with a regex
// literal. This will run but I can't see any use for it.)
switch (tok.type()) {
case "PUNCTUATION":
// few punctuators can end an expression, but e.g. `)`
return !! punctuationBeforeDivision[tok.text()];
case "KEYWORD":
// few keywords can end an expression, but e.g. `this`
return !! keywordsBeforeDivision[tok.text()];
case "IDENTIFIER":
return true;
default: // literal
return true;
}
};
////////// PUBLIC API
var Lexeme = function (pos, type, text) {
this._pos = pos;
this._type = type;
this._text = text;
};
Lexeme.prototype.startPos = function () {
return this._pos;
};
Lexeme.prototype.endPos = function () {
return this._pos + this._text.length;
};
Lexeme.prototype.type = function () {
return this._type;
};
Lexeme.prototype.text = function () {
return this._text;
};
Lexeme.prototype.isToken = function () {
return ! nonTokenTypes[this._type];
};
Lexeme.prototype.isError = function () {
return this._type === "ERROR";
};
Lexeme.prototype.isEOF = function () {
return this._type === "EOF";
};
Lexeme.prototype.prev = function () {
return this._prev;
};
Lexeme.prototype.next = function () {
return this._next;
};
Lexeme.prototype.toString = function () {
return this.isError() ? "ERROR" :
this.isEOF() ? "EOF" : "`" + this.text() + "`";
};
// Create a Lexer for the given string of JavaScript code.
//
// A lexer keeps a pointer `pos` into the string that is
// advanced when you ask for the next lexeme with `next()`.
//
// XXXXX UPDATE DOCS
// Properties:
// code: Original JavaScript code string.
// pos: Current index into the string. You can assign to it
// to continue lexing from a different position. After
// calling next(), it is the ending index of the most
// recent lexeme.
// lastPos: The starting index of the most recent lexeme.
// Equal to `pos - text.length`.
// text: Text of the last lexeme as a string.
// type: Type of the last lexeme, as returned by `next()`.
// divisionPermitted: Whether a '/' character should be interpreted
// as division rather than the start of a regular expression.
// This flag is set automatically during lexing based on the
// previous token (i.e. the most recent token lexeme), but
// it is technically only a heuristic.
// Thie flag can be read and set manually to affect the
// parsing of the next token.
JSLexer = function (code) {
this.code = code;
this.pos = 0;
this.divisionPermitted = false;
this.lastLexeme = null;
};
JSLexer.Lexeme = Lexeme;
// XXXX UPDATE DOCS
// Return the type of the next of lexeme starting at `pos`, and advance
// `pos` to the end of the lexeme. The text of the lexeme is available
// in `text`. The text is always the substring of `code` between the
// old and new values of `pos`. An "EOF" lexeme terminates
// the stream. "ERROR" lexemes indicate a bad input string. Out of all
// lexemes, only "EOF" has empty text, and it always has empty text.
// All others contain at least one character from the source code.
//
// Lexeme types:
// Literals: BOOLEAN, NULL, REGEX, NUMBER, STRING
// Whitespace-like: WHITESPACE, COMMENT, NEWLINE, EOF
// Other Tokens: IDENTIFIER, KEYWORD, PUNCTUATION
// ... and ERROR
JSLexer.prototype.next = function () {
var self = this;
var code = self.code;
var origPos = self.pos;
var divisionPermitted = self.divisionPermitted;
if (origPos > code.length)
throw new Error("out of range");
// Running regexes inside this function will move this local
// `pos` forward.
// When we commit to emitting a lexeme, we'll set self.pos
// based on it.
var pos = origPos;
// Emit a lexeme. Always called as `return lexeme(type)`.
var lexeme = function (type) {
// If `pos` hasn't moved, we consider this an error.
// This means that grammar cases that only run one regex
// or an alternation ('||') of regexes don't need to
// check for failure.
// This also guarantees that only EOF lexemes are empty.
if (pos === origPos && type !== 'EOF') {
type = 'ERROR';
pos = origPos + 1;
}
self.pos = pos;
var lex = new JSLexer.Lexeme(origPos, type, code.substring(origPos, pos));
if (self.lastLexeme) {
self.lastLexeme._next = lex;
lex._prev = self.lastLexeme;
}
self.lastLexeme = lex;
if (lex.isToken())
self.divisionPermitted = guessIsDivisionPermittedAfterToken(lex);
return lex;
};
if (pos === code.length)
return lexeme('EOF');
// Result of the regex match in the most recent call to `run`.
var match = null;
// Run a regex starting from `pos`, recording the end of the matched
// string in `pos` and the match data in `match`. The regex must have
// the 'g' (global) flag. If it doesn't match at `pos`, set `match`
// to null. The caller should expect the regex to match at `pos`, as
// failure is too expensive to run in a tight loop.
var run = function (regex) {
// Cause regex matching to start at `pos`.
regex.lastIndex = pos;
match = regex.exec(code);
// Simulate "sticky" matching by throwing out the match if it
// didn't match exactly at `pos`. If it didn't, we may have
// just searched the entire string.
if (match && (match.index !== pos))
match = null;
// Record the end position of the match back into `pos`.
// Avoid an IE7 bug where lastIndex is incremented when
// the match has 0 length.
if (match && match[0].length !== 0)
pos = regex.lastIndex;
return match;
};
// Decide which case of the grammar we are in based on one or two
// characters, then roll back `pos`.
run(rDecider);
pos = origPos;
// Grammar cases
if (match[1]) { // \s
run(rWhiteSpace);
return lexeme('WHITESPACE');
}
if (match[2]) { // one of //, /*, /
if (match[2] === '//') {
run(rSingleLineComment);
return lexeme('COMMENT');
}
if (match[2] === '/*') {
run(rMultiLineComment);
return lexeme(match ? 'COMMENT' : 'ERROR');
}
if (match[2] === '/') {
if (divisionPermitted) {
run(rDivPunctuator);
return lexeme('PUNCTUATION');
} else {
run(rRegexLiteral);
if (! match)
return lexeme('ERROR');
run(rRegexFlags);
return lexeme('REGEX');
}
}
}
if (match[3]) { // any other punctuation char
run(rPunctuator);
return lexeme(match ? 'PUNCTUATION' : 'ERROR');
}
if (match[4]) { // 0-9
run(rDecLiteral) || run(rHexLiteral) || run(rOctLiteral);
return lexeme(match ? 'NUMBER' : 'ERROR');
}
if (match[5]) { // " or '
run(rStringQuote);
var quote = match[0];
do {
run(rStringMiddle) || run(rEscapeSequence) ||
run(rLineContinuation) || run(rStringQuote);
} while (match && match[0] !== quote);
if (! (match && match[0] === quote))
return lexeme('ERROR');
return lexeme('STRING');
}
if (match[7]) { // non-dot (line terminator)
run(rLineTerminator);
return lexeme('NEWLINE');
}
// dot (any non-line-terminator)
run(rIdentifierPrefix);
// Use non-short-circuiting bitwise OR, '|', to always try
// both regexes in sequence, returning false only if neither
// matched.
while ((!! run(rIdentifierMiddle)) |
(!! run(rIdentifierPrefix))) { /*continue*/ }
var word = code.substring(origPos, pos);
return lexeme(keywordLookup[' '+word] || 'IDENTIFIER');
};
JSLexer.prettyOffset = function (code, pos) {
var codeUpToPos = code.substring(0, pos);
var startOfLine = codeUpToPos.lastIndexOf('\n') + 1;
var indexInLine = pos - startOfLine; // 0-based
var lineNum = codeUpToPos.replace(/[^\n]+/g, '').length + 1; // 1-based
return "line " + lineNum + ", offset " + indexInLine;
};

View File

@@ -1,21 +0,0 @@
Package.describe({
summary: "Full-featured JavaScript parser",
version: "1.0.10"
});
Package.onUse(function (api) {
api.export(['JSLexer', 'JSParser', 'ParseNode']);
api.addFiles(['lexer.js', 'parserlib.js', 'stringify.js', 'parser.js'],
['client', 'server']);
});
Package.onTest(function (api) {
api.use(['tinytest', 'underscore']);
api.use('jsparse', 'client');
api.addFiles('parser_tests.js',
// Test just on client for faster running; should run
// identically on server.
'client');
//['client', 'server']);
});

View File

@@ -1,838 +0,0 @@
///// JAVASCRIPT PARSER
// What we don't support from ECMA-262 5.1:
// - object literal trailing comma
// - object literal get/set
var expecting = Parser.expecting;
var assertion = Parsers.assertion;
var node = Parsers.node;
var or = Parsers.or;
var and = Parsers.and;
var not = Parsers.not;
var list = Parsers.list;
var seq = Parsers.seq;
var opt = Parsers.opt;
var constant = Parsers.constant;
var mapResult = Parsers.mapResult;
var makeSet = function (array) {
var s = {};
for (var i = 0, N = array.length; i < N; i++)
s[array[i]] = true;
return s;
};
JSParser = function (code, options) {
this.lexer = new JSLexer(code);
this.oldToken = null;
this.newToken = null;
this.pos = 0;
this.isLineTerminatorHere = false;
this.includeComments = false;
// the last COMMENT lexeme between oldToken and newToken
// that we've consumed, if any.
this.lastCommentConsumed = null;
options = options || {};
// pass {tokens:'strings'} to get strings for
// tokens instead of token objects
if (options.tokens === 'strings') {
this.tokenFunc = function (tok) {
return tok.text();
};
} else {
this.tokenFunc = function (tok) {
return tok;
};
}
// pass {includeComments: true} to include comments in the AST. For
// a comment to be included, it must occur where a series of
// statements could occur, and it must be preceded by only comments
// and whitespace on the same line.
if (options.includeComments) {
this.includeComments = true;
}
};
JSParser.prototype.consumeNewToken = function () {
var self = this;
var lexer = self.lexer;
self.oldToken = self.newToken;
self.isLineTerminatorHere = false;
var lex;
do {
lex = lexer.next();
if (lex.isError())
throw new Error("Bad token at " +
JSLexer.prettyOffset(lexer.code, lex.startPos()) +
", text `" + lex.text() + "`");
else if (lex.type() === "NEWLINE")
self.isLineTerminatorHere = true;
else if (lex.type() === "COMMENT" && ! /^.*$/.test(lex.text()))
// multiline comments containing line terminators count
// as line terminators.
self.isLineTerminatorHere = true;
} while (! lex.isEOF() && ! lex.isToken());
self.newToken = lex;
self.pos = lex.startPos();
self.lastCommentConsumed = null;
};
JSParser.prototype.getParseError = function (expecting, found) {
var msg = (expecting ? "Expected " + expecting : "Unexpected token");
if (this.oldToken)
msg += " after " + this.oldToken;
var pos = this.pos;
msg += " at " + JSLexer.prettyOffset(this.lexer.code, pos);
msg += ", found " + (found || this.newToken);
return new Error(msg);
};
JSParser.prototype.getSyntaxTree = function () {
var self = this;
self.consumeNewToken();
var NIL = new ParseNode('nil', []);
var booleanFlaggedParser = function (parserConstructFunc) {
return {
'false': parserConstructFunc(false),
'true': parserConstructFunc(true)
};
};
// Takes a space-separated list of either punctuation or keyword tokens
var lookAheadToken = function (tokens) {
var type = (/\w/.test(tokens) ? 'KEYWORD' : 'PUNCTUATION');
var textSet = makeSet(tokens.split(' '));
return expecting(
tokens.split(' ').join(', '),
assertion(function (t) {
return (t.newToken.type() === type && textSet[t.newToken.text()]);
}));
};
var lookAheadTokenType = function (type) {
return expecting(type, assertion(function (t) {
return t.newToken.type() === type;
}));
};
// Takes a space-separated list of either punctuation or keyword tokens
var token = function (tokens) {
var type = (/\w/.test(tokens) ? 'KEYWORD' : 'PUNCTUATION');
var textSet = makeSet(tokens.split(' '));
return new Parser(
tokens.split(' ').join(', '),
function (t) {
if (t.newToken.type() === type && textSet[t.newToken.text()]) {
t.consumeNewToken();
return self.tokenFunc(t.oldToken);
}
return null;
});
};
var tokenType = function (type) {
return new Parser(type, function (t) {
if (t.newToken.type() === type) {
t.consumeNewToken();
return self.tokenFunc(t.oldToken);
}
return null;
});
};
var noLineTerminatorHere = expecting(
'noLineTerminator', assertion(function (t) {
return ! t.isLineTerminatorHere;
}));
var nonLHSExpressionNames = makeSet(
'unary binary postfix ternary assignment comma'.split(' '));
var isExpressionLHS = function (exprNode) {
return ! nonLHSExpressionNames[exprNode.name];
};
// Like token, but marks tokens that need to defy the lexer's
// heuristic about whether the next '/' is a division or
// starts a regex.
var preSlashToken = function (text, divisionNotRegex) {
var inner = token(text);
return new Parser(
inner.expecting,
function (t) {
// temporarily set divisionPermitted,
// restoring it if we don't match.
var oldValue = t.lexer.divisionPermitted;
var result;
try {
t.lexer.divisionPermitted = divisionNotRegex;
result = inner.parse(t);
return result;
} finally {
if (! result)
t.lexer.divisionPermitted = oldValue;
}
});
};
// Mark some productions "lazy" to allow grammar circularity, i.e. accessing
// later parsers from earlier ones.
// These lazy versions will be replaced with real ones, which they will
// access when run.
var expressionMaybeNoIn = {
'false': Parsers.lazy(
'expression',
function () { return expressionMaybeNoIn[false]; }),
'true': Parsers.lazy(
'expression',
function () { return expressionMaybeNoIn[true]; })
};
var expression = expressionMaybeNoIn[false];
var assignmentExpressionMaybeNoIn = {
'false': Parsers.lazy(
'expression',
function () { return assignmentExpressionMaybeNoIn[false]; }),
'true': Parsers.lazy(
'expression',
function () { return assignmentExpressionMaybeNoIn[true]; })
};
var assignmentExpression = assignmentExpressionMaybeNoIn[false];
var functionBody = Parsers.lazy(
'statement', function () { return functionBody; });
var statement = Parsers.lazy(
'statement', function () { return statement; });
////
var arrayLiteral =
node('array',
seq(token('['),
opt(list(token(','))),
or(
lookAheadToken(']'),
list(
expecting(
'expression',
or(assignmentExpression,
// count a peeked-at ']' as an expression
// to support elisions at end, e.g.
// `[1,2,3,,,,,,]`.
lookAheadToken(']'))),
// list seperator is one or more commas
// to support elision
list(token(',')))),
token(']')));
// "IdentifierName" in ES5 allows reserved words, like in a property access
// or a key of an object literal.
// Put IDENTIFIER last so it shows up in the error message.
var identifierName = or(tokenType('NULL'), tokenType('BOOLEAN'),
tokenType('KEYWORD'), tokenType('IDENTIFIER'));
var propertyName = expecting('propertyName', or(
node('idPropName', identifierName),
node('numPropName', tokenType('NUMBER')),
node('strPropName', tokenType('STRING'))));
var nameColonValue = expecting(
'propertyName',
node('prop', seq(propertyName, token(':'), assignmentExpression)));
// Allow trailing comma in object literal, per ES5. Trailing comma
// must follow a `name:value`, that is, `{,}` is invalid.
//
// We can't just use a normal comma list(), because it will seize
// on the comma as a sign that the list continues. Instead,
// we specify a list of either ',' or nameColonValue, using positive
// and negative lookAheads to constrain the sequence. The grammar
// is ordered so that error messages will always say
// "Expected propertyName" or "Expected ," as appropriate, not
// "Expected ," when the look-ahead is negative or "Expected }".
var objectLiteral =
node('object',
seq(token('{'),
or(lookAheadToken('}'),
and(not(lookAheadToken(',')),
list(or(seq(token(','),
expecting('propertyName',
not(lookAheadToken(',')))),
seq(nameColonValue,
or(lookAheadToken('}'),
lookAheadToken(','))))))),
expecting('propertyName', token('}'))));
var functionMaybeNameRequired = booleanFlaggedParser(
function (nameRequired) {
return seq(token('function'),
(nameRequired ? tokenType('IDENTIFIER') :
or(tokenType('IDENTIFIER'),
and(lookAheadToken('('), constant(NIL)))),
token('('),
or(lookAheadToken(')'),
list(tokenType('IDENTIFIER'), token(','))),
token(')'),
token('{'),
functionBody,
token('}'));
});
var functionExpression = node('functionExpr',
functionMaybeNameRequired[false]);
var primaryOrFunctionExpression =
expecting('expression',
or(node('this', token('this')),
node('identifier', tokenType('IDENTIFIER')),
node('number', tokenType('NUMBER')),
node('boolean', tokenType('BOOLEAN')),
node('null', tokenType('NULL')),
node('regex', tokenType('REGEX')),
node('string', tokenType('STRING')),
node('parens',
seq(token('('), expression, token(')'))),
arrayLiteral,
objectLiteral,
functionExpression));
var dotEnding = seq(token('.'), identifierName);
var bracketEnding = seq(token('['), expression, token(']'));
var callArgs = seq(token('('),
or(lookAheadToken(')'),
list(assignmentExpression,
token(','))),
token(')'));
var newKeyword = token('new');
// This is a completely equivalent refactor of the spec's production
// for a LeftHandSideExpression.
//
// An lhsExpression is basically an expression that can serve as
// the left-hand-side of an assignment, though function calls and
// "new" invocation are included because they have the same
// precedence. Actually, the spec technically allows a function
// call to "return" a valid l-value, as in `foo(bar) = baz`,
// though no built-in or user-specifiable call has this property
// (it would have to be defined by a browser or other "host").
var lhsExpression = new Parser(
'expression',
function (t) {
// Accumulate all initial "new" keywords, not yet knowing
// if they have a corresponding argument list later.
var news = [];
var n;
while ((n = newKeyword.parse(t)))
news.push(n);
// Read the primaryOrFunctionExpression that will be the "core"
// of this lhsExpression. It is preceded by zero or more `new`
// keywords, and followed by any sequence of (...), [...],
// and .foo add-ons.
// if we have 'new' keywords, we are committed and must
// match an expression or error.
var result = primaryOrFunctionExpression.parseRequiredIf(t, news.length);
if (! result)
return null;
// Our plan of attack is to apply each dot, bracket, or call
// as we come across it. Whether a call is a `new` call depends
// on whether there are `new` keywords we haven't used. If so,
// we pop one off the stack.
var done = false;
while (! done) {
var r;
if ((r = dotEnding.parse(t))) {
result = new ParseNode('dot', [result].concat(r));
} else if ((r = bracketEnding.parse(t))) {
result = new ParseNode('bracket', [result].concat(r));
} else if ((r = callArgs.parse(t))) {
if (news.length)
result = new ParseNode('newcall', [news.pop(), result].concat(r));
else
result = new ParseNode('call', [result].concat(r));
} else {
done = true;
}
}
// There may be more `new` keywords than calls, which is how
// paren-less constructions (`new Date`) are parsed. We've
// already handled `new foo().bar()`, now handle `new new foo().bar`.
while (news.length)
result = new ParseNode('new', [news.pop(), result]);
return result;
});
var postfixToken = token('++ --');
var postfixLookahead = lookAheadToken('++ --');
var postfixExpression = expecting(
'expression',
mapResult(seq(lhsExpression,
opt(and(noLineTerminatorHere,
postfixLookahead,
postfixToken))),
function (v) {
if (v.length === 1)
return v[0];
return new ParseNode('postfix', v);
}));
var unaryExpression = Parsers.unary(
'unary', postfixExpression,
or(token('delete void typeof'),
preSlashToken('++ -- + - ~ !', false)));
// The "noIn" business is all to facilitate parsing
// of for-in constructs, though the cases that make
// this required are quite obscure.
// The `for(var x in y)` form is allowed to take
// an initializer for `x` (which is only useful for
// its side effects, or if `y` has no properties).
// So an example might be:
// `for(var x = a().b in c);`
// In this example, `var x = a().b` is parsed without
// the `in`, which would otherwise be part of the
// varDecl, using varDeclNoIn.
// Our binaryExpression is the spec's LogicalORExpression,
// which includes all the higher-precendence operators.
var binaryExpressionMaybeNoIn = booleanFlaggedParser(
function (noIn) {
// high to low precedence
var binaryOps = [token('* / %'),
token('+ -'),
token('<< >> >>>'),
or(token('< > <= >='),
noIn ? token('instanceof') :
token('instanceof in')),
token('== != === !=='),
token('&'),
token('^'),
token('|'),
token('&&'),
token('||')];
return expecting(
'expression',
Parsers.binaryLeft('binary', unaryExpression, binaryOps));
});
var binaryExpression = binaryExpressionMaybeNoIn[false];
var conditionalExpressionMaybeNoIn = booleanFlaggedParser(
function (noIn) {
return expecting(
'expression',
mapResult(
seq(binaryExpressionMaybeNoIn[noIn],
opt(seq(
token('?'),
assignmentExpression, token(':'),
assignmentExpressionMaybeNoIn[noIn]))),
function (v) {
if (v.length === 1)
return v[0];
return new ParseNode('ternary', v);
}));
});
var conditionalExpression = conditionalExpressionMaybeNoIn[false];
var assignOp = token('= *= /= %= += -= <<= >>= >>>= &= ^= |=');
assignmentExpressionMaybeNoIn = booleanFlaggedParser(
function (noIn) {
return new Parser(
'expression',
function (t) {
var r = conditionalExpressionMaybeNoIn[noIn].parse(t);
if (! r)
return null;
// Assignment is right-associative.
// Plan of attack: make a list of all the parts
// [expression, op, expression, op, ... expression]
// and then fold them up at the end.
var parts = [r];
var op;
while (isExpressionLHS(r) &&(op = assignOp.parse(t)))
parts.push(op,
conditionalExpressionMaybeNoIn[noIn].parseRequired(t));
var result = parts.pop();
while (parts.length) {
op = parts.pop();
var lhs = parts.pop();
result = new ParseNode('assignment', [lhs, op, result]);
}
return result;
});
});
assignmentExpression = assignmentExpressionMaybeNoIn[false];
expressionMaybeNoIn = booleanFlaggedParser(
function (noIn) {
return expecting(
'expression',
mapResult(
list(assignmentExpressionMaybeNoIn[noIn], token(',')),
function (v) {
if (v.length === 1)
return v[0];
return new ParseNode('comma', v);
}));
});
expression = expressionMaybeNoIn[false];
// STATEMENTS
var comment = node('comment', new Parser(null, function (t) {
if (! t.includeComments)
return null;
// Match a COMMENT lexeme between oldToken and newToken.
//
// This is an unusual Parser because it doesn't match and consume
// newToken, but instead uses the next()/prev() API on lexemes.
// It assumes it can walk the linked list backwards from newToken
// (though not necessarily forwards).
//
// We start at the last comment we've visited for this
// oldToken/newToken pair, if any, or else oldToken, or else the
// beginning of the token stream. We ignore comments that are
// preceded by any non-comment source code on the same line.
var lexeme = (t.lastCommentConsumed || t.oldToken || null);
if (! lexeme) {
// no oldToken, must be on first token. walk backwards
// to start with first lexeme (which may be a comment
// or whitespace)
lexeme = t.newToken;
while (lexeme.prev())
lexeme = lexeme.prev();
} else {
// start with lexeme after last token or comment consumed
lexeme = lexeme.next();
}
var seenNewline = ((! t.oldToken) || t.lastCommentConsumed || false);
while (lexeme !== t.newToken) {
var type = lexeme.type();
if (type === "NEWLINE") {
seenNewline = true;
} else if (type === "COMMENT") {
t.lastCommentConsumed = lexeme;
if (seenNewline)
return lexeme;
}
lexeme = lexeme.next();
}
return null;
}));
var statements = list(or(comment, statement));
// implements JavaScript's semicolon "insertion" rules
var maybeSemicolon = expecting(
'semicolon',
or(token(';'),
and(
or(
lookAheadToken('}'),
lookAheadTokenType('EOF'),
assertion(function (t) {
return t.isLineTerminatorHere;
})),
constant(new ParseNode(';', [])))));
var expressionStatement = node(
'expressionStmnt',
and(
not(or(lookAheadToken('{'), lookAheadToken('function'))),
seq(expression,
expecting('semicolon',
or(maybeSemicolon,
// allow presence of colon to terminate
// statement legally, for the benefit of
// expressionOrLabelStatement. Basically assume
// an implicit semicolon. This
// is safe because a colon can never legally
// follow a semicolon anyway.
and(lookAheadToken(':'),
constant(new ParseNode(';', []))))))));
// it's hard to parse statement labels, as in
// `foo: x = 1`, because we can't tell from the
// first token whether we are looking at an expression
// statement or a label statement. To work around this,
// expressionOrLabelStatement parses the expression and
// then rewrites the result if it is an identifier
// followed by a colon.
var labelColonAndStatement = seq(token(':'), statement);
var noColon = expecting(
'semicolon', not(lookAheadToken(':')));
var expressionOrLabelStatement = new Parser(
null,
function (t) {
var exprStmnt = expressionStatement.parse(t);
if (! exprStmnt)
return null;
var expr = exprStmnt.children[0];
var maybeSemi = exprStmnt.children[1];
if (expr.name !== 'identifier' ||
! (maybeSemi instanceof ParseNode)) {
// We either have a non-identifier expression or a present
// semicolon. This is not a label.
//
// Fail now if we are looking at a colon, causing an
// error message on input like `1+1:` of the same kind
// you'd get without statement label parsing.
noColon.parseRequired(t);
return exprStmnt;
}
var rest = labelColonAndStatement.parse(t);
if (! rest)
return exprStmnt;
return new ParseNode('labelStmnt',
[expr.children[0]].concat(rest));
});
var emptyStatement = node('emptyStmnt', token(';')); // required semicolon
var blockStatement = expecting('block', node('blockStmnt', seq(
token('{'), or(lookAheadToken('}'), statements),
token('}'))));
var varDeclMaybeNoIn = booleanFlaggedParser(function (noIn) {
return node(
'varDecl',
seq(tokenType('IDENTIFIER'),
opt(seq(token('='),
assignmentExpressionMaybeNoIn[noIn]))));
});
var varDecl = varDeclMaybeNoIn[false];
var variableStatement = node(
'varStmnt',
seq(token('var'), list(varDecl, token(',')),
maybeSemicolon));
// A paren that may be followed by a statement
// beginning with a regex literal.
var closeParenBeforeStatement = preSlashToken(')', false);
var ifStatement = node(
'ifStmnt',
seq(token('if'), token('('), expression,
closeParenBeforeStatement, statement,
opt(seq(token('else'), statement))));
var secondThirdClauses = expecting(
'semicolon',
and(lookAheadToken(';'),
seq(
expecting('semicolon', token(';')),
or(and(lookAheadToken(';'),
constant(NIL)),
expression),
expecting('semicolon', token(';')),
or(and(lookAheadToken(')'),
constant(NIL)),
expression))));
var inExpr = seq(token('in'), expression);
var inExprExpectingSemi = expecting('semicolon',
seq(token('in'), expression));
var forSpec = mapResult(node(
'forSpec',
or(seq(token('var'),
varDeclMaybeNoIn[true],
expecting(
'commaOrIn',
or(inExpr,
seq(
or(
lookAheadToken(';'),
seq(token(','),
list(varDeclMaybeNoIn[true], token(',')))),
secondThirdClauses)))),
// get the case where the first clause is empty out of the way.
// the lookAhead's return value is the empty placeholder for the
// missing expression.
seq(and(lookAheadToken(';'),
constant(NIL)), secondThirdClauses),
// custom parser the non-var case because we have to
// read the first expression before we know if there's
// an "in".
new Parser(
null,
function (t) {
var firstExpr = expressionMaybeNoIn[true].parse(t);
if (! firstExpr)
return null;
var rest = secondThirdClauses.parse(t);
if (! rest) {
// we need a left-hand-side expression for a
// `for (x in y)` loop.
if (! isExpressionLHS(firstExpr))
throw t.getParseError("semicolon");
// if we don't see 'in' at this point, it's probably
// a missing semicolon
rest = inExprExpectingSemi.parseRequired(t);
}
return [firstExpr].concat(rest);
}))),
function (clauses) {
// There are four kinds of for-loop, and we call the
// part between the parens one of forSpec, forVarSpec,
// forInSpec, and forVarInSpec. Having parsed it
// already, we rewrite the node name based on how
// many items came out. forIn and forVarIn always
// have 3 and 4 items respectively. for has 5
// (the optional expressions are present as nils).
// forVar has 6 or more, because `for(var x;;);`
// produces [`var` `x` `;` nil `;` nil].
var numChildren = clauses.children.length;
if (numChildren === 3)
return new ParseNode('forInSpec', clauses.children);
else if (numChildren === 4)
return new ParseNode('forVarInSpec', clauses.children);
else if (numChildren >= 6)
return new ParseNode('forVarSpec', clauses.children);
return clauses;
});
var iterationStatement = or(
node('doStmnt', seq(token('do'), statement, token('while'),
token('('), expression, token(')'),
maybeSemicolon)),
node('whileStmnt', seq(token('while'), token('('), expression,
closeParenBeforeStatement, statement)),
// semicolons must be real, not maybeSemicolons
node('forStmnt', seq(
token('for'), token('('), forSpec, closeParenBeforeStatement,
statement)));
var returnStatement = node(
'returnStmnt',
seq(token('return'), or(
and(noLineTerminatorHere, expression), constant(NIL)),
maybeSemicolon));
var continueStatement = node(
'continueStmnt',
seq(token('continue'), or(
and(noLineTerminatorHere, tokenType('IDENTIFIER')), constant(NIL)),
maybeSemicolon));
var breakStatement = node(
'breakStmnt',
seq(token('break'), or(
and(noLineTerminatorHere, tokenType('IDENTIFIER')), constant(NIL)),
maybeSemicolon));
var throwStatement = node(
'throwStmnt',
seq(token('throw'),
and(or(noLineTerminatorHere,
// If there is a line break here and more tokens after,
// we want to error appropriately. `throw \n e` should
// complain about the "end of line", not the `e`.
and(not(lookAheadTokenType("EOF")),
new Parser(null,
function (t) {
throw t.getParseError('expression', 'end of line');
}))),
expression),
maybeSemicolon));
var withStatement = node(
'withStmnt',
seq(token('with'), token('('), expression, closeParenBeforeStatement,
statement));
var switchCase = node(
'case',
seq(token('case'), expression, token(':'),
or(lookAheadToken('}'),
lookAheadToken('case default'),
statements)));
var switchDefault = node(
'default',
seq(token('default'), token(':'),
or(lookAheadToken('}'),
lookAheadToken('case'),
statements)));
var switchStatement = node(
'switchStmnt',
seq(token('switch'), token('('), expression, token(')'),
token('{'),
or(lookAheadToken('}'),
lookAheadToken('default'),
list(switchCase)),
opt(seq(switchDefault,
opt(list(switchCase)))),
token('}')));
var catchFinally = expecting(
'catch',
and(lookAheadToken('catch finally'),
seq(
or(node(
'catch',
seq(token('catch'), token('('), tokenType('IDENTIFIER'),
token(')'), blockStatement)),
constant(NIL)),
or(node(
'finally',
seq(token('finally'), blockStatement)),
constant(NIL)))));
var tryStatement = node(
'tryStmnt',
seq(token('try'), blockStatement, catchFinally));
var debuggerStatement = node(
'debuggerStmnt', seq(token('debugger'), maybeSemicolon));
statement = expecting('statement',
or(expressionOrLabelStatement,
emptyStatement,
blockStatement,
variableStatement,
ifStatement,
iterationStatement,
returnStatement,
continueStatement,
breakStatement,
withStatement,
switchStatement,
throwStatement,
tryStatement,
debuggerStatement));
// PROGRAM
var functionDecl = node(
'functionDecl', functionMaybeNameRequired[true]);
// Look for statement before functionDecl, to catch comments in
// includeComments mode. A statement can't start with 'function'
// anyway, so the order doesn't matter otherwise.
var sourceElement = or(statement, functionDecl);
var sourceElements = list(or(comment, sourceElement));
functionBody = expecting(
'functionBody', or(lookAheadToken('}'), sourceElements));
var program = node(
'program',
seq(opt(sourceElements),
// If not at EOF, complain "expecting statement"
expecting('statement', lookAheadTokenType("EOF"))));
return program.parse(this);
};

View File

@@ -1,733 +0,0 @@
var parserTestOptions = { includeComments: true };
var allNodeNames = [
";",
"array",
"assignment",
"binary",
"blockStmnt",
"boolean",
"bracket",
"breakStmnt",
"call",
"case",
"catch",
"comma",
"comment",
"continueStmnt",
"debuggerStmnt",
"default",
"doStmnt",
"dot",
"emptyStmnt",
"expressionStmnt",
"finally",
"forInSpec",
"forSpec",
"forStmnt",
"forVarInSpec",
"forVarSpec",
"functionDecl",
"functionExpr",
"idPropName",
"identifier",
"ifStmnt",
"labelStmnt",
"new",
"newcall",
"nil",
"null",
"numPropName",
"number",
"object",
"parens",
"postfix",
"program",
"prop",
"regex",
"returnStmnt",
"strPropName",
"string",
"switchStmnt",
"ternary",
"this",
"throwStmnt",
"tryStmnt",
"unary",
"varDecl",
"varStmnt",
"whileStmnt",
"withStmnt"
];
var allNodeNamesSet = {};
_.each(allNodeNames, function (n) { allNodeNamesSet[n] = true; });
var makeTester = function (test) {
return {
// Parse code and make sure it matches expectedTreeString.
goodParse: function (code, expectedTreeString, regexTokenHints) {
var expectedTree = ParseNode.unstringify(expectedTreeString);
// first use lexer to collect all tokens
var lexer = new JSLexer(code);
var allTokensInOrder = [];
while (! lexer.next().isEOF()) {
var lex = lexer.lastLexeme;
if (lex.isError())
test.fail("Lexer error at " + lex.startPos());
if (lex.isToken())
allTokensInOrder.push(lex);
if (regexTokenHints && regexTokenHints[allTokensInOrder.length])
lexer.divisionPermitted = false;
}
var parser = new JSParser(code, parserTestOptions);
var actualTree = parser.getSyntaxTree();
var nextTokenIndex = 0;
var check = function (tree) {
if (tree instanceof ParseNode) {
// This is a NODE (non-terminal).
var nodeName = tree.name;
if (! (nodeName && typeof nodeName === "string" &&
allNodeNamesSet[nodeName] === true))
test.fail("Not a node name: " + nodeName);
_.each(tree.children, check);
} else if (typeof tree === 'object' &&
typeof tree.text === 'function') {
// This is a TOKEN (terminal).
// Make sure we are visiting every token once, in order.
// Make an exception for any comment lexemes present,
// because we couldn't know whether to include them in
// allTokensInOrder.
if (tree.type() !== "COMMENT") {
if (nextTokenIndex >= allTokensInOrder.length)
test.fail("Too many tokens: " + (nextTokenIndex + 1));
var referenceToken = allTokensInOrder[nextTokenIndex++];
if (tree.text() !== referenceToken.text())
test.fail(tree.text() + " !== " + referenceToken.text());
if (tree.startPos() !== referenceToken.startPos())
test.fail(tree.startPos() + " !== " + referenceToken.startPos());
if (code.substring(tree.startPos(), tree.endPos()) !== tree.text())
test.fail("Didn't see " + tree.text() + " at " + tree.startPos() +
" in " + code);
}
} else {
test.fail("Unknown tree part: " + tree);
}
};
check(actualTree);
if (nextTokenIndex !== allTokensInOrder.length)
test.fail("Too few tokens: " + nextTokenIndex);
test.equal(parser.pos, code.length);
test.equal(ParseNode.stringify(actualTree),
ParseNode.stringify(expectedTree), code);
},
// Takes code with part of it surrounding with backticks.
// Removes the two backtick characters, tries to parse the code,
// and then asserts that there was a tokenization-level error,
// with the part that was between the backticks called out as
// the bad token.
//
// For example, the test "123`@`" will try to parse "123@" and
// assert that a tokenization error occurred at '@'.
badToken: function (code) {
var constructMessage = function (pos, text) {
var nicePos = JSLexer.prettyOffset(code, pos);
return "Bad token at " + nicePos + ", text `" + text + "`";
};
var pos = code.indexOf('`');
var text = code.match(/`(.*?)`/)[1];
code = code.replace(/`/g, '');
var parsed = false;
var error = null;
try {
var tree = new JSParser(code, parserTestOptions).getSyntaxTree();
parsed = true;
} catch (e) {
error = e;
}
test.isFalse(parsed);
test.isTrue(error);
test.equal(error.message, constructMessage(pos, text));
},
// Takes code with a backtick-quoted string embedded in it.
// Removes the backticks and their contents, tries to parse the code,
// and then asserts that there was a parse error at the location
// where the backtick-quoted string was embedded. The embedded
// string must match whatever the error message says was "expected".
//
// For example, the test "{`statement`" will try to parse the code
// "{" and then assert that an error occured at the end of the string
// saying "Expected statement". The test "1 `semicolon`2" will try
// to parse "1 2" and assert that the error "Expected semicolon"
// appeared after the space and before the 2.
//
// A second backtick-quoted string is used as the "found" token
// in the error message.
badParse: function (code) {
var constructMessage = function (whatExpected, pos, found, after) {
return "Expected " + whatExpected + (after ? " after " + after : "") +
" at " + JSLexer.prettyOffset(code, pos) + ", found " + found;
};
var pos = code.indexOf('`');
var backticked = code.match(/`.*?`/g);
var whatExpected = backticked[0] && backticked[0].slice(1,-1);
var found = backticked[1] && backticked[1].slice(1, -1);
code = code.replace(/`.*?`/g, '');
var parsed = false;
var error = null;
var parser = new JSParser(code, parserTestOptions);
try {
var tree = parser.getSyntaxTree();
parsed = true;
} catch (e) {
error = e;
}
test.isFalse(parsed);
test.isTrue(error);
if (! parsed && error) {
var after = parser.oldToken;
found = (found || parser.newToken);
test.equal(error.message,
constructMessage(whatExpected, pos, found, after),
code);
}
}
};
};
Tinytest.add("jsparse - basics", function (test) {
var tester = makeTester(test);
tester.goodParse('1', "program(expressionStmnt(number(1) ;()))");
tester.goodParse('1 + 1', "program(expressionStmnt(binary(number(1) + number(1)) ;()))");
tester.goodParse('1*2+3*4', "program(expressionStmnt(binary(binary(number(1) * number(2)) + " +
"binary(number(3) * number(4))) ;()))");
tester.goodParse('1 + 1;', "program(expressionStmnt(binary(number(1) + number(1)) ;))");
tester.goodParse('1 + 1;;', "program(expressionStmnt(binary(number(1) + number(1)) ;) emptyStmnt(;))");
tester.goodParse('', "program()");
tester.goodParse('\n', "program()");
tester.goodParse(';;;\n\n;\n', "program(emptyStmnt(;) emptyStmnt(;) emptyStmnt(;) emptyStmnt(;))");
tester.goodParse('foo', "program(expressionStmnt(identifier(foo) ;()))");
tester.goodParse('foo();', "program(expressionStmnt(call(identifier(foo) `(` `)`) ;))");
tester.goodParse('var x = 3', "program(varStmnt(var varDecl(x = number(3)) ;()))");
tester.goodParse('++x;', "program(expressionStmnt(unary(++ identifier(x)) ;))");
tester.goodParse('x++;', "program(expressionStmnt(postfix(identifier(x) ++) ;))");
tester.goodParse(
'throw new Error',
"program(throwStmnt(throw new(new identifier(Error)) ;()))");
tester.goodParse(
'var x = function () { return 123; };',
'program(varStmnt(var varDecl(x = functionExpr(function nil() `(` `)` ' +
'{ returnStmnt(return number(123) ;) })) ;))');
tester.badParse("var x = `expression`");
tester.badParse("1 `semicolon`1");
tester.badParse("1+1`semicolon`:");
});
Tinytest.add("jsparse - tokenization errors", function (test) {
var tester = makeTester(test);
tester.badToken("123`@`");
tester.badToken("thisIsATestOf = `'unterminated `\n strings'");
// make sure newlines aren't quietly included in regex literals
tester.badToken("var x = `/`a\nb/;");
tester.badToken("var x = `/`a\\\nb/;");
tester.badToken("var x = `/`a[\n]b/;");
});
Tinytest.add("jsparse - syntax forms", function (test) {
var tester = makeTester(test);
var trials = [
// STATEMENTS
['1',
'program(expressionStmnt(number(1) ;()))'],
['1;;;;2',
'program(expressionStmnt(number(1) ;) emptyStmnt(;) emptyStmnt(;) emptyStmnt(;) ' +
'expressionStmnt(number(2) ;()))'],
['{}',
'program(blockStmnt({ }))'],
['{null}',
'program(blockStmnt({ expressionStmnt(null(null) ;()) }))'],
['{\nfoo()\nbar();\n}',
'program(blockStmnt({ expressionStmnt(call(identifier(foo) `(` `)`) ;()) ' +
'expressionStmnt(call(identifier(bar) `(` `)`) ;) }))'],
['{{{}}}',
'program(blockStmnt({ blockStmnt({ blockStmnt({ }) }) }))'],
['var x = y, z,\n a = b = c;',
'program(varStmnt(var varDecl(x = identifier(y)) , varDecl(z) , varDecl(a = ' +
'assignment(identifier(b) = identifier(c))) ;))'],
['if (x === y);',
'program(ifStmnt(if `(` binary(identifier(x) === identifier(y)) `)` emptyStmnt(;)))'],
['if (z) return',
'program(ifStmnt(if `(` identifier(z) `)` returnStmnt(return nil() ;())))'],
['if (a) b; else c',
'program(ifStmnt(if `(` identifier(a) `)` expressionStmnt(identifier(b) ;) else ' +
'expressionStmnt(identifier(c) ;())))'],
['if (n === 1) { foo(); } else if (n === 2) { bar(); } else { baz(); }',
'program(ifStmnt(if `(` binary(identifier(n) === number(1)) `)` blockStmnt(' +
'{ expressionStmnt(call(identifier(foo) `(` `)`) ;) }) else ifStmnt(' +
'if `(` binary(identifier(n) === number(2)) `)` blockStmnt(' +
'{ expressionStmnt(call(identifier(bar) `(` `)`) ;) }) else blockStmnt(' +
'{ expressionStmnt(call(identifier(baz) `(` `)`) ;) }))))'],
['while (false);',
'program(whileStmnt(while `(` boolean(false) `)` emptyStmnt(;)))'],
['while (/foo/.test(bar.baz)) {\n bar = bar.baz;\n}',
'program(whileStmnt(while `(` call(dot(regex(/foo/) . test) `(` ' +
'dot(identifier(bar) . baz) `)`) `)` blockStmnt({ expressionStmnt(' +
'assignment(identifier(bar) = dot(identifier(bar) . baz)) ;) })))'],
['while (false) while (false);',
'program(whileStmnt(while `(` boolean(false) `)` ' +
'whileStmnt(while `(` boolean (false) `)` emptyStmnt(;))))'],
['do a; while (b);',
'program(doStmnt(do expressionStmnt(identifier(a) ;) while `(` identifier(b) `)` ;))'],
['do { x-- } while (x);',
'program(doStmnt(do blockStmnt({ expressionStmnt(postfix(identifier(x) --) ;()) }) ' +
'while `(` identifier(x) `)` ;))'],
['do a\n while (b)\n x++',
'program(doStmnt(do expressionStmnt(identifier(a) ;()) while `(` identifier(b) `)` ;()) ' +
'expressionStmnt(postfix(identifier(x) ++) ;()))'],
["for(;;);",
"program(forStmnt(for `(` forSpec(nil() ; nil() ; nil()) `)` emptyStmnt(;)))"],
["for(x in y);",
"program(forStmnt(for `(` forInSpec(identifier(x) in identifier(y)) `)` emptyStmnt(;)))"],
["for(var x in y);",
"program(forStmnt(for `(` forVarInSpec(var varDecl(x) in identifier(y)) `)` emptyStmnt(;)))"],
["for(var x;;);",
"program(forStmnt(for `(` forVarSpec(var varDecl(x) ; nil() ; nil()) `)` emptyStmnt(;)))"],
["for(var i=0;i<N;i++) {}",
"program(forStmnt(for `(` forVarSpec(var varDecl(i = number(0)) ; " +
"binary(identifier(i) < identifier(N)) ; postfix(identifier(i) ++)) `)` blockStmnt({ })))"],
["for (var x=3 in y);",
"program(forStmnt(for `(` forVarInSpec(var varDecl(x = number(3)) in identifier(y)) `)` " +
"emptyStmnt(;)))"],
["for (x.foo in y);",
"program(forStmnt(for `(` forInSpec(dot(identifier(x) . foo) in identifier(y)) `)` emptyStmnt(;)))"],
["return",
"program(returnStmnt(return nil() ;()))"],
["return;",
"program(returnStmnt(return nil() ;))"],
["return null",
"program(returnStmnt(return null(null) ;()))"],
["return null;",
"program(returnStmnt(return null(null) ;))"],
["return\n1+1",
"program(returnStmnt(return nil() ;()) expressionStmnt(binary(number(1) + number(1)) ;()))"],
["return 1\n +1",
"program(returnStmnt(return binary(number(1) + number(1)) ;()))"],
["continue",
"program(continueStmnt(continue nil() ;()))"],
["continue foo",
"program(continueStmnt(continue foo ;()))"],
["continue foo;",
"program(continueStmnt(continue foo ;))"],
["continue\n foo;",
"program(continueStmnt(continue nil() ;()) expressionStmnt(identifier(foo) ;))"],
["break",
"program(breakStmnt(break nil() ;()))"],
["break foo",
"program(breakStmnt(break foo ;()))"],
["break foo;",
"program(breakStmnt(break foo ;))"],
["break\n foo;",
"program(breakStmnt(break nil() ;()) expressionStmnt(identifier(foo) ;))"],
["throw e;",
"program(throwStmnt(throw identifier(e) ;))"],
["throw e",
"program(throwStmnt(throw identifier(e) ;()))"],
["throw new Error;",
"program(throwStmnt(throw new(new identifier(Error)) ;))"],
["with(x);",
"program(withStmnt(with `(` identifier(x) `)` emptyStmnt(;)))"],
["with(a=b) {}",
"program(withStmnt(with `(` assignment(identifier(a) = identifier(b)) `)` blockStmnt({ })))"],
["switch(x) {}",
"program(switchStmnt(switch `(` identifier(x) `)` { }))"],
["switch(x) {case 1:case 2:case 3:default:case 4:}",
"program(switchStmnt(switch `(` identifier(x) `)` { " +
"case(case number(1) :) case(case number(2) :) case(case number(3) :) " +
"default(default :) case(case number(4) :) }))"],
["switch(x) {\ncase 1:\n return\ncase 2:\ncase 3:\n throw e}",
"program(switchStmnt(switch `(` identifier(x) `)` { " +
"case(case number(1) : returnStmnt(return nil() ;())) " +
"case(case number(2) :) case(case number(3) : " +
"throwStmnt(throw identifier(e) ;())) }))"],
["switch(x) {default:;}",
"program(switchStmnt(switch `(` identifier(x) `)` { default(default : emptyStmnt(;)) }))"],
["try {} catch (e) {} finally {}",
"program(tryStmnt(try blockStmnt({ }) catch(catch `(` e `)` blockStmnt({ })) " +
"finally(finally blockStmnt({ }))))"],
["try {} finally {}",
"program(tryStmnt(try blockStmnt({ }) nil() finally(finally blockStmnt({ }))))"],
["try {} catch (e) {}",
"program(tryStmnt(try blockStmnt({ }) catch(catch `(` e `)` blockStmnt({ })) nil()))"],
["a:;",
"program(labelStmnt(a : emptyStmnt(;)))"],
["{x:1}",
"program(blockStmnt({ labelStmnt(x : expressionStmnt(number(1) ;())) }))"],
["{x:y:z:1}",
"program(blockStmnt({ labelStmnt(x : labelStmnt(y : " +
"labelStmnt(z : expressionStmnt(number(1) ;())))) }))"],
[";;foo:\nfor(;;);",
"program(emptyStmnt(;) emptyStmnt(;) labelStmnt(foo : " +
"forStmnt(for `(` forSpec(nil() ; nil() ; nil()) `)` emptyStmnt(;))))"],
["debugger",
"program(debuggerStmnt(debugger ;()))"],
["debugger;",
"program(debuggerStmnt(debugger ;))"],
["function foo() {}",
"program(functionDecl(function foo `(` `)` { }))"],
["function foo() {function bar() {}}",
"program(functionDecl(function foo `(` `)` { functionDecl(function bar `(` `)` { }) }))"],
[";;function f() {};;",
"program(emptyStmnt(;) emptyStmnt(;) functionDecl(function f `(` `)` { }) " +
"emptyStmnt(;) emptyStmnt(;))"],
["function foo(a,b,c) {}",
"program(functionDecl(function foo `(` a , b , c `)` { }))"],
// EXPRESSIONS
["null + this - 3 + true",
"program(expressionStmnt(binary(binary(binary(null(null) + this(this)) - " +
"number(3)) + boolean(true)) ;()))"],
["+.5",
"program(expressionStmnt(unary(+ number(.5)) ;()))"],
["a1a1a",
"program(expressionStmnt(identifier(a1a1a) ;()))"],
["/abc/mig",
"program(expressionStmnt(regex(/abc/mig) ;()))"],
["/[]/",
"program(expressionStmnt(regex(/[]/) ;()))"],
["/[/]/",
"program(expressionStmnt(regex(/[/]/) ;()))"],
["/[[/]/",
"program(expressionStmnt(regex(/[[/]/) ;()))"],
["/.\\/[a//b]\\[\\][[\\d/]/",
"program(expressionStmnt(regex(/.\\/[a//b]\\[\\][[\\d/]/) ;()))"],
["a / /b/mgi / c",
"program(expressionStmnt(binary(binary(identifier(a) / " +
"regex(/b/mgi)) / identifier(c)) ;()))"],
["'a' + \"\" + \"b\" + '\\''",
"program(expressionStmnt(binary(binary(binary(string('a') + string(\"\")) + " +
"string(\"b\")) + string('\\'')) ;()))"],
["_ + x0123 + $",
"program(expressionStmnt(binary(binary(identifier(_) + " +
"identifier(x0123)) + identifier($)) ;()))"],
["if ((x = 1)) return ((1+2))*((1<<2));",
"program(ifStmnt(if `(` parens(`(` assignment(identifier(x) = number(1)) `)`) " +
"`)` returnStmnt(return binary(parens(`(` parens(`(` binary(number(1) + " +
"number(2)) `)`) `)`) * parens(`(` parens(`(` binary(number(1) << number(2)) " +
"`)`) `)`)) ;)))"],
["[];",
"program(expressionStmnt(array([ ]) ;))"],
["[,,,];",
"program(expressionStmnt(array([ , , , ]) ;))"],
["[(1,2),,3];",
"program(expressionStmnt(array([ parens(`(` comma(number(1) , " +
"number(2)) `)`) , , number(3) ]) ;))"],
["({});",
"program(expressionStmnt(parens(`(` object({ }) `)`) ;))"],
["({1:1});",
"program(expressionStmnt(parens(`(` object({ prop(numPropName(1) : number(1)) }) `)`) ;))"],
["({x:true});",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : boolean(true)) }) `)`) ;))"],
["({'a':b, c:'d', 1:null});",
"program(expressionStmnt(parens(`(` object({ prop(strPropName('a') : " +
"identifier(b)) , prop(idPropName(c) : string('d')) , prop(numPropName(1) " +
": null(null)) }) `)`) ;))"],
["(function () {});",
"program(expressionStmnt(parens(`(` functionExpr(function nil() `(` `)` { }) `)`) ;))"],
["(function foo() {});",
"program(expressionStmnt(parens(`(` functionExpr(function foo `(` `)` { }) `)`) ;))"],
["x = function () {}.y;",
"program(expressionStmnt(assignment(identifier(x) = dot(functionExpr(" +
"function nil() `(` `)` { }) . y)) ;))"],
["(function (a) {})",
"program(expressionStmnt(parens(`(` functionExpr(function nil() " +
"`(` a `)` { }) `)`) ;()))"],
["(function (a,b,c) {})",
"program(expressionStmnt(parens(`(` functionExpr(function nil() `(` " +
"a , b , c `)` { }) `)`) ;()))"],
["foo.bar.baz;",
"program(expressionStmnt(dot(dot(identifier(foo) . bar) . baz) ;))"],
["foo[bar,bar][baz].qux[1+1];",
"program(expressionStmnt(bracket(dot(bracket(bracket(identifier(foo) " +
"[ comma(identifier(bar) , identifier(bar)) ]) [ identifier(baz) ]) . qux) " +
"[ binary(number(1) + number(1)) ]) ;))"],
["new new a.b.c[d]",
"program(expressionStmnt(new(new new(new bracket(dot(dot(identifier(a) " +
". b) . c) [ identifier(d) ]))) ;()))"],
["new new a.b.c[d]()",
"program(expressionStmnt(new(new newcall(new " +
"bracket(dot(dot(identifier(a) . b) . c) [ identifier(d) ]) `(` `)`)) ;()))"],
["new new a.b.c[d]()()",
"program(expressionStmnt(newcall(new newcall(new " +
"bracket(dot(dot(identifier(a) . b) . c) [ identifier(d) ]) `(` `)`) `(` `)`) ;()))"],
["new foo(x).bar(y)",
"program(expressionStmnt(call(dot(newcall(new identifier(foo) `(` " +
"identifier(x) `)`) . bar) `(` identifier(y) `)`) ;()))"],
["new new foo().bar",
"program(expressionStmnt(new(new dot(newcall(new identifier(foo) `(` `)`) . bar)) ;()))"],
["delete void typeof - + ~ ! -- ++ x;",
"program(expressionStmnt(unary(delete unary(void unary(typeof unary(- unary(+ " +
"unary(~ unary(! unary(-- unary(++ identifier(x)))))))))) ;))"],
["x++ + ++y",
"program(expressionStmnt(binary(postfix(identifier(x) ++) + " +
"unary(++ identifier(y))) ;()))"],
["1*2+3*4",
"program(expressionStmnt(binary(binary(number(1) * number(2)) " +
"+ binary(number(3) * number(4))) ;()))"],
["a*b/c%d+e-f<<g>>h>>>i<j>k<=l>=m instanceof n in o==p!=q===r!==s&t^u|v&&w||x",
"program(expressionStmnt(binary(binary(binary(binary(binary(binary(binary(" +
"binary(binary(binary(binary(binary(binary(binary(binary(binary(binary(binary(" +
"binary(binary(binary(binary(binary(identifier(a) * identifier(b)) / " +
"identifier(c)) % identifier(d)) + identifier(e)) - identifier(f)) << identifier(g)) " +
">> identifier(h)) >>> identifier(i)) < identifier(j)) > identifier(k)) <= " +
"identifier(l)) >= identifier(m)) instanceof identifier(n)) in identifier(o)) == " +
"identifier(p)) != identifier(q)) === identifier(r)) !== identifier(s)) & " +
"identifier(t)) ^ identifier(u)) | identifier(v)) && identifier(w)) || " +
"identifier(x)) ;()))"],
["a||b&&c|d^e&f!==g===h!=i==j in k instanceof l>=m<=n<o<p>>>q>>r<<s-t+u%v/w*x",
"program(expressionStmnt(binary(identifier(a) || binary(identifier(b) && " +
"binary(identifier(c) | binary(identifier(d) ^ binary(identifier(e) & " +
"binary(binary(binary(binary(identifier(f) !== identifier(g)) === identifier(h)) " +
"!= identifier(i)) == binary(binary(binary(binary(binary(binary(identifier(j) in " +
"identifier(k)) instanceof identifier(l)) >= identifier(m)) <= identifier(n)) < " +
"identifier(o)) < binary(binary(binary(identifier(p) >>> identifier(q)) >> " +
"identifier(r)) << binary(binary(identifier(s) - identifier(t)) + " +
"binary(binary(binary(identifier(u) % identifier(v)) / identifier(w)) * " +
"identifier(x))))))))))) ;()))"],
["a?b:c",
"program(expressionStmnt(ternary(identifier(a) ? identifier(b) : " +
"identifier(c)) ;()))"],
["1==2?3=4:5=6",
"program(expressionStmnt(ternary(binary(number(1) == number(2)) ? " +
"assignment(number(3) = number(4)) : assignment(number(5) = number(6))) ;()))"],
["a=b,c=d",
"program(expressionStmnt(comma(assignment(identifier(a) = identifier(b)) , " +
"assignment(identifier(c) = identifier(d))) ;()))"],
["a=b=c=d",
"program(expressionStmnt(assignment(identifier(a) = assignment(identifier(b) " +
"= assignment(identifier(c) = identifier(d)))) ;()))"],
["x[0]=x[1]=true",
"program(expressionStmnt(assignment(bracket(identifier(x) [ number(0) ]) = " +
"assignment(bracket(identifier(x) [ number(1) ]) = boolean(true))) ;()))"],
["a*=b/=c%=d+=e-=f<<=g>>=h>>>=i&=j^=k|=l",
"program(expressionStmnt(assignment(identifier(a) *= assignment(identifier(b) " +
"/= assignment(identifier(c) %= assignment(identifier(d) += " +
"assignment(identifier(e) -= assignment(identifier(f) <<= " +
"assignment(identifier(g) >>= assignment(identifier(h) >>>= " +
"assignment(identifier(i) &= assignment(identifier(j) ^= " +
"assignment(identifier(k) |= identifier(l)))))))))))) ;()))"],
["1;\n\n\n\n/* foo */\n// bar\n", // trailing whitespace and comments
"program(expressionStmnt(number(1) ;) comment(`/* foo */`) comment(`// bar`))"],
// includeComments option; comments in AST
["//foo",
"program(comment(//foo))"],
["//foo\n",
"program(comment(//foo))"],
["/*foo*/",
"program(comment(/*foo*/))"],
["/*foo*/\n",
"program(comment(/*foo*/))"],
[";\n//foo",
"program(emptyStmnt(;) comment(//foo))"],
[";\n/*foo*/",
"program(emptyStmnt(;) comment(/*foo*/))"],
[";\n//foo\n;",
"program(emptyStmnt(;) comment(//foo) emptyStmnt(;))"],
[";\n/*foo*/\n;",
"program(emptyStmnt(;) comment(/*foo*/) emptyStmnt(;))"],
[";\n//foo\n//bar\n;",
"program(emptyStmnt(;) comment(//foo) comment(//bar) emptyStmnt(;))"],
[";\n/*foo*/ /*bar*/\n;",
"program(emptyStmnt(;) comment(/*foo*/) comment(/*bar*/) emptyStmnt(;))"],
[";//foo\n//bar\n;",
"program(emptyStmnt(;) comment(//bar) emptyStmnt(;))"],
[";/*foo*/\n/*bar*/\n;",
"program(emptyStmnt(;) comment(/*bar*/) emptyStmnt(;))"],
[";/*foo*//*bar*///baz\n;",
"program(emptyStmnt(;) emptyStmnt(;))"],
[";/*foo*//*bar*///baz",
"program(emptyStmnt(;))"],
["/*foo*//*bar*///baz",
"program(comment(/*foo*/) comment(/*bar*/) comment(//baz))"],
["//foo\n//bar\nfunction aaa() {}\nfunction bbb() {}",
"program(comment(//foo) comment(//bar) functionDecl(function aaa `(` `)` { }) " +
"functionDecl(function bbb `(` `)` { }))"],
// comments don't interfere with parse
["if (true)\n//comment\nfoo();",
"program(ifStmnt(if `(` boolean(true) `)` " +
"expressionStmnt(call(identifier(foo) `(` `)`) ;)))"],
// bare keywords allowed in property access and object literal
["foo.return();",
"program(expressionStmnt(call(dot(identifier(foo) . return) `(` `)`) ;))"],
["foo.true();",
"program(expressionStmnt(call(dot(identifier(foo) . true) `(` `)`) ;))"],
["foo.null();",
"program(expressionStmnt(call(dot(identifier(foo) . null) `(` `)`) ;))"],
["({true:3})",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(true) : number(3)) }) `)`) ;()))"],
["({null:3})",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(null) : number(3)) }) `)`) ;()))"],
["({if:3})",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(if) : number(3)) }) `)`) ;()))"],
// ES5 line continuations in string literals
["var x = 'a\\\nb\\\nc';",
"program(varStmnt(var varDecl(x = string(`'a\\\nb\\\nc'`)) ;))"],
// ES5 trailing comma in object literal
["({});",
"program(expressionStmnt(parens(`(` object({ }) `)`) ;))"],
["({x:1});",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) }) `)`) ;))"],
["({x:1,});",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , }) `)`) ;))"],
["({x:1,y:2});",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , " +
"prop(idPropName(y) : number(2)) }) `)`) ;))"],
["({x:1,y:2,});",
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , " +
"prop(idPropName(y) : number(2)) , }) `)`) ;))"]
];
_.each(trials, function (tr) {
tester.goodParse(tr[0], tr[1]);
});
});
Tinytest.add("jsparse - bad parses", function (test) {
var tester = makeTester(test);
// string between backticks is pulled out and becomes what's "expected"
// at that location, according to the correct error message
var trials = [
'{`statement`',
'if (`expression`)',
'if `(`else',
'var`varDecl`;',
'while (`expression`);',
'while`(`;',
'do a `semicolon`while b;',
'do a\n while `(`b;',
'1 `semicolon`2',
'for (`forSpec`);',
'for (1\n`semicolon`2\n3);',
'continue `semicolon`1+1;',
'break `semicolon`1+1;',
'throw`expression`',
'throw`expression`;',
'throw\n`expression`',
'throw\n`expression``end of line`e',
'throw `expression`=;',
'with(`expression`);',
'switch(`expression`)',
'switch(x)`{`;',
'try`block`',
'try {}`catch`',
'try {} catch`(`;',
'try {} catch(e)`block`;',
'1+1`semicolon`:',
'{a:`statement`}',
'function `IDENTIFIER`() {}',
'foo: `statement`function foo() {}',
'[`expression`=',
'[,,`expression`=',
'({`propertyName`|:3})',
'({1:2,3`:`})',
'({1:2,`propertyName`',
'x.`IDENTIFIER`,',
'foo;`semicolon`:;',
'1;`statement`=',
'a+b`semicolon`=c;',
'for(1+1 `semicolon`in {});',
'`statement`=',
'for(;`expression`var;) {}',
'({`propertyName`',
'({`propertyName`,})',
'({`propertyName`:})',
'({x`:`})',
'({x:1,`propertyName`',
'({x:1,`propertyName`,})',
'({x:1`,`',
'({x:1,`propertyName`,y:2})',
'({x:1,`propertyName`,})',
'({x:1,y:2`,`:',
'({x:1,y:2,`propertyName`',
'({x:1,y:2,`propertyName`:',
'({x:1,y:2,`propertyName`,})'
];
_.each(trials, function (tr) {
tester.badParse(tr);
});
});
Tinytest.add("jsparse - regex division ambiguity", function (test) {
var tester = makeTester(test);
tester.goodParse("if (e) /f/g;",
"program(ifStmnt(if `(` identifier(e) `)` expressionStmnt(regex(/f/g) ;)))",
{4: true});
tester.goodParse("++/x/.y;",
"program(expressionStmnt(unary(++ dot(regex(/x/) . y)) ;))",
{1: true});
tester.goodParse("x++/2/g;",
"program(expressionStmnt(binary(binary(postfix(identifier(x) ++) / " +
"number(2)) / identifier(g)) ;))");
tester.goodParse("(1+1)/2/g;",
"program(expressionStmnt(binary(binary(parens(`(` binary(number(1) + " +
"number(1)) `)`) / " +
"number(2)) / identifier(g)) ;))");
tester.goodParse("/x/",
"program(expressionStmnt(regex(/x/) ;()))");
});
Tinytest.add("jsparse - semicolon insertion", function (test) {
var tester = makeTester(test);
// Spec section 7.9.2
tester.badParse("{ 1 `semicolon`2 } 3");
tester.goodParse("{ 1\n2 } 3", "program(blockStmnt({ expressionStmnt(number(1) " +
";()) expressionStmnt(number(2) ;()) }) expressionStmnt(number(3) ;()))");
tester.badParse("for (a; b\n`semicolon`)");
tester.goodParse("return\na + b",
"program(returnStmnt(return nil() ;()) " +
"expressionStmnt(binary(identifier(a) + identifier(b)) ;()))");
tester.goodParse("a = b\n++c",
"program(expressionStmnt(assignment(identifier(a) = identifier(b)) ;())" +
"expressionStmnt(unary(++ identifier(c)) ;()))");
tester.badParse("if (a > b)\n`statement`else c = d");
tester.goodParse("a = b + c\n(d + e).print()",
"program(expressionStmnt(assignment(identifier(a) = " +
"binary(identifier(b) + call(dot(call(identifier(c) `(` " +
"binary(identifier(d) + identifier(e)) `)`) . print) `(` `)`))) ;()))");
});
Tinytest.add("jsparse - comments", function (test) {
var tester = makeTester(test);
// newline in multi-line comment makes it into a line break for semicolon
// insertion purposes
tester.badParse("1/**/`semicolon`2");
tester.goodParse("1/*\n*/2",
"program(expressionStmnt(number(1) ;()) expressionStmnt(number(2) ;()))");
});
Tinytest.add("jsparse - initial lex error", function (test) {
var doTest = function (code) {
// this shouldn't throw
var parser = new JSParser(code, parserTestOptions);
// this should throw
try {
parser.getSyntaxTree();
test.fail();
} catch (e) {
test.isTrue(/^Bad token/.test(e.message), e.message);
}
};
doTest('/');
doTest('@');
});

View File

@@ -1,260 +0,0 @@
///// TOKENIZER AND PARSER COMBINATORS
// XXX track line/col position, for errors and maybe token info
var isArray = function (obj) {
return obj && (typeof obj === 'object') && (typeof obj.length === 'number');
};
ParseNode = function (name, children) {
this.name = name;
this.children = children;
if (! isArray(children))
throw new Error("Expected array in new ParseNode(" + name + ", ...)");
};
Parser = function (expecting, runFunc) {
this.expecting = expecting;
this._run = runFunc;
};
Parser.prototype.parse = function (t) {
return this._run(t);
};
Parser.prototype.parseRequired = function (t) {
return this.parseRequiredIf(t, true);
};
Parser.prototype.parseRequiredIf = function (t, required) {
var result = this._run(t);
if (required && ! result)
throw t.getParseError(this.expecting);
return result;
};
Parser.expecting = function (expecting, parser) {
return new Parser(expecting, parser._run);
};
// A parser that consume()s has to succeed.
// Similarly, a parser that fails can't have consumed.
Parsers = {};
Parsers.assertion = function (test) {
return new Parser(
null, function (t) {
return test(t) ? [] : null;
});
};
Parsers.node = function (name, childrenParser) {
return new Parser(name, function (t) {
var children = childrenParser.parse(t);
if (! children)
return null;
if (! isArray(children))
children = [children];
return new ParseNode(name, children);
});
};
Parsers.or = function (/*parsers*/) {
var args = arguments;
return new Parser(
args[args.length - 1].expecting,
function (t) {
var result;
for(var i = 0, N = args.length; i < N; i++) {
result = args[i].parse(t);
if (result)
return result;
}
return null;
});
};
// Parses a left-recursive expression with zero or more occurrences
// of a binary op. Leaves the term unwrapped if no op. For example
// (in a hypothetical use case):
// `1` => "1"
// `1+2` => ["binary", "1", "+", "2"]
// `1+2+3` => ["binary", ["binary", "1", "+", "2"], "+", "3"]
//
// opParsers is an array of op parsers from high to low
// precedence (tightest-binding first)
Parsers.binaryLeft = function (name, termParser, opParsers) {
var opParser;
if (opParsers.length === 1) {
// take single opParser out of its array
opParser = opParsers[0];
} else {
// pop off last opParser (non-destructively) and replace
// termParser with a recursive binaryLeft on the remaining
// ops.
termParser = Parsers.binaryLeft(name, termParser, opParsers.slice(0, -1));
opParser = opParsers[opParsers.length - 1];
}
return new Parser(
termParser.expecting,
function (t) {
var result = termParser.parse(t);
if (! result)
return null;
var op;
while ((op = opParser.parse(t))) {
result = new ParseNode(
name,
[result, op, termParser.parseRequired(t)]);
}
return result;
});
};
Parsers.unary = function (name, termParser, opParser) {
var unaryList = Parsers.opt(Parsers.list(opParser));
return new Parser(
termParser.expecting,
function (t) {
var unaries = unaryList.parse(t);
// if we have unaries, we are committed and
// have to match a term or error.
var result = termParser.parseRequiredIf(t, unaries.length);
if (! result)
return null;
while (unaries.length)
result = new ParseNode(name, [unaries.pop(), result]);
return result;
});
};
// Parses a list of one or more items with a separator, listing the
// items and separators. (Separator is optional.) For example:
// `x` => ["x"]
// `x,y` => ["x", ",", "y"]
// `x,y,z` => ["x", ",", "y", ",", "z"]
// Unpacks.
Parsers.list = function (itemParser, sepParser) {
var push = function(array, newThing) {
if (isArray(newThing))
array.push.apply(array, newThing);
else
array.push(newThing);
};
return new Parser(
itemParser.expecting,
function (t) {
var result = [];
var firstItem = itemParser.parse(t);
if (! firstItem)
return null;
push(result, firstItem);
if (sepParser) {
var sep;
while ((sep = sepParser.parse(t))) {
push(result, sep);
push(result, itemParser.parseRequired(t));
}
} else {
var item;
while ((item = itemParser.parse(t)))
push(result, item);
}
return result;
});
};
// Unpacks arrays (nested seqs).
Parsers.seq = function (/*parsers*/) {
var args = arguments;
if (! args.length)
return Parsers.constant([]);
return new Parser(
args[0].expecting,
function (t) {
var result = [];
for (var i = 0, N = args.length; i < N; i++) {
// first item in sequence can fail, and we
// fail (without error); after that, error on failure
var r = args[i].parseRequiredIf(t, i > 0);
if (! r)
return null;
if (isArray(r)) // append array!
result.push.apply(result, r);
else
result.push(r);
}
return result;
});
};
// parsers except last must never consume
Parsers.and = function (/*parsers*/) {
var args = arguments;
if (! args.length)
return Parsers.constant([]);
return new Parser(
args[args.length - 1].expecting,
function (t) {
var result;
for(var i = 0, N = args.length; i < N; i++) {
result = args[i].parse(t);
if (! result)
return null;
}
return result;
});
};
// parser must not consume
Parsers.not = function (parser) {
return new Parser(
null,
function (t) {
return parser.parse(t) ? null : [];
});
};
// parser that looks at nothing and returns result
Parsers.constant = function (result) {
return new Parser(null,
function (t) { return result; });
};
Parsers.opt = function (parser) {
return Parser.expecting(
parser.expecting,
Parsers.or(parser, Parsers.seq()));
};
Parsers.mapResult = function (parser, func) {
return new Parser(
parser.expecting,
function (t) {
var v = parser.parse(t);
return v ? func(v, t) : null;
});
};
Parsers.lazy = function (expecting, parserFunc) {
var inner = null;
return new Parser(expecting, function (t) {
if (! inner)
inner = parserFunc();
return inner.parse(t);
});
};

View File

@@ -1,118 +0,0 @@
// The "tree string" format is a simple format for representing syntax trees.
//
// For example, the parse of `x++;` is written as:
// "program(expressionStmnt(postfix(identifier(x) ++) ;))"
//
// A Node is written as "name(item1 item2 item3)", with additional whitespace
// allowed anywhere between the name, parentheses, and items.
//
// Tokens don't need to be escaped unless they contain '(', ')', whitespace, or
// backticks, or are empty. If they do, they can be written enclosed in backticks.
// To escape a backtick within backticks, double it.
//
// `stringify` generates "canonical" tree strings, which have no extra escaping
// or whitespace, just one space between items in a Node.
ParseNode.prototype.stringify = function () {
return ParseNode.stringify(this);
};
var backtickEscape = function (str) {
if (/[\s()`]/.test(str))
return '`' + str.replace(/`/g, '``') + '`';
else if (! str)
return '``';
else
return str;
};
var backtickUnescape = function (str) {
if (str.charAt(0) === '`') {
if (str.length === 1 || str.slice(-1) !== '`')
throw new Error("Mismatched ` in " + str);
if (str.length === 2)
str = '';
else
str = str.slice(1, -1).replace(/``/g, '`');
}
return str;
};
ParseNode.stringify = function (tree) {
if (tree instanceof ParseNode) {
var str = backtickEscape(tree.name);
str += '(';
var escapedChildren = [];
for(var i = 0, N = tree.children.length; i < N; i++)
escapedChildren.push(ParseNode.stringify(tree.children[i]));
str += escapedChildren.join(' ');
str += ')';
return str;
}
// Treat a token object or string as a token.
if (typeof tree.text === 'function')
tree = tree.text();
else if (typeof tree.text === 'string')
tree = tree.text;
return backtickEscape(String(tree));
};
ParseNode.unstringify = function (str) {
var lexemes = str.match(/\(|\)|`([^`]||``)*`|`|[^\s()`]+/g) || [];
var N = lexemes.length;
var state = {
i: 0,
getParseError: function (expecting) {
throw new Error("unstringify: Expecting " + expecting +", found " +
(lexemes[this.i] || "end of string"));
},
peek: function () { return lexemes[this.i]; },
advance: function () { this.i++; }
};
var paren = function (chr) {
return new Parser(chr, function (t) {
if (t.peek() !== chr)
return null;
t.advance();
return chr;
});
};
var EMPTY_STRING = [""];
var token = new Parser('token', function (t) {
var txt = t.peek();
if (!txt || txt.charAt(0) === '(' || txt.charAt(0) === ')')
return null;
t.advance();
// can't return falsy value from successful parser
return backtickUnescape(txt) || EMPTY_STRING;
});
// Make "item" lazy so it can be recursive.
var item = Parsers.lazy('token', function () { return item; });
// Parse a single node or token.
item = Parsers.mapResult(
Parsers.seq(token,
Parsers.opt(Parsers.seq(
paren('('), Parsers.opt(Parsers.list(item)), paren(')')))),
function (v) {
for(var i = 0, N = v.length; i < N; i++)
if (v[i] === EMPTY_STRING)
v[i] = "";
if (v.length === 1)
// token
return v[0];
// node. exclude parens
return new ParseNode(v[0], v.slice(2, -1));
});
var endOfString = new Parser("end of string", function (t) {
return t.i === N ? [] : null;
});
return Parsers.seq(item, endOfString).parseRequired(state)[0];
};

View File

@@ -1,48 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}