| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774 |
- /*
- * The MIT License (MIT)
- * Copyright (C) 2016 by Roman Dvornov
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- // derived from https://github.com/csstree/csstree
- this.cssTree = this.cssTree || (() => {
- function createItem(data) {
- return {
- prev: null,
- next: null,
- data: data
- };
- }
- function allocateCursor(node, prev, next) {
- let cursor;
- if (cursors !== null) {
- cursor = cursors;
- cursors = cursors.cursor;
- cursor.prev = prev;
- cursor.next = next;
- cursor.cursor = node.cursor;
- } else {
- cursor = {
- prev: prev,
- next: next,
- cursor: node.cursor
- };
- }
- node.cursor = cursor;
- return cursor;
- }
- function releaseCursor(node) {
- const cursor = node.cursor;
- node.cursor = cursor.cursor;
- cursor.prev = null;
- cursor.next = null;
- cursor.cursor = cursors;
- cursors = cursor;
- }
- let cursors = null;
- function List() {
- this.cursor = null;
- this.head = null;
- this.tail = null;
- }
- List.createItem = createItem;
- List.prototype.createItem = createItem;
- List.prototype.updateCursors = function (prevOld, prevNew, nextOld, nextNew) {
- let cursor = this.cursor;
- while (cursor !== null) {
- if (cursor.prev === prevOld) {
- cursor.prev = prevNew;
- }
- if (cursor.next === nextOld) {
- cursor.next = nextNew;
- }
- cursor = cursor.cursor;
- }
- };
- List.prototype.getSize = function () {
- let size = 0;
- let cursor = this.head;
- while (cursor) {
- size++;
- cursor = cursor.next;
- }
- return size;
- };
- List.prototype.fromArray = function (array) {
- let cursor = null;
- this.head = null;
- for (let i = 0; i < array.length; i++) {
- const item = createItem(array[i]);
- if (cursor !== null) {
- cursor.next = item;
- } else {
- this.head = item;
- }
- item.prev = cursor;
- cursor = item;
- }
- this.tail = cursor;
- return this;
- };
- List.prototype.toArray = function () {
- let cursor = this.head;
- const result = [];
- while (cursor) {
- result.push(cursor.data);
- cursor = cursor.next;
- }
- return result;
- };
- List.prototype.toJSON = List.prototype.toArray;
- List.prototype.isEmpty = function () {
- return this.head === null;
- };
- List.prototype.first = function () {
- return this.head && this.head.data;
- };
- List.prototype.last = function () {
- return this.tail && this.tail.data;
- };
- List.prototype.each = function (fn, context) {
- let item;
- if (context === undefined) {
- context = this;
- }
- // push cursor
- const cursor = allocateCursor(this, null, this.head);
- while (cursor.next !== null) {
- item = cursor.next;
- cursor.next = item.next;
- fn.call(context, item.data, item, this);
- }
- // pop cursor
- releaseCursor(this);
- };
- List.prototype.forEach = List.prototype.each;
- List.prototype.eachRight = function (fn, context) {
- let item;
- if (context === undefined) {
- context = this;
- }
- // push cursor
- const cursor = allocateCursor(this, this.tail, null);
- while (cursor.prev !== null) {
- item = cursor.prev;
- cursor.prev = item.prev;
- fn.call(context, item.data, item, this);
- }
- // pop cursor
- releaseCursor(this);
- };
- List.prototype.forEachRight = List.prototype.eachRight;
- List.prototype.nextUntil = function (start, fn, context) {
- if (start === null) {
- return;
- }
- let item;
- if (context === undefined) {
- context = this;
- }
- // push cursor
- const cursor = allocateCursor(this, null, start);
- while (cursor.next !== null) {
- item = cursor.next;
- cursor.next = item.next;
- if (fn.call(context, item.data, item, this)) {
- break;
- }
- }
- // pop cursor
- releaseCursor(this);
- };
- List.prototype.prevUntil = function (start, fn, context) {
- if (start === null) {
- return;
- }
- let item;
- if (context === undefined) {
- context = this;
- }
- // push cursor
- const cursor = allocateCursor(this, start, null);
- while (cursor.prev !== null) {
- item = cursor.prev;
- cursor.prev = item.prev;
- if (fn.call(context, item.data, item, this)) {
- break;
- }
- }
- // pop cursor
- releaseCursor(this);
- };
- List.prototype.some = function (fn, context) {
- let cursor = this.head;
- if (context === undefined) {
- context = this;
- }
- while (cursor !== null) {
- if (fn.call(context, cursor.data, cursor, this)) {
- return true;
- }
- cursor = cursor.next;
- }
- return false;
- };
- List.prototype.map = function (fn, context) {
- const result = new List();
- let cursor = this.head;
- if (context === undefined) {
- context = this;
- }
- while (cursor !== null) {
- result.appendData(fn.call(context, cursor.data, cursor, this));
- cursor = cursor.next;
- }
- return result;
- };
- List.prototype.filter = function (fn, context) {
- const result = new List();
- let cursor = this.head;
- if (context === undefined) {
- context = this;
- }
- while (cursor !== null) {
- if (fn.call(context, cursor.data, cursor, this)) {
- result.appendData(cursor.data);
- }
- cursor = cursor.next;
- }
- return result;
- };
- List.prototype.clear = function () {
- this.head = null;
- this.tail = null;
- };
- List.prototype.copy = function () {
- const result = new List();
- let cursor = this.head;
- while (cursor !== null) {
- result.insert(createItem(cursor.data));
- cursor = cursor.next;
- }
- return result;
- };
- List.prototype.prepend = function (item) {
- // head
- // ^
- // item
- this.updateCursors(null, item, this.head, item);
- // insert to the beginning of the list
- if (this.head !== null) {
- // new item <- first item
- this.head.prev = item;
- // new item -> first item
- item.next = this.head;
- } else {
- // if list has no head, then it also has no tail
- // in this case tail points to the new item
- this.tail = item;
- }
- // head always points to new item
- this.head = item;
- return this;
- };
- List.prototype.prependData = function (data) {
- return this.prepend(createItem(data));
- };
- List.prototype.append = function (item) {
- return this.insert(item);
- };
- List.prototype.appendData = function (data) {
- return this.insert(createItem(data));
- };
- List.prototype.insert = function (item, before) {
- if (before !== undefined && before !== null) {
- // prev before
- // ^
- // item
- this.updateCursors(before.prev, item, before, item);
- if (before.prev === null) {
- // insert to the beginning of list
- if (this.head !== before) {
- throw new Error("before doesn\"t belong to list");
- }
- // since head points to before therefore list doesn"t empty
- // no need to check tail
- this.head = item;
- before.prev = item;
- item.next = before;
- this.updateCursors(null, item);
- } else {
- // insert between two items
- before.prev.next = item;
- item.prev = before.prev;
- before.prev = item;
- item.next = before;
- }
- } else {
- // tail
- // ^
- // item
- this.updateCursors(this.tail, item, null, item);
- // insert to the ending of the list
- if (this.tail !== null) {
- // last item -> new item
- this.tail.next = item;
- // last item <- new item
- item.prev = this.tail;
- } else {
- // if list has no tail, then it also has no head
- // in this case head points to new item
- this.head = item;
- }
- // tail always points to new item
- this.tail = item;
- }
- return this;
- };
- List.prototype.insertData = function (data, before) {
- return this.insert(createItem(data), before);
- };
- List.prototype.remove = function (item) {
- // item
- // ^
- // prev next
- this.updateCursors(item, item.prev, item, item.next);
- if (item.prev !== null) {
- item.prev.next = item.next;
- } else {
- if (this.head !== item) {
- throw new Error("item doesn\"t belong to list");
- }
- this.head = item.next;
- }
- if (item.next !== null) {
- item.next.prev = item.prev;
- } else {
- if (this.tail !== item) {
- throw new Error("item doesn\"t belong to list");
- }
- this.tail = item.prev;
- }
- item.prev = null;
- item.next = null;
- return item;
- };
- List.prototype.push = function (data) {
- this.insert(createItem(data));
- };
- List.prototype.pop = function () {
- if (this.tail !== null) {
- return this.remove(this.tail);
- }
- };
- List.prototype.unshift = function (data) {
- this.prepend(createItem(data));
- };
- List.prototype.shift = function () {
- if (this.head !== null) {
- return this.remove(this.head);
- }
- };
- List.prototype.prependList = function (list) {
- return this.insertList(list, this.head);
- };
- List.prototype.appendList = function (list) {
- return this.insertList(list);
- };
- List.prototype.insertList = function (list, before) {
- // ignore empty lists
- if (list.head === null) {
- return this;
- }
- if (before !== undefined && before !== null) {
- this.updateCursors(before.prev, list.tail, before, list.head);
- // insert in the middle of dist list
- if (before.prev !== null) {
- // before.prev <-> list.head
- before.prev.next = list.head;
- list.head.prev = before.prev;
- } else {
- this.head = list.head;
- }
- before.prev = list.tail;
- list.tail.next = before;
- } else {
- this.updateCursors(this.tail, list.tail, null, list.head);
- // insert to end of the list
- if (this.tail !== null) {
- // if destination list has a tail, then it also has a head,
- // but head doesn"t change
- // dest tail -> source head
- this.tail.next = list.head;
- // dest tail <- source head
- list.head.prev = this.tail;
- } else {
- // if list has no a tail, then it also has no a head
- // in this case points head to new item
- this.head = list.head;
- }
- // tail always start point to new item
- this.tail = list.tail;
- }
- list.head = null;
- list.tail = null;
- return this;
- };
- List.prototype.replace = function (oldItem, newItemOrList) {
- if ("head" in newItemOrList) {
- this.insertList(newItemOrList, oldItem);
- } else {
- this.insert(newItemOrList, oldItem);
- }
- this.remove(oldItem);
- };
- // ---
- function createCustomError(name, message) {
- // use Object.create(), because some VMs prevent setting line/column otherwise
- // (iOS Safari 10 even throws an exception)
- const error = Object.create(SyntaxError.prototype);
- const errorStack = new Error();
- error.name = name;
- error.message = message;
- Object.defineProperty(error, "stack", {
- get: function () {
- return (errorStack.stack || "").replace(/^(.+\n){1,3}/, name + ": " + message + "\n");
- }
- });
- return error;
- }
- // ---
- const MAX_LINE_LENGTH = 100;
- const OFFSET_CORRECTION = 60;
- const TAB_REPLACEMENT = " ";
- function sourceFragment(error, extraLines) {
- function processLines(start, end) {
- return lines.slice(start, end).map(function (line, idx) {
- let num = String(start + idx + 1);
- while (num.length < maxNumLength) {
- num = " " + num;
- }
- return num + " |" + line;
- }).join("\n");
- }
- const lines = error.source.split(/\r\n?|\n|\f/);
- let line = error.line;
- let column = error.column;
- const startLine = Math.max(1, line - extraLines) - 1;
- const endLine = Math.min(line + extraLines, lines.length + 1);
- const maxNumLength = Math.max(4, String(endLine).length) + 1;
- let cutLeft = 0;
- // column correction according to replaced tab before column
- column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length;
- if (column > MAX_LINE_LENGTH) {
- cutLeft = column - OFFSET_CORRECTION + 3;
- column = OFFSET_CORRECTION - 2;
- }
- for (let i = startLine; i <= endLine; i++) {
- if (i >= 0 && i < lines.length) {
- lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT);
- lines[i] =
- (cutLeft > 0 && lines[i].length > cutLeft ? "\u2026" : "") +
- lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +
- (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? "\u2026" : "");
- }
- }
- return [
- processLines(startLine, line),
- new Array(column + maxNumLength + 2).join("-") + "^",
- processLines(line, endLine)
- ].filter(Boolean).join("\n");
- }
- function CssSyntaxError(message, source, offset, line, column) {
- const error = createCustomError("CssSyntaxError", message);
- error.source = source;
- error.offset = offset;
- error.line = line;
- error.column = column;
- error.sourceFragment = function (extraLines) {
- return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines);
- };
- Object.defineProperty(error, "formattedMessage", {
- get: function () {
- return (
- "Parse error: " + error.message + "\n" +
- sourceFragment(error, 2)
- );
- }
- });
- // for backward capability
- error.parseError = {
- offset: offset,
- line: line,
- column: column
- };
- return error;
- }
- // ---
- // token types (note: value shouldn't intersect with used char codes)
- const WHITESPACE = 1;
- const IDENTIFIER = 2;
- const NUMBER = 3;
- const STRING = 4;
- const COMMENT = 5;
- const PUNCTUATOR = 6;
- const CDO = 7;
- const CDC = 8;
- const ATKEYWORD = 14;
- const FUNCTION = 15;
- const URL = 16;
- const RAW = 17;
- const TAB = 9;
- const NEW_LINE = 10;
- const F = 12;
- const R = 13;
- const SPACE = 32;
- const TYPE = {
- WhiteSpace: WHITESPACE,
- Identifier: IDENTIFIER,
- Number: NUMBER,
- String: STRING,
- Comment: COMMENT,
- Punctuator: PUNCTUATOR,
- CDO: CDO,
- CDC: CDC,
- AtKeyword: ATKEYWORD,
- Function: FUNCTION,
- Url: URL,
- Raw: RAW,
- ExclamationMark: 33, // !
- QuotationMark: 34, // "
- NumberSign: 35, // #
- DollarSign: 36, // $
- PercentSign: 37, // %
- Ampersand: 38, // &
- Apostrophe: 39, // '
- LeftParenthesis: 40, // (
- RightParenthesis: 41, // )
- Asterisk: 42, // *
- PlusSign: 43, // +
- Comma: 44, // ,
- HyphenMinus: 45, // -
- FullStop: 46, // .
- Solidus: 47, // /
- Colon: 58, // :
- Semicolon: 59, // ;
- LessThanSign: 60, // <
- EqualsSign: 61, // =
- GreaterThanSign: 62, // >
- QuestionMark: 63, // ?
- CommercialAt: 64, // @
- LeftSquareBracket: 91, // [
- Backslash: 92, // \
- RightSquareBracket: 93, // ]
- CircumflexAccent: 94, // ^
- LowLine: 95, // _
- GraveAccent: 96, // `
- LeftCurlyBracket: 123, // {
- VerticalLine: 124, // |
- RightCurlyBracket: 125, // }
- Tilde: 126 // ~
- };
- const NAME = Object.keys(TYPE).reduce(function (result, key) {
- result[TYPE[key]] = key;
- return result;
- }, {});
- // https://drafts.csswg.org/css-syntax/#tokenizer-definitions
- // > non-ASCII code point
- // > A code point with a value equal to or greater than U+0080 <control>
- // > name-start code point
- // > A letter, a non-ASCII code point, or U+005F LOW LINE (_).
- // > name code point
- // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-)
- // That means only ASCII code points has a special meaning and we a maps for 0..127 codes only
- const SafeUint32Array = typeof Uint32Array !== "undefined" ? Uint32Array : Array; // fallback on Array when TypedArray is not supported
- const SYMBOL_TYPE = new SafeUint32Array(0x80);
- const PUNCTUATION = new SafeUint32Array(0x80);
- const STOP_URL_RAW = new SafeUint32Array(0x80);
- for (let i = 0; i < SYMBOL_TYPE.length; i++) {
- SYMBOL_TYPE[i] = IDENTIFIER;
- }
- // fill categories
- [
- TYPE.ExclamationMark, // !
- TYPE.QuotationMark, // "
- TYPE.NumberSign, // #
- TYPE.DollarSign, // $
- TYPE.PercentSign, // %
- TYPE.Ampersand, // &
- TYPE.Apostrophe, // '
- TYPE.LeftParenthesis, // (
- TYPE.RightParenthesis, // )
- TYPE.Asterisk, // *
- TYPE.PlusSign, // +
- TYPE.Comma, // ,
- TYPE.HyphenMinus, // -
- TYPE.FullStop, // .
- TYPE.Solidus, // /
- TYPE.Colon, // :
- TYPE.Semicolon, // ;
- TYPE.LessThanSign, // <
- TYPE.EqualsSign, // =
- TYPE.GreaterThanSign, // >
- TYPE.QuestionMark, // ?
- TYPE.CommercialAt, // @
- TYPE.LeftSquareBracket, // [
- // TYPE.Backslash, // \
- TYPE.RightSquareBracket, // ]
- TYPE.CircumflexAccent, // ^
- // TYPE.LowLine, // _
- TYPE.GraveAccent, // `
- TYPE.LeftCurlyBracket, // {
- TYPE.VerticalLine, // |
- TYPE.RightCurlyBracket, // }
- TYPE.Tilde // ~
- ].forEach(function (key) {
- SYMBOL_TYPE[Number(key)] = PUNCTUATOR;
- PUNCTUATION[Number(key)] = PUNCTUATOR;
- });
- for (let i = 48; i <= 57; i++) {
- SYMBOL_TYPE[i] = NUMBER;
- }
- SYMBOL_TYPE[SPACE] = WHITESPACE;
- SYMBOL_TYPE[TAB] = WHITESPACE;
- SYMBOL_TYPE[NEW_LINE] = WHITESPACE;
- SYMBOL_TYPE[R] = WHITESPACE;
- SYMBOL_TYPE[F] = WHITESPACE;
- SYMBOL_TYPE[TYPE.Apostrophe] = STRING;
- SYMBOL_TYPE[TYPE.QuotationMark] = STRING;
- STOP_URL_RAW[SPACE] = 1;
- STOP_URL_RAW[TAB] = 1;
- STOP_URL_RAW[NEW_LINE] = 1;
- STOP_URL_RAW[R] = 1;
- STOP_URL_RAW[F] = 1;
- STOP_URL_RAW[TYPE.Apostrophe] = 1;
- STOP_URL_RAW[TYPE.QuotationMark] = 1;
- STOP_URL_RAW[TYPE.LeftParenthesis] = 1;
- STOP_URL_RAW[TYPE.RightParenthesis] = 1;
- // whitespace is punctuation ...
- PUNCTUATION[SPACE] = PUNCTUATOR;
- PUNCTUATION[TAB] = PUNCTUATOR;
- PUNCTUATION[NEW_LINE] = PUNCTUATOR;
- PUNCTUATION[R] = PUNCTUATOR;
- PUNCTUATION[F] = PUNCTUATOR;
- // ... hyper minus is not
- PUNCTUATION[TYPE.HyphenMinus] = 0;
- const constants = {
- TYPE: TYPE,
- NAME: NAME,
- SYMBOL_TYPE: SYMBOL_TYPE,
- PUNCTUATION: PUNCTUATION,
- STOP_URL_RAW: STOP_URL_RAW
- };
- // ---
- const BACK_SLASH = 92;
- const E = 101; // 'e'.charCodeAt(0)
- function firstCharOffset(source) {
- // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
- if (source.charCodeAt(0) === 0xFEFF || // UTF-16BE
- source.charCodeAt(0) === 0xFFFE) { // UTF-16LE
- return 1;
- }
- return 0;
- }
- function isHex(code) {
- return (code >= 48 && code <= 57) || // 0 .. 9
- (code >= 65 && code <= 70) || // A .. F
- (code >= 97 && code <= 102); // a .. f
- }
- function isNumber(code) {
- return code >= 48 && code <= 57;
- }
- function isWhiteSpace(code) {
- return code === SPACE || code === TAB || isNewline(code);
- }
- function isNewline(code) {
- return code === R || code === NEW_LINE || code === F;
- }
- function getNewlineLength(source, offset, code) {
- if (isNewline(code)) {
- if (code === R && offset + 1 < source.length && source.charCodeAt(offset + 1) === NEW_LINE) {
- return 2;
- }
- return 1;
- }
- return 0;
- }
- function cmpChar(testStr, offset, referenceCode) {
- let code = testStr.charCodeAt(offset);
- // code.toLowerCase() for A..Z
- if (code >= 65 && code <= 90) {
- code = code | 32;
- }
- return code === referenceCode;
- }
- function cmpStr(testStr, start, end, referenceStr) {
- if (end - start !== referenceStr.length) {
- return false;
- }
- if (start < 0 || end > testStr.length) {
- return false;
- }
- for (let i = start; i < end; i++) {
- let testCode = testStr.charCodeAt(i);
- const refCode = referenceStr.charCodeAt(i - start);
- // testCode.toLowerCase() for A..Z
- if (testCode >= 65 && testCode <= 90) {
- testCode = testCode | 32;
- }
- if (testCode !== refCode) {
- return false;
- }
- }
- return true;
- }
- function findWhiteSpaceStart(source, offset) {
- while (offset >= 0 && isWhiteSpace(source.charCodeAt(offset))) {
- offset--;
- }
- return offset + 1;
- }
- function findWhiteSpaceEnd(source, offset) {
- while (offset < source.length && isWhiteSpace(source.charCodeAt(offset))) {
- offset++;
- }
- return offset;
- }
- function findCommentEnd(source, offset) {
- const commentEnd = source.indexOf("*/", offset);
- if (commentEnd === -1) {
- return source.length;
- }
- return commentEnd + 2;
- }
- function findStringEnd(source, offset, quote) {
- for (; offset < source.length; offset++) {
- const code = source.charCodeAt(offset);
- // TODO: bad string
- if (code === BACK_SLASH) {
- offset++;
- } else if (code === quote) {
- offset++;
- break;
- }
- }
- return offset;
- }
- function findDecimalNumberEnd(source, offset) {
- while (offset < source.length && isNumber(source.charCodeAt(offset))) {
- offset++;
- }
- return offset;
- }
- function findNumberEnd(source, offset, allowFraction) {
- let code;
- offset = findDecimalNumberEnd(source, offset);
- // fraction: .\d+
- if (allowFraction && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) {
- code = source.charCodeAt(offset + 1);
- if (isNumber(code)) {
- offset = findDecimalNumberEnd(source, offset + 1);
- }
- }
- // exponent: e[+-]\d+
- if (offset + 1 < source.length) {
- if ((source.charCodeAt(offset) | 32) === E) { // case insensitive check for `e`
- code = source.charCodeAt(offset + 1);
- if (code === PLUSSIGN || code === HYPHENMINUS) {
- if (offset + 2 < source.length) {
- code = source.charCodeAt(offset + 2);
- }
- }
- if (isNumber(code)) {
- offset = findDecimalNumberEnd(source, offset + 2);
- }
- }
- }
- return offset;
- }
- // skip escaped unicode sequence that can ends with space
- // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
- function findEscapeEnd(source, offset) {
- for (let i = 0; i < 7 && offset + i < source.length; i++) {
- const code = source.charCodeAt(offset + i);
- if (i !== 6 && isHex(code)) {
- continue;
- }
- if (i > 0) {
- offset += i - 1 + getNewlineLength(source, offset + i, code);
- if (code === SPACE || code === TAB) {
- offset++;
- }
- }
- break;
- }
- return offset;
- }
- function findIdentifierEnd(source, offset) {
- for (; offset < source.length; offset++) {
- const code = source.charCodeAt(offset);
- if (code === BACK_SLASH) {
- offset = findEscapeEnd(source, offset + 1);
- } else if (code < 0x80 && PUNCTUATION[code] === PUNCTUATOR) {
- break;
- }
- }
- return offset;
- }
- function findUrlRawEnd(source, offset) {
- for (; offset < source.length; offset++) {
- const code = source.charCodeAt(offset);
- if (code === BACK_SLASH) {
- offset = findEscapeEnd(source, offset + 1);
- } else if (code < 0x80 && STOP_URL_RAW[code] === 1) {
- break;
- }
- }
- return offset;
- }
- const utils = {
- firstCharOffset: firstCharOffset,
- isHex: isHex,
- isNumber: isNumber,
- isWhiteSpace: isWhiteSpace,
- isNewline: isNewline,
- getNewlineLength: getNewlineLength,
- cmpChar: cmpChar,
- cmpStr: cmpStr,
- findWhiteSpaceStart: findWhiteSpaceStart,
- findWhiteSpaceEnd: findWhiteSpaceEnd,
- findCommentEnd: findCommentEnd,
- findStringEnd: findStringEnd,
- findDecimalNumberEnd: findDecimalNumberEnd,
- findNumberEnd: findNumberEnd,
- findEscapeEnd: findEscapeEnd,
- findIdentifierEnd: findIdentifierEnd,
- findUrlRawEnd: findUrlRawEnd
- };
- // ---
- const STAR = TYPE.Asterisk;
- const SLASH = TYPE.Solidus;
- const FULLSTOP = TYPE.FullStop;
- const PLUSSIGN = TYPE.PlusSign;
- const HYPHENMINUS = TYPE.HyphenMinus;
- const GREATERTHANSIGN = TYPE.GreaterThanSign;
- const LESSTHANSIGN = TYPE.LessThanSign;
- const EXCLAMATIONMARK = TYPE.ExclamationMark;
- const COMMERCIALAT = TYPE.CommercialAt;
- const QUOTATIONMARK = TYPE.QuotationMark;
- const APOSTROPHE = TYPE.Apostrophe;
- const LEFTPARENTHESIS = TYPE.LeftParenthesis;
- const RIGHTPARENTHESIS = TYPE.RightParenthesis;
- const LEFTCURLYBRACKET = TYPE.LeftCurlyBracket;
- const RIGHTCURLYBRACKET = TYPE.RightCurlyBracket;
- const LEFTSQUAREBRACKET = TYPE.LeftSquareBracket;
- const RIGHTSQUAREBRACKET = TYPE.RightSquareBracket;
- const NUMBERSIGN = TYPE.NumberSign;
- const COMMA = TYPE.Comma;
- const SOLIDUS = TYPE.Solidus;
- const ASTERISK = TYPE.Asterisk;
- const PERCENTSIGN = TYPE.PercentSign;
- const BACKSLASH = TYPE.Backslash;
- const VERTICALLINE = TYPE.VerticalLine;
- const TILDE = TYPE.Tilde;
- const SEMICOLON = TYPE.Semicolon;
- const COLON = TYPE.Colon;
- const DOLLARSIGN = TYPE.DollarSign;
- const EQUALSSIGN = TYPE.EqualsSign;
- const CIRCUMFLEXACCENT = TYPE.CircumflexAccent;
- const TYPE_CDC = TYPE.CDC;
- const TYPE_CDO = TYPE.CDO;
- const QUESTIONMARK = TYPE.QuestionMark;
- const NULL = 0;
- const MIN_BUFFER_SIZE = 16 * 1024;
- const OFFSET_MASK = 0x00FFFFFF;
- const TYPE_SHIFT = 24;
- function computeLinesAndColumns(tokenizer, source) {
- const sourceLength = source.length;
- const start = firstCharOffset(source);
- let lines = tokenizer.lines;
- let line = tokenizer.startLine;
- let columns = tokenizer.columns;
- let column = tokenizer.startColumn;
- if (lines === null || lines.length < sourceLength + 1) {
- lines = new SafeUint32Array(Math.max(sourceLength + 1024, MIN_BUFFER_SIZE));
- columns = new SafeUint32Array(lines.length);
- }
- let i;
- for (i = start; i < sourceLength; i++) {
- const code = source.charCodeAt(i);
- lines[i] = line;
- columns[i] = column++;
- if (code === NEW_LINE || code === R || code === F) {
- if (code === R && i + 1 < sourceLength && source.charCodeAt(i + 1) === NEW_LINE) {
- i++;
- lines[i] = line;
- columns[i] = column;
- }
- line++;
- column = 1;
- }
- }
- lines[i] = line;
- columns[i] = column;
- tokenizer.linesAnsColumnsComputed = true;
- tokenizer.lines = lines;
- tokenizer.columns = columns;
- }
- function tokenLayout(tokenizer, source, startPos) {
- const sourceLength = source.length;
- let offsetAndType = tokenizer.offsetAndType;
- let balance = tokenizer.balance;
- let tokenCount = 0;
- let prevType = 0;
- let offset = startPos;
- let anchor = 0;
- let balanceCloseCode = 0;
- let balanceStart = 0;
- let balancePrev = 0;
- if (offsetAndType === null || offsetAndType.length < sourceLength + 1) {
- offsetAndType = new SafeUint32Array(sourceLength + 1024);
- balance = new SafeUint32Array(sourceLength + 1024);
- }
- while (offset < sourceLength) {
- let code = source.charCodeAt(offset);
- let type = code < 0x80 ? SYMBOL_TYPE[code] : IDENTIFIER;
- balance[tokenCount] = sourceLength;
- switch (type) {
- case WHITESPACE:
- offset = findWhiteSpaceEnd(source, offset + 1);
- break;
- case PUNCTUATOR:
- switch (code) {
- case balanceCloseCode:
- balancePrev = balanceStart & OFFSET_MASK;
- balanceStart = balance[balancePrev];
- balanceCloseCode = balanceStart >> TYPE_SHIFT;
- balance[tokenCount] = balancePrev;
- balance[balancePrev++] = tokenCount;
- for (; balancePrev < tokenCount; balancePrev++) {
- if (balance[balancePrev] === sourceLength) {
- balance[balancePrev] = tokenCount;
- }
- }
- break;
- case LEFTSQUAREBRACKET:
- balance[tokenCount] = balanceStart;
- balanceCloseCode = RIGHTSQUAREBRACKET;
- balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
- break;
- case LEFTCURLYBRACKET:
- balance[tokenCount] = balanceStart;
- balanceCloseCode = RIGHTCURLYBRACKET;
- balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
- break;
- case LEFTPARENTHESIS:
- balance[tokenCount] = balanceStart;
- balanceCloseCode = RIGHTPARENTHESIS;
- balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
- break;
- }
- // /*
- if (code === STAR && prevType === SLASH) {
- type = COMMENT;
- offset = findCommentEnd(source, offset + 1);
- tokenCount--; // rewrite prev token
- break;
- }
- // edge case for -.123 and +.123
- if (code === FULLSTOP && (prevType === PLUSSIGN || prevType === HYPHENMINUS)) {
- if (offset + 1 < sourceLength && isNumber(source.charCodeAt(offset + 1))) {
- type = NUMBER;
- offset = findNumberEnd(source, offset + 2, false);
- tokenCount--; // rewrite prev token
- break;
- }
- }
- // <!--
- if (code === EXCLAMATIONMARK && prevType === LESSTHANSIGN) {
- if (offset + 2 < sourceLength &&
- source.charCodeAt(offset + 1) === HYPHENMINUS &&
- source.charCodeAt(offset + 2) === HYPHENMINUS) {
- type = CDO;
- offset = offset + 3;
- tokenCount--; // rewrite prev token
- break;
- }
- }
- // -->
- if (code === HYPHENMINUS && prevType === HYPHENMINUS) {
- if (offset + 1 < sourceLength && source.charCodeAt(offset + 1) === GREATERTHANSIGN) {
- type = CDC;
- offset = offset + 2;
- tokenCount--; // rewrite prev token
- break;
- }
- }
- // ident(
- if (code === LEFTPARENTHESIS && prevType === IDENTIFIER) {
- offset = offset + 1;
- tokenCount--; // rewrite prev token
- balance[tokenCount] = balance[tokenCount + 1];
- balanceStart--;
- // 4 char length identifier and equal to `url(` (case insensitive)
- if (offset - anchor === 4 && cmpStr(source, anchor, offset, "url(")) {
- // special case for url() because it can contain any symbols sequence with few exceptions
- anchor = findWhiteSpaceEnd(source, offset);
- code = source.charCodeAt(anchor);
- if (code !== LEFTPARENTHESIS &&
- code !== RIGHTPARENTHESIS &&
- code !== QUOTATIONMARK &&
- code !== APOSTROPHE) {
- // url(
- offsetAndType[tokenCount++] = (URL << TYPE_SHIFT) | offset;
- balance[tokenCount] = sourceLength;
- // ws*
- if (anchor !== offset) {
- offsetAndType[tokenCount++] = (WHITESPACE << TYPE_SHIFT) | anchor;
- balance[tokenCount] = sourceLength;
- }
- // raw
- type = RAW;
- offset = findUrlRawEnd(source, anchor);
- } else {
- type = URL;
- }
- } else {
- type = FUNCTION;
- }
- break;
- }
- type = code;
- offset = offset + 1;
- break;
- case NUMBER:
- offset = findNumberEnd(source, offset + 1, prevType !== FULLSTOP);
- // merge number with a preceding dot, dash or plus
- if (prevType === FULLSTOP ||
- prevType === HYPHENMINUS ||
- prevType === PLUSSIGN) {
- tokenCount--; // rewrite prev token
- }
- break;
- case STRING:
- offset = findStringEnd(source, offset + 1, code);
- break;
- default:
- anchor = offset;
- offset = findIdentifierEnd(source, offset);
- // merge identifier with a preceding dash
- if (prevType === HYPHENMINUS) {
- // rewrite prev token
- tokenCount--;
- // restore prev prev token type
- // for case @-prefix-ident
- prevType = tokenCount === 0 ? 0 : offsetAndType[tokenCount - 1] >> TYPE_SHIFT;
- }
- if (prevType === COMMERCIALAT) {
- // rewrite prev token and change type to <at-keyword-token>
- tokenCount--;
- type = ATKEYWORD;
- }
- }
- offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset;
- prevType = type;
- }
- // finalize arrays
- offsetAndType[tokenCount] = offset;
- balance[tokenCount] = sourceLength;
- balance[sourceLength] = sourceLength; // prevents false positive balance match with any token
- while (balanceStart !== 0) {
- balancePrev = balanceStart & OFFSET_MASK;
- balanceStart = balance[balancePrev];
- balance[balancePrev] = sourceLength;
- }
- tokenizer.offsetAndType = offsetAndType;
- tokenizer.tokenCount = tokenCount;
- tokenizer.balance = balance;
- }
- //
- // tokenizer
- //
- function Tokenizer(source, startOffset, startLine, startColumn) {
- this.offsetAndType = null;
- this.balance = null;
- this.lines = null;
- this.columns = null;
- this.setSource(source, startOffset, startLine, startColumn);
- }
- Tokenizer.prototype = {
- setSource: function (source, startOffset, startLine, startColumn) {
- const safeSource = String(source || "");
- const start = firstCharOffset(safeSource);
- this.source = safeSource;
- this.firstCharOffset = start;
- this.startOffset = typeof startOffset === "undefined" ? 0 : startOffset;
- this.startLine = typeof startLine === "undefined" ? 1 : startLine;
- this.startColumn = typeof startColumn === "undefined" ? 1 : startColumn;
- this.linesAnsColumnsComputed = false;
- this.eof = false;
- this.currentToken = -1;
- this.tokenType = 0;
- this.tokenStart = start;
- this.tokenEnd = start;
- tokenLayout(this, safeSource, start);
- this.next();
- },
- lookupType: function (offset) {
- offset += this.currentToken;
- if (offset < this.tokenCount) {
- return this.offsetAndType[offset] >> TYPE_SHIFT;
- }
- return NULL;
- },
- lookupNonWSType: function (offset) {
- offset += this.currentToken;
- for (let type; offset < this.tokenCount; offset++) {
- type = this.offsetAndType[offset] >> TYPE_SHIFT;
- if (type !== WHITESPACE) {
- return type;
- }
- }
- return NULL;
- },
- lookupValue: function (offset, referenceStr) {
- offset += this.currentToken;
- if (offset < this.tokenCount) {
- return cmpStr(
- this.source,
- this.offsetAndType[offset - 1] & OFFSET_MASK,
- this.offsetAndType[offset] & OFFSET_MASK,
- referenceStr
- );
- }
- return false;
- },
- getTokenStart: function (tokenNum) {
- if (tokenNum === this.currentToken) {
- return this.tokenStart;
- }
- if (tokenNum > 0) {
- return tokenNum < this.tokenCount
- ? this.offsetAndType[tokenNum - 1] & OFFSET_MASK
- : this.offsetAndType[this.tokenCount] & OFFSET_MASK;
- }
- return this.firstCharOffset;
- },
- getOffsetExcludeWS: function () {
- if (this.currentToken > 0) {
- if ((this.offsetAndType[this.currentToken - 1] >> TYPE_SHIFT) === WHITESPACE) {
- return this.currentToken > 1
- ? this.offsetAndType[this.currentToken - 2] & OFFSET_MASK
- : this.firstCharOffset;
- }
- }
- return this.tokenStart;
- },
- getRawLength: function (startToken, endTokenType1, endTokenType2, includeTokenType2) {
- let cursor = startToken;
- let balanceEnd;
- loop:
- for (; cursor < this.tokenCount; cursor++) {
- balanceEnd = this.balance[cursor];
- // belance end points to offset before start
- if (balanceEnd < startToken) {
- break loop;
- }
- // check token is stop type
- switch (this.offsetAndType[cursor] >> TYPE_SHIFT) {
- case endTokenType1:
- break loop;
- case endTokenType2:
- if (includeTokenType2) {
- cursor++;
- }
- break loop;
- default:
- // fast forward to the end of balanced block
- if (this.balance[balanceEnd] === cursor) {
- cursor = balanceEnd;
- }
- }
- }
- return cursor - this.currentToken;
- },
- isBalanceEdge: function (pos) {
- const balanceStart = this.balance[this.currentToken];
- return balanceStart < pos;
- },
- getTokenValue: function () {
- return this.source.substring(this.tokenStart, this.tokenEnd);
- },
- substrToCursor: function (start) {
- return this.source.substring(start, this.tokenStart);
- },
- skipWS: function () {
- let skipTokenCount = 0;
- for (let i = this.currentToken; i < this.tokenCount; i++ , skipTokenCount++) {
- if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE) {
- break;
- }
- }
- if (skipTokenCount > 0) {
- this.skip(skipTokenCount);
- }
- },
- skipSC: function () {
- while (this.tokenType === WHITESPACE || this.tokenType === COMMENT) {
- this.next();
- }
- },
- skip: function (tokenCount) {
- let next = this.currentToken + tokenCount;
- if (next < this.tokenCount) {
- this.currentToken = next;
- this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK;
- next = this.offsetAndType[next];
- this.tokenType = next >> TYPE_SHIFT;
- this.tokenEnd = next & OFFSET_MASK;
- } else {
- this.currentToken = this.tokenCount;
- this.next();
- }
- },
- next: function () {
- let next = this.currentToken + 1;
- if (next < this.tokenCount) {
- this.currentToken = next;
- this.tokenStart = this.tokenEnd;
- next = this.offsetAndType[next];
- this.tokenType = next >> TYPE_SHIFT;
- this.tokenEnd = next & OFFSET_MASK;
- } else {
- this.currentToken = this.tokenCount;
- this.eof = true;
- this.tokenType = NULL;
- this.tokenStart = this.tokenEnd = this.source.length;
- }
- },
- eat: function (tokenType) {
- if (this.tokenType !== tokenType) {
- let offset = this.tokenStart;
- let message = NAME[tokenType] + " is expected";
- // tweak message and offset
- if (tokenType === IDENTIFIER) {
- // when identifier is expected but there is a function or url
- if (this.tokenType === FUNCTION || this.tokenType === URL) {
- offset = this.tokenEnd - 1;
- message += " but function found";
- }
- } else {
- // when test type is part of another token show error for current position + 1
- // e.g. eat(HYPHENMINUS) will fail on "-foo", but pointing on "-" is odd
- if (this.source.charCodeAt(this.tokenStart) === tokenType) {
- offset = offset + 1;
- }
- }
- this.error(message, offset);
- }
- this.next();
- },
- eatNonWS: function (tokenType) {
- this.skipWS();
- this.eat(tokenType);
- },
- consume: function (tokenType) {
- const value = this.getTokenValue();
- this.eat(tokenType);
- return value;
- },
- consumeFunctionName: function () {
- const name = this.source.substring(this.tokenStart, this.tokenEnd - 1);
- this.eat(FUNCTION);
- return name;
- },
- consumeNonWS: function (tokenType) {
- this.skipWS();
- return this.consume(tokenType);
- },
- expectIdentifier: function (name) {
- if (this.tokenType !== IDENTIFIER || cmpStr(this.source, this.tokenStart, this.tokenEnd, name) === false) {
- this.error("Identifier `" + name + "` is expected");
- }
- this.next();
- },
- getLocation: function (offset, filename) {
- if (!this.linesAnsColumnsComputed) {
- computeLinesAndColumns(this, this.source);
- }
- return {
- source: filename,
- offset: this.startOffset + offset,
- line: this.lines[offset],
- column: this.columns[offset]
- };
- },
- getLocationRange: function (start, end, filename) {
- if (!this.linesAnsColumnsComputed) {
- computeLinesAndColumns(this, this.source);
- }
- return {
- source: filename,
- start: {
- offset: this.startOffset + start,
- line: this.lines[start],
- column: this.columns[start]
- },
- end: {
- offset: this.startOffset + end,
- line: this.lines[end],
- column: this.columns[end]
- }
- };
- },
- error: function (message, offset) {
- const location = typeof offset !== "undefined" && offset < this.source.length
- ? this.getLocation(offset)
- : this.eof
- ? this.getLocation(findWhiteSpaceStart(this.source, this.source.length - 1))
- : this.getLocation(this.tokenStart);
- throw new CssSyntaxError(
- message || "Unexpected input",
- this.source,
- location.offset,
- location.line,
- location.column
- );
- },
- dump: function () {
- let offset = 0;
- return Array.prototype.slice.call(this.offsetAndType, 0, this.tokenCount).map(function (item, idx) {
- const start = offset;
- const end = item & OFFSET_MASK;
- offset = end;
- return {
- idx: idx,
- type: NAME[item >> TYPE_SHIFT],
- chunk: this.source.substring(start, end),
- balance: this.balance[idx]
- };
- }, this);
- }
- };
- // extend with error class
- Tokenizer.CssSyntaxError = CssSyntaxError;
- // extend tokenizer with constants
- Object.keys(constants).forEach(function (key) {
- Tokenizer[key] = constants[key];
- });
- // extend tokenizer with static methods from utils
- Object.keys(utils).forEach(function (key) {
- Tokenizer[key] = utils[key];
- });
- // warm up tokenizer to elimitate code branches that never execute
- // fix soft deoptimizations (insufficient type feedback)
- new Tokenizer("\n\r\r\n\f<!---->//\"\"''/*\r\n\f*/1a;.\\31\t+2{url(a);func();+1.2e3 -.4e-5 .6e+7}").getLocation();
- // ---
- const sequence = function readSequence(recognizer) {
- const children = this.createList();
- let child = null;
- const context = {
- recognizer: recognizer,
- space: null,
- ignoreWS: false,
- ignoreWSAfter: false
- };
- this.scanner.skipSC();
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case COMMENT:
- this.scanner.next();
- continue;
- case WHITESPACE:
- if (context.ignoreWS) {
- this.scanner.next();
- } else {
- context.space = this.WhiteSpace();
- }
- continue;
- }
- child = recognizer.getNode.call(this, context);
- if (child === undefined) {
- break;
- }
- if (context.space !== null) {
- children.push(context.space);
- context.space = null;
- }
- children.push(child);
- if (context.ignoreWSAfter) {
- context.ignoreWSAfter = false;
- context.ignoreWS = true;
- } else {
- context.ignoreWS = false;
- }
- }
- return children;
- };
- // ---
- const noop = function () { };
- function createParseContext(name) {
- return function () {
- return this[name]();
- };
- }
- function processConfig(config) {
- const parserConfig = {
- context: {},
- scope: {},
- atrule: {},
- pseudo: {}
- };
- if (config.parseContext) {
- for (let name in config.parseContext) {
- switch (typeof config.parseContext[name]) {
- case "function":
- parserConfig.context[name] = config.parseContext[name];
- break;
- case "string":
- parserConfig.context[name] = createParseContext(config.parseContext[name]);
- break;
- }
- }
- }
- if (config.scope) {
- for (let name in config.scope) {
- parserConfig.scope[name] = config.scope[name];
- }
- }
- if (config.atrule) {
- for (let name in config.atrule) {
- const atrule = config.atrule[name];
- if (atrule.parse) {
- parserConfig.atrule[name] = atrule.parse;
- }
- }
- }
- if (config.pseudo) {
- for (let name in config.pseudo) {
- const pseudo = config.pseudo[name];
- if (pseudo.parse) {
- parserConfig.pseudo[name] = pseudo.parse;
- }
- }
- }
- if (config.node) {
- for (let name in config.node) {
- parserConfig[name] = config.node[name].parse;
- }
- }
- return parserConfig;
- }
- function createParser(config) {
- const parser = {
- scanner: new Tokenizer(),
- filename: "<unknown>",
- needPositions: false,
- onParseError: noop,
- onParseErrorThrow: false,
- parseAtrulePrelude: true,
- parseRulePrelude: true,
- parseValue: true,
- parseCustomProperty: false,
- readSequence: sequence,
- createList: function () {
- return new List();
- },
- createSingleNodeList: function (node) {
- return new List().appendData(node);
- },
- getFirstListNode: function (list) {
- return list && list.first();
- },
- getLastListNode: function (list) {
- return list.last();
- },
- parseWithFallback: function (consumer, fallback) {
- const startToken = this.scanner.currentToken;
- try {
- return consumer.call(this);
- } catch (e) {
- if (this.onParseErrorThrow) {
- throw e;
- }
- const fallbackNode = fallback.call(this, startToken);
- this.onParseErrorThrow = true;
- this.onParseError(e, fallbackNode);
- this.onParseErrorThrow = false;
- return fallbackNode;
- }
- },
- getLocation: function (start, end) {
- if (this.needPositions) {
- return this.scanner.getLocationRange(
- start,
- end,
- this.filename
- );
- }
- return null;
- },
- getLocationFromList: function (list) {
- if (this.needPositions) {
- const head = this.getFirstListNode(list);
- const tail = this.getLastListNode(list);
- return this.scanner.getLocationRange(
- head !== null ? head.loc.start.offset - this.scanner.startOffset : this.scanner.tokenStart,
- tail !== null ? tail.loc.end.offset - this.scanner.startOffset : this.scanner.tokenStart,
- this.filename
- );
- }
- return null;
- }
- };
- config = processConfig(config || {});
- for (let key in config) {
- parser[key] = config[key];
- }
- return function (source, options) {
- options = options || {};
- const context = options.context || "default";
- let ast;
- parser.scanner.setSource(source, options.offset, options.line, options.column);
- parser.filename = options.filename || "<unknown>";
- parser.needPositions = Boolean(options.positions);
- parser.onParseError = typeof options.onParseError === "function" ? options.onParseError : noop;
- parser.onParseErrorThrow = false;
- parser.parseAtrulePrelude = "parseAtrulePrelude" in options ? Boolean(options.parseAtrulePrelude) : true;
- parser.parseRulePrelude = "parseRulePrelude" in options ? Boolean(options.parseRulePrelude) : true;
- parser.parseValue = "parseValue" in options ? Boolean(options.parseValue) : true;
- parser.parseCustomProperty = "parseCustomProperty" in options ? Boolean(options.parseCustomProperty) : false;
- if (!parser.context.hasOwnProperty(context)) {
- throw new Error("Unknown context `" + context + "`");
- }
- ast = parser.context[context].call(parser, options);
- if (!parser.scanner.eof) {
- parser.scanner.error();
- }
- return ast;
- };
- }
- // ---
- const U = 117; // 'u'.charCodeAt(0)
- const getNode = function defaultRecognizer(context) {
- switch (this.scanner.tokenType) {
- case NUMBERSIGN:
- return this.HexColor();
- case COMMA:
- context.space = null;
- context.ignoreWSAfter = true;
- return this.Operator();
- case SOLIDUS:
- case ASTERISK:
- case PLUSSIGN:
- case HYPHENMINUS:
- return this.Operator();
- case LEFTPARENTHESIS:
- return this.Parentheses(this.readSequence, context.recognizer);
- case LEFTSQUAREBRACKET:
- return this.Brackets(this.readSequence, context.recognizer);
- case STRING:
- return this.String();
- case NUMBER:
- switch (this.scanner.lookupType(1)) {
- case PERCENTSIGN:
- return this.Percentage();
- case IDENTIFIER:
- // edge case: number with folowing \0 and \9 hack shouldn"t to be a Dimension
- if (cmpChar(this.scanner.source, this.scanner.tokenEnd, BACKSLASH)) {
- return this.Number();
- } else {
- return this.Dimension();
- }
- default:
- return this.Number();
- }
- case FUNCTION:
- return this.Function(this.readSequence, context.recognizer);
- case URL:
- return this.Url();
- case IDENTIFIER:
- // check for unicode range, it should start with u+ or U+
- if (cmpChar(this.scanner.source, this.scanner.tokenStart, U) &&
- cmpChar(this.scanner.source, this.scanner.tokenStart + 1, PLUSSIGN)) {
- return this.UnicodeRange();
- } else {
- return this.Identifier();
- }
- }
- };
- // ---
- const AtrulePrelude = {
- getNode: getNode
- };
- // ---
- function Selector_getNode(context) {
- switch (this.scanner.tokenType) {
- case PLUSSIGN:
- case GREATERTHANSIGN:
- case TILDE:
- context.space = null;
- context.ignoreWSAfter = true;
- return this.Combinator();
- case SOLIDUS: // /deep/
- return this.Combinator();
- case FULLSTOP:
- return this.ClassSelector();
- case LEFTSQUAREBRACKET:
- return this.AttributeSelector();
- case NUMBERSIGN:
- return this.IdSelector();
- case COLON:
- if (this.scanner.lookupType(1) === COLON) {
- return this.PseudoElementSelector();
- } else {
- return this.PseudoClassSelector();
- }
- case IDENTIFIER:
- case ASTERISK:
- case VERTICALLINE:
- return this.TypeSelector();
- case NUMBER:
- return this.Percentage();
- }
- }
- const Selector = {
- getNode: Selector_getNode
- };
- // ---
- const Value_getNode = function defaultRecognizer(context) {
- switch (this.scanner.tokenType) {
- case NUMBERSIGN:
- return this.HexColor();
- case COMMA:
- context.space = null;
- context.ignoreWSAfter = true;
- return this.Operator();
- case SOLIDUS:
- case ASTERISK:
- case PLUSSIGN:
- case HYPHENMINUS:
- return this.Operator();
- case LEFTPARENTHESIS:
- return this.Parentheses(this.readSequence, context.recognizer);
- case LEFTSQUAREBRACKET:
- return this.Brackets(this.readSequence, context.recognizer);
- case STRING:
- return this.String();
- case NUMBER:
- switch (this.scanner.lookupType(1)) {
- case PERCENTSIGN:
- return this.Percentage();
- case IDENTIFIER:
- // edge case: number with folowing \0 and \9 hack shouldn't to be a Dimension
- if (cmpChar(this.scanner.source, this.scanner.tokenEnd, BACKSLASH)) {
- return this.Number();
- } else {
- return this.Dimension();
- }
- default:
- return this.Number();
- }
- case FUNCTION:
- return this.Function(this.readSequence, context.recognizer);
- case URL:
- return this.Url();
- case IDENTIFIER:
- // check for unicode range, it should start with u+ or U+
- if (cmpChar(this.scanner.source, this.scanner.tokenStart, U) &&
- cmpChar(this.scanner.source, this.scanner.tokenStart + 1, PLUSSIGN)) {
- return this.UnicodeRange();
- } else {
- return this.Identifier();
- }
- }
- };
- // ---
- // https://drafts.csswg.org/css-images-4/#element-notation
- // https://developer.mozilla.org/en-US/docs/Web/CSS/element
- const Value_Element = function () {
- this.scanner.skipSC();
- const children = this.createSingleNodeList(
- this.IdSelector()
- );
- this.scanner.skipSC();
- return children;
- };
- // ---
- // legacy IE function
- // expression '(' raw ')'
- const Value_expression = function () {
- return this.createSingleNodeList(
- this.Raw(this.scanner.currentToken, 0, 0, false, false)
- );
- };
- // ---
- // let '(' ident (',' <value>? )? ')'
- const Value_var = function () {
- const children = this.createList();
- this.scanner.skipSC();
- const identStart = this.scanner.tokenStart;
- this.scanner.eat(HYPHENMINUS);
- if (this.scanner.source.charCodeAt(this.scanner.tokenStart) !== HYPHENMINUS) {
- this.scanner.error("HyphenMinus is expected");
- }
- this.scanner.eat(IDENTIFIER);
- children.push({
- type: "Identifier",
- loc: this.getLocation(identStart, this.scanner.tokenStart),
- name: this.scanner.substrToCursor(identStart)
- });
- this.scanner.skipSC();
- if (this.scanner.tokenType === COMMA) {
- children.push(this.Operator());
- children.push(this.parseCustomProperty
- ? this.Value(null)
- : this.Raw(this.scanner.currentToken, EXCLAMATIONMARK, SEMICOLON, false, false)
- );
- }
- return children;
- };
- // ---
- const Value = {
- getNode: Value_getNode,
- "-moz-element": Value_Element,
- "element": Value_Element,
- "expression": Value_expression,
- "let": Value_var
- };
- // ---
- const scope = {
- AtrulePrelude: AtrulePrelude,
- Selector: Selector,
- Value: Value
- };
- // ---
- const fontFace = {
- parse: {
- prelude: null,
- block: function () {
- return this.Block(true);
- }
- }
- };
- // ---
- const _import = {
- parse: {
- prelude: function () {
- const children = this.createList();
- this.scanner.skipSC();
- switch (this.scanner.tokenType) {
- case STRING:
- children.push(this.String());
- break;
- case URL:
- children.push(this.Url());
- break;
- default:
- this.scanner.error("String or url() is expected");
- }
- if (this.scanner.lookupNonWSType(0) === IDENTIFIER ||
- this.scanner.lookupNonWSType(0) === LEFTPARENTHESIS) {
- children.push(this.WhiteSpace());
- children.push(this.MediaQueryList());
- }
- return children;
- },
- block: null
- }
- };
- // ---
- const media = {
- parse: {
- prelude: function () {
- return this.createSingleNodeList(
- this.MediaQueryList()
- );
- },
- block: function () {
- return this.Block(false);
- }
- }
- };
- // ---
- const page = {
- parse: {
- prelude: function () {
- return this.createSingleNodeList(
- this.SelectorList()
- );
- },
- block: function () {
- return this.Block(true);
- }
- }
- };
- // ---
- function supports_consumeRaw() {
- return this.createSingleNodeList(
- this.Raw(this.scanner.currentToken, 0, 0, false, false)
- );
- }
- function parentheses() {
- let index = 0;
- this.scanner.skipSC();
- // TODO: make it simplier
- if (this.scanner.tokenType === IDENTIFIER) {
- index = 1;
- } else if (this.scanner.tokenType === HYPHENMINUS &&
- this.scanner.lookupType(1) === IDENTIFIER) {
- index = 2;
- }
- if (index !== 0 && this.scanner.lookupNonWSType(index) === COLON) {
- return this.createSingleNodeList(
- this.Declaration()
- );
- }
- return readSequence.call(this);
- }
- function readSequence() {
- const children = this.createList();
- let space = null;
- let child;
- this.scanner.skipSC();
- scan:
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case WHITESPACE:
- space = this.WhiteSpace();
- continue;
- case COMMENT:
- this.scanner.next();
- continue;
- case FUNCTION:
- child = this.Function(supports_consumeRaw, this.scope.AtrulePrelude);
- break;
- case IDENTIFIER:
- child = this.Identifier();
- break;
- case LEFTPARENTHESIS:
- child = this.Parentheses(parentheses, this.scope.AtrulePrelude);
- break;
- default:
- break scan;
- }
- if (space !== null) {
- children.push(space);
- space = null;
- }
- children.push(child);
- }
- return children;
- }
- const supports = {
- parse: {
- prelude: function () {
- const children = readSequence.call(this);
- if (this.getFirstListNode(children) === null) {
- this.scanner.error("Condition is expected");
- }
- return children;
- },
- block: function () {
- return this.Block(false);
- }
- }
- };
- // ---
- const atrule = {
- "font-face": fontFace,
- "import": _import,
- media: media,
- page: page,
- supports: supports
- };
- // ---
- const dir = {
- parse: function () {
- return this.createSingleNodeList(
- this.Identifier()
- );
- }
- };
- // ---
- const has = {
- parse: function () {
- return this.createSingleNodeList(
- this.SelectorList()
- );
- }
- };
- // ---
- const lang = {
- parse: function () {
- return this.createSingleNodeList(
- this.Identifier()
- );
- }
- };
- // ---
- const matches = {
- parse: function selectorList() {
- return this.createSingleNodeList(
- this.SelectorList()
- );
- }
- };
- const not = matches;
- // ---
- const ALLOW_OF_CLAUSE = true;
- const nthChild = {
- parse: function nthWithOfClause() {
- return this.createSingleNodeList(
- this.Nth(ALLOW_OF_CLAUSE)
- );
- }
- };
- const nthLastChild = nthChild;
- // ---
- const DISALLOW_OF_CLAUSE = false;
- const nthLastOfType = {
- parse: function nth() {
- return this.createSingleNodeList(
- this.Nth(DISALLOW_OF_CLAUSE)
- );
- }
- };
- const nthOfType = nthLastOfType;
- // ---
- const slotted = {
- parse: function compoundSelector() {
- return this.createSingleNodeList(
- this.Selector()
- );
- }
- };
- // ---
- const pseudo = {
- dir: dir,
- has: has,
- lang: lang,
- matches: matches,
- not: not,
- "nth-child": nthChild,
- "nth-last-child": nthLastChild,
- "nth-last-of-type": nthLastOfType,
- "nth-of-type": nthOfType,
- slotted: slotted
- };
- // ---
- const AnPlusB_N = 110; // 'n'.charCodeAt(0)
- const DISALLOW_SIGN = true;
- const ALLOW_SIGN = false;
- function checkTokenIsInteger(scanner, disallowSign) {
- let pos = scanner.tokenStart;
- if (scanner.source.charCodeAt(pos) === PLUSSIGN ||
- scanner.source.charCodeAt(pos) === HYPHENMINUS) {
- if (disallowSign) {
- scanner.error();
- }
- pos++;
- }
- for (; pos < scanner.tokenEnd; pos++) {
- if (!isNumber(scanner.source.charCodeAt(pos))) {
- scanner.error("Unexpected input", pos);
- }
- }
- }
- // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
- const AnPlusB = {
- name: "AnPlusB",
- structure: {
- a: [String, null],
- b: [String, null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let end = start;
- let prefix = "";
- let a = null;
- let b = null;
- if (this.scanner.tokenType === NUMBER ||
- this.scanner.tokenType === PLUSSIGN) {
- checkTokenIsInteger(this.scanner, ALLOW_SIGN);
- prefix = this.scanner.getTokenValue();
- this.scanner.next();
- end = this.scanner.tokenStart;
- }
- if (this.scanner.tokenType === IDENTIFIER) {
- let bStart = this.scanner.tokenStart;
- if (cmpChar(this.scanner.source, bStart, HYPHENMINUS)) {
- if (prefix === "") {
- prefix = "-";
- bStart++;
- } else {
- this.scanner.error("Unexpected hyphen minus");
- }
- }
- if (!cmpChar(this.scanner.source, bStart, AnPlusB_N)) {
- this.scanner.error();
- }
- a = prefix === "" ? "1" :
- prefix === "+" ? "+1" :
- prefix === "-" ? "-1" :
- prefix;
- const len = this.scanner.tokenEnd - bStart;
- if (len > 1) {
- // ..n-..
- if (this.scanner.source.charCodeAt(bStart + 1) !== HYPHENMINUS) {
- this.scanner.error("Unexpected input", bStart + 1);
- }
- if (len > 2) {
- // ..n-{number}..
- this.scanner.tokenStart = bStart + 2;
- } else {
- // ..n- {number}
- this.scanner.next();
- this.scanner.skipSC();
- }
- checkTokenIsInteger(this.scanner, DISALLOW_SIGN);
- b = "-" + this.scanner.getTokenValue();
- this.scanner.next();
- end = this.scanner.tokenStart;
- } else {
- prefix = "";
- this.scanner.next();
- end = this.scanner.tokenStart;
- this.scanner.skipSC();
- if (this.scanner.tokenType === HYPHENMINUS ||
- this.scanner.tokenType === PLUSSIGN) {
- prefix = this.scanner.getTokenValue();
- this.scanner.next();
- this.scanner.skipSC();
- }
- if (this.scanner.tokenType === NUMBER) {
- checkTokenIsInteger(this.scanner, prefix !== "");
- if (!isNumber(this.scanner.source.charCodeAt(this.scanner.tokenStart))) {
- prefix = this.scanner.source.charAt(this.scanner.tokenStart);
- this.scanner.tokenStart++;
- }
- if (prefix === "") {
- // should be an operator before number
- this.scanner.error();
- } else if (prefix === "+") {
- // plus is using by default
- prefix = "";
- }
- b = prefix + this.scanner.getTokenValue();
- this.scanner.next();
- end = this.scanner.tokenStart;
- } else {
- if (prefix) {
- this.scanner.eat(NUMBER);
- }
- }
- }
- } else {
- if (prefix === "" || prefix === "+") { // no number
- this.scanner.error(
- "Number or identifier is expected",
- this.scanner.tokenStart + (
- this.scanner.tokenType === PLUSSIGN ||
- this.scanner.tokenType === HYPHENMINUS
- )
- );
- }
- b = prefix;
- }
- return {
- type: "AnPlusB",
- loc: this.getLocation(start, end),
- a: a,
- b: b
- };
- },
- generate: function (node) {
- const a = node.a !== null && node.a !== undefined;
- let b = node.b !== null && node.b !== undefined;
- if (a) {
- this.chunk(
- node.a === "+1" ? "+n" :
- node.a === "1" ? "n" :
- node.a === "-1" ? "-n" :
- node.a + "n"
- );
- if (b) {
- b = String(node.b);
- if (b.charAt(0) === "-" || b.charAt(0) === "+") {
- this.chunk(b.charAt(0));
- this.chunk(b.substr(1));
- } else {
- this.chunk("+");
- this.chunk(b);
- }
- }
- } else {
- this.chunk(String(node.b));
- }
- }
- };
- // ---
- function Atrule_consumeRaw(startToken) {
- return this.Raw(startToken, SEMICOLON, LEFTCURLYBRACKET, false, true);
- }
- function isDeclarationBlockAtrule() {
- for (let offset = 1, type; type = this.scanner.lookupType(offset); offset++) { // eslint-disable-line no-cond-assign
- if (type === RIGHTCURLYBRACKET) {
- return true;
- }
- if (type === LEFTCURLYBRACKET ||
- type === ATKEYWORD) {
- return false;
- }
- }
- return false;
- }
- const Atrule = {
- name: "Atrule",
- structure: {
- name: String,
- prelude: ["AtrulePrelude", "Raw", null],
- block: ["Block", null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let name;
- let nameLowerCase;
- let prelude = null;
- let block = null;
- this.scanner.eat(ATKEYWORD);
- name = this.scanner.substrToCursor(start + 1);
- nameLowerCase = name.toLowerCase();
- this.scanner.skipSC();
- // parse prelude
- if (this.scanner.eof === false &&
- this.scanner.tokenType !== LEFTCURLYBRACKET &&
- this.scanner.tokenType !== SEMICOLON) {
- if (this.parseAtrulePrelude) {
- prelude = this.parseWithFallback(this.AtrulePrelude.bind(this, name), Atrule_consumeRaw);
- // turn empty AtrulePrelude into null
- if (prelude.type === "AtrulePrelude" && prelude.children.head === null) {
- prelude = null;
- }
- } else {
- prelude = Atrule_consumeRaw.call(this, this.scanner.currentToken);
- }
- this.scanner.skipSC();
- }
- switch (this.scanner.tokenType) {
- case SEMICOLON:
- this.scanner.next();
- break;
- case LEFTCURLYBRACKET:
- if (this.atrule.hasOwnProperty(nameLowerCase) &&
- typeof this.atrule[nameLowerCase].block === "function") {
- block = this.atrule[nameLowerCase].block.call(this);
- } else {
- // TODO: should consume block content as Raw?
- block = this.Block(isDeclarationBlockAtrule.call(this));
- }
- break;
- }
- return {
- type: "Atrule",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- prelude: prelude,
- block: block
- };
- },
- generate: function (node) {
- this.chunk("@");
- this.chunk(node.name);
- if (node.prelude !== null) {
- this.chunk(" ");
- this.node(node.prelude);
- }
- if (node.block) {
- this.node(node.block);
- } else {
- this.chunk(";");
- }
- },
- walkContext: "atrule"
- };
- // ---
- const Syntax_AtrulePrelude = {
- name: "AtrulePrelude",
- structure: {
- children: [[]]
- },
- parse: function (name) {
- let children = null;
- if (name !== null) {
- name = name.toLowerCase();
- }
- this.scanner.skipSC();
- if (this.atrule.hasOwnProperty(name) &&
- typeof this.atrule[name].prelude === "function") {
- // custom consumer
- children = this.atrule[name].prelude.call(this);
- } else {
- // default consumer
- children = this.readSequence(this.scope.AtrulePrelude);
- }
- this.scanner.skipSC();
- if (this.scanner.eof !== true &&
- this.scanner.tokenType !== LEFTCURLYBRACKET &&
- this.scanner.tokenType !== SEMICOLON) {
- this.scanner.error("Semicolon or block is expected");
- }
- if (children === null) {
- children = this.createList();
- }
- return {
- type: "AtrulePrelude",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node);
- },
- walkContext: "atrulePrelude"
- };
- // ---
- function getAttributeName() {
- if (this.scanner.eof) {
- this.scanner.error("Unexpected end of input");
- }
- const start = this.scanner.tokenStart;
- let expectIdentifier = false;
- let checkColon = true;
- if (this.scanner.tokenType === ASTERISK) {
- expectIdentifier = true;
- checkColon = false;
- this.scanner.next();
- } else if (this.scanner.tokenType !== VERTICALLINE) {
- this.scanner.eat(IDENTIFIER);
- }
- if (this.scanner.tokenType === VERTICALLINE) {
- if (this.scanner.lookupType(1) !== EQUALSSIGN) {
- this.scanner.next();
- this.scanner.eat(IDENTIFIER);
- } else if (expectIdentifier) {
- this.scanner.error("Identifier is expected", this.scanner.tokenEnd);
- }
- } else if (expectIdentifier) {
- this.scanner.error("Vertical line is expected");
- }
- if (checkColon && this.scanner.tokenType === COLON) {
- this.scanner.next();
- this.scanner.eat(IDENTIFIER);
- }
- return {
- type: "Identifier",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: this.scanner.substrToCursor(start)
- };
- }
- function getOperator() {
- const start = this.scanner.tokenStart;
- const tokenType = this.scanner.tokenType;
- if (tokenType !== EQUALSSIGN && // =
- tokenType !== TILDE && // ~=
- tokenType !== CIRCUMFLEXACCENT && // ^=
- tokenType !== DOLLARSIGN && // $=
- tokenType !== ASTERISK && // *=
- tokenType !== VERTICALLINE // |=
- ) {
- this.scanner.error("Attribute selector (=, ~=, ^=, $=, *=, |=) is expected");
- }
- if (tokenType === EQUALSSIGN) {
- this.scanner.next();
- } else {
- this.scanner.next();
- this.scanner.eat(EQUALSSIGN);
- }
- return this.scanner.substrToCursor(start);
- }
- // "[" S* attrib_name "]"
- // "[" S* attrib_name S* attrib_matcher S* [ IDENT | STRING ] S* attrib_flags? S* "]"
- const AttributeSelector = {
- name: "AttributeSelector",
- structure: {
- name: "Identifier",
- matcher: [String, null],
- value: ["String", "Identifier", null],
- flags: [String, null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let name;
- let matcher = null;
- let value = null;
- let flags = null;
- this.scanner.eat(LEFTSQUAREBRACKET);
- this.scanner.skipSC();
- name = getAttributeName.call(this);
- this.scanner.skipSC();
- if (this.scanner.tokenType !== RIGHTSQUAREBRACKET) {
- // avoid case `[name i]`
- if (this.scanner.tokenType !== IDENTIFIER) {
- matcher = getOperator.call(this);
- this.scanner.skipSC();
- value = this.scanner.tokenType === STRING
- ? this.String()
- : this.Identifier();
- this.scanner.skipSC();
- }
- // attribute flags
- if (this.scanner.tokenType === IDENTIFIER) {
- flags = this.scanner.getTokenValue();
- this.scanner.next();
- this.scanner.skipSC();
- }
- }
- this.scanner.eat(RIGHTSQUAREBRACKET);
- return {
- type: "AttributeSelector",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- matcher: matcher,
- value: value,
- flags: flags
- };
- },
- generate: function (node) {
- let flagsPrefix = " ";
- this.chunk("[");
- this.node(node.name);
- if (node.matcher !== null) {
- this.chunk(node.matcher);
- if (node.value !== null) {
- this.node(node.value);
- // space between string and flags is not required
- if (node.value.type === "String") {
- flagsPrefix = "";
- }
- }
- }
- if (node.flags !== null) {
- this.chunk(flagsPrefix);
- this.chunk(node.flags);
- }
- this.chunk("]");
- }
- };
- // ---
- function Block_consumeRaw(startToken) {
- return this.Raw(startToken, 0, 0, false, true);
- }
- function consumeRule() {
- return this.parseWithFallback(this.Rule, Block_consumeRaw);
- }
- function consumeRawDeclaration(startToken) {
- return this.Raw(startToken, 0, SEMICOLON, true, true);
- }
- function consumeDeclaration() {
- if (this.scanner.tokenType === SEMICOLON) {
- return consumeRawDeclaration.call(this, this.scanner.currentToken);
- }
- const node = this.parseWithFallback(this.Declaration, consumeRawDeclaration);
- if (this.scanner.tokenType === SEMICOLON) {
- this.scanner.next();
- }
- return node;
- }
- const Block = {
- name: "Block",
- structure: {
- children: [[
- "Atrule",
- "Rule",
- "Declaration"
- ]]
- },
- parse: function (isDeclaration) {
- const consumer = isDeclaration ? consumeDeclaration : consumeRule;
- const start = this.scanner.tokenStart;
- const children = this.createList();
- this.scanner.eat(LEFTCURLYBRACKET);
- scan:
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case RIGHTCURLYBRACKET:
- break scan;
- case WHITESPACE:
- case COMMENT:
- this.scanner.next();
- break;
- case ATKEYWORD:
- children.push(this.parseWithFallback(this.Atrule, Block_consumeRaw));
- break;
- default:
- children.push(consumer.call(this));
- }
- }
- if (!this.scanner.eof) {
- this.scanner.eat(RIGHTCURLYBRACKET);
- }
- return {
- type: "Block",
- loc: this.getLocation(start, this.scanner.tokenStart),
- children: children
- };
- },
- generate: function (node) {
- this.chunk("{");
- this.children(node, function (prev) {
- if (prev.type === "Declaration") {
- this.chunk(";");
- }
- });
- this.chunk("}");
- },
- walkContext: "block"
- };
- // ---
- const Brackets = {
- name: "Brackets",
- structure: {
- children: [[]]
- },
- parse: function (readSequence, recognizer) {
- const start = this.scanner.tokenStart;
- let children = null;
- this.scanner.eat(LEFTSQUAREBRACKET);
- children = readSequence.call(this, recognizer);
- if (!this.scanner.eof) {
- this.scanner.eat(RIGHTSQUAREBRACKET);
- }
- return {
- type: "Brackets",
- loc: this.getLocation(start, this.scanner.tokenStart),
- children: children
- };
- },
- generate: function (node) {
- this.chunk("[");
- this.children(node);
- this.chunk("]");
- }
- };
- // ---
- const Syntax_CDC = {
- name: "CDC",
- structure: [],
- parse: function () {
- const start = this.scanner.tokenStart;
- this.scanner.eat(TYPE_CDC); // -->
- return {
- type: "CDC",
- loc: this.getLocation(start, this.scanner.tokenStart)
- };
- },
- generate: function () {
- this.chunk("-->");
- }
- };
- // ---
- const Syntax_CDO = {
- name: "CDO",
- structure: [],
- parse: function () {
- const start = this.scanner.tokenStart;
- this.scanner.eat(TYPE_CDO); // <!--
- return {
- type: "CDO",
- loc: this.getLocation(start, this.scanner.tokenStart)
- };
- },
- generate: function () {
- this.chunk("<!--");
- }
- };
- // ---
- // '.' ident
- const ClassSelector = {
- name: "ClassSelector",
- structure: {
- name: String
- },
- parse: function () {
- this.scanner.eat(FULLSTOP);
- return {
- type: "ClassSelector",
- loc: this.getLocation(this.scanner.tokenStart - 1, this.scanner.tokenEnd),
- name: this.scanner.consume(IDENTIFIER)
- };
- },
- generate: function (node) {
- this.chunk(".");
- this.chunk(node.name);
- }
- };
- // ---
- // + | > | ~ | /deep/
- const Combinator = {
- name: "Combinator",
- structure: {
- name: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- switch (this.scanner.tokenType) {
- case GREATERTHANSIGN:
- case PLUSSIGN:
- case TILDE:
- this.scanner.next();
- break;
- case SOLIDUS:
- this.scanner.next();
- this.scanner.expectIdentifier("deep");
- this.scanner.eat(SOLIDUS);
- break;
- default:
- this.scanner.error("Combinator is expected");
- }
- return {
- type: "Combinator",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: this.scanner.substrToCursor(start)
- };
- },
- generate: function (node) {
- this.chunk(node.name);
- }
- };
- // ---
- // '/*' .* '*/'
- const Syntax_Comment = {
- name: "Comment",
- structure: {
- value: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let end = this.scanner.tokenEnd;
- if ((end - start + 2) >= 2 &&
- this.scanner.source.charCodeAt(end - 2) === ASTERISK &&
- this.scanner.source.charCodeAt(end - 1) === SOLIDUS) {
- end -= 2;
- }
- this.scanner.next();
- return {
- type: "Comment",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: this.scanner.source.substring(start + 2, end)
- };
- },
- generate: function (node) {
- this.chunk("/*");
- this.chunk(node.value);
- this.chunk("*/");
- }
- };
- // ---
- const hasOwnProperty = Object.prototype.hasOwnProperty;
- const keywords = Object.create(null);
- const properties = Object.create(null);
- const NAMES_HYPHENMINUS = 45; // "-".charCodeAt()
- function isCustomProperty(str, offset) {
- offset = offset || 0;
- return str.length - offset >= 2 &&
- str.charCodeAt(offset) === NAMES_HYPHENMINUS &&
- str.charCodeAt(offset + 1) === NAMES_HYPHENMINUS;
- }
- function getVendorPrefix(str, offset) {
- offset = offset || 0;
- // verdor prefix should be at least 3 chars length
- if (str.length - offset >= 3) {
- // vendor prefix starts with hyper minus following non-hyper minus
- if (str.charCodeAt(offset) === NAMES_HYPHENMINUS &&
- str.charCodeAt(offset + 1) !== NAMES_HYPHENMINUS) {
- // vendor prefix should contain a hyper minus at the ending
- const secondDashIndex = str.indexOf("-", offset + 2);
- if (secondDashIndex !== -1) {
- return str.substring(offset, secondDashIndex + 1);
- }
- }
- }
- return "";
- }
- function getKeywordDescriptor(keyword) {
- if (hasOwnProperty.call(keywords, keyword)) {
- return keywords[keyword];
- }
- const name = keyword.toLowerCase();
- if (hasOwnProperty.call(keywords, name)) {
- return keywords[keyword] = keywords[name];
- }
- const custom = names.isCustomProperty(name, 0);
- const vendor = !custom ? getVendorPrefix(name, 0) : "";
- return keywords[keyword] = Object.freeze({
- basename: name.substr(vendor.length),
- name: name,
- vendor: vendor,
- prefix: vendor,
- custom: custom
- });
- }
- function getPropertyDescriptor(property) {
- if (hasOwnProperty.call(properties, property)) {
- return properties[property];
- }
- let name = property;
- let hack = property[0];
- if (hack === "/") {
- hack = property[1] === "/" ? "//" : "/";
- } else if (hack !== "_" &&
- hack !== "*" &&
- hack !== "$" &&
- hack !== "#" &&
- hack !== "+") {
- hack = "";
- }
- const custom = isCustomProperty(name, hack.length);
- // re-use result when possible (the same as for lower case)
- if (!custom) {
- name = name.toLowerCase();
- if (hasOwnProperty.call(properties, name)) {
- return properties[property] = properties[name];
- }
- }
- const vendor = !custom ? getVendorPrefix(name, hack.length) : "";
- const prefix = name.substr(0, hack.length + vendor.length);
- return properties[property] = Object.freeze({
- basename: name.substr(prefix.length),
- name: name.substr(hack.length),
- hack: hack,
- vendor: vendor,
- prefix: prefix,
- custom: custom
- });
- }
- const names = {
- keyword: getKeywordDescriptor,
- property: getPropertyDescriptor,
- isCustomProperty: isCustomProperty,
- vendorPrefix: getVendorPrefix
- };
- // ---
- function consumeValueRaw(startToken) {
- return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, true);
- }
- function consumeCustomPropertyRaw(startToken) {
- return this.Raw(startToken, EXCLAMATIONMARK, SEMICOLON, false, false);
- }
- function consumeValue() {
- const startValueToken = this.scanner.currentToken;
- const value = this.Value();
- if (value.type !== "Raw" &&
- this.scanner.eof === false &&
- this.scanner.tokenType !== SEMICOLON &&
- this.scanner.tokenType !== EXCLAMATIONMARK &&
- this.scanner.isBalanceEdge(startValueToken) === false) {
- this.scanner.error();
- }
- return value;
- }
- const Declaration = {
- name: "Declaration",
- structure: {
- important: [Boolean, String],
- property: String,
- value: ["Value", "Raw"]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const startToken = this.scanner.currentToken;
- const property = readProperty.call(this);
- const customProperty = names.isCustomProperty(property);
- const parseValue = customProperty ? this.parseCustomProperty : this.parseValue;
- const consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw;
- let important = false;
- let value;
- this.scanner.skipSC();
- this.scanner.eat(COLON);
- if (!customProperty) {
- this.scanner.skipSC();
- }
- if (parseValue) {
- value = this.parseWithFallback(consumeValue, consumeRaw);
- } else {
- value = consumeRaw.call(this, this.scanner.currentToken);
- }
- if (this.scanner.tokenType === EXCLAMATIONMARK) {
- important = getImportant(this.scanner);
- this.scanner.skipSC();
- }
- // Do not include semicolon to range per spec
- // https://drafts.csswg.org/css-syntax/#declaration-diagram
- if (this.scanner.eof === false &&
- this.scanner.tokenType !== SEMICOLON &&
- this.scanner.isBalanceEdge(startToken) === false) {
- this.scanner.error();
- }
- return {
- type: "Declaration",
- loc: this.getLocation(start, this.scanner.tokenStart),
- important: important,
- property: property,
- value: value
- };
- },
- generate: function (node) {
- this.chunk(node.property);
- this.chunk(":");
- this.node(node.value);
- if (node.important) {
- this.chunk(node.important === true ? "!important" : "!" + node.important);
- }
- },
- walkContext: "declaration"
- };
- function readProperty() {
- const start = this.scanner.tokenStart;
- let prefix = 0;
- // hacks
- switch (this.scanner.tokenType) {
- case ASTERISK:
- case DOLLARSIGN:
- case PLUSSIGN:
- case NUMBERSIGN:
- prefix = 1;
- break;
- // TODO: not sure we should support this hack
- case SOLIDUS:
- prefix = this.scanner.lookupType(1) === SOLIDUS ? 2 : 1;
- break;
- }
- if (this.scanner.lookupType(prefix) === HYPHENMINUS) {
- prefix++;
- }
- if (prefix) {
- this.scanner.skip(prefix);
- }
- this.scanner.eat(IDENTIFIER);
- return this.scanner.substrToCursor(start);
- }
- // ! ws* important
- function getImportant(scanner) {
- scanner.eat(EXCLAMATIONMARK);
- scanner.skipSC();
- const important = scanner.consume(IDENTIFIER);
- // store original value in case it differ from `important`
- // for better original source restoring and hacks like `!ie` support
- return important === "important" ? true : important;
- }
- // ---
- function DeclarationList_consumeRaw(startToken) {
- return this.Raw(startToken, 0, SEMICOLON, true, true);
- }
- const DeclarationList = {
- name: "DeclarationList",
- structure: {
- children: [[
- "Declaration"
- ]]
- },
- parse: function () {
- const children = this.createList();
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case WHITESPACE:
- case COMMENT:
- case SEMICOLON:
- this.scanner.next();
- break;
- default:
- children.push(this.parseWithFallback(this.Declaration, DeclarationList_consumeRaw));
- }
- }
- return {
- type: "DeclarationList",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node, function (prev) {
- if (prev.type === "Declaration") {
- this.chunk(";");
- }
- });
- }
- };
- // ---
- // special reader for units to avoid adjoined IE hacks (i.e. '1px\9')
- function readUnit(scanner) {
- const unit = scanner.getTokenValue();
- const backSlashPos = unit.indexOf("\\");
- if (backSlashPos > 0) {
- // patch token offset
- scanner.tokenStart += backSlashPos;
- // return part before backslash
- return unit.substring(0, backSlashPos);
- }
- // no backslash in unit name
- scanner.next();
- return unit;
- }
- // number ident
- const Dimension = {
- name: "Dimension",
- structure: {
- value: String,
- unit: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const value = this.scanner.consume(NUMBER);
- const unit = readUnit(this.scanner);
- return {
- type: "Dimension",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: value,
- unit: unit
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- this.chunk(node.unit);
- }
- };
- // ---
- // <function-token> <sequence> ')'
- const Syntax_Function = {
- name: "Function",
- structure: {
- name: String,
- children: [[]]
- },
- parse: function (readSequence, recognizer) {
- const start = this.scanner.tokenStart;
- const name = this.scanner.consumeFunctionName();
- const nameLowerCase = name.toLowerCase();
- let children;
- children = recognizer.hasOwnProperty(nameLowerCase)
- ? recognizer[nameLowerCase].call(this, recognizer)
- : readSequence.call(this, recognizer);
- if (!this.scanner.eof) {
- this.scanner.eat(RIGHTPARENTHESIS);
- }
- return {
- type: "Function",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- children: children
- };
- },
- generate: function (node) {
- this.chunk(node.name);
- this.chunk("(");
- this.children(node);
- this.chunk(")");
- },
- walkContext: "function"
- };
- // ---
- function consumeHexSequence(scanner, required) {
- if (!isHex(scanner.source.charCodeAt(scanner.tokenStart))) {
- if (required) {
- scanner.error("Unexpected input", scanner.tokenStart);
- } else {
- return;
- }
- }
- for (let pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
- const code = scanner.source.charCodeAt(pos);
- // break on non-hex char
- if (!isHex(code)) {
- // break token, exclude symbol
- scanner.tokenStart = pos;
- return;
- }
- }
- // token is full hex sequence, go to next token
- scanner.next();
- }
- // # ident
- const HexColor = {
- name: "HexColor",
- structure: {
- value: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- this.scanner.eat(NUMBERSIGN);
- switch (this.scanner.tokenType) {
- case NUMBER:
- consumeHexSequence(this.scanner, true);
- // if token is identifier then number consists of hex only,
- // try to add identifier to result
- if (this.scanner.tokenType === IDENTIFIER) {
- consumeHexSequence(this.scanner, false);
- }
- break;
- case IDENTIFIER:
- consumeHexSequence(this.scanner, true);
- break;
- default:
- this.scanner.error("Number or identifier is expected");
- }
- return {
- type: "HexColor",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: this.scanner.substrToCursor(start + 1) // skip #
- };
- },
- generate: function (node) {
- this.chunk("#");
- this.chunk(node.value);
- }
- };
- // ---
- const Identifier = {
- name: "Identifier",
- structure: {
- name: String
- },
- parse: function () {
- return {
- type: "Identifier",
- loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
- name: this.scanner.consume(IDENTIFIER)
- };
- },
- generate: function (node) {
- this.chunk(node.name);
- }
- };
- // ---
- // '#' ident
- const IdSelector = {
- name: "IdSelector",
- structure: {
- name: String
- },
- parse: function () {
- this.scanner.eat(NUMBERSIGN);
- return {
- type: "IdSelector",
- loc: this.getLocation(this.scanner.tokenStart - 1, this.scanner.tokenEnd),
- name: this.scanner.consume(IDENTIFIER)
- };
- },
- generate: function (node) {
- this.chunk("#");
- this.chunk(node.name);
- }
- };
- // ---
- const MediaFeature = {
- name: "MediaFeature",
- structure: {
- name: String,
- value: ["Identifier", "Number", "Dimension", "Ratio", null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let name;
- let value = null;
- this.scanner.eat(LEFTPARENTHESIS);
- this.scanner.skipSC();
- name = this.scanner.consume(IDENTIFIER);
- this.scanner.skipSC();
- if (this.scanner.tokenType !== RIGHTPARENTHESIS) {
- this.scanner.eat(COLON);
- this.scanner.skipSC();
- switch (this.scanner.tokenType) {
- case NUMBER:
- if (this.scanner.lookupType(1) === IDENTIFIER) {
- value = this.Dimension();
- } else if (this.scanner.lookupNonWSType(1) === SOLIDUS) {
- value = this.Ratio();
- } else {
- value = this.Number();
- }
- break;
- case IDENTIFIER:
- value = this.Identifier();
- break;
- default:
- this.scanner.error("Number, dimension, ratio or identifier is expected");
- }
- this.scanner.skipSC();
- }
- this.scanner.eat(RIGHTPARENTHESIS);
- return {
- type: "MediaFeature",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- value: value
- };
- },
- generate: function (node) {
- this.chunk("(");
- this.chunk(node.name);
- if (node.value !== null) {
- this.chunk(":");
- this.node(node.value);
- }
- this.chunk(")");
- }
- };
- // ---
- const MediaQuery = {
- name: "MediaQuery",
- structure: {
- children: [[
- "Identifier",
- "MediaFeature",
- "WhiteSpace"
- ]]
- },
- parse: function () {
- this.scanner.skipSC();
- const children = this.createList();
- let child = null;
- let space = null;
- scan:
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case COMMENT:
- this.scanner.next();
- continue;
- case WHITESPACE:
- space = this.WhiteSpace();
- continue;
- case IDENTIFIER:
- child = this.Identifier();
- break;
- case LEFTPARENTHESIS:
- child = this.MediaFeature();
- break;
- default:
- break scan;
- }
- if (space !== null) {
- children.push(space);
- space = null;
- }
- children.push(child);
- }
- if (child === null) {
- this.scanner.error("Identifier or parenthesis is expected");
- }
- return {
- type: "MediaQuery",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node);
- }
- };
- // ---
- const MediaQueryList = {
- name: "MediaQueryList",
- structure: {
- children: [[
- "MediaQuery"
- ]]
- },
- parse: function (relative) {
- const children = this.createList();
- this.scanner.skipSC();
- while (!this.scanner.eof) {
- children.push(this.MediaQuery(relative));
- if (this.scanner.tokenType !== COMMA) {
- break;
- }
- this.scanner.next();
- }
- return {
- type: "MediaQueryList",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node, function () {
- this.chunk(",");
- });
- }
- };
- // ---
- // https://drafts.csswg.org/css-syntax-3/#the-anb-type
- const Nth = {
- name: "Nth",
- structure: {
- nth: ["AnPlusB", "Identifier"],
- selector: ["SelectorList", null]
- },
- parse: function (allowOfClause) {
- this.scanner.skipSC();
- const start = this.scanner.tokenStart;
- let end = start;
- let selector = null;
- let query;
- if (this.scanner.lookupValue(0, "odd") || this.scanner.lookupValue(0, "even")) {
- query = this.Identifier();
- } else {
- query = this.AnPlusB();
- }
- this.scanner.skipSC();
- if (allowOfClause && this.scanner.lookupValue(0, "of")) {
- this.scanner.next();
- selector = this.SelectorList();
- if (this.needPositions) {
- end = this.getLastListNode(selector.children).loc.end.offset;
- }
- } else {
- if (this.needPositions) {
- end = query.loc.end.offset;
- }
- }
- return {
- type: "Nth",
- loc: this.getLocation(start, end),
- nth: query,
- selector: selector
- };
- },
- generate: function (node) {
- this.node(node.nth);
- if (node.selector !== null) {
- this.chunk(" of ");
- this.node(node.selector);
- }
- }
- };
- // ---
- const Syntax_Number = {
- name: "Number",
- structure: {
- value: String
- },
- parse: function () {
- return {
- type: "Number",
- loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
- value: this.scanner.consume(NUMBER)
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- // '/' | '*' | ',' | ':' | '+' | '-'
- const Operator = {
- name: "Operator",
- structure: {
- value: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- this.scanner.next();
- return {
- type: "Operator",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: this.scanner.substrToCursor(start)
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- const Parentheses = {
- name: "Parentheses",
- structure: {
- children: [[]]
- },
- parse: function (readSequence, recognizer) {
- const start = this.scanner.tokenStart;
- let children = null;
- this.scanner.eat(LEFTPARENTHESIS);
- children = readSequence.call(this, recognizer);
- if (!this.scanner.eof) {
- this.scanner.eat(RIGHTPARENTHESIS);
- }
- return {
- type: "Parentheses",
- loc: this.getLocation(start, this.scanner.tokenStart),
- children: children
- };
- },
- generate: function (node) {
- this.chunk("(");
- this.children(node);
- this.chunk(")");
- }
- };
- // ---
- const Percentage = {
- name: "Percentage",
- structure: {
- value: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const number = this.scanner.consume(NUMBER);
- this.scanner.eat(PERCENTSIGN);
- return {
- type: "Percentage",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: number
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- this.chunk("%");
- }
- };
- // ---
- // : ident [ "(" .. ")" ]?
- const PseudoClassSelector = {
- name: "PseudoClassSelector",
- structure: {
- name: String,
- children: [["Raw"], null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let children = null;
- let name;
- let nameLowerCase;
- this.scanner.eat(COLON);
- if (this.scanner.tokenType === FUNCTION) {
- name = this.scanner.consumeFunctionName();
- nameLowerCase = name.toLowerCase();
- if (this.pseudo.hasOwnProperty(nameLowerCase)) {
- this.scanner.skipSC();
- children = this.pseudo[nameLowerCase].call(this);
- this.scanner.skipSC();
- } else {
- children = this.createList();
- children.push(
- this.Raw(this.scanner.currentToken, 0, 0, false, false)
- );
- }
- this.scanner.eat(RIGHTPARENTHESIS);
- } else {
- name = this.scanner.consume(IDENTIFIER);
- }
- return {
- type: "PseudoClassSelector",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- children: children
- };
- },
- generate: function (node) {
- this.chunk(":");
- this.chunk(node.name);
- if (node.children !== null) {
- this.chunk("(");
- this.children(node);
- this.chunk(")");
- }
- },
- walkContext: "function"
- };
- // ---
- // :: ident [ "(" .. ")" ]?
- const PseudoElementSelector = {
- name: "PseudoElementSelector",
- structure: {
- name: String,
- children: [["Raw"], null]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let children = null;
- let name;
- let nameLowerCase;
- this.scanner.eat(COLON);
- this.scanner.eat(COLON);
- if (this.scanner.tokenType === FUNCTION) {
- name = this.scanner.consumeFunctionName();
- nameLowerCase = name.toLowerCase();
- if (this.pseudo.hasOwnProperty(nameLowerCase)) {
- this.scanner.skipSC();
- children = this.pseudo[nameLowerCase].call(this);
- this.scanner.skipSC();
- } else {
- children = this.createList();
- children.push(
- this.Raw(this.scanner.currentToken, 0, 0, false, false)
- );
- }
- this.scanner.eat(RIGHTPARENTHESIS);
- } else {
- name = this.scanner.consume(IDENTIFIER);
- }
- return {
- type: "PseudoElementSelector",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: name,
- children: children
- };
- },
- generate: function (node) {
- this.chunk("::");
- this.chunk(node.name);
- if (node.children !== null) {
- this.chunk("(");
- this.children(node);
- this.chunk(")");
- }
- },
- walkContext: "function"
- };
- // ---
- // Terms of <ratio> should to be a positive number (not zero or negative)
- // (see https://drafts.csswg.org/mediaqueries-3/#values)
- // However, -o-min-device-pixel-ratio takes fractional values as a ratio"s term
- // and this is using by letious sites. Therefore we relax checking on parse
- // to test a term is unsigned number without exponent part.
- // Additional checks may to be applied on lexer validation.
- function consumeNumber(scanner) {
- const value = scanner.consumeNonWS(NUMBER);
- for (let i = 0; i < value.length; i++) {
- const code = value.charCodeAt(i);
- if (!isNumber(code) && code !== FULLSTOP) {
- scanner.error("Unsigned number is expected", scanner.tokenStart - value.length + i);
- }
- }
- if (Number(value) === 0) {
- scanner.error("Zero number is not allowed", scanner.tokenStart - value.length);
- }
- return value;
- }
- // <positive-integer> S* "/" S* <positive-integer>
- const Ratio = {
- name: "Ratio",
- structure: {
- left: String,
- right: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const left = consumeNumber(this.scanner);
- let right;
- this.scanner.eatNonWS(SOLIDUS);
- right = consumeNumber(this.scanner);
- return {
- type: "Ratio",
- loc: this.getLocation(start, this.scanner.tokenStart),
- left: left,
- right: right
- };
- },
- generate: function (node) {
- this.chunk(node.left);
- this.chunk("/");
- this.chunk(node.right);
- }
- };
- // ---
- const Raw = {
- name: "Raw",
- structure: {
- value: String
- },
- parse: function (startToken, endTokenType1, endTokenType2, includeTokenType2, excludeWhiteSpace) {
- const startOffset = this.scanner.getTokenStart(startToken);
- let endOffset;
- this.scanner.skip(
- this.scanner.getRawLength(
- startToken,
- endTokenType1,
- endTokenType2,
- includeTokenType2
- )
- );
- if (excludeWhiteSpace && this.scanner.tokenStart > startOffset) {
- endOffset = this.scanner.getOffsetExcludeWS();
- } else {
- endOffset = this.scanner.tokenStart;
- }
- return {
- type: "Raw",
- loc: this.getLocation(startOffset, endOffset),
- value: this.scanner.source.substring(startOffset, endOffset)
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- function Rule_consumeRaw(startToken) {
- return this.Raw(startToken, LEFTCURLYBRACKET, 0, false, true);
- }
- function consumePrelude() {
- const prelude = this.SelectorList();
- if (prelude.type !== "Raw" &&
- this.scanner.eof === false &&
- this.scanner.tokenType !== LEFTCURLYBRACKET) {
- this.scanner.error();
- }
- return prelude;
- }
- const Rule = {
- name: "Rule",
- structure: {
- prelude: ["SelectorList", "Raw"],
- block: ["Block"]
- },
- parse: function () {
- const startToken = this.scanner.currentToken;
- const startOffset = this.scanner.tokenStart;
- let prelude;
- let block;
- if (this.parseRulePrelude) {
- prelude = this.parseWithFallback(consumePrelude, Rule_consumeRaw);
- } else {
- prelude = Rule_consumeRaw.call(this, startToken);
- }
- block = this.Block(true);
- return {
- type: "Rule",
- loc: this.getLocation(startOffset, this.scanner.tokenStart),
- prelude: prelude,
- block: block
- };
- },
- generate: function (node) {
- this.node(node.prelude);
- this.node(node.block);
- },
- walkContext: "rule"
- };
- // ---
- const Syntax_Selector = {
- name: "Selector",
- structure: {
- children: [[
- "TypeSelector",
- "IdSelector",
- "ClassSelector",
- "AttributeSelector",
- "PseudoClassSelector",
- "PseudoElementSelector",
- "Combinator",
- "WhiteSpace"
- ]]
- },
- parse: function () {
- const children = this.readSequence(this.scope.Selector);
- // nothing were consumed
- if (this.getFirstListNode(children) === null) {
- this.scanner.error("Selector is expected");
- }
- return {
- type: "Selector",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node);
- }
- };
- // ---
- const SelectorList = {
- name: "SelectorList",
- structure: {
- children: [[
- "Selector",
- "Raw"
- ]]
- },
- parse: function () {
- const children = this.createList();
- while (!this.scanner.eof) {
- children.push(this.Selector());
- if (this.scanner.tokenType === COMMA) {
- this.scanner.next();
- continue;
- }
- break;
- }
- return {
- type: "SelectorList",
- loc: this.getLocationFromList(children),
- children: children
- };
- },
- generate: function (node) {
- this.children(node, function () {
- this.chunk(",");
- });
- },
- walkContext: "selector"
- };
- // ---
- const Syntax_String = {
- name: "String",
- structure: {
- value: String
- },
- parse: function () {
- return {
- type: "String",
- loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
- value: this.scanner.consume(STRING)
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- function StyleSheet_consumeRaw(startToken) {
- return this.Raw(startToken, 0, 0, false, false);
- }
- const Syntax_StyleSheet = {
- name: "StyleSheet",
- structure: {
- children: [[
- "Comment",
- "CDO",
- "CDC",
- "Atrule",
- "Rule",
- "Raw"
- ]]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const children = this.createList();
- let child;
- while (!this.scanner.eof) {
- switch (this.scanner.tokenType) {
- case WHITESPACE:
- this.scanner.next();
- continue;
- case COMMENT:
- // ignore comments except exclamation comments (i.e. /*! .. */) on top level
- if (this.scanner.source.charCodeAt(this.scanner.tokenStart + 2) !== EXCLAMATIONMARK) {
- this.scanner.next();
- continue;
- }
- child = this.Comment();
- break;
- case CDO: // <!--
- child = this.CDO();
- break;
- case CDC: // -->
- child = this.CDC();
- break;
- // CSS Syntax Module Level 3
- // §2.2 Error handling
- // At the "top level" of a stylesheet, an <at-keyword-token> starts an at-rule.
- case ATKEYWORD:
- child = this.parseWithFallback(this.Atrule, StyleSheet_consumeRaw);
- break;
- // Anything else starts a qualified rule ...
- default:
- child = this.parseWithFallback(this.Rule, StyleSheet_consumeRaw);
- }
- children.push(child);
- }
- return {
- type: "StyleSheet",
- loc: this.getLocation(start, this.scanner.tokenStart),
- children: children
- };
- },
- generate: function (node) {
- this.children(node);
- },
- walkContext: "stylesheet"
- };
- // ---
- function eatIdentifierOrAsterisk() {
- if (this.scanner.tokenType !== IDENTIFIER &&
- this.scanner.tokenType !== ASTERISK) {
- this.scanner.error("Identifier or asterisk is expected");
- }
- this.scanner.next();
- }
- // ident
- // ident|ident
- // ident|*
- // *
- // *|ident
- // *|*
- // |ident
- // |*
- const TypeSelector = {
- name: "TypeSelector",
- structure: {
- name: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- if (this.scanner.tokenType === VERTICALLINE) {
- this.scanner.next();
- eatIdentifierOrAsterisk.call(this);
- } else {
- eatIdentifierOrAsterisk.call(this);
- if (this.scanner.tokenType === VERTICALLINE) {
- this.scanner.next();
- eatIdentifierOrAsterisk.call(this);
- }
- }
- return {
- type: "TypeSelector",
- loc: this.getLocation(start, this.scanner.tokenStart),
- name: this.scanner.substrToCursor(start)
- };
- },
- generate: function (node) {
- this.chunk(node.name);
- }
- };
- // ---
- function scanUnicodeNumber(scanner) {
- for (let pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
- const code = scanner.source.charCodeAt(pos);
- // break on fullstop or hyperminus/plussign after exponent
- if (code === FULLSTOP || code === PLUSSIGN) {
- // break token, exclude symbol
- scanner.tokenStart = pos;
- return false;
- }
- }
- return true;
- }
- // https://drafts.csswg.org/css-syntax-3/#urange
- function scanUnicodeRange(scanner) {
- const hexStart = scanner.tokenStart + 1; // skip +
- let hexLength = 0;
- scan: {
- if (scanner.tokenType === NUMBER) {
- if (scanner.source.charCodeAt(scanner.tokenStart) !== FULLSTOP && scanUnicodeNumber(scanner)) {
- scanner.next();
- } else if (scanner.source.charCodeAt(scanner.tokenStart) !== HYPHENMINUS) {
- break scan;
- }
- } else {
- scanner.next(); // PLUSSIGN
- }
- if (scanner.tokenType === HYPHENMINUS) {
- scanner.next();
- }
- if (scanner.tokenType === NUMBER) {
- scanner.next();
- }
- if (scanner.tokenType === IDENTIFIER) {
- scanner.next();
- }
- if (scanner.tokenStart === hexStart) {
- scanner.error("Unexpected input", hexStart);
- }
- }
- // validate for U+x{1,6} or U+x{1,6}-x{1,6}
- // where x is [0-9a-fA-F]
- let i;
- let wasHyphenMinus = false;
- for (i = hexStart; i < scanner.tokenStart; i++) {
- const code = scanner.source.charCodeAt(i);
- if (isHex(code) === false && (code !== HYPHENMINUS || wasHyphenMinus)) {
- scanner.error("Unexpected input", i);
- }
- if (code === HYPHENMINUS) {
- // hex sequence shouldn"t be an empty
- if (hexLength === 0) {
- scanner.error("Unexpected input", i);
- }
- wasHyphenMinus = true;
- hexLength = 0;
- } else {
- hexLength++;
- // too long hex sequence
- if (hexLength > 6) {
- scanner.error("Too long hex sequence", i);
- }
- }
- }
- // check we have a non-zero sequence
- if (hexLength === 0) {
- scanner.error("Unexpected input", i - 1);
- }
- // U+abc???
- if (!wasHyphenMinus) {
- // consume as many U+003F QUESTION MARK (?) code points as possible
- for (; hexLength < 6 && !scanner.eof; scanner.next()) {
- if (scanner.tokenType !== QUESTIONMARK) {
- break;
- }
- hexLength++;
- }
- }
- }
- const UnicodeRange = {
- name: "UnicodeRange",
- structure: {
- value: String
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- this.scanner.next(); // U or u
- scanUnicodeRange(this.scanner);
- return {
- type: "UnicodeRange",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: this.scanner.substrToCursor(start)
- };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- // url "(" S* (string | raw) S* ")"
- const Url = {
- name: "Url",
- structure: {
- value: ["String", "Raw"]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- let value;
- this.scanner.eat(URL);
- this.scanner.skipSC();
- switch (this.scanner.tokenType) {
- case STRING:
- value = this.String();
- break;
- case RAW:
- value = this.Raw(this.scanner.currentToken, 0, RAW, true, false);
- break;
- default:
- this.scanner.error("String or Raw is expected");
- }
- this.scanner.skipSC();
- this.scanner.eat(RIGHTPARENTHESIS);
- return {
- type: "Url",
- loc: this.getLocation(start, this.scanner.tokenStart),
- value: value
- };
- },
- generate: function (node) {
- this.chunk("url");
- this.chunk("(");
- this.node(node.value);
- this.chunk(")");
- }
- };
- // ---
- const Syntax_Value = {
- name: "Value",
- structure: {
- children: [[]]
- },
- parse: function () {
- const start = this.scanner.tokenStart;
- const children = this.readSequence(this.scope.Value);
- return {
- type: "Value",
- loc: this.getLocation(start, this.scanner.tokenStart),
- children: children
- };
- },
- generate: function (node) {
- this.children(node);
- }
- };
- // ---
- const WhiteSpace_SPACE = Object.freeze({
- type: "WhiteSpace",
- loc: null,
- value: " "
- });
- const WhiteSpace = {
- name: "WhiteSpace",
- structure: {
- value: String
- },
- parse: function () {
- this.scanner.eat(WHITESPACE);
- return WhiteSpace_SPACE;
- // return {
- // type: "WhiteSpace",
- // loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd),
- // value: this.scanner.consume(WHITESPACE)
- // };
- },
- generate: function (node) {
- this.chunk(node.value);
- }
- };
- // ---
- function processChildren(node, delimeter) {
- const list = node.children;
- let prev = null;
- if (typeof delimeter !== "function") {
- list.forEach(this.node, this);
- } else {
- list.forEach(function (node) {
- if (prev !== null) {
- delimeter.call(this, prev);
- }
- this.node(node);
- prev = node;
- }, this);
- }
- }
- function createGenerator(config) {
- function processNode(node) {
- if (hasOwnProperty.call(types, node.type)) {
- types[node.type].call(this, node);
- } else {
- throw new Error("Unknown node type: " + node.type);
- }
- }
- const types = {};
- if (config.node) {
- for (const name in config.node) {
- types[name] = config.node[name].generate;
- }
- }
- return function (node, options) {
- let buffer = "";
- let handlers = {
- children: processChildren,
- node: processNode,
- chunk: function (chunk) {
- buffer += chunk;
- },
- result: function () {
- return buffer;
- }
- };
- if (options) {
- if (typeof options.decorator === "function") {
- handlers = options.decorator(handlers);
- }
- }
- handlers.node(node);
- return handlers.result();
- };
- }
- // ---
- const node = {
- AnPlusB: AnPlusB,
- Atrule: Atrule,
- AtrulePrelude: Syntax_AtrulePrelude,
- AttributeSelector: AttributeSelector,
- Block: Block,
- Brackets: Brackets,
- CDC: Syntax_CDC,
- CDO: Syntax_CDO,
- ClassSelector: ClassSelector,
- Combinator: Combinator,
- Comment: Syntax_Comment,
- Declaration: Declaration,
- DeclarationList: DeclarationList,
- Dimension: Dimension,
- Function: Syntax_Function,
- HexColor: HexColor,
- Identifier: Identifier,
- IdSelector: IdSelector,
- MediaFeature: MediaFeature,
- MediaQuery: MediaQuery,
- MediaQueryList: MediaQueryList,
- Nth: Nth,
- Number: Syntax_Number,
- Operator: Operator,
- Parentheses: Parentheses,
- Percentage: Percentage,
- PseudoClassSelector: PseudoClassSelector,
- PseudoElementSelector: PseudoElementSelector,
- Ratio: Ratio,
- Raw: Raw,
- Rule: Rule,
- Selector: Syntax_Selector,
- SelectorList: SelectorList,
- String: Syntax_String,
- StyleSheet: Syntax_StyleSheet,
- TypeSelector: TypeSelector,
- UnicodeRange: UnicodeRange,
- Url: Url,
- Value: Syntax_Value,
- WhiteSpace: WhiteSpace
- };
- // ---
- const config = {
- parseContext: {
- default: "StyleSheet",
- stylesheet: "StyleSheet",
- atrule: "Atrule",
- atrulePrelude: function (options) {
- return this.AtrulePrelude(options.atrule ? String(options.atrule) : null);
- },
- mediaQueryList: "MediaQueryList",
- mediaQuery: "MediaQuery",
- rule: "Rule",
- selectorList: "SelectorList",
- selector: "Selector",
- block: function () {
- return this.Block(true);
- },
- declarationList: "DeclarationList",
- declaration: "Declaration",
- value: "Value"
- },
- scope: scope,
- atrule: atrule,
- pseudo: pseudo,
- node: node
- };
- return {
- parse: createParser(config),
- generate: createGenerator(config)
- };
- })();
|