Something went wrong. Try again.
MIRROR: javascript for 🐜's, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513// TODO: cleanup module, make cleaner. MODULARIZE.
#include "ant.h"#include "cfunc.h"#include "utf8.h"#include "errors.h"#include "internal.h"#include "utils.h"#include "ascii.h"#include "descriptors.h"#include "ptr.h"#include "gc/roots.h"
#include <stdlib.h>#include <string.h>#include <stdio.h>#include <limits.h>#include <math.h>#include <pcre2.h>
#include "silver/call.h"#include "modules/regex.h"#include "realm.h"#include "modules/symbol.h"#include "modules/iterator.h"
static constexpr size_t REGEXP_REQUIRED_LITERAL_MAX = 32;
typedef struct compiled_regex_cache_entry { char *pattern; size_t pattern_len; uint64_t key_hash; uint8_t flags_mask; pcre2_code *code; pcre2_match_data *scratch_match_data; ant_shape_t *lastindex_shape; uint32_t lastindex_slot; uint32_t namecount; bool jit_ready; bool scratch_in_use; bool plain_literal; size_t object_refs; size_t active_refs; uint8_t cache_refs; uint8_t required_literal_len; char required_literal[REGEXP_REQUIRED_LITERAL_MAX];} compiled_regex_cache_entry_t;
typedef struct { compiled_regex_cache_entry_t **slots; size_t count; size_t cap;} compiled_regex_cache_table_t;
typedef struct { compiled_regex_cache_entry_t *compiled; pcre2_match_data *match_data; bool borrowed;} regex_match_scope_t;
typedef struct { ant_shape_t *shape; uint32_t index_slot; uint32_t input_slot; uint32_t groups_slot; uint32_t indices_slot;} regexp_result_shape_t;
typedef struct { ant_value_t array; ant_value_t index; ant_value_t input; ant_value_t groups_meta; ant_value_t indices; bool with_indices;} regexp_exec_result_ctx_t;
enum {#define REGEXP_FLAG(id, name, ch) REGEXP_FLAG_INDEX_##id,#include "modules/regex_flags.h" REGEXP_FLAG_COUNT};
enum {#define REGEXP_FLAG(id, name, ch) REGEXP_FLAG_##id = 1 << REGEXP_FLAG_INDEX_##id,#include "modules/regex_flags.h"};
static constexpr int REGEXP_INLINE_CAPTURES = 32;static constexpr ant_offset_t REGEXP_PLAIN_LITERAL_MAX = 32;
static_assert(sizeof(PCRE2_SIZE) == sizeof(size_t), "realm ovector element");static constexpr int REGEXP_STATIC_IDX_LAST_MATCH = ANT_REGEXP_STATIC_CAPTURE_COUNT;static constexpr int REGEXP_STATIC_IDX_AMP = REGEXP_STATIC_IDX_LAST_MATCH + 1;static constexpr int REGEXP_STATIC_VALUE_COUNT = REGEXP_STATIC_IDX_AMP + 1;
typedef struct { ant_shape_t *base; ant_shape_t *shape;} regexp_instance_shape_t;
struct ant_regex_state { compiled_regex_cache_table_t compiled[2]; size_t live_object_data;
regexp_instance_shape_t instance_shape; const ant_shape_t *pristine_flags_shape; const ant_realm_t *pristine_flags_realm; uint32_t pristine_flags_epoch; regexp_result_shape_t result_shape; regexp_result_shape_t result_indices_shape;
pcre2_match_context *match_ctx; pcre2_jit_stack *jit_stack;
};
static constexpr uint32_t REGEXP_NATIVE_TAG = Ant_NativeTag("REGX");static constexpr uint16_t REGEXP_STATIC_ALL_MATERIALIZED = (1u << REGEXP_STATIC_VALUE_COUNT) - 1;
static constexpr size_t REGEX_COMPILED_CACHE_MAX = 1024;static constexpr size_t REGEX_COMPILED_TABLE_LOAD_NUM = 3;static constexpr size_t REGEX_COMPILED_TABLE_LOAD_DEN = 4;
static bool regexp_result_shape_init( ant_t *js, regexp_result_shape_t *cache, ant_object_t *array, bool with_indices) { if (cache->shape) return true; if (!array || !array->shape) return false;
const char *index_key = intern_string(js, "index", 5); const char *input_key = intern_string(js, "input", 5); const char *groups_key = intern_string(js, "groups", 6); const char *indices_key = with_indices ? intern_string(js, "indices", 7) : NULL; if (!index_key || !input_key || !groups_key || (with_indices && !indices_key)) return false;
ant_shape_t *shape = ant_shape_clone(array->shape); if (!shape) return false;
regexp_result_shape_t built = { .shape = shape }; bool ok = ant_shape_add_interned(js, shape, index_key, ANT_PROP_ATTR_DEFAULT, &built.index_slot) && ant_shape_add_interned(js, shape, input_key, ANT_PROP_ATTR_DEFAULT, &built.input_slot) && ant_shape_add_interned(js, shape, groups_key, ANT_PROP_ATTR_DEFAULT, &built.groups_slot); if (ok && with_indices) ok = ant_shape_add_interned(js, shape, indices_key, ANT_PROP_ATTR_DEFAULT, &built.indices_slot); if (!ok) { ant_shape_release(shape); return false; }
*cache = built; return true;}
static __attribute__((noinline)) bool regexp_result_apply_shape( ant_t *js, ant_value_t array_value, ant_value_t index, ant_value_t input, ant_value_t groups, ant_value_t indices, bool with_indices) { ant_regex_state_t *state = js->regex_state; ant_object_t *array = js_obj_ptr(array_value); if (!state || !array || array->type_tag != kTypeArray) goto fallback;
regexp_result_shape_t *cache = with_indices ? &state->result_indices_shape : &state->result_shape; if (!regexp_result_shape_init(js, cache, array, with_indices)) goto fallback;
if (array->shape != cache->shape) { ant_shape_retain(cache->shape); ant_shape_release(array->shape); array->shape = cache->shape; } if (!js_obj_ensure_prop_capacity(js, array, with_indices ? 4 : 3)) goto fallback;
ant_object_prop_set_unchecked(array, cache->index_slot, index); ant_object_prop_set_unchecked(array, cache->input_slot, input); ant_object_prop_set_unchecked(array, cache->groups_slot, groups); gc_write_barrier(js, array, input); gc_write_barrier(js, array, groups); if (with_indices) { ant_object_prop_set_unchecked(array, cache->indices_slot, indices); gc_write_barrier(js, array, indices); } return true;
fallback: return false;}
static ant_value_t regexp_attach_plain_exec_result( ant_t *js, ant_value_t array, ant_value_t index, ant_value_t input) { if (regexp_result_apply_shape( js, array, index, input, js_mkundef(), js_mkundef(), false )) return array;
if ( is_err(js_mkprop_fast(js, array, "index", 5, index)) || is_err(js_mkprop_fast(js, array, "input", 5, input)) || is_err(js_mkprop_fast(js, array, "groups", 6, js_mkundef())) ) return js_mkerr(js, "oom"); return array;}
static void regexp_note_write(ant_t *js, bool exec) { if (!js->regexp.write_guard_armed) return; if (exec) js->regexp.exec_written = true; else js->regexp.replace_written = true; if (__builtin_expect(!ant_realm_multiple(js), 1)) return;
for (ant_realm_t *realm = js->realms; realm; realm = realm->next) { if (realm == js->realm || !realm->regexp.write_guard_armed) continue; if (exec) realm->regexp.exec_written = true; else realm->regexp.replace_written = true; }}
void regexp_note_exec_property_write(ant_t *js) { regexp_note_write(js, true);}
void regexp_note_replace_property_write(ant_t *js) { regexp_note_write(js, false);}
static inline uint8_t regexp_parse_flags_mask(const char *fstr, ant_offset_t flen) { uint8_t mask = 0; for (ant_offset_t k = 0; k < flen; k++) { switch (fstr[k]) {#define REGEXP_FLAG(id, name, ch) case ch: mask |= REGEXP_FLAG_##id; break;#include "modules/regex_flags.h" default: break; }} return mask;}
static inline uint8_t regexp_flags_mask( ant_t *js, ant_value_t regexp, compiled_regex_cache_entry_t **compiled_out) { compiled_regex_cache_entry_t *compiled = js_get_native( regexp, REGEXP_NATIVE_TAG ); if (compiled_out) *compiled_out = compiled; if (compiled) { return compiled->flags_mask; }
ant_value_t internal_mask = js_get_slot(regexp, SLOT_REGEXP_FLAGS_MASK); return vtype(internal_mask) == kTypeNumber ? (uint8_t)tod(internal_mask) : 0;}
static ant_value_t regexp_build_named_groups_meta(ant_t *js, pcre2_code *code) { uint32_t namecount = 0; pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &namecount); if (namecount == 0) return js_mkundef();
uint32_t nameentrysize = 0; PCRE2_SPTR nametable = NULL; pcre2_pattern_info(code, PCRE2_INFO_NAMEENTRYSIZE, &nameentrysize); pcre2_pattern_info(code, PCRE2_INFO_NAMETABLE, (void *)&nametable);
ant_value_t meta = js_mkarr(js); if (is_err(meta)) return meta;
PCRE2_SPTR tabptr = nametable; for (uint32_t i = 0; i < namecount; i++) { int n = (tabptr[0] << 8) | tabptr[1]; const char *name = (const char *)(tabptr + 2); ant_value_t name_val = js_mkstr(js, name, strlen(name)); if (is_err(name_val)) return name_val; js_arr_push(js, meta, name_val); js_arr_push(js, meta, tov((double)n)); tabptr += nameentrysize; }
return meta;}
static inline ant_value_t regexp_static_empty(ant_t *js) { return js->builtins.regexp_empty_string ? js->builtins.regexp_empty_string : js_mkundef();}
static inline void regexp_statics_clear_materialized(ant_t *js) { ant_regexp_realm_t *statics = &js->regexp; uint16_t mask = statics->static_materialized_mask; if (__builtin_expect(mask == 0, 1)) return;
ant_value_t empty = regexp_static_empty(js); while (mask) { unsigned idx = (unsigned)__builtin_ctz((unsigned)mask); js->mutable_roots.regexp_static_values[idx] = empty; mask &= (uint16_t)(mask - 1); } statics->static_materialized_mask = 0;}
static ant_value_t regexp_static_value(ant_t *js, size_t idx) { if (idx >= REGEXP_STATIC_VALUE_COUNT) return regexp_static_empty(js);
ant_regexp_realm_t *statics = &js->regexp; uint16_t bit = (uint16_t)(1u << idx); if (!statics->static_pending || (statics->static_materialized_mask & bit)) { ant_value_t value = js->mutable_roots.regexp_static_values[idx]; return value ? value : regexp_static_empty(js); }
size_t capture = idx < ANT_REGEXP_STATIC_CAPTURE_COUNT ? idx + 1 : 0; ant_value_t value = regexp_static_empty(js); if ( capture < statics->static_ovcount && statics->static_ovector[capture * 2] != PCRE2_UNSET ) { PCRE2_SIZE start = statics->static_ovector[capture * 2]; PCRE2_SIZE end = statics->static_ovector[capture * 2 + 1]; ant_offset_t subject_len; ant_offset_t subject_off = vstr( js, js->mutable_roots.regexp_static_subject, &subject_len ); if (end < start || end > subject_len) return js_mkerr(js, "invalid regexp static snapshot"); value = js_mkstr_byte_range( js, (const char *)(uintptr_t)subject_off, start, end - start ); if (is_err(value)) return value; }
if (idx >= REGEXP_STATIC_IDX_LAST_MATCH) { js->mutable_roots.regexp_static_values[REGEXP_STATIC_IDX_LAST_MATCH] = value; js->mutable_roots.regexp_static_values[REGEXP_STATIC_IDX_AMP] = value; statics->static_materialized_mask |= (uint16_t)( (1u << REGEXP_STATIC_IDX_LAST_MATCH) | (1u << REGEXP_STATIC_IDX_AMP) ); } else { js->mutable_roots.regexp_static_values[idx] = value; statics->static_materialized_mask |= bit; } if (statics->static_materialized_mask == REGEXP_STATIC_ALL_MATERIALIZED) { statics->static_pending = false; js->mutable_roots.regexp_static_subject = regexp_static_empty(js); } return value;}
static inline ant_value_t regexp_static_set(ant_t *js) { return js_mkundef();}
#define REGEXP_STATIC_ACCESSORS(name, idx) \ static ant_value_t regexp_static_get_##name(ant_params_t) { \ (void)args; (void)nargs; \ return regexp_static_value(js, idx); \ } \ static ant_value_t regexp_static_set_##name(ant_params_t) { \ (void)args; (void)nargs; \ return regexp_static_set(js); \ }
REGEXP_STATIC_ACCESSORS(d1, 0)REGEXP_STATIC_ACCESSORS(d2, 1)REGEXP_STATIC_ACCESSORS(d3, 2)REGEXP_STATIC_ACCESSORS(d4, 3)REGEXP_STATIC_ACCESSORS(d5, 4)REGEXP_STATIC_ACCESSORS(d6, 5)REGEXP_STATIC_ACCESSORS(d7, 6)REGEXP_STATIC_ACCESSORS(d8, 7)REGEXP_STATIC_ACCESSORS(d9, 8)REGEXP_STATIC_ACCESSORS(last_match, REGEXP_STATIC_IDX_LAST_MATCH)REGEXP_STATIC_ACCESSORS(amp, REGEXP_STATIC_IDX_AMP)
#undef REGEXP_STATIC_ACCESSORS
static __attribute__((noinline, cold)) void update_regexp_statics_eager( ant_t *js, ant_value_t subject, PCRE2_SIZE *ovector, uint32_t ovcount) { ant_offset_t subject_len; ant_offset_t subject_off = vstr(js, subject, &subject_len); const char *str_ptr = (const char *)(uintptr_t)subject_off; ant_value_t empty = regexp_static_empty(js); for (int i = 1; i <= ANT_REGEXP_STATIC_CAPTURE_COUNT; i++) { ant_value_t val = empty; if ((uint32_t)i < ovcount) { PCRE2_SIZE start = ovector[2*i]; PCRE2_SIZE end = ovector[2*i + 1]; if ( start != PCRE2_UNSET && end >= start && end <= subject_len ) { val = js_mkstr_byte_range(js, str_ptr, start, end - start); if (is_err(val)) val = empty; } } js->mutable_roots.regexp_static_values[i - 1] = val; }
bool has_match0 = ( ovcount > 0 && ovector[0] != PCRE2_UNSET && ovector[1] >= ovector[0] && ovector[1] <= subject_len ); ant_value_t match0 = has_match0 ? js_mkstr_byte_range(js, str_ptr, ovector[0], ovector[1] - ovector[0]) : empty; if (is_err(match0)) match0 = empty; js->mutable_roots.regexp_static_values[REGEXP_STATIC_IDX_LAST_MATCH] = match0; js->mutable_roots.regexp_static_values[REGEXP_STATIC_IDX_AMP] = match0;}
static void update_regexp_statics( ant_t *js, ant_value_t subject, PCRE2_SIZE *ovector, uint32_t ovcount) { ant_regex_state_t *state = js->regex_state; if (!state) { update_regexp_statics_eager(js, subject, ovector, ovcount); return; }
regexp_statics_clear_materialized(js); js->mutable_roots.regexp_static_subject = subject; uint32_t stored = ovcount < ANT_REGEXP_STATIC_OVECTOR_COUNT ? ovcount : ANT_REGEXP_STATIC_OVECTOR_COUNT; memcpy( js->regexp.static_ovector, ovector, (size_t)stored * 2 * sizeof(*ovector) ); js->regexp.static_ovcount = (uint8_t)stored; js->regexp.static_pending = true;}
static inline bool is_pcre2_passthrough_escape(char c) {switch (c) { case 'd': case 'D': case 'w': case 'W': case 's': case 'S': case 'b': case 'B': case 'n': case 'r': case 't': case 'f': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '*': case '+': case '?': case '(': case ')': case '[': case ']': case '{': case '}': case '|': case '^': case '$': case '\\': case '/': case '-': return true; default: return false;}}
static inline bool is_class_shorthand(char c) { return c == 'w' || c == 'W' || c == 'd' || c == 'D' || c == 's' || c == 'S';}
static size_t v_close_bracket(const char *src, size_t src_len, size_t open) { int depth = 0; for (size_t i = open; i < src_len; i++) { if (src[i] == '\\' && i + 1 < src_len) { i++; continue; } if (src[i] == '[') depth++; else if (src[i] == ']') { if (--depth == 0) return i; } } return src_len;}
typedef struct { char *data; size_t capacity; size_t length;} regexp_pattern_builder_t;
static inline void regexp_pattern_builder_puts( regexp_pattern_builder_t *builder, const char *value, size_t length) { size_t offset = builder->length; size_t available = offset < builder->capacity ? builder->capacity - offset : 0; size_t copy_length = length < available ? length : available; if (copy_length) memcpy(builder->data + offset, value, copy_length); builder->length += length;}
static inline void regexp_pattern_builder_put_any_char( regexp_pattern_builder_t *builder) { static const char any_char[] = "[\\s\\S]"; regexp_pattern_builder_puts(builder, any_char, sizeof(any_char) - 1);}
static void js_to_pcre2_pattern_append( const char *src, size_t src_len, regexp_pattern_builder_t *builder, bool v_flag, int initial_charclass_depth);
static void v_translate_part_append( const char *part, size_t length, regexp_pattern_builder_t *builder) { if (length && part[0] == '[') { js_to_pcre2_pattern_append(part, length, builder, false, 0); return; }
if (length == 1 && part[0] == '^') { regexp_pattern_builder_put_any_char(builder); return; }
if (length >= 2 && part[0] == '^' && part[1] == ']') { regexp_pattern_builder_put_any_char(builder); js_to_pcre2_pattern_append(part + 2, length - 2, builder, false, 0); regexp_pattern_builder_puts(builder, "]", 1); return; }
regexp_pattern_builder_puts(builder, "[", 1); js_to_pcre2_pattern_append(part, length, builder, false, 1); regexp_pattern_builder_puts(builder, "]", 1);}
static int v_set_op(const char *src, size_t start, size_t end, size_t *op_pos) { int depth = 0; for (size_t i = start; i < end; ) { if (src[i] == '\\' && i + 1 < end) { char n = src[i + 1]; if ((n == 'p' || n == 'P') && i + 2 < end && src[i + 2] == '{') { i += 3; while (i < end && src[i] != '}') i++; if (i < end) i++; continue; } if ((n == 'u' || n == 'x') && i + 2 < end && src[i + 2] == '{') { i += 3; while (i < end && src[i] != '}') i++; if (i < end) i++; continue; } i += 2; continue; } if (src[i] == '[') { depth++; i++; continue; } if (src[i] == ']') { if (depth > 0) { depth--; i++; continue; } break; } if (!depth && i + 1 < end) { if (src[i] == '&' && src[i+1] == '&') { *op_pos = i; return 1; } if (src[i] == '-' && src[i+1] == '-') { *op_pos = i; return 2; } } i++; } return 0;}
static void js_to_pcre2_pattern_append( const char *src, size_t src_len, regexp_pattern_builder_t *builder, bool v_flag, int initial_charclass_depth) { int charclass_depth = initial_charclass_depth; char *output = builder->data; size_t output_capacity = builder->capacity; size_t output_length = builder->length;
#define OUT(ch) do { \ size_t output_offset = output_length++; \ if (output_offset < output_capacity) output[output_offset] = (ch); \} while (0)
for (size_t si = 0; si < src_len; si++) { if (src[si] == '[') { if (si + 2 < src_len && src[si + 1] == '^' && src[si + 2] == ']') { OUT('['); OUT('\\'); OUT('s'); OUT('\\'); OUT('S'); OUT(']'); si += 2; continue; }
if (v_flag && charclass_depth == 0) { size_t close = v_close_bracket(src, src_len, si); size_t op_pos; int op_type = v_set_op(src, si + 1, close, &op_pos); if (op_type && close < src_len) { const char *left = &src[si + 1]; size_t left_len = op_pos - si - 1; const char *right = &src[op_pos + 2]; size_t right_len = close - op_pos - 2; const char *lookahead = op_type == 1 ? left : right; size_t lookahead_len = op_type == 1 ? left_len : right_len; const char *match = op_type == 1 ? right : left; size_t match_len = op_type == 1 ? right_len : left_len; OUT('('); OUT('?'); OUT(op_type == 1 ? '=' : '!'); builder->length = output_length; v_translate_part_append(lookahead, lookahead_len, builder); output_length = builder->length; OUT(')'); builder->length = output_length; v_translate_part_append(match, match_len, builder); output_length = builder->length; si = close; continue; } } charclass_depth++; OUT('['); continue; } if (src[si] == ']' && charclass_depth > 0) { charclass_depth--; OUT(']'); continue; }
if (charclass_depth > 0 && src[si] == '-' && si > 0 && src[si - 1] != '[' && si + 1 < src_len && src[si + 1] != ']') { bool prev_is_shorthand = (si >= 2 && src[si - 2] == '\\' && is_class_shorthand(src[si - 1])); bool next_is_shorthand = (si + 2 < src_len && src[si + 1] == '\\' && is_class_shorthand(src[si + 2])); if (prev_is_shorthand || next_is_shorthand) { OUT('\\'); OUT('-'); continue; } OUT('-'); continue; }
if (src[si] != '\\' || si + 1 >= src_len) { OUT(src[si]); continue; }
char next = src[si + 1];
if (next == 'v') { OUT('\\'); OUT('x'); OUT('{'); OUT('0'); OUT('b'); OUT('}'); si++; continue; }
if (next == 'u' && si + 2 < src_len && src[si + 2] == '{') { size_t brace_start = si + 3; size_t brace_end = brace_start; while (brace_end < src_len && src[brace_end] != '}' && ant_ascii_is_xdigit(src[brace_end])) brace_end++; if (brace_end < src_len && src[brace_end] == '}' && brace_end > brace_start) { OUT('\\'); OUT('x'); OUT('{'); for (size_t k = brace_start; k < brace_end; k++) OUT(src[k]); OUT('}'); si = brace_end; continue; } }
if (next == 'u' && si + 5 < src_len && ant_ascii_is_xdigit(src[si+2]) && ant_ascii_is_xdigit(src[si+3]) && ant_ascii_is_xdigit(src[si+4]) && ant_ascii_is_xdigit(src[si+5])) { OUT('\\'); OUT('x'); OUT('{'); OUT(src[si+2]); OUT(src[si+3]); OUT(src[si+4]); OUT(src[si+5]); OUT('}'); si += 5; continue; }
if (next == 'u') { si++; OUT('u'); continue; }
if (next == 'x' && si + 3 < src_len && ant_ascii_is_xdigit(src[si+2]) && ant_ascii_is_xdigit(src[si+3])) { OUT('\\'); OUT('x'); OUT(src[si+2]); OUT(src[si+3]); si += 3; continue; }
if (next == 'x') { si++; OUT('x'); continue; }
if (next == '0' && (si + 2 >= src_len || src[si+2] < '0' || src[si+2] > '9')) { OUT('\\'); OUT('x'); OUT('{'); OUT('0'); OUT('}'); si++; continue; }
if (next >= '0' && next <= '7') { unsigned int octal = next - '0'; size_t advance = 1; if (si + 2 < src_len && src[si+2] >= '0' && src[si+2] <= '7') { octal = octal * 8 + (src[si+2] - '0'); advance = 2; if (si + 3 < src_len && src[si+3] >= '0' && src[si+3] <= '7' && octal * 8 + (src[si+3] - '0') <= 255) { octal = octal * 8 + (src[si+3] - '0'); advance = 3; } } if (advance > 1 || next == '0') { char hex[8]; int hlen = snprintf(hex, sizeof(hex), "\\x{%02x}", octal); for (int k = 0; k < hlen; k++) OUT(hex[k]); si += advance; continue; } }
if (next == 'c' && si + 2 < src_len && ((src[si+2] >= 'A' && src[si+2] <= 'Z') || (src[si+2] >= 'a' && src[si+2] <= 'z'))) { OUT('\\'); OUT('c'); OUT(src[si+2]); si += 2; continue; }
if (next == 'c') { OUT('\\'); OUT('\\'); OUT('c'); si++; continue; }
if ((next == 'p' || next == 'P') && si + 2 < src_len && src[si + 2] == '{') { size_t brace_start = si + 3; size_t brace_end = brace_start; while (brace_end < src_len && src[brace_end] != '}') brace_end++; if (brace_end < src_len && src[brace_end] == '}') { const char *prop = &src[brace_start]; size_t prop_len = brace_end - brace_start; static const struct { const char *name; const char *code; } gc_map[] = { {"Letter","L"},{"Cased_Letter","LC"},{"Uppercase_Letter","Lu"}, {"Lowercase_Letter","Ll"},{"Titlecase_Letter","Lt"}, {"Modifier_Letter","Lm"},{"Other_Letter","Lo"}, {"Mark","M"},{"Nonspacing_Mark","Mn"},{"Spacing_Mark","Mc"}, {"Enclosing_Mark","Me"}, {"Number","N"},{"Decimal_Number","Nd"},{"Letter_Number","Nl"}, {"Other_Number","No"}, {"Punctuation","P"},{"Connector_Punctuation","Pc"}, {"Dash_Punctuation","Pd"},{"Open_Punctuation","Ps"}, {"Close_Punctuation","Pe"},{"Initial_Punctuation","Pi"}, {"Final_Punctuation","Pf"},{"Other_Punctuation","Po"}, {"Symbol","S"},{"Math_Symbol","Sm"},{"Currency_Symbol","Sc"}, {"Modifier_Symbol","Sk"},{"Other_Symbol","So"}, {"Separator","Z"},{"Space_Separator","Zs"}, {"Line_Separator","Zl"},{"Paragraph_Separator","Zp"}, {"Other","C"},{"Control","Cc"},{"Format","Cf"}, {"Surrogate","Cs"},{"Private_Use","Co"},{"Unassigned","Cn"}, }; static const struct { const char *script; const char *range; } u17_scripts[] = { {"Sidetic", "\\x{10940}-\\x{1095F}"}, {"Garay", "\\x{10D40}-\\x{10D8F}"}, {"Gurung_Khema", "\\x{16100}-\\x{1613F}"}, {"Kirat_Rai", "\\x{16D40}-\\x{16D7F}"}, {"Ol_Onal", "\\x{1E5D0}-\\x{1E5FF}"}, {"Sunuwar", "\\x{11BC0}-\\x{11BFF}"}, {"Tulu_Tigalari", "\\x{11380}-\\x{113FF}"}, }; bool has_eq = (memchr(prop, '=', prop_len) != NULL); bool has_colon = (memchr(prop, ':', prop_len) != NULL); if (!has_eq && !has_colon && next == 'p' && charclass_depth == 0) { static const struct { const char *name; const char *exp; } sprops[] = { {"Emoji_Keycap_Sequence", "(?:\\x{23}\\x{fe0f}\\x{20e3}|\\x{2a}\\x{fe0f}\\x{20e3}|[\\x{30}-\\x{39}]\\x{fe0f}\\x{20e3})"}, {"RGI_Emoji", "(?:" "[\\x{1f1e6}-\\x{1f1ff}]{2}" "|(?:(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}|(?!\\p{Emoji_Modifier})\\p{Emoji_Presentation}|(?![#*0-9]|\\p{Emoji_Presentation})\\p{Emoji}\\x{fe0f})\\x{200d})+" "(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}|(?!\\p{Emoji_Modifier})\\p{Emoji_Presentation}|(?![#*0-9]|\\p{Emoji_Presentation})\\p{Emoji}\\x{fe0f})" "|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}" "|[#*0-9]\\x{fe0f}\\x{20e3}" "|\\x{1f3f4}\\x{e0067}\\x{e0062}(?:\\x{e0065}\\x{e006e}\\x{e0067}|\\x{e0073}\\x{e0063}\\x{e0074}|\\x{e0077}\\x{e006c}\\x{e0073})\\x{e007f}" "|\\p{Emoji_Presentation}" "|(?![#*0-9]|\\p{Emoji_Presentation})\\p{Emoji}\\x{fe0f}" ")"}, }; for (size_t m = 0; m < sizeof(sprops)/sizeof(sprops[0]); m++) { if (strlen(sprops[m].name) == prop_len && memcmp(sprops[m].name, prop, prop_len) == 0) { for (const char *r = sprops[m].exp; *r; r++) OUT(*r); si = brace_end; goto next_char; } } } if (has_eq || has_colon) { char sep = has_eq ? '=' : ':'; const char *val = memchr(prop, sep, prop_len); if (val) { val++; size_t val_len = prop_len - (size_t)(val - prop); for (size_t m = 0; m < sizeof(u17_scripts)/sizeof(u17_scripts[0]); m++) { if (strlen(u17_scripts[m].script) == val_len && memcmp(u17_scripts[m].script, val, val_len) == 0) { const char *r = u17_scripts[m].range; OUT('['); if (next == 'P') OUT('^'); for (; *r; r++) OUT(*r); OUT(']'); si = brace_end; goto next_char; } } } } if (!has_eq && !has_colon) { static const struct { const char *name; const char *range; } rangeprops[] = { {"ASCII", "\\x{0}-\\x{7f}"}, {"Any", "\\x{0}-\\x{10ffff}"}, }; for (size_t m = 0; m < sizeof(rangeprops)/sizeof(rangeprops[0]); m++) { if (strlen(rangeprops[m].name) == prop_len && memcmp(rangeprops[m].name, prop, prop_len) == 0) { if (charclass_depth > 0) { for (const char *r = rangeprops[m].range; *r; r++) OUT(*r); } else { OUT('['); if (next == 'P') OUT('^'); for (const char *r = rangeprops[m].range; *r; r++) OUT(*r); OUT(']'); } si = brace_end; goto next_char; } } } const char *replacement = NULL; if (!has_eq && !has_colon) { for (size_t m = 0; m < sizeof(gc_map)/sizeof(gc_map[0]); m++) { if (strlen(gc_map[m].name) == prop_len && memcmp(gc_map[m].name, prop, prop_len) == 0) { replacement = gc_map[m].code; break; } } } static const struct { const char *prop; const char *extra; } u17_props[] = { {"Emoji", "\\x{1FACD}-\\x{1FACE}\\x{1FAE9}\\x{1FAF9}"}, }; const char *extra_range = NULL; if (!has_eq && !has_colon && !replacement) { for (size_t m = 0; m < sizeof(u17_props)/sizeof(u17_props[0]); m++) { if (strlen(u17_props[m].prop) == prop_len && memcmp(u17_props[m].prop, prop, prop_len) == 0) { extra_range = u17_props[m].extra; break; } } } if (extra_range && charclass_depth == 0) { const char *pfx = (next == 'p') ? "(?:\\p{" : "(?:\\P{"; for (const char *r = pfx; *r; r++) OUT(*r); for (size_t k = brace_start; k < brace_end; k++) OUT(src[k]); OUT('}'); OUT('|'); OUT('['); if (next == 'P') OUT('^'); for (const char *r = extra_range; *r; r++) OUT(*r); OUT(']'); OUT(')'); } else { OUT('\\'); OUT(next); OUT('{'); if (replacement) { for (const char *r = replacement; *r; r++) OUT(*r); } else { for (size_t k = brace_start; k < brace_end; k++) OUT(src[k]); } OUT('}'); } si = brace_end; continue; } OUT('\\'); OUT(next); si++; continue; }
if (is_pcre2_passthrough_escape(next)) { OUT('\\'); OUT(next); si++; continue; }
si++; OUT(next); next_char:; }
#undef OUT builder->length = output_length;}
size_t js_to_pcre2_pattern( const char *src, size_t src_len, char *dst, size_t dst_size, bool v_flag) { regexp_pattern_builder_t builder = { .data = dst, .capacity = dst_size ? dst_size - 1 : 0, }; js_to_pcre2_pattern_append(src, src_len, &builder, v_flag, 0); if (dst_size) { size_t terminator = builder.length < dst_size ? builder.length : dst_size - 1; dst[terminator] = '\0'; } return builder.length;}
js_cstr_t js_to_pcre2_pattern_cstr( const char *src, size_t src_len, char *stack_buf, size_t stack_size, bool v_flag) { js_cstr_t result = {0}; size_t required = js_to_pcre2_pattern( src, src_len, stack_buf, stack_size, v_flag ); if (required < stack_size) { result.ptr = stack_buf; result.len = required; return result; } if (required == SIZE_MAX) return result;
char *allocated = malloc(required + 1); if (!allocated) return result; size_t translated = js_to_pcre2_pattern( src, src_len, allocated, required + 1, v_flag ); if (translated != required) { free(allocated); return result; }
result.ptr = allocated; result.len = translated; result.needs_free = true; return result;}
static bool regexp_init_last_index(ant_t *js, ant_value_t value) { ant_object_t *obj = js_obj_ptr(value); if (!js->regex_state || !obj || obj->prop_count != 0) return false; regexp_instance_shape_t *cache = &js->regex_state->instance_shape; if (!cache->shape) { if (ant_shape_count(obj->shape) != 0) return false; ant_shape_t *shape = ant_shape_clone(obj->shape); const char *key = intern_string(js, "lastIndex", 9); uint32_t slot; if (!shape) return false; if (!key || !ant_shape_add_interned(js, shape, key, ANT_PROP_ATTR_WRITABLE, &slot)) { ant_shape_release(shape); return false; } ant_shape_retain(obj->shape); *cache = (regexp_instance_shape_t){.base = obj->shape, .shape = shape}; } if (obj->shape != cache->base || !js_obj_ensure_prop_capacity(js, obj, 1)) return false; ant_shape_transition_existing(&obj->shape, cache->shape); ant_object_invalidate_guarded_absence(js, obj); ant_object_prop_set_unchecked(obj, 0, tov(0)); return true;}
static void regexp_init_flags(ant_t *js, ant_value_t obj, ant_value_t flags, bool is_new) { ant_offset_t flen; const char *fstr = (const char *)(uintptr_t)vstr(js, flags, &flen); uint8_t mask = regexp_parse_flags_mask(fstr, flen);
char sorted[REGEXP_FLAG_COUNT]; int si = 0;#define REGEXP_FLAG(id, name, ch) if (mask & REGEXP_FLAG_##id) sorted[si++] = ch;#include "modules/regex_flags.h"
ant_value_t flags_value = flen == (ant_offset_t)si && memcmp(fstr, sorted, si) == 0 ? flags : js_mkstr(js, sorted, si); if (!is_new) setprop_cstr(js, obj, "lastIndex", 9, tov(0)); else if (!regexp_init_last_index(js, obj)) mkprop_exact_attrs(js, obj, ANT_STRING("lastIndex"), tov(0), ANT_PROP_ATTR_WRITABLE); if (is_new) { unsigned count = ant_object_extra_count(js_obj_ptr(obj)); if (count <= UINT8_MAX - 3) (void)js_reserve_slots(obj, (uint8_t)(count + 3)); } js_set_slot(obj, SLOT_REGEXP_FLAGS_MASK, tov((double)mask)); js_set_slot(obj, SLOT_REGEXP_FLAGS_STRING, flags_value); js_set_slot(obj, SLOT_REGEXP_NAMED_GROUPS, js_mkundef());}
ant_value_t is_regexp_like(ant_t *js, ant_value_t value) { if (!is_object_type(value)) return js_false;
ant_value_t match_sym = js->sym.match_sym; if (vtype(match_sym) == kTypeSymbol) { ant_value_t match_val = js_get_sym(js, value, match_sym); if (is_err(match_val)) return match_val; if (vtype(match_val) != kTypeUndefined) return js_bool(js_truthy(js, match_val)); }
return js_bool(proto_chain_contains(js, value, js->builtins.regexp_proto));}
ant_value_t reject_regexp_arg(ant_t *js, ant_value_t value, const char *method_name) { ant_value_t is_re = is_regexp_like(js, value); if (is_err(is_re)) return is_re; if (js_truthy(js, is_re)) return js_mkerr_typed(js, JS_ERR_TYPE, "First argument to String.prototype.%s must not be a regular expression", method_name); return js_mkundef();}
static ant_value_t regexp_species_construct(ant_t *js, ant_value_t rx, ant_value_t ctor, ant_value_t *ctor_args, int nargs) { ant_value_t seed = Ant_Object_CreateBare(js); if (is_err(seed)) return seed;
ant_value_t proto = js_get(js, ctor, "prototype"); if (is_err(proto)) return proto; if (is_object_type(proto)) js_set_proto_init(seed, proto);
ant_value_t result = sv_vm_call(js->vm, js, ctor, seed, ctor_args, nargs, NULL, ctor);
if (is_err(result)) return result; if (!is_object_type(result)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp species constructor returned non-object");
return result;}
static ant_value_t regexp_exec_abstract(ant_t *js, ant_value_t rx, ant_value_t str);static ant_value_t builtin_regexp_exec(ant_params_t);
static __attribute__((aligned(16)))pcre2_match_context *regex_get_match_context(ant_t *js) { ant_regex_state_t *state = js->regex_state; if (!state) return NULL; if (state->match_ctx) return state->match_ctx;
state->match_ctx = pcre2_match_context_create(NULL); if (!state->match_ctx) return NULL;
state->jit_stack = pcre2_jit_stack_create(32 * 1024, 512 * 1024, NULL); if (state->jit_stack) { pcre2_jit_stack_assign(state->match_ctx, NULL, state->jit_stack); }
return state->match_ctx;}
static uint64_t compiled_regex_key_hash( const char *pattern, size_t pattern_len, uint8_t flags_mask) { return ant_hash_mix( hash_key(pattern, pattern_len) ^ ant_hash_secret[0], ((uint64_t)flags_mask << 32) ^ ant_hash_secret[4] );}
static bool compiled_regex_key_matches( const compiled_regex_cache_entry_t *entry, uint64_t key_hash, const char *pattern, size_t pattern_len, uint8_t flags_mask) { return entry && entry->key_hash == key_hash && entry->flags_mask == flags_mask && entry->pattern_len == pattern_len && memcmp(entry->pattern, pattern, pattern_len) == 0;}
static void compiled_regex_entry_free(compiled_regex_cache_entry_t *entry) { if (!entry) return; ant_shape_release(entry->lastindex_shape); pcre2_match_data_free(entry->scratch_match_data); pcre2_code_free(entry->code); free(entry->pattern); free(entry);}
static void compiled_regex_entry_maybe_free( compiled_regex_cache_entry_t *entry) { if ( entry && entry->object_refs == 0 && entry->active_refs == 0 && entry->cache_refs == 0 ) compiled_regex_entry_free(entry);}
static bool compiled_regex_table_resize( compiled_regex_cache_table_t *table, size_t need) { if (!table || need > REGEX_COMPILED_CACHE_MAX) return false; size_t cap = 64; while (cap < need * 2) cap <<= 1; if (table->cap >= cap) return true;
compiled_regex_cache_entry_t **slots = calloc(cap, sizeof(*slots)); if (!slots) return false;
for (size_t i = 0; i < table->cap; i++) { compiled_regex_cache_entry_t *entry = table->slots[i]; if (!entry) continue; size_t slot = (size_t)entry->key_hash & (cap - 1); while (slots[slot]) slot = (slot + 1) & (cap - 1); slots[slot] = entry; }
free(table->slots); table->slots = slots; table->cap = cap; return true;}
static compiled_regex_cache_entry_t *compiled_regex_table_lookup( const compiled_regex_cache_table_t *table, uint64_t key_hash, const char *pattern, size_t pattern_len, uint8_t flags_mask) { if (!table || !table->slots || table->cap == 0) return NULL; size_t mask = table->cap - 1; size_t slot = (size_t)key_hash & mask; for (size_t probe = 0; probe < table->cap; probe++) { compiled_regex_cache_entry_t *entry = table->slots[slot]; if (!entry) return NULL; if (compiled_regex_key_matches( entry, key_hash, pattern, pattern_len, flags_mask )) return entry; slot = (slot + 1) & mask; } return NULL;}
static bool compiled_regex_table_insert( compiled_regex_cache_table_t *table, compiled_regex_cache_entry_t *entry) { if (!table || !entry || table->count >= REGEX_COMPILED_CACHE_MAX) return false; if ( !table->slots || (table->count + 1) * REGEX_COMPILED_TABLE_LOAD_DEN >= table->cap * REGEX_COMPILED_TABLE_LOAD_NUM ) { if (!compiled_regex_table_resize(table, table->count + 1)) return false; }
size_t mask = table->cap - 1; size_t slot = (size_t)entry->key_hash & mask; for (size_t probe = 0; probe < table->cap; probe++) { compiled_regex_cache_entry_t *existing = table->slots[slot]; if (!existing) { table->slots[slot] = entry; table->count++; return true; } if (existing == entry) return false; slot = (slot + 1) & mask; } return false;}
static void compiled_regex_table_clear( compiled_regex_cache_table_t *table) { if (!table) return; for (size_t i = 0; i < table->cap; i++) { compiled_regex_cache_entry_t *entry = table->slots[i]; if (!entry) continue; if (entry->cache_refs > 0) entry->cache_refs--; compiled_regex_entry_maybe_free(entry); } free(table->slots); memset(table, 0, sizeof(*table));}
static void compiled_regex_cache_rotate(ant_regex_state_t *state) { compiled_regex_table_clear(&state->compiled[1]); state->compiled[1] = state->compiled[0]; memset(&state->compiled[0], 0, sizeof(state->compiled[0]));}
static void compiled_regex_cache_admit_new( ant_regex_state_t *state, compiled_regex_cache_entry_t *entry) { bool admitted = compiled_regex_table_insert(&state->compiled[0], entry); if (!admitted && state->compiled[0].count >= REGEX_COMPILED_CACHE_MAX) { compiled_regex_cache_rotate(state); admitted = compiled_regex_table_insert(&state->compiled[0], entry); } if (admitted) entry->cache_refs++;}
static compiled_regex_cache_entry_t *compiled_regex_cache_lookup( ant_regex_state_t *state, const char *pattern, size_t pattern_len, uint8_t flags_mask, uint64_t key_hash) { compiled_regex_cache_entry_t *entry = compiled_regex_table_lookup( &state->compiled[0], key_hash, pattern, pattern_len, flags_mask ); if (entry) { return entry; }
entry = compiled_regex_table_lookup( &state->compiled[1], key_hash, pattern, pattern_len, flags_mask ); if (!entry) { return NULL; }
if (compiled_regex_table_insert(&state->compiled[0], entry)) { entry->cache_refs++; } return entry;}
static void regexp_keep_required_literal( compiled_regex_cache_entry_t *entry, const char *run, size_t length) { if (length > REGEXP_REQUIRED_LITERAL_MAX) length = REGEXP_REQUIRED_LITERAL_MAX; if (length < 4 || length <= entry->required_literal_len) return; memcpy(entry->required_literal, run, length); entry->required_literal_len = (uint8_t)length;}
static bool regex_source_is_plain_literal(const char *source, ant_offset_t len) { if (len == 0 || len > REGEXP_PLAIN_LITERAL_MAX) return false; for (ant_offset_t i = 0; i < len; i++) { switch (source[i]) { case '\\': case '^': case '$': case '.': case '*': case '+': case '?': case '(': case ')': case '[': case ']': case '{': case '}': case '|': return false; default: break; } } return true;}
static void regexp_find_required_literal(compiled_regex_cache_entry_t *entry) { if (entry->flags_mask & (REGEXP_FLAG_IGNORE_CASE | REGEXP_FLAG_UNICODE_SET)) return; uint32_t first_type = 0; pcre2_pattern_info(entry->code, PCRE2_INFO_FIRSTCODETYPE, &first_type); if (first_type != 0) return;
const char *pattern = entry->pattern; size_t length = entry->pattern_len, run_len = 0; char run[REGEXP_REQUIRED_LITERAL_MAX]; unsigned depth = 0; bool in_class = false; for (size_t i = 0; i < length; i++) { unsigned char c = (unsigned char)pattern[i]; bool literal = false; if (c == '\\') { if (++i == length) goto unsupported; c = (unsigned char)pattern[i]; if (c == 0 || c >= 0x80) goto unsupported; if (strchr("\\^$.*+?()[]{}|/-\"'", c)) literal = true; else if (!strchr("dDsSwWbBfnrtv", c)) goto unsupported; } else if (in_class) { if (c == '[') goto unsupported; if (c == ']') in_class = false; continue; } else if (c == '[') { size_t first = i + 1; if (first < length && pattern[first] == '^') first++; if (first == length || pattern[first] == ']') goto unsupported; in_class = true; } else if (c == '(') { if (i + 1 < length && pattern[i + 1] == '*') goto unsupported; if (i + 1 < length && pattern[i + 1] == '?') { if (i + 2 == length || pattern[i + 2] != ':') goto unsupported; i += 2; } depth++; } else if (c == ')') { if (depth == 0) goto unsupported; depth--; } else if (c == '|') { if (depth == 0) goto unsupported; } else if (c == '*' || c == '+' || c == '?' || c == '{') { // The preceding atom may be optional. Even a required repetition can // safely lose its last literal byte from this conservative candidate. if (run_len) run_len--; if (c == '{') { while (++i < length && pattern[i] != '}') { if ((pattern[i] < '0' || pattern[i] > '9') && pattern[i] != ',') goto unsupported; } if (i == length) goto unsupported; } } else if (c == ']' || c == '}') { goto unsupported; } else { literal = c < 0x80 && c != '.' && c != '^' && c != '$'; }
if (literal && depth == 0 && !in_class) { if (run_len < sizeof(run)) run[run_len] = (char)c; run_len++; } else { regexp_keep_required_literal(entry, run, run_len); run_len = 0; } } if (depth || in_class) goto unsupported; regexp_keep_required_literal(entry, run, run_len); return;
unsupported: entry->required_literal_len = 0;}
static compiled_regex_cache_entry_t *compiled_regex_cache_get_or_compile( ant_regex_state_t *state, const char *pattern, size_t pattern_len, uint8_t flags_mask) { uint64_t key_hash = compiled_regex_key_hash( pattern, pattern_len, flags_mask ); compiled_regex_cache_entry_t *cached = compiled_regex_cache_lookup( state, pattern, pattern_len, flags_mask, key_hash ); if (cached) return cached;
char pcre2_pattern_stack[4096]; js_cstr_t pcre2_pattern = js_to_pcre2_pattern_cstr( pattern, pattern_len, pcre2_pattern_stack, sizeof(pcre2_pattern_stack), (flags_mask & REGEXP_FLAG_UNICODE_SET) != 0 ); if (!pcre2_pattern.ptr) return NULL;
uint32_t options = PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_UNSET_BACKREF | PCRE2_DUPNAMES; if (flags_mask & REGEXP_FLAG_IGNORE_CASE) options |= PCRE2_CASELESS; if (flags_mask & REGEXP_FLAG_MULTILINE) options |= PCRE2_MULTILINE; if (flags_mask & REGEXP_FLAG_DOTALL) options |= PCRE2_DOTALL;
int errcode; PCRE2_SIZE erroffset; pcre2_compile_context *compile_ctx = pcre2_compile_context_create(NULL); if (compile_ctx) pcre2_set_newline(compile_ctx, PCRE2_NEWLINE_ANYCRLF); pcre2_code *re = pcre2_compile( (PCRE2_SPTR)pcre2_pattern.ptr, pcre2_pattern.len, options, &errcode, &erroffset, compile_ctx ); if (compile_ctx) pcre2_compile_context_free(compile_ctx); if (pcre2_pattern.needs_free) free((void *)pcre2_pattern.ptr); if (re == NULL) return NULL;
compiled_regex_cache_entry_t *entry = calloc(1, sizeof(compiled_regex_cache_entry_t)); if (!entry) { pcre2_code_free(re); return NULL; }
char *pattern_copy = malloc(pattern_len ? pattern_len : 1); if (!pattern_copy) { pcre2_code_free(re); free(entry); return NULL; } if (pattern_len) memcpy(pattern_copy, pattern, pattern_len);
entry->pattern = pattern_copy; entry->pattern_len = pattern_len; entry->key_hash = key_hash; entry->flags_mask = flags_mask; entry->plain_literal = regex_source_is_plain_literal(pattern, pattern_len); entry->code = re; pcre2_pattern_info(re, PCRE2_INFO_NAMECOUNT, &entry->namecount); entry->jit_ready = pcre2_jit_compile(re, PCRE2_JIT_COMPLETE) == 0; regexp_find_required_literal(entry);
compiled_regex_cache_admit_new(state, entry); return entry;}
static compiled_regex_cache_entry_t *compiled_regex_cache_acquire( ant_t *js, const char *pattern, size_t pattern_len, uint8_t flags_mask) { ant_regex_state_t *state = js->regex_state; if (!state) return NULL; compiled_regex_cache_entry_t *entry = compiled_regex_cache_get_or_compile( state, pattern, pattern_len, flags_mask ); if (entry) entry->active_refs++; return entry;}
static void compiled_regex_cache_release( compiled_regex_cache_entry_t *entry) { if (!entry) return; ANT_ASSERT(entry->active_refs > 0, "active compiled regexp reference underflow"); entry->active_refs--; compiled_regex_entry_maybe_free(entry);}
static bool regex_match_scope_begin( compiled_regex_cache_entry_t *entry, regex_match_scope_t *scope) { memset(scope, 0, sizeof(*scope)); scope->compiled = entry; if (!entry) return false;
if (!entry->scratch_in_use) { if (!entry->scratch_match_data) entry->scratch_match_data = pcre2_match_data_create_from_pattern(entry->code, NULL); if (!entry->scratch_match_data) return false; entry->scratch_in_use = true; scope->match_data = entry->scratch_match_data; scope->borrowed = true; return true; }
scope->match_data = pcre2_match_data_create_from_pattern(entry->code, NULL); if (!scope->match_data) return false; return true;}
static void regex_match_scope_end(regex_match_scope_t *scope) { if (!scope || !scope->match_data) return; if (scope->borrowed) { scope->compiled->scratch_in_use = false; } else { pcre2_match_data_free(scope->match_data); } scope->match_data = NULL;}
static void regexp_object_compiled_release( ant_t *js, ant_value_t regexp, compiled_regex_cache_entry_t *compiled) { if (!compiled) return; js_clear_native(regexp, REGEXP_NATIVE_TAG); ant_regex_state_t *state = js->regex_state; if (state && state->live_object_data > 0) state->live_object_data--; if (compiled && compiled->object_refs > 0) compiled->object_refs--; compiled_regex_entry_maybe_free(compiled);}
static void regexp_object_finalize(ant_t *js, ant_object_t *obj) { ant_value_t regexp = js_obj_from_ptr(obj); compiled_regex_cache_entry_t *compiled = js_get_native(regexp, REGEXP_NATIVE_TAG); regexp_object_compiled_release(js, regexp, compiled);}
static void regexp_object_compiled_detach(ant_t *js, ant_value_t regexp) { compiled_regex_cache_entry_t *compiled = js_get_native(regexp, REGEXP_NATIVE_TAG); regexp_object_compiled_release(js, regexp, compiled);}
static bool regexp_source_pattern(ant_t *js, ant_value_t regexp_obj, const char **pattern_ptr, ant_offset_t *pattern_len) { ant_value_t source_val = js_get_slot(regexp_obj, SLOT_DATA); if (vtype(source_val) != kTypeString) return false;
ant_offset_t poff = vstr(js, source_val, pattern_len); *pattern_ptr = (const char *)(uintptr_t)poff; return true;}
static bool regexp_compile_shared_from_object( ant_t *js, ant_value_t regexp_obj, uint8_t flags_mask, compiled_regex_cache_entry_t **out) { ant_offset_t plen; const char *pattern_ptr; if (!regexp_source_pattern(js, regexp_obj, &pattern_ptr, &plen)) return false;
ant_regex_state_t *state = js->regex_state; if (!state) return false; compiled_regex_cache_entry_t *compiled = compiled_regex_cache_get_or_compile( state, pattern_ptr, plen, flags_mask ); if (!compiled) return false; *out = compiled; return true;}
static compiled_regex_cache_entry_t *regex_get_or_compile( ant_t *js, ant_value_t regexp_obj, uint8_t flags_mask, compiled_regex_cache_entry_t *compiled) { ant_regex_state_t *state = js->regex_state; if (!state) return NULL; ant_object_t *obj_ptr = js_obj_ptr(regexp_obj);
if (!compiled) { compiled = js_get_native(regexp_obj, REGEXP_NATIVE_TAG); } if (compiled && compiled->flags_mask == flags_mask) { return compiled; } if (compiled) regexp_object_compiled_release(js, regexp_obj, compiled);
if (!regexp_compile_shared_from_object( js, regexp_obj, flags_mask, &compiled )) return NULL;
compiled->object_refs++; ant_value_t groups_meta = regexp_build_named_groups_meta(js, compiled->code); if (is_err(groups_meta)) { compiled->object_refs--; compiled_regex_entry_maybe_free(compiled); return NULL; }
if (obj_ptr->finalizer && obj_ptr->finalizer != regexp_object_finalize) { compiled->object_refs--; compiled_regex_entry_maybe_free(compiled); return NULL; }
js_set_native(regexp_obj, compiled, REGEXP_NATIVE_TAG); if (js_get_native(regexp_obj, REGEXP_NATIVE_TAG) != compiled) { compiled->object_refs--; compiled_regex_entry_maybe_free(compiled); return NULL; } js_set_finalizer(regexp_obj, regexp_object_finalize); state->live_object_data++; js_set_slot(regexp_obj, SLOT_REGEXP_NAMED_GROUPS, groups_meta); return compiled;}
static bool regexp_has_internal_slots(ant_t *js, ant_value_t value) { if (!is_object_type(value)) return false; ant_object_t *obj = js_obj_ptr(value); return obj && obj->flags.regexp_brand;}
static ant_value_t regexp_escape_source(ant_t *js, ant_value_t pattern) { size_t len = 0; const char *src = js_getstr(js, pattern, &len); if (!src) return pattern; if (len == 0) return ANT_STRING("(?:)");
bool needs_escape = false; for (size_t i = 0; i < len && !needs_escape; i++) { unsigned char c = (unsigned char)src[i]; needs_escape = c == '/' || c == '\n' || c == '\r' || c == 0xe2; } if (!needs_escape) return pattern;
char *out = malloc(len * 2); if (!out) return js_mkerr(js, "out of memory"); size_t n = 0; bool in_class = false;
for (size_t i = 0; i < len; i++) { unsigned char c = (unsigned char)src[i]; if (c == '\\' && i + 1 < len) { out[n++] = src[i++]; out[n++] = src[i]; } else if (c == '/' && !in_class) { out[n++] = '\\'; out[n++] = '/'; } else if (c == '\n' || c == '\r') { out[n++] = '\\'; out[n++] = c == '\n' ? 'n' : 'r'; } else if (c == 0xe2 && i + 2 < len && (unsigned char)src[i + 1] == 0x80 && ((unsigned char)src[i + 2] == 0xa8 || (unsigned char)src[i + 2] == 0xa9)) { memcpy(out + n, (unsigned char)src[i + 2] == 0xa8 ? "\\u2028" : "\\u2029", 6); n += 6; i += 2; } else { if (c == '[') in_class = true; else if (c == ']') in_class = false; out[n++] = (char)c; } }
ant_value_t escaped = js_mkstr(js, out, n); free(out); return escaped;}
static ant_value_t builtin_RegExp(ant_params_t) { bool pattern_is_regexp = false; if (nargs > 0) { ant_value_t is_re = is_regexp_like(js, args[0]); if (is_err(is_re)) return is_re; pattern_is_regexp = js_truthy(js, is_re); }
if (vtype(call_new_target) == kTypeUndefined && nargs > 0 && pattern_is_regexp) { if (nargs < 2 || vtype(args[1]) == kTypeUndefined) { ant_value_t ctor = js_getprop_fallback(js, args[0], "constructor"); if (is_err(ctor)) return ctor; if (same_ctor_identity(js, ctor, js->builtins.regexp_ctor_value)) return args[0]; } }
ant_value_t regexp_obj = js->this_val; bool use_this = (vtype(call_new_target) != kTypeUndefined && vtype(regexp_obj) == kTypeObject);
if (!use_this) { regexp_obj = mkobj(js, 0); if (is_err(regexp_obj)) return regexp_obj; }
ant_value_t regexp_proto = js->builtins.regexp_proto; ant_value_t instance_proto = js_instance_proto_from_new_target(js, regexp_proto, call_new_target);
if (is_object_type(instance_proto)) js_set_proto_init(regexp_obj, instance_proto); if (vtype(call_new_target) == kTypeFunction || vtype(call_new_target) == kTypeBuiltin) { js_set_slot(regexp_obj, SLOT_CTOR, call_new_target); }
ant_value_t pattern = js_mkstr(js, "", 0); ant_value_t flags = js_mkstr(js, "", 0); if (nargs > 0) { if (pattern_is_regexp) { ant_value_t src = js_getprop_fallback(js, args[0], "source"); if (is_err(src)) return src; pattern = js_tostring_val(js, src); if (is_err(pattern)) return pattern; if (nargs >= 2 && vtype(args[1]) != kTypeUndefined) { flags = js_tostring_val(js, args[1]); } else { ant_value_t fl = js_getprop_fallback(js, args[0], "flags"); if (is_err(fl)) return fl; flags = js_tostring_val(js, fl); } if (is_err(flags)) return flags; } else if (vtype(args[0]) == kTypeString) { pattern = args[0]; if (nargs > 1 && vtype(args[1]) == kTypeString) flags = args[1]; } else if (vtype(args[0]) != kTypeUndefined) { ant_value_t s = js_tostring_val(js, args[0]); if (is_err(s)) return s; pattern = s; if (nargs > 1 && vtype(args[1]) == kTypeString) flags = args[1]; } }
js_set_slot(regexp_obj, SLOT_DATA, pattern); regexp_init_flags(js, regexp_obj, flags, true);
return regexp_obj;}
static ant_value_t builtin_regexp_groups_getter(ant_params_t) { ant_value_t result_arr = js->this_val; if (!is_object_type(result_arr)) return js_mkundef();
ant_value_t cached = js_get_slot(result_arr, SLOT_REGEXP_GROUPS_CACHE); if (is_object_type(cached)) return cached;
ant_value_t meta = js_get_slot(result_arr, SLOT_REGEXP_RESULT_GROUPS); if (!is_object_type(meta)) return js_mkundef();
ant_value_t groups = Ant_Object_CreateBare(js); if (is_err(groups)) return groups; js_set_proto_init(groups, js_mknull());
for (ant_offset_t i = 0; ; i += 2) { ant_value_t name = js_arr_get(js, meta, i); if (vtype(name) == kTypeUndefined) break; ant_value_t index_val = js_arr_get(js, meta, i + 1); ant_offset_t index = (vtype(index_val) == kTypeNumber) ? (ant_offset_t)tod(index_val) : 0; char idxstr[16]; (void)uint_to_str(idxstr, sizeof(idxstr), (uint64_t)index); ant_value_t value = js_getprop_fallback(js, result_arr, idxstr); ant_offset_t name_len, name_off = vstr(js, name, &name_len); ant_value_t status = setprop_cstr(js, groups, (const char *)(uintptr_t)name_off, (size_t)name_len, value); if (is_err(status)) return status; }
js_set_slot(result_arr, SLOT_REGEXP_GROUPS_CACHE, groups); return groups;}
static ant_value_t regexp_build_indices_pair(ant_t *js, PCRE2_SIZE start, PCRE2_SIZE end) { if (start == PCRE2_UNSET) return js_mkundef();
ant_value_t pair = js_mkarr(js); if (is_err(pair)) return pair; js_arr_push(js, pair, tov((double)start)); js_arr_push(js, pair, tov((double)end)); return pair;}
static ant_value_t regexp_build_indices_groups( ant_t *js, ant_value_t groups_meta, ant_value_t indices_arr) { ant_value_t groups = Ant_Object_CreateBare(js); if (is_err(groups)) return groups; js_set_proto_init(groups, js_mknull());
for (ant_offset_t i = 0; ; i += 2) { ant_value_t name = js_arr_get(js, groups_meta, i); if (vtype(name) == kTypeUndefined) break; ant_value_t index_val = js_arr_get(js, groups_meta, i + 1); ant_offset_t index = (vtype(index_val) == kTypeNumber) ? (ant_offset_t)tod(index_val) : 0; char idxstr[16]; (void)uint_to_str(idxstr, sizeof(idxstr), (uint64_t)index); ant_value_t value = js_getprop_fallback(js, indices_arr, idxstr); ant_offset_t name_len, name_off = vstr(js, name, &name_len); ant_value_t status = setprop_cstr(js, groups, (const char *)(uintptr_t)name_off, (size_t)name_len, value); if (is_err(status)) return status; }
return groups;}
static ant_value_t regexp_build_indices_result( ant_t *js, ant_value_t regexp, PCRE2_SIZE *ovector, uint32_t ovcount) { ant_value_t indices_arr = js_mkarr(js); if (is_err(indices_arr)) return indices_arr;
for (uint32_t i = 0; i < ovcount; i++) { ant_value_t pair = regexp_build_indices_pair(js, ovector[2*i], ovector[2*i+1]); if (is_err(pair)) return pair; js_arr_push(js, indices_arr, pair); }
ant_value_t groups_meta = js_get_slot(regexp, SLOT_REGEXP_NAMED_GROUPS); if (is_object_type(groups_meta)) { ant_value_t groups = regexp_build_indices_groups(js, groups_meta, indices_arr); if (is_err(groups)) return groups; if (is_err(setprop_cstr(js, indices_arr, "groups", 6, groups))) return js_mkerr(js, "oom"); } else if (is_err(setprop_cstr(js, indices_arr, "groups", 6, js_mkundef()))) return js_mkerr(js, "oom");
return indices_arr;}
static ant_value_t regexp_attach_exec_result(ant_t *js,const regexp_exec_result_ctx_t *ctx) { bool has_named_groups = is_object_type(ctx->groups_meta); if (!has_named_groups && regexp_result_apply_shape( js, ctx->array, ctx->index, ctx->input, js_mkundef(), ctx->indices, ctx->with_indices )) return ctx->array;
if ( is_err(setprop_cstr(js, ctx->array, "index", 5, ctx->index)) || is_err(setprop_cstr(js, ctx->array, "input", 5, ctx->input)) ) return js_mkerr(js, "oom");
if (has_named_groups) { js_set_slot(ctx->array, SLOT_REGEXP_RESULT_GROUPS, ctx->groups_meta); js_set_slot(ctx->array, SLOT_REGEXP_GROUPS_CACHE, js_mkundef()); js_set_getter_desc( js, js_as_obj(ctx->array), "groups", 6, Ant_Function_Create(builtin_regexp_groups_getter), JS_DESC_E | JS_DESC_C ); } else if (is_err(setprop_cstr(js, ctx->array, "groups", 6, js_mkundef()))) return js_mkerr(js, "oom");
if ( ctx->with_indices && is_err(setprop_cstr(js, ctx->array, "indices", 7, ctx->indices)) ) return js_mkerr(js, "oom");
return ctx->array;}
static const char *find_bytes(const char *haystack, ant_offset_t haystack_len, const char *needle, ant_offset_t needle_len);static bool regexp_plain_literal_pattern( ant_t *js, ant_value_t rx, uint8_t flags_mask, compiled_regex_cache_entry_t *compiled, const char **pattern_ptr, ant_offset_t *pattern_len);
static ant_value_t regexp_exec_plain_literal_fast( ant_t *js, ant_value_t regexp, ant_value_t str_arg, const char *str_ptr, ant_offset_t str_len, uint8_t flags_mask, compiled_regex_cache_entry_t *compiled, bool truthy_only, bool *used_fast_path) { *used_fast_path = false;
const char *needle; ant_offset_t needle_len; if (!regexp_plain_literal_pattern(js, regexp, flags_mask, compiled, &needle, &needle_len)) return js_mkundef(); const char *match = find_bytes(str_ptr, str_len, needle, needle_len);
*used_fast_path = true; if (!match) return js_mknull();
PCRE2_SIZE ovector[2]; ovector[0] = (PCRE2_SIZE)(match - str_ptr); ovector[1] = ovector[0] + (PCRE2_SIZE)needle_len; update_regexp_statics(js, str_arg, ovector, 1);
if (truthy_only) return js_true;
ant_value_t result_arr = js_mkarr(js); if (is_err(result_arr)) return result_arr;
ant_value_t match_str = js_mkstr_byte_range(js, str_ptr, (size_t)(match - str_ptr), needle_len); if (is_err(match_str)) return match_str; js_arr_push(js, result_arr, match_str);
return regexp_attach_plain_exec_result( js, result_arr, tov((double)byte_offset_to_utf16(str_ptr, ovector[0])), str_arg );}
static bool regex_subject_can_skip_utf_check(const char *str, size_t len) { if (str_is_valid_utf8(str)) return true;
static _Thread_local struct { const char *str; size_t len; uint64_t epoch; bool valid; } cache;
uint64_t epoch = gc_strings_sweep_epoch(); if (cache.str == str && cache.len == len && cache.epoch == epoch) return cache.valid;
bool valid = wtf8_validate_bytes(str, len); cache.str = str; cache.len = len; cache.epoch = epoch; cache.valid = valid;
return valid;}
static __attribute__((always_inline)) inline int compiled_regex_run_in_scope( ant_t *js, compiled_regex_cache_entry_t *compiled, const char *str_ptr, ant_offset_t str_len, PCRE2_SIZE start_offset, bool sticky, regex_match_scope_t *scope, PCRE2_SIZE **ovector, uint32_t *ovcount) { *ovector = NULL; *ovcount = 0;
if (compiled->required_literal_len && str_len >= 128 && start_offset <= str_len && !find_bytes(str_ptr + start_offset, str_len - start_offset, compiled->required_literal, compiled->required_literal_len)) return PCRE2_ERROR_NOMATCH;
pcre2_match_context *match_ctx = regex_get_match_context(js); uint32_t match_options = sticky ? PCRE2_ANCHORED : 0; int rc; if (__builtin_expect(compiled->jit_ready && !sticky, 1)) { rc = pcre2_jit_match( compiled->code, (PCRE2_SPTR)str_ptr, str_len, start_offset, match_options, scope->match_data, match_ctx ); } else { if (regex_subject_can_skip_utf_check(str_ptr, (size_t)str_len)) match_options |= PCRE2_NO_UTF_CHECK; rc = pcre2_match( compiled->code, (PCRE2_SPTR)str_ptr, str_len, start_offset, match_options, scope->match_data, match_ctx ); }
if (rc >= 0) { *ovector = pcre2_get_ovector_pointer(scope->match_data); *ovcount = pcre2_get_ovector_count(scope->match_data); } return rc;}
static __attribute__((always_inline)) inline int compiled_regex_run( ant_t *js, compiled_regex_cache_entry_t *compiled, const char *str_ptr, ant_offset_t str_len, PCRE2_SIZE start_offset, bool sticky, regex_match_scope_t *scope, PCRE2_SIZE **ovector, uint32_t *ovcount) { if (!regex_match_scope_begin(compiled, scope)) { *ovector = NULL; *ovcount = 0; return PCRE2_ERROR_NOMEMORY; } return compiled_regex_run_in_scope( js, compiled, str_ptr, str_len, start_offset, sticky, scope, ovector, ovcount );}
static const ant_shape_prop_t *regexp_lastindex_lookup_own_property( ant_t *js, ant_object_t *obj, uint32_t *out_slot) { if (!obj || !obj->shape) return NULL;
const char *key = intern_string(js, "lastIndex", 9); if (!key) return NULL; int32_t found = ant_shape_lookup_interned(obj->shape, key); if (found < 0 || (uint32_t)found >= obj->prop_count) return NULL;
uint32_t slot = (uint32_t)found; if (out_slot) *out_slot = slot; return ant_shape_prop_at(obj->shape, slot);}
static bool regexp_lastindex_is_writable(ant_t *js, ant_value_t value) { ant_object_t *obj = js_obj_ptr(value); if (!obj) return false;
const ant_shape_prop_t *prop = regexp_lastindex_lookup_own_property(js, obj, NULL); if (!prop) return true;
if (prop->has_getter || prop->has_setter) return false; return (prop->attrs & ANT_PROP_ATTR_WRITABLE) != 0;}
static bool regexp_can_use_internal_fast_path(ant_t *js, ant_value_t value) { return is_object_type(value) && !is_proxy(value) && regexp_has_internal_slots(js, value);}
static bool regexp_lastindex_fast_location( compiled_regex_cache_entry_t *compiled, ant_value_t regexp, ant_object_t **out_obj, uint32_t *out_slot) { if (!compiled || !compiled->lastindex_shape || is_proxy(regexp)) return false; ant_object_t *obj = js_obj_ptr(regexp); if ( !obj || obj->flags.is_exotic || obj->shape != compiled->lastindex_shape || compiled->lastindex_slot >= obj->prop_count ) return false; *out_obj = obj; *out_slot = compiled->lastindex_slot; return true;}
static void regexp_lastindex_cache_location( ant_t *js, compiled_regex_cache_entry_t *compiled, ant_value_t regexp) { if (!compiled || is_proxy(regexp)) return; ant_object_t *obj = js_obj_ptr(regexp); if (!obj || obj->flags.is_exotic || !obj->shape) return; if ( compiled->lastindex_shape == obj->shape && compiled->lastindex_slot < obj->prop_count ) return;
uint32_t slot; const ant_shape_prop_t *prop = regexp_lastindex_lookup_own_property( js, obj, &slot ); if ( !prop || prop->has_getter || prop->has_setter || !(prop->attrs & ANT_PROP_ATTR_WRITABLE) ) return;
if (compiled->lastindex_shape != obj->shape) { ant_shape_retain(obj->shape); ant_shape_release(compiled->lastindex_shape); compiled->lastindex_shape = obj->shape; } compiled->lastindex_slot = slot;}
static ant_value_t regexp_set_lastindex( ant_t *js, compiled_regex_cache_entry_t *compiled, ant_value_t regexp, ant_value_t value) { ant_object_t *obj; uint32_t slot; if (regexp_lastindex_fast_location( compiled, regexp, &obj, &slot )) { ant_object_prop_set_unchecked(obj, slot, value); gc_write_barrier(js, obj, value); return value; }
ant_object_t *receiver = js_obj_ptr(regexp); if (!is_proxy(regexp)) { const ant_shape_prop_t *prop = regexp_lastindex_lookup_own_property( js, receiver, NULL ); if ( prop && !prop->has_getter && !prop->has_setter && !(prop->attrs & ANT_PROP_ATTR_WRITABLE) ) { return js_mkerr_typed( js, JS_ERR_TYPE, "Cannot assign to read only property 'lastIndex'" ); } } return setprop_cstr(js, regexp, "lastIndex", 9, value);}
static ant_value_t regexp_exec_shared_fast( ant_t *js, ant_value_t regexp, ant_value_t str_arg, const char *str_ptr, ant_offset_t str_len, uint8_t flags_mask, compiled_regex_cache_entry_t *compiled_hint, bool global_flag, bool sticky_flag, PCRE2_SIZE start_offset, bool truthy_only, bool *used_fast_path) { *used_fast_path = false; if (global_flag || sticky_flag || (flags_mask & REGEXP_FLAG_HAS_INDICES)) return js_mkundef();
ant_value_t literal_result = regexp_exec_plain_literal_fast( js, regexp, str_arg, str_ptr, str_len, flags_mask, compiled_hint, truthy_only, used_fast_path ); if (is_err(literal_result) || *used_fast_path) return literal_result; compiled_regex_cache_entry_t *compiled = regex_get_or_compile(js, regexp, flags_mask, compiled_hint); if (!compiled) return js_mkundef(); if (!truthy_only && compiled->namecount != 0) return js_mkundef();
regex_match_scope_t match_scope; PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run( js, compiled, str_ptr, str_len, start_offset, sticky_flag, &match_scope, &ovector, &ovcount );
*used_fast_path = true; if (rc < 0) { regex_match_scope_end(&match_scope); return js_mknull(); }
update_regexp_statics(js, str_arg, ovector, ovcount);
if (truthy_only) { regex_match_scope_end(&match_scope); return js_true; }
ant_value_t result; ant_value_t result_arr = js_mkarr(js); if (is_err(result_arr)) { result = result_arr; goto done; } for (uint32_t i = 0; i < ovcount; i++) { PCRE2_SIZE start = ovector[2*i]; PCRE2_SIZE end = ovector[2*i+1]; if (start == PCRE2_UNSET) { js_arr_push(js, result_arr, js_mkundef()); } else { ant_value_t match_str = js_mkstr_byte_range(js, str_ptr, start, end - start); if (is_err(match_str)) { result = match_str; goto done; } js_arr_push(js, result_arr, match_str); } }
result = regexp_attach_plain_exec_result( js, result_arr, tov((double)byte_offset_to_utf16(str_ptr, ovector[0])), str_arg );
done: regex_match_scope_end(&match_scope); return result;}
static __attribute__((aligned(16)))ant_value_t regexp_exec_internal(ant_t *js, ant_value_t regexp, ant_value_t str_arg, bool truthy_only) { ant_offset_t str_len, str_off = vstr(js, str_arg, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off); compiled_regex_cache_entry_t *compiled_hint; uint8_t flags_mask = regexp_flags_mask( js, regexp, &compiled_hint ); bool global_flag = (flags_mask & REGEXP_FLAG_GLOBAL) != 0; bool has_indices = (flags_mask & REGEXP_FLAG_HAS_INDICES) != 0; bool sticky_flag = (flags_mask & REGEXP_FLAG_STICKY) != 0;
// TODO: reduce nesting PCRE2_SIZE start_offset = 0; if (global_flag || sticky_flag) { ant_object_t *lastindex_obj; uint32_t lastindex_slot; ant_value_t li_val; if (regexp_lastindex_fast_location( compiled_hint, regexp, &lastindex_obj, &lastindex_slot )) { li_val = ant_object_prop_get_unchecked(lastindex_obj, lastindex_slot); } else { ant_prop_loc_t lastindex_off = lkp(js, regexp, "lastIndex", 9); li_val = lastindex_off.obj ? js_prop_load(lastindex_off) : js_mkundef(); } if (vtype(li_val) == kTypeNumber) { double li = tod(li_val); if (li >= 0 && li <= (double)str_len) start_offset = (PCRE2_SIZE)li; else { ant_value_t stored = regexp_set_lastindex( js, compiled_hint, regexp, tov(0) ); if (is_err(stored)) return stored; return js_mknull(); } } }
bool used_fast_path = false; ant_value_t fast_result = regexp_exec_shared_fast( js, regexp, str_arg, str_ptr, str_len, flags_mask, compiled_hint, global_flag, sticky_flag, start_offset, truthy_only, &used_fast_path ); if (is_err(fast_result) || used_fast_path) return fast_result;
compiled_regex_cache_entry_t *compiled = regex_get_or_compile(js, regexp, flags_mask, compiled_hint); if (!compiled) return js_mknull(); regexp_lastindex_cache_location(js, compiled, regexp);
regex_match_scope_t match_scope; PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run( js, compiled, str_ptr, str_len, start_offset, sticky_flag, &match_scope, &ovector, &ovcount );
if (rc < 0) { regex_match_scope_end(&match_scope); if (global_flag || sticky_flag) { ant_value_t stored = regexp_set_lastindex( js, compiled, regexp, tov(0) ); if (is_err(stored)) return stored; } return js_mknull(); }
update_regexp_statics(js, str_arg, ovector, ovcount);
if (global_flag || sticky_flag) { ant_value_t next_idx = tov((double)ovector[1]); ant_value_t stored = regexp_set_lastindex( js, compiled, regexp, next_idx ); if (is_err(stored)) { regex_match_scope_end(&match_scope); return stored; } }
if (truthy_only) { regex_match_scope_end(&match_scope); return js_true; }
ant_value_t result; ant_value_t groups_meta = js_get_slot(regexp, SLOT_REGEXP_NAMED_GROUPS); ant_value_t result_arr = js_mkarr(js); if (is_err(result_arr)) { result = result_arr; goto done; } for (uint32_t i = 0; i < ovcount; i++) { PCRE2_SIZE start = ovector[2*i]; PCRE2_SIZE end = ovector[2*i+1]; if (start == PCRE2_UNSET) { js_arr_push(js, result_arr, js_mkundef()); } else { ant_value_t match_str = js_mkstr_byte_range(js, str_ptr, start, end - start); if (is_err(match_str)) { result = match_str; goto done; } js_arr_push(js, result_arr, match_str); } }
ant_value_t indices = js_mkundef(); if (has_indices) { indices = regexp_build_indices_result(js, regexp, ovector, ovcount); if (is_err(indices)) { result = indices; goto done; } }
result = regexp_attach_exec_result(js, &(regexp_exec_result_ctx_t) { .array = result_arr, .index = tov((double)byte_offset_to_utf16(str_ptr, ovector[0])), .input = str_arg, .groups_meta = groups_meta, .indices = indices, .with_indices = has_indices, });
done: regex_match_scope_end(&match_scope); return result;}
static ant_value_t builtin_regexp_exec(ant_params_t) { ant_value_t regexp = js->this_val; if (!regexp_has_internal_slots(js, regexp)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype.exec called on incompatible receiver");
ant_value_t str_arg = nargs > 0 ? coerce_to_str(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str_arg)) return str_arg;
return regexp_exec_internal(js, regexp, str_arg, false);}
static ant_value_t builtin_regexp_toString(ant_params_t) { ant_value_t regexp = js->this_val; if (!is_object_type(regexp)) return js_mkerr_typed(js, JS_ERR_TYPE, "toString called on non-object");
GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, regexp);
ant_value_t source_val = js_getprop_fallback(js, regexp, "source"); if (is_err(source_val)) { GC_ROOT_RESTORE(js, root_mark); return source_val; } ant_value_t source_str = js_tostring_val(js, source_val); GC_ROOT_PIN(js, source_str); if (is_err(source_str)) { GC_ROOT_RESTORE(js, root_mark); return source_str; }
ant_value_t flags_val = js_getprop_fallback(js, regexp, "flags"); if (is_err(flags_val)) { GC_ROOT_RESTORE(js, root_mark); return flags_val; } ant_value_t flags_str = js_tostring_val(js, flags_val); GC_ROOT_PIN(js, flags_str); if (is_err(flags_str)) { GC_ROOT_RESTORE(js, root_mark); return flags_str; }
ant_offset_t src_len, src_off = vstr(js, source_str, &src_len); ant_offset_t fl_len, fl_off = vstr(js, flags_str, &fl_len);
if ((size_t)fl_len > SIZE_MAX - 2 || (size_t)src_len > SIZE_MAX - (size_t)fl_len - 2) { ant_value_t err = js_mkerr(js, "string too large"); GC_ROOT_RESTORE(js, root_mark); return err; } size_t total = (size_t)src_len + (size_t)fl_len + 2; ant_value_t result = js_mkstr(js, NULL, total); GC_ROOT_PIN(js, result); if (is_err(result)) { GC_ROOT_RESTORE(js, root_mark); return result; }
ant_flat_string_t *flat = (ant_flat_string_t *)vptr(result); src_off = vstr(js, source_str, &src_len); fl_off = vstr(js, flags_str, &fl_len); size_t n = 0; flat->bytes[n++] = '/'; memcpy(flat->bytes + n, (const void *)(uintptr_t)src_off, (size_t)src_len); n += (size_t)src_len; flat->bytes[n++] = '/'; memcpy(flat->bytes + n, (const void *)(uintptr_t)fl_off, (size_t)fl_len); n += (size_t)fl_len; flat->bytes[n] = '\0'; str_flat_init_meta(flat, str_detect_ascii_bytes(flat->bytes, n));
GC_ROOT_RESTORE(js, root_mark); return result;}
static ant_value_t builtin_regexp_compile(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "compile called on non-object");
ant_value_t pattern = js_mkstr(js, "", 0); ant_value_t flags = js_mkstr(js, "", 0);
if (nargs > 0 && vtype(args[0]) != kTypeUndefined) { ant_value_t is_re = is_regexp_like(js, args[0]); if (is_err(is_re)) return is_re; if (js_truthy(js, is_re)) { ant_value_t src = js_getprop_fallback(js, args[0], "source"); if (is_err(src)) return src; pattern = js_tostring_val(js, src); if (is_err(pattern)) return pattern; ant_value_t fl = js_getprop_fallback(js, args[0], "flags"); if (is_err(fl)) return fl; flags = js_tostring_val(js, fl); if (is_err(flags)) return flags; } else { pattern = js_tostring_val(js, args[0]); if (is_err(pattern)) return pattern; } } if (nargs > 1 && vtype(args[1]) != kTypeUndefined) { flags = js_tostring_val(js, args[1]); if (is_err(flags)) return flags; }
js_set_slot(rx, SLOT_DATA, pattern); regexp_init_flags(js, rx, flags, false); regexp_object_compiled_detach(js, rx);
return rx;}
static inline bool is_syntax_char(char c) { return c == '^' || c == '$' || c == '\\' || c == '.' || c == '*' || c == '+' || c == '?' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '|' || c == '/';}
static inline bool is_other_punctuator(char c) { return c == ',' || c == '-' || c == ':' || c == ';' || c == '<' || c == '=' || c == '>' || c == '@' || c == '!' || c == '"' || c == '#' || c == '%' || c == '&' || c == '\'' || c == '`' || c == '~';}
static ant_value_t builtin_regexp_escape(ant_params_t) { if (nargs < 1 || vtype(args[0]) != kTypeString) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.escape requires a string argument");
ant_offset_t slen, soff = vstr(js, args[0], &slen); const char *src = (const char *)(uintptr_t)(soff); if ((size_t)slen > (SIZE_MAX - 1) / 6) return js_mkerr(js, "string too large"); size_t capacity = (size_t)slen * 6 + 1; char static_buf[256]; bool needs_free = capacity > sizeof(static_buf); char *buf = needs_free ? (char *)calloc(1, capacity) : static_buf; if (!buf) return js_mkerr(js, "oom");
static const char hex[] = "0123456789abcdef"; size_t di = 0; bool first = true;
for (size_t si = 0; si < slen; ) { unsigned char c = (unsigned char)src[si];
if (c >= 0x80) { utf8proc_int32_t cp; int bytes = (int)utf8_next( (const utf8proc_uint8_t *)&src[si], (utf8proc_ssize_t)(slen - si), &cp ); if (bytes <= 0) bytes = 1; if ((size_t)bytes > (size_t)slen - si) bytes = (int)((size_t)slen - si); memcpy(buf + di, src + si, (size_t)bytes); di += (size_t)bytes; si += (size_t)bytes; first = false; continue; }
bool short_escape = c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; bool hex_escape = (first && ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) || is_other_punctuator(c) || c == ' ';
if (short_escape) { buf[di++] = '\\'; switch (c) { case '\t': buf[di++] = 't'; break; case '\n': buf[di++] = 'n'; break; case '\v': buf[di++] = 'v'; break; case '\f': buf[di++] = 'f'; break; default: buf[di++] = 'r'; break; } } else if (hex_escape) { buf[di++] = '\\'; buf[di++] = 'x'; buf[di++] = hex[c >> 4]; buf[di++] = hex[c & 0x0F]; } else if (is_syntax_char(c)) { buf[di++] = '\\'; buf[di++] = (char)c; } else buf[di++] = (char)c;
si++; first = false; }
ant_value_t result = js_mkstr(js, buf, di); if (needs_free) free(buf); return result;}
static ant_value_t regexp_exec_with_exec_fn(ant_t *js, ant_value_t rx, ant_value_t str, ant_value_t exec_fn) { if (vtype(exec_fn) == kTypeFunction || vtype(exec_fn) == kTypeBuiltin) { ant_value_t call_args[1] = { str }; ant_value_t result = sv_vm_call(js->vm, js, exec_fn, rx, call_args, 1, NULL, js_mkundef()); if (is_err(result)) return result; if (!is_object_type(result) && vtype(result) != kTypeNull) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp exec returned non-object"); return result; }
ant_value_t call_args[1] = { str }; return sv_call_native_this(js, builtin_regexp_exec, rx, call_args, 1);}
static ant_value_t regexp_exec_abstract(ant_t *js, ant_value_t rx, ant_value_t str) { ant_value_t exec_fn = js_getprop_fallback(js, rx, "exec"); if (is_err(exec_fn)) return exec_fn; return regexp_exec_with_exec_fn(js, rx, str, exec_fn);}
bool regexp_exec_truthy_try_fast( ant_t *js, ant_value_t call_func, ant_value_t regexp, ant_value_t arg, ant_value_t *out_result) { if (!out_result || vtype(call_func) != kTypeBuiltin) return false; if (!js_cfunc_same_entrypoint(call_func, builtin_regexp_exec)) return false; if (!is_object_type(regexp) || vtype(arg) != kTypeString) return false;
ant_value_t result = regexp_exec_internal(js, regexp, arg, true); if (is_err(result)) { *out_result = result; return true; }
*out_result = mkval(kTypeBool, vtype(result) != kTypeNull ? 1 : 0); return true;}
static ant_value_t builtin_regexp_test(ant_params_t) { ant_value_t regexp = js->this_val; if (!is_object_type(regexp)) return js_mkerr_typed(js, JS_ERR_TYPE, "test called on non-object"); ant_value_t str_arg = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str_arg)) return str_arg; ant_value_t exec_fn = js_getprop_fallback(js, regexp, "exec"); if (is_err(exec_fn)) return exec_fn;
ant_value_t result; if (vtype(exec_fn) == kTypeBuiltin && js_cfunc_same_entrypoint(exec_fn, builtin_regexp_exec)) { result = regexp_exec_internal(js, regexp, str_arg, true); } else result = regexp_exec_with_exec_fn(js, regexp, str_arg, exec_fn); if (is_err(result)) return result; return mkval(kTypeBool, vtype(result) != kTypeNull ? 1 : 0);}
static inline bool regexp_is_proto(ant_t *js, ant_value_t value) { return is_object_type(value) && vdata(js_as_obj(value)) == vdata(js_as_obj(js->builtins.regexp_proto));}
static ant_value_t regexp_flag_getter(ant_t *js, ant_value_t rx, uint8_t bit, const char *name) { if (regexp_has_internal_slots(js, rx)) return js_bool((regexp_flags_mask(js, rx, NULL) & bit) != 0); if (regexp_is_proto(js, rx)) return js_mkundef(); return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype.%s getter called on incompatible receiver", name);}
#define REGEXP_FLAG(id, name, ch) \ static ant_value_t builtin_regexp_get_##name(ant_params_t) { \ return regexp_flag_getter(js, js->this_val, REGEXP_FLAG_##id, #name); \ }#include "modules/regex_flags.h"
typedef struct { const char *name; uint8_t len; uint8_t bit; char ch; ant_cfunc_t getter;} regexp_flag_info_t;
static const regexp_flag_info_t regexp_flag_info[REGEXP_FLAG_COUNT] = {#define REGEXP_FLAG(id, name, ch) { #name, sizeof(#name) - 1, REGEXP_FLAG_##id, ch, builtin_regexp_get_##name },#include "modules/regex_flags.h"};
static bool regexp_flag_getter_is_builtin(ant_t *js, const ant_shape_t *own_shape, const regexp_flag_info_t *flag) { const char *key = intern_string(js, flag->name, flag->len); if (!key || (own_shape && ant_shape_lookup_interned(own_shape, key) >= 0)) return false; ant_object_t *proto = js_obj_ptr(js->builtins.regexp_proto); if (!proto || !proto->shape || proto->flags.is_exotic) return false; int32_t slot = ant_shape_lookup_interned(proto->shape, key); const ant_shape_prop_t *prop = slot >= 0 ? ant_shape_prop_at(proto->shape, (uint32_t)slot) : NULL; return prop && prop->has_getter && js_cfunc_same_entrypoint(prop->getter, flag->getter);}
static bool regexp_shape_flags_pristine(ant_t *js, const ant_shape_t *own_shape) { ant_regex_state_t *state = js->regex_state; if (!state) return false; if ( state->pristine_flags_epoch == js->ic.epoch && state->pristine_flags_shape == own_shape && state->pristine_flags_realm == js->realm ) return true; for (int i = 0; i < REGEXP_FLAG_COUNT; i++) if (!regexp_flag_getter_is_builtin(js, own_shape, ®exp_flag_info[i])) return false; state->pristine_flags_epoch = js->ic.epoch; state->pristine_flags_shape = own_shape; state->pristine_flags_realm = js->realm; return true;}
static bool regexp_flags_pristine(ant_t *js, ant_value_t rx) { if (!regexp_has_internal_slots(js, rx)) return false; ant_object_t *obj = js_obj_ptr(rx); return !obj->flags.is_exotic && js_get_proto(js, rx) == js->builtins.regexp_proto && regexp_shape_flags_pristine(js, obj->shape);}
static ant_value_t regexp_read_flag(ant_t *js, ant_value_t rx, int index) { const regexp_flag_info_t *flag = ®exp_flag_info[index]; if (regexp_flags_pristine(js, rx)) return js_bool((regexp_flags_mask(js, rx, NULL) & flag->bit) != 0); return js_getprop_fallback(js, rx, flag->name);}
ant_value_t regexp_source_value(ant_t *js, ant_value_t rx) { ant_value_t pattern = js_get_slot(rx, SLOT_DATA); return vtype(pattern) == kTypeString ? regexp_escape_source(js, pattern) : ANT_STRING("(?:)");}
static ant_value_t builtin_regexp_source_getter(ant_params_t) { ant_value_t rx = js->this_val; if (regexp_has_internal_slots(js, rx)) return regexp_source_value(js, rx); if (regexp_is_proto(js, rx)) return ANT_STRING("(?:)"); return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype.source getter called on incompatible receiver");}
static ant_value_t builtin_regexp_flags_getter(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype.flags called on non-object"); if (regexp_flags_pristine(js, rx)) return js_get_slot(rx, SLOT_REGEXP_FLAGS_STRING);
char buf[REGEXP_FLAG_COUNT]; int n = 0; for (int i = 0; i < REGEXP_FLAG_COUNT; i++) { ant_value_t v = regexp_read_flag(js, rx, i); if (is_err(v)) return v; if (js_truthy(js, v)) buf[n++] = regexp_flag_info[i].ch; }
return js_mkstr(js, buf, n);}
static bool regexp_has_builtin_exec(ant_t *js, ant_value_t rx) { ant_prop_loc_t exec_loc = lkp_proto(js, rx, "exec", 4); if ( !exec_loc.obj || !exec_loc.obj->shape || exec_loc.obj->flags.is_exotic || exec_loc.slot >= exec_loc.obj->prop_count ) { return false; }
const ant_shape_prop_t *exec_prop = ant_shape_prop_at( exec_loc.obj->shape, exec_loc.slot ); if (!exec_prop || exec_prop->has_getter || exec_prop->has_setter) { return false; }
ant_value_t exec_fn = js_prop_load(exec_loc); return vtype(exec_fn) == kTypeBuiltin && js_cfunc_same_entrypoint(exec_fn, builtin_regexp_exec);}
static bool regexp_prepare_builtin_exec( ant_t *js, ant_value_t rx, uint8_t required_flags, compiled_regex_cache_entry_t **compiled_out, uint8_t *flags_out, ant_object_t **lastindex_obj_out, uint32_t *lastindex_slot_out) { ant_regex_state_t *state = js->regex_state; if ( !state || !regexp_can_use_internal_fast_path(js, rx) ) { goto reject; }
if (!regexp_has_builtin_exec(js, rx)) goto reject;
compiled_regex_cache_entry_t *compiled_hint; uint8_t flags = regexp_flags_mask(js, rx, &compiled_hint); if ((flags & required_flags) != required_flags) { goto reject; }
compiled_regex_cache_entry_t *compiled = regex_get_or_compile( js, rx, flags, compiled_hint ); if (!compiled) { goto reject; }
regexp_lastindex_cache_location(js, compiled, rx); ant_object_t *lastindex_obj; uint32_t lastindex_slot; if (!regexp_lastindex_fast_location( compiled, rx, &lastindex_obj, &lastindex_slot )) { goto reject; }
*compiled_out = compiled; if (flags_out) *flags_out = flags; if (lastindex_obj_out) *lastindex_obj_out = lastindex_obj; if (lastindex_slot_out) *lastindex_slot_out = lastindex_slot; return true;
reject: return false;}
static bool regexp_prepare_batch_builtin_exec(ant_t *js, ant_value_t rx, compiled_regex_cache_entry_t **compiled_out, uint8_t *flags_out) { return regexp_prepare_builtin_exec(js, rx, REGEXP_FLAG_GLOBAL, compiled_out, flags_out, NULL, NULL);}
static inline PCRE2_SIZE regexp_advance_empty_match(const char *str_ptr, ant_offset_t str_len, PCRE2_SIZE offset) { if (offset < (PCRE2_SIZE)str_len) return offset + (PCRE2_SIZE)utf8_char_len_at(str_ptr, str_len, (ant_offset_t)offset); return offset + 1;}
static ant_value_t regexp_match_batch_fast( ant_t *js, ant_value_t rx, ant_value_t str, bool *used_fast_path) { *used_fast_path = false;
compiled_regex_cache_entry_t *compiled; uint8_t flags; if (!regexp_prepare_batch_builtin_exec(js, rx, &compiled, &flags)) return js_mkundef();
*used_fast_path = true;
ant_value_t stored = regexp_set_lastindex(js, compiled, rx, tov(0)); if (is_err(stored)) return stored;
ant_value_t matches = js_mkarr(js); if (is_err(matches)) return matches;
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; bool sticky = (flags & REGEXP_FLAG_STICKY) != 0; PCRE2_SIZE offset = 0; ant_offset_t count = 0;
regex_match_scope_t scope; if (regex_match_scope_begin(compiled, &scope)) { while (offset <= (PCRE2_SIZE)str_len) { PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run_in_scope( js, compiled, str_ptr, str_len, offset, sticky, &scope, &ovector, &ovcount ); if (rc < 0) break;
PCRE2_SIZE start = ovector[0]; PCRE2_SIZE end = ovector[1]; update_regexp_statics(js, str, ovector, ovcount); ant_value_t match = js_mkstr_byte_range(js, str_ptr, start, end - start); if (is_err(match)) { regex_match_scope_end(&scope); return match; } js_arr_push(js, matches, match); count++;
offset = end; if (start == end) offset = regexp_advance_empty_match(str_ptr, str_len, offset); } } regex_match_scope_end(&scope); stored = regexp_set_lastindex(js, compiled, rx, tov(0)); if (is_err(stored)) return stored; return count == 0 ? js_mknull() : matches;}
static ant_value_t builtin_regexp_symbol_match(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@match] called on non-object");
ant_value_t str = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str)) return str;
ant_value_t global_val = regexp_read_flag(js, rx, REGEXP_FLAG_INDEX_GLOBAL); if (is_err(global_val)) return global_val;
if (!js_truthy(js, global_val)) return regexp_exec_abstract(js, rx, str);
ant_value_t unicode_val = regexp_read_flag(js, rx, REGEXP_FLAG_INDEX_UNICODE); if (is_err(unicode_val)) return unicode_val;
bool used_fast_path = false; ant_value_t fast = regexp_match_batch_fast(js, rx, str, &used_fast_path); if (is_err(fast) || used_fast_path) return fast;
js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), tov(0));
ant_value_t A = js_mkarr(js); if (is_err(A)) return A; ant_offset_t n = 0;
for (;;) { ant_value_t result = regexp_exec_abstract(js, rx, str); if (is_err(result)) return result; if (vtype(result) == kTypeNull) return n == 0 ? js_mknull() : mkval(kTypeArray, vdata(A));
ant_value_t match_str = js_tostring_val(js, js_arr_get(js, result, 0)); if (is_err(match_str)) return match_str; js_arr_push(js, A, match_str); n++;
ant_offset_t mlen; vstr(js, match_str, &mlen); if (mlen == 0) { ant_value_t li_val = js_getprop_fallback(js, rx, "lastIndex"); if (is_err(li_val)) return li_val; double li = vtype(li_val) == kTypeNumber ? tod(li_val) : 0; ant_offset_t str_len, str_off = vstr(js, str, &str_len); double advance = 1; if (li >= 0 && li < (double)str_len) advance = (double)utf8_char_len_at((const char *)(uintptr_t)(str_off), str_len, (ant_offset_t)li); js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), tov(li + advance)); } }}
static ant_value_t regexp_matchall_next(ant_params_t) { ant_value_t iter = js->this_val; ant_value_t rx = js_get_slot(iter, SLOT_MATCHALL_RX); ant_value_t str = js_get_slot(iter, SLOT_MATCHALL_STR); ant_value_t done_val = js_get_slot(iter, SLOT_MATCHALL_DONE); if (js_truthy(js, done_val)) return js_iter_result(js, false, js_mkundef());
ant_value_t result = regexp_exec_abstract(js, rx, str); if (is_err(result)) return result;
if (vtype(result) == kTypeNull) { js_set_slot(iter, SLOT_MATCHALL_DONE, js_true); return js_iter_result(js, false, js_mkundef()); }
ant_value_t global_val = regexp_read_flag(js, rx, REGEXP_FLAG_INDEX_GLOBAL); if (js_truthy(js, global_val)) { ant_value_t match_str = js_tostring_val(js, js_arr_get(js, result, 0)); if (is_err(match_str)) return match_str; ant_offset_t mlen; vstr(js, match_str, &mlen); if (mlen == 0) { ant_value_t li_val = js_getprop_fallback(js, rx, "lastIndex"); double li = vtype(li_val) == kTypeNumber ? tod(li_val) : 0; ant_offset_t sl, so = vstr(js, str, &sl); double advance = 1; if (li >= 0 && li < (double)sl) advance = (double)utf8_char_len_at((const char *)(uintptr_t)so, sl, (ant_offset_t)li); js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), tov(li + advance)); } } else js_set_slot(iter, SLOT_MATCHALL_DONE, js_true);
return js_iter_result(js, true, result);}
static ant_value_t builtin_regexp_symbol_matchAll(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@matchAll] called on non-object");
ant_value_t str = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str)) return str;
ant_value_t flags_val = js_getprop_fallback(js, rx, "flags"); if (is_err(flags_val)) return flags_val; ant_value_t flags_str = js_tostring_val(js, flags_val); if (is_err(flags_str)) return flags_str;
ant_value_t source_val = js_getprop_fallback(js, rx, "source"); if (is_err(source_val)) return source_val;
ant_value_t ctor_args[2] = { source_val, flags_str }; ant_value_t regexp_ctor = js->builtins.regexp_ctor_value; ant_value_t new_rx = sv_vm_call(js->vm, js, regexp_ctor, js_mkundef(), ctor_args, 2, NULL, regexp_ctor); if (is_err(new_rx)) return new_rx;
ant_value_t li_val = js_getprop_fallback(js, rx, "lastIndex"); js_setprop(js, new_rx, js_mkstr(js, "lastIndex", 9), li_val);
ant_value_t iter = Ant_Object_CreateBare(js); js_set_slot(iter, SLOT_MATCHALL_RX, new_rx); js_set_slot(iter, SLOT_MATCHALL_STR, str); js_set_slot(iter, SLOT_MATCHALL_DONE, js_false);
js_set_proto_init(iter, js->builtins.regexp_matchall_iter_proto_val);
return iter;}
static ant_value_t builtin_string_matchAll(ant_params_t) { ant_value_t str = Ant_String_CoerceThis(js, "matchAll"); if (is_err(str)) return str; if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "matchAll requires at least 1 argument");
if (is_object_type(args[0])) { ant_value_t is_re = is_regexp_like(js, args[0]); if (js_truthy(js, is_re)) { ant_value_t flags_val = js_getprop_fallback(js, args[0], "flags"); if (is_err(flags_val)) return flags_val; ant_value_t flags_str = js_tostring_val(js, flags_val); ant_offset_t flen, foff = vstr(js, flags_str, &flen); const char *fp = (const char *)(uintptr_t)(foff); bool has_g = false; for (ant_offset_t i = 0; i < flen; i++) if (fp[i] == 'g') has_g = true; if (!has_g) return js_mkerr_typed(js, JS_ERR_TYPE, "String.prototype.matchAll called with a non-global RegExp"); }
bool called = false; ant_value_t call_args[1] = { str }; ant_value_t dispatched = maybe_call_symbol_method( js, args[0], js->sym.matchAll_sym, args[0], call_args, 1, &called ); if (is_err(dispatched)) return dispatched; if (called) return dispatched; }
ant_value_t pattern_str = js_tostring_val(js, args[0]); if (is_err(pattern_str)) return pattern_str;
ant_value_t ctor_args[2] = { pattern_str, js_mkstr(js, "g", 1) }; ant_value_t regexp_ctor = js->builtins.regexp_ctor_value; ant_value_t rx = sv_vm_call(js->vm, js, regexp_ctor, js_mkundef(), ctor_args, 2, NULL, regexp_ctor); if (is_err(rx)) return rx;
ant_value_t ma_args[1] = { str }; return sv_call_native_this(js, builtin_regexp_symbol_matchAll, rx, ma_args, 1);}
static __attribute__((noinline)) const char *find_bytes_long( const char *haystack, size_t haystack_len, const char *needle, size_t needle_len) { size_t prefix_inline[128]; size_t *prefix = NULL; if (needle_len <= sizeof(prefix_inline) / sizeof(prefix_inline[0])) prefix = prefix_inline; else if (needle_len <= SIZE_MAX / sizeof(*prefix)) prefix = malloc(needle_len * sizeof(*prefix)); if (!prefix) { size_t last = haystack_len - needle_len; for (size_t i = 0; i <= last; i++) { if ( haystack[i] == needle[0] && memcmp(haystack + i, needle, needle_len) == 0 ) return haystack + i; } return NULL; }
prefix[0] = 0; for (size_t i = 1, matched = 0; i < needle_len; i++) { while (matched && needle[i] != needle[matched]) matched = prefix[matched - 1]; if (needle[i] == needle[matched]) matched++; prefix[i] = matched; }
const char *result = NULL; for (size_t i = 0, matched = 0; i < haystack_len; i++) { while (matched && haystack[i] != needle[matched]) matched = prefix[matched - 1]; if (haystack[i] == needle[matched]) matched++; if (matched == needle_len) { result = haystack + i + 1 - needle_len; break; } }
if (prefix != prefix_inline) free(prefix); return result;}
static const char *find_bytes(const char *haystack, ant_offset_t haystack_len, const char *needle, ant_offset_t needle_len) { if (needle_len == 0 || needle_len > haystack_len) return NULL; if (needle_len > REGEXP_PLAIN_LITERAL_MAX) { return find_bytes_long( haystack, (size_t)haystack_len, needle, (size_t)needle_len ); }
const char *end = haystack + haystack_len - needle_len + 1; if (needle_len > 1) { const char *last = memchr(haystack + needle_len - 1, (unsigned char)needle[needle_len - 1], (size_t)(end - haystack)); if (!last) return NULL; haystack = last - needle_len + 1; } while (haystack < end) { const char *match = memchr(haystack, (unsigned char)needle[0], (size_t)(end - haystack)); if (!match) return NULL; if (needle_len == 1 || memcmp(match, needle, needle_len) == 0) return match; haystack = match + 1; } return NULL;}
static bool str_buf_append(char **buf, size_t *len, size_t *cap, const char *data, size_t n);
static bool replacement_has_substitution(ant_t *js, ant_value_t replacement) { ant_offset_t len, off = vstr(js, replacement, &len); const char *ptr = (const char *)(uintptr_t)off; for (ant_offset_t i = 0; i < len; i++) { if (ptr[i] == '$') return true; } return false;}
static bool regexp_plain_literal_pattern( ant_t *js, ant_value_t rx, uint8_t flags_mask, compiled_regex_cache_entry_t *compiled, const char **pattern_ptr, ant_offset_t *pattern_len) { if (flags_mask & (REGEXP_FLAG_IGNORE_CASE | REGEXP_FLAG_STICKY | REGEXP_FLAG_UNICODE_SET)) return false; if (compiled && compiled->flags_mask == flags_mask) { if (!compiled->plain_literal || !regexp_can_use_internal_fast_path(js, rx)) return false; *pattern_ptr = compiled->pattern; *pattern_len = compiled->pattern_len; return true; } if (!regexp_can_use_internal_fast_path(js, rx)) return false; if (!regexp_source_pattern(js, rx, pattern_ptr, pattern_len)) return false; return regex_source_is_plain_literal(*pattern_ptr, *pattern_len);}
static ant_value_t regexp_replace_plain_literal_fast( ant_t *js, ant_value_t rx, ant_value_t str, ant_value_t replace_str, bool global, bool *used_fast_path) { *used_fast_path = false;
if (!regexp_can_use_internal_fast_path(js, rx)) return js_mkundef();
compiled_regex_cache_entry_t *compiled; uint8_t flags_mask = regexp_flags_mask(js, rx, &compiled); const char *needle; ant_offset_t needle_len; if ( !regexp_plain_literal_pattern(js, rx, flags_mask, compiled, &needle, &needle_len) || !regexp_lastindex_is_writable(js, rx) || !regexp_has_builtin_exec(js, rx) ) return js_mkundef();
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; ant_offset_t repl_len, repl_off = vstr(js, replace_str, &repl_len); const char *repl_ptr = (const char *)(uintptr_t)repl_off;
const char *first = find_bytes(str_ptr, str_len, needle, needle_len); *used_fast_path = true; if (!first) return str;
size_t cap = str_len + repl_len + 256; char *buf = malloc(cap); if (!buf) return js_mkerr(js, "oom");
size_t len = 0; const char *scan = str_ptr; const char *end = str_ptr + str_len; const char *match = first; PCRE2_SIZE ovector[2];
for (;;) { if (!str_buf_append(&buf, &len, &cap, scan, (size_t)(match - scan)) || !str_buf_append(&buf, &len, &cap, repl_ptr, repl_len)) { free(buf); return js_mkerr(js, "oom"); }
ovector[0] = (PCRE2_SIZE)(match - str_ptr); ovector[1] = ovector[0] + (PCRE2_SIZE)needle_len; update_regexp_statics(js, str, ovector, 1);
scan = match + needle_len; if (!global) break; match = find_bytes(scan, (ant_offset_t)(end - scan), needle, needle_len); if (!match) break; }
if (!str_buf_append(&buf, &len, &cap, scan, (size_t)(end - scan))) { free(buf); return js_mkerr(js, "oom"); }
if (global && is_err(setprop_cstr(js, rx, "lastIndex", 9, tov(0)))) { free(buf); return js_mkerr(js, "oom"); }
ant_value_t ret = js_mkstr(js, buf, len); free(buf); return ret;}
static ant_value_t regexp_replace_batch_fast( ant_t *js, ant_value_t rx, ant_value_t str, ant_value_t replacement, bool global, bool literal_replacement, bool *used_fast_path) { *used_fast_path = false;
compiled_regex_cache_entry_t *compiled; uint8_t flags; if (!regexp_prepare_builtin_exec( js, rx, global ? REGEXP_FLAG_GLOBAL : 0, &compiled, &flags, NULL, NULL)) return js_mkundef(); if (((flags & REGEXP_FLAG_GLOBAL) != 0) != global || (!global && (flags & REGEXP_FLAG_STICKY) != 0)) return js_mkundef();
*used_fast_path = true;
ant_value_t stored = js_mkundef(); if (global) { stored = regexp_set_lastindex(js, compiled, rx, tov(0)); if (is_err(stored)) return stored; }
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; ant_offset_t replacement_len, replacement_off = vstr(js, replacement, &replacement_len); const char *replacement_ptr = (const char *)(uintptr_t)replacement_off;
size_t buf_cap = 0; char *buf = NULL; size_t buf_len = 0;
bool sticky = (flags & REGEXP_FLAG_STICKY) != 0; PCRE2_SIZE offset = 0; PCRE2_SIZE next_src_pos = 0; size_t match_count = 0;
regex_match_scope_t scope; if (regex_match_scope_begin(compiled, &scope)) { while (offset <= (PCRE2_SIZE)str_len) { PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run_in_scope( js, compiled, str_ptr, str_len, offset, sticky, &scope, &ovector, &ovcount ); if (rc < 0) break;
if (!buf) { buf_cap = (size_t)str_len + 256; buf = malloc(buf_cap); if (!buf) { regex_match_scope_end(&scope); return js_mkerr(js, "oom"); } }
PCRE2_SIZE start = ovector[0]; PCRE2_SIZE end = ovector[1]; if ( start > next_src_pos && !str_buf_append( &buf, &buf_len, &buf_cap, str_ptr + next_src_pos, start - next_src_pos ) ) { regex_match_scope_end(&scope); free(buf); return js_mkerr(js, "oom"); }
bool replaced; if (literal_replacement) { update_regexp_statics(js, str, ovector, ovcount); replaced = str_buf_append( &buf, &buf_len, &buf_cap, replacement_ptr, replacement_len ); } else { int capture_count = ovcount > 1 ? (int)(ovcount - 1) : 0; repl_capture_t captures_inline[REGEXP_INLINE_CAPTURES - 1]; repl_capture_t *captures = capture_count < REGEXP_INLINE_CAPTURES ? captures_inline : malloc(sizeof(*captures) * (size_t)capture_count); if (capture_count >= REGEXP_INLINE_CAPTURES && !captures) { regex_match_scope_end(&scope); free(buf); return js_mkerr(js, "oom"); } for (int i = 0; i < capture_count; i++) { PCRE2_SIZE capture_start = ovector[2 * (i + 1)]; PCRE2_SIZE capture_end = ovector[2 * (i + 1) + 1]; captures[i] = capture_start == PCRE2_UNSET ? (repl_capture_t){ NULL, 0 } : (repl_capture_t){ str_ptr + capture_start, (size_t)(capture_end - capture_start) }; }
update_regexp_statics(js, str, ovector, ovcount); replaced = repl_template( replacement_ptr, replacement_len, str_ptr + start, end - start, str_ptr, str_len, start, captures, capture_count, &buf, &buf_len, &buf_cap ); if (captures != captures_inline) free(captures); } if (!replaced) { regex_match_scope_end(&scope); free(buf); return js_mkerr(js, "oom"); }
match_count++; next_src_pos = end; if (!global) break; offset = end; if (start == end) offset = regexp_advance_empty_match(str_ptr, str_len, offset); } } regex_match_scope_end(&scope);
if (global) { stored = regexp_set_lastindex(js, compiled, rx, tov(0)); if (is_err(stored)) { free(buf); return stored; } } if (match_count == 0) return str;
if ( next_src_pos < (PCRE2_SIZE)str_len && !str_buf_append( &buf, &buf_len, &buf_cap, str_ptr + next_src_pos, (size_t)str_len - next_src_pos ) ) { free(buf); return js_mkerr(js, "oom"); }
ant_value_t result = js_mkstr(js, buf, buf_len); free(buf); return result;}
static ant_value_t regexp_search_plain_literal_fast( ant_t *js, ant_value_t rx, ant_value_t str, bool *used_fast_path) { *used_fast_path = false;
if (!regexp_can_use_internal_fast_path(js, rx)) return js_mkundef();
compiled_regex_cache_entry_t *compiled; uint8_t flags_mask = regexp_flags_mask(js, rx, &compiled); const char *needle; ant_offset_t needle_len; if ( !regexp_plain_literal_pattern(js, rx, flags_mask, compiled, &needle, &needle_len) || !regexp_lastindex_is_writable(js, rx) || !regexp_has_builtin_exec(js, rx) ) return js_mkundef();
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; const char *match = find_bytes(str_ptr, str_len, needle, needle_len);
*used_fast_path = true; if (!match) return tov(-1);
PCRE2_SIZE ovector[2]; ovector[0] = (PCRE2_SIZE)(match - str_ptr); ovector[1] = ovector[0] + (PCRE2_SIZE)needle_len; update_regexp_statics(js, str, ovector, 1); return tov((double)byte_offset_to_utf16(str_ptr, ovector[0]));}
static bool regexp_literal_exec_builtin_guard(ant_t *js) { if (!js->regexp.write_guard_armed || js->regexp.exec_written) return false;
ant_value_t exec_fn; if (!js_try_get_own_data_prop(js, js->builtins.regexp_proto, "exec", 4, &exec_fn)) return false; return vtype(exec_fn) == kTypeBuiltin && js_cfunc_same_entrypoint(exec_fn, builtin_regexp_exec);}
static bool regexp_literal_exec_fast_parts( ant_t *js, ant_value_t pattern, ant_value_t flags, const char **needle, ant_offset_t *needle_len) { if (vtype(pattern) != kTypeString || vtype(flags) != kTypeString) return false;
ant_offset_t flags_len, flags_off = vstr(js, flags, &flags_len); uint8_t flags_mask = regexp_parse_flags_mask((const char *)(uintptr_t)flags_off, flags_len); if ( flags_mask & (REGEXP_FLAG_HAS_INDICES | REGEXP_FLAG_IGNORE_CASE | REGEXP_FLAG_STICKY | REGEXP_FLAG_UNICODE_SET) ) return false;
ant_offset_t pattern_off = vstr(js, pattern, needle_len); *needle = (const char *)(uintptr_t)pattern_off; return regex_source_is_plain_literal(*needle, *needle_len);}
ant_value_t regexp_create_literal(ant_t *js, ant_value_t pattern, ant_value_t flags) { ant_value_t regexp_obj = mkobj(js, 0); if (is_err(regexp_obj)) return regexp_obj;
ant_value_t regexp_proto = js->builtins.regexp_proto; if (is_object_type(regexp_proto)) js_set_proto_init(regexp_obj, regexp_proto);
js_set_slot(regexp_obj, SLOT_DATA, pattern); regexp_init_flags(js, regexp_obj, flags, true); return regexp_obj;}
ant_value_t regexp_literal_exec_call( ant_t *js, ant_value_t pattern, ant_value_t flags, ant_value_t arg) { if ( vtype(arg) == kTypeString && regexp_literal_exec_builtin_guard(js) ) { const char *needle; ant_offset_t needle_len; if (regexp_literal_exec_fast_parts(js, pattern, flags, &needle, &needle_len)) { ant_offset_t str_len, str_off = vstr(js, arg, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; const char *match = find_bytes(str_ptr, str_len, needle, needle_len); if (!match) return js_mknull();
PCRE2_SIZE ovector[2]; ovector[0] = (PCRE2_SIZE)(match - str_ptr); ovector[1] = ovector[0] + (PCRE2_SIZE)needle_len; update_regexp_statics(js, arg, ovector, 1);
ant_value_t result_arr = js_mkarr(js); if (is_err(result_arr)) return result_arr;
ant_value_t match_str = js_mkstr_byte_range(js, str_ptr, (size_t)(match - str_ptr), needle_len); if (is_err(match_str)) return match_str; js_arr_push(js, result_arr, match_str);
return regexp_attach_plain_exec_result( js, result_arr, tov((double)byte_offset_to_utf16(str_ptr, ovector[0])), arg ); } }
ant_value_t regexp_obj = regexp_create_literal(js, pattern, flags); if (is_err(regexp_obj)) return regexp_obj; return regexp_exec_abstract(js, regexp_obj, arg);}
static ant_value_t builtin_regexp_symbol_replace(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@replace] called on non-object");
ant_value_t str = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str)) return str; ant_value_t replace_value = nargs > 1 ? args[1] : js_mkundef(); bool func_replace = (vtype(replace_value) == kTypeFunction || vtype(replace_value) == kTypeBuiltin); ant_value_t replace_str = js_mkundef(); if (!func_replace) { replace_str = js_tostring_val(js, replace_value); if (is_err(replace_str)) return replace_str; }
ant_value_t global_val = regexp_read_flag(js, rx, REGEXP_FLAG_INDEX_GLOBAL); if (is_err(global_val)) return global_val; bool global = js_truthy(js, global_val);
if (global) { ant_value_t unicode_val = regexp_read_flag(js, rx, REGEXP_FLAG_INDEX_UNICODE); if (is_err(unicode_val)) return unicode_val; ant_value_t stored = regexp_set_lastindex( js, js_get_native(rx, REGEXP_NATIVE_TAG), rx, tov(0) ); if (is_err(stored)) return stored; }
bool literal_replacement = !func_replace && !replacement_has_substitution(js, replace_str); if (literal_replacement) { bool used_fast_path = false; ant_value_t fast = regexp_replace_plain_literal_fast(js, rx, str, replace_str, global, &used_fast_path); if (is_err(fast) || used_fast_path) return fast; }
if (!func_replace) { bool used_fast_path = false; ant_value_t fast = regexp_replace_batch_fast( js, rx, str, replace_str, global, literal_replacement, &used_fast_path ); if (is_err(fast) || used_fast_path) return fast; }
ant_value_t results = js_mkarr(js); if (is_err(results)) return results; ant_offset_t nresults = 0;
for (;;) { ant_value_t result = regexp_exec_abstract(js, rx, str); if (is_err(result)) return result; if (vtype(result) == kTypeNull) break; js_arr_push(js, results, result); nresults++; if (!global) break;
ant_value_t match_str = js_tostring_val(js, js_arr_get(js, result, 0)); if (is_err(match_str)) return match_str; ant_offset_t mlen; vstr(js, match_str, &mlen); if (mlen == 0) { ant_value_t li_val = js_getprop_fallback(js, rx, "lastIndex"); if (is_err(li_val)) return li_val; double li = vtype(li_val) == kTypeNumber ? tod(li_val) : 0; ant_offset_t sl, so = vstr(js, str, &sl); double advance = 1; if (li >= 0 && li < (double)sl) advance = (double)utf8_char_len_at((const char *)(uintptr_t)(so), sl, (ant_offset_t)li); js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), tov(li + advance)); } }
ant_offset_t str_len, str_off = vstr(js, str, &str_len); ant_offset_t str_units_len = str_utf16_len(js, str); size_t buf_cap = str_len + 256; char *buf = malloc(buf_cap); if (!buf) return js_mkerr(js, "oom"); size_t buf_len = 0; ant_offset_t next_src_pos = 0;
#define SB_APPEND(data, dlen) do { \ if (buf_len + (dlen) >= buf_cap) { \ buf_cap = (buf_len + (dlen) + 1) * 2; \ char *nb = realloc(buf, buf_cap); \ if (!nb) { free(buf); return js_mkerr(js, "oom"); } \ buf = nb; \ } \ memcpy(buf + buf_len, data, dlen); buf_len += (dlen); \} while(0)
for (ant_offset_t i = 0; i < nresults; i++) { ant_value_t result = js_arr_get(js, results, i); ant_value_t matched = js_tostring_val(js, js_arr_get(js, result, 0)); if (is_err(matched)) { free(buf); return matched; }
ant_value_t pos_val = js_getprop_fallback(js, result, "index"); if (is_err(pos_val)) { free(buf); return pos_val; } if (is_object_type(pos_val) || vtype(pos_val) == kTypeBuiltin) { pos_val = js_to_primitive(js, pos_val, 2); if (is_err(pos_val)) { free(buf); return pos_val; } } if (vtype(pos_val) == kTypeBigInt || vtype(pos_val) == kTypeSymbol) { free(buf); return js_mkerr_typed( js, JS_ERR_TYPE, "Cannot convert a %s value to a number", vtype(pos_val) == kTypeSymbol ? "Symbol" : "BigInt"); }
double position_units = js_to_number(js, pos_val); position_units = isnan(position_units) ? 0 : trunc(position_units); if (position_units < 0) position_units = 0; if (position_units > (double)str_units_len) position_units = (double)str_units_len;
str_off = vstr(js, str, &str_len); int position_bytes = utf16_index_to_byte_offset( (const char *)(uintptr_t)str_off, (size_t)str_len, (size_t)position_units, NULL ); ant_offset_t position = position_bytes < 0 ? str_len : (ant_offset_t)position_bytes;
ant_value_t replacement; if (func_replace) { ant_offset_t ncaptures = js_arr_len(js, result); if (ncaptures > INT_MAX - 2) { free(buf); return js_mkerr(js, "too many regexp captures"); } int call_capacity = (int)ncaptures + 2; ant_value_t call_args_inline[REGEXP_INLINE_CAPTURES]; ant_value_t *call_args = call_capacity <= REGEXP_INLINE_CAPTURES ? call_args_inline : malloc(sizeof(*call_args) * (size_t)call_capacity); if (!call_args) { free(buf); return js_mkerr(js, "oom"); } int ca = 0; for (ant_offset_t c = 0; c < ncaptures; c++) call_args[ca++] = js_arr_get(js, result, c); call_args[ca++] = tov(position_units); call_args[ca++] = str; replacement = sv_vm_call(js->vm, js, replace_value, js_mkundef(), call_args, ca, NULL, js_mkundef()); if (call_args != call_args_inline) free(call_args); } else { replacement = replace_str; } if (is_err(replacement)) { free(buf); return replacement; } ant_value_t rep_str = js_tostring_val(js, replacement); if (is_err(rep_str)) { free(buf); return rep_str; }
if (position >= next_src_pos) { str_off = vstr(js, str, &str_len); if (position > next_src_pos) SB_APPEND((const char *)(uintptr_t)(str_off + next_src_pos), position - next_src_pos); ant_offset_t rep_len, rep_off = vstr(js, rep_str, &rep_len); if (func_replace) { SB_APPEND((const char *)(uintptr_t)(rep_off), rep_len); } else { ant_offset_t ncap = js_arr_len(js, result); int num_caps = ncap > 1 ? (int)(ncap - 1) : 0; repl_capture_t caps_buf[16], *caps = num_caps <= 16 ? caps_buf : malloc(sizeof(repl_capture_t) * (size_t)num_caps); if (num_caps > 16 && !caps) { free(buf); return js_mkerr(js, "oom"); } for (int ci = 0; ci < num_caps; ci++) { ant_value_t cap = js_arr_get(js, result, (ant_offset_t)(ci + 1)); if (vtype(cap) == kTypeString) { ant_offset_t cl, co = vstr(js, cap, &cl); caps[ci] = (repl_capture_t){ (const char *)(uintptr_t)(co), cl }; } else caps[ci] = (repl_capture_t){ NULL, 0 }; } ant_offset_t mlen, moff = vstr(js, matched, &mlen); str_off = vstr(js, str, &str_len); bool ok = repl_template( (const char *)(uintptr_t)(rep_off), rep_len, (const char *)(uintptr_t)(moff), mlen, (const char *)(uintptr_t)(str_off), str_len, position, caps, num_caps, &buf, &buf_len, &buf_cap ); if (caps != caps_buf) free(caps); if (!ok) { free(buf); return js_mkerr(js, "oom"); } } double match_end_units = position_units + (double)str_utf16_len(js, matched); if (match_end_units > (double)str_units_len) match_end_units = (double)str_units_len; str_off = vstr(js, str, &str_len); int match_end_bytes = utf16_index_to_byte_offset( (const char *)(uintptr_t)str_off, (size_t)str_len, (size_t)match_end_units, NULL ); next_src_pos = match_end_bytes < 0 ? str_len : (ant_offset_t)match_end_bytes; } }
str_off = vstr(js, str, &str_len); if (next_src_pos < str_len) SB_APPEND((const char *)(uintptr_t)(str_off + next_src_pos), str_len - next_src_pos);
#undef SB_APPEND
ant_value_t ret = js_mkstr(js, buf, buf_len); free(buf); return ret;}
static ant_value_t builtin_regexp_symbol_search(ant_params_t) { ant_value_t rx = js->this_val; if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@search] called on non-object");
ant_value_t str = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "undefined", 9); if (is_err(str)) return str;
bool used_fast_path = false; ant_value_t fast = regexp_search_plain_literal_fast(js, rx, str, &used_fast_path); if (is_err(fast) || used_fast_path) return fast;
ant_value_t prev_li = js_getprop_fallback(js, rx, "lastIndex"); if (is_err(prev_li)) return prev_li; js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), tov(0));
ant_value_t result = regexp_exec_abstract(js, rx, str); if (is_err(result)) return result;
ant_value_t cur_li = js_getprop_fallback(js, rx, "lastIndex"); if (is_err(cur_li)) return cur_li; js_setprop(js, rx, js_mkstr(js, "lastIndex", 9), prev_li);
if (vtype(result) == kTypeNull) return tov(-1);
ant_value_t idx = js_getprop_fallback(js, result, "index"); if (is_err(idx)) return idx; return vtype(idx) == kTypeNumber ? idx : tov(-1);}
static inline void regexp_split_store_lastindex( ant_object_t *obj, uint32_t slot, PCRE2_SIZE value) { ant_object_prop_set_unchecked(obj, slot, tov((double)value));}
static ant_value_t regexp_split_batch_fast( ant_t *js, ant_value_t splitter, ant_value_t str, ant_value_t result, uint32_t limit, bool *used_fast_path) { *used_fast_path = false;
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; if (!str_is_ascii(str_ptr)) return js_mkundef();
compiled_regex_cache_entry_t *compiled; ant_object_t *lastindex_obj; uint32_t lastindex_slot; if (!regexp_prepare_builtin_exec( js, splitter, REGEXP_FLAG_STICKY, &compiled, NULL, &lastindex_obj, &lastindex_slot )) return js_mkundef();
regex_match_scope_t scope; if (!regex_match_scope_begin(compiled, &scope)) return js_mkundef();
*used_fast_path = true;
PCRE2_SIZE size = (PCRE2_SIZE)str_len; PCRE2_SIZE p = 0; PCRE2_SIZE q = 0; uint32_t result_len = 0;
while (q < size) { regexp_split_store_lastindex(lastindex_obj, lastindex_slot, q);
PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run_in_scope( js, compiled, str_ptr, str_len, q, false, &scope, &ovector, &ovcount ); if (rc < 0 || ovector[0] >= size) { regexp_split_store_lastindex(lastindex_obj, lastindex_slot, 0); break; }
PCRE2_SIZE start = ovector[0]; PCRE2_SIZE end = ovector[1]; regexp_split_store_lastindex(lastindex_obj, lastindex_slot, end); update_regexp_statics(js, str, ovector, ovcount);
if (end == p) { q = start + 1; continue; }
ant_value_t piece = js_mkstr_byte_range( js, str_ptr, p, (ant_offset_t)(start - p) ); if (is_err(piece)) { regex_match_scope_end(&scope); return piece; } js_arr_push(js, result, piece); if (++result_len == limit) { regex_match_scope_end(&scope); return result; }
for (uint32_t i = 1; i < ovcount; i++) { PCRE2_SIZE capture_start = ovector[2 * i]; PCRE2_SIZE capture_end = ovector[2 * i + 1]; ant_value_t capture = capture_start == PCRE2_UNSET ? js_mkundef() : js_mkstr_byte_range( js, str_ptr, capture_start, (ant_offset_t)(capture_end - capture_start) ); if (is_err(capture)) { regex_match_scope_end(&scope); return capture; } js_arr_push(js, result, capture); if (++result_len == limit) { regex_match_scope_end(&scope); return result; } }
p = end; q = p; }
ant_value_t trailing = p == 0 ? str : js_mkstr_byte_range(js, str_ptr, p, (ant_offset_t)(size - p)); regex_match_scope_end(&scope); if (is_err(trailing)) return trailing; js_arr_push(js, result, trailing); return result;}
static ant_value_t builtin_regexp_symbol_split(ant_params_t) { ant_value_t rx = js_getthis(js); if (!is_object_type(rx)) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@split] called on non-object");
ant_value_t str = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "", 0); if (is_err(str)) return str;
ant_value_t ctor = js_get(js, rx, "constructor"); if (is_err(ctor)) return ctor;
ant_value_t C; if (vtype(ctor) == kTypeUndefined) { C = js->builtins.regexp_ctor_value; } else if (!is_object_type(ctor)) { return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp.prototype[@@split]: constructor is not an object"); } else { ant_value_t species = get_ctor_species_value(js, ctor); if (is_err(species)) return species; if (vtype(species) == kTypeUndefined || vtype(species) == kTypeNull) C = js->builtins.regexp_ctor_value; else C = species; }
if (is_err(C)) return C; if (vtype(C) != kTypeFunction && vtype(C) != kTypeBuiltin) return js_mkerr_typed(js, JS_ERR_TYPE, "RegExp species is not a constructor");
ant_value_t flags_val = js_get(js, rx, "flags"); if (is_err(flags_val)) return flags_val; ant_value_t flags_str = js_tostring_val(js, flags_val); if (is_err(flags_str)) return flags_str;
ant_offset_t flen, foff = vstr(js, flags_str, &flen); const char *fptr = (const char *)(uintptr_t)(foff); bool has_sticky = false; for (ant_offset_t i = 0; i < flen; i++) { if (fptr[i] == 'y') has_sticky = true; }
ant_value_t new_flags; if (has_sticky) new_flags = flags_str; else { char fbuf[16]; if (flen > 14) flen = 14; foff = vstr(js, flags_str, &flen); fptr = (const char *)(uintptr_t)(foff); memcpy(fbuf, fptr, flen); fbuf[flen] = 'y'; new_flags = js_mkstr(js, fbuf, flen + 1); }
ant_value_t ctor_args[2] = { rx, new_flags }; ant_value_t splitter = regexp_species_construct(js, rx, C, ctor_args, 2); if (is_err(splitter)) return splitter;
ant_value_t A = js_mkarr(js); if (is_err(A)) return A; ant_offset_t lengthA = 0;
uint32_t lim = UINT32_MAX; if (nargs >= 2 && vtype(args[1]) != kTypeUndefined) { double d = tod(args[1]); if (d >= 0 && d <= UINT32_MAX) lim = (uint32_t)d; } if (lim == 0) return mkval(kTypeArray, vdata(A));
ant_offset_t str_len, str_off = vstr(js, str, &str_len); ant_offset_t size = str_len;
if (size == 0) { ant_value_t z = regexp_exec_abstract(js, splitter, str); if (is_err(z)) return z; if (vtype(z) == kTypeNull) js_arr_push(js, A, str); return mkval(kTypeArray, vdata(A)); }
bool used_fast_path = false; ant_value_t fast_result = regexp_split_batch_fast( js, splitter, str, A, lim, &used_fast_path ); if (is_err(fast_result) || used_fast_path) return fast_result;
ant_offset_t p = 0, q = p; ant_value_t lastIndex_key = js_mkstr(js, "lastIndex", 9);
while (q < size) { js_setprop(js, splitter, lastIndex_key, tov((double)q));
ant_value_t z = regexp_exec_abstract(js, splitter, str); if (is_err(z)) return z;
if (vtype(z) == kTypeNull) { str_off = vstr(js, str, &str_len); q += utf8_char_len_at((const char *)(uintptr_t)(str_off), str_len, q); continue; }
ant_value_t li_val = js_get(js, splitter, "lastIndex"); if (is_err(li_val)) return li_val; double e_raw = vtype(li_val) == kTypeNumber ? tod(li_val) : 0; ant_offset_t e = (ant_offset_t)(e_raw < 0 ? 0 : (e_raw > (double)size ? (double)size : e_raw));
if (e == p) { str_off = vstr(js, str, &str_len); q += utf8_char_len_at((const char *)(uintptr_t)(str_off), str_len, q); continue; }
str_off = vstr(js, str, NULL); ant_value_t T_val = js_mkstr_byte_range(js, (const char *)(uintptr_t)str_off, p, q - p); js_arr_push(js, A, T_val); lengthA++; if (lengthA == lim) return mkval(kTypeArray, vdata(A));
ant_offset_t num_caps = js_arr_len(js, z); for (ant_offset_t i = 1; i < num_caps; i++) { ant_value_t cap = js_arr_get(js, z, i); js_arr_push(js, A, cap); lengthA++; if (lengthA == lim) return mkval(kTypeArray, vdata(A)); }
p = e; q = p; }
str_off = vstr(js, str, &str_len); ant_value_t trailing = js_mkstr_byte_range(js, (const char *)(uintptr_t)str_off, p, str_len - p); js_arr_push(js, A, trailing); return mkval(kTypeArray, vdata(A));}
ant_value_t do_regex_match_pcre2(ant_t *js, regex_match_args_t args) { uint8_t flags_mask = 0; if (args.ignore_case) flags_mask |= REGEXP_FLAG_IGNORE_CASE; if (args.multiline) flags_mask |= REGEXP_FLAG_MULTILINE; compiled_regex_cache_entry_t *compiled = compiled_regex_cache_acquire( js, args.pattern_ptr, args.pattern_len, flags_mask ); if (!compiled) return js_mknull();
regex_match_scope_t scope; if (!regex_match_scope_begin(compiled, &scope)) { compiled_regex_cache_release(compiled); return js_mknull(); }
ant_value_t result_arr = js_mkarr(js); if (is_err(result_arr)) { regex_match_scope_end(&scope); compiled_regex_cache_release(compiled); return result_arr; }
PCRE2_SIZE pos = 0; int match_count = 0;
while (pos <= (PCRE2_SIZE)args.str_len) { PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run_in_scope( js, compiled, args.str_ptr, args.str_len, pos, false, &scope, &ovector, &ovcount ); if (rc < 0) break;
PCRE2_SIZE match_start = ovector[0]; PCRE2_SIZE match_end = ovector[1];
if (args.global) { ant_value_t match_str = js_mkstr_byte_range(js, args.str_ptr, match_start, match_end - match_start); if (is_err(match_str)) { regex_match_scope_end(&scope); compiled_regex_cache_release(compiled); return match_str; } js_arr_push(js, result_arr, match_str); } else { for (uint32_t i = 0; i < ovcount; i++) { PCRE2_SIZE start = ovector[2*i]; PCRE2_SIZE end = ovector[2*i+1]; if (start == PCRE2_UNSET) { js_arr_push(js, result_arr, js_mkundef()); } else { ant_value_t match_str = js_mkstr_byte_range(js, args.str_ptr, start, end - start); if (is_err(match_str)) { regex_match_scope_end(&scope); compiled_regex_cache_release(compiled); return match_str; } js_arr_push(js, result_arr, match_str); } } js_setprop(js, result_arr, js_mkstr(js, "index", 5), tov((double)byte_offset_to_utf16(args.str_ptr, match_start))); } match_count++;
if (!args.global) break; if (match_start == match_end) { pos = match_end + utf8_char_len_at(args.str_ptr, args.str_len, match_end); } else pos = match_end; }
regex_match_scope_end(&scope); compiled_regex_cache_release(compiled);
if (match_count == 0) return js_mknull(); return result_arr;}
static bool str_buf_append(char **buf, size_t *len, size_t *cap, const char *data, size_t n) { if (n == 0) return true; if (*len + n >= *cap) { size_t new_cap = (*len + n + 1) * 2; char *nb = (char *)realloc(*buf, new_cap); if (!nb) return false; *buf = nb; *cap = new_cap; } memcpy(*buf + *len, data, n); *len += n; return true;}
static inline ant_value_t emit_str_replacement( ant_t *js, ant_value_t replacement, bool is_func, const char *repl_ptr, ant_offset_t repl_len, const char *str_ptr, ant_value_t str, ant_offset_t pos, ant_offset_t match_len, char **buf, size_t *buf_len, size_t *buf_cap) { if (is_func) { ant_value_t cb_args[3] = { js_mkstr_byte_range(js, str_ptr, pos, match_len), tov((double)pos), str }; ant_value_t r = sv_vm_call(js->vm, js, replacement, js_mkundef(), cb_args, 3, NULL, js_mkundef()); if (vtype(r) == kTypeError) return r; ant_value_t r_str = js_tostring_val(js, r); if (is_err(r_str)) return r_str; ant_offset_t rlen, roff = vstr(js, r_str, &rlen); if (!str_buf_append(buf, buf_len, buf_cap, (const char *)(uintptr_t)roff, rlen)) return js_mkerr(js, "oom"); } else if (!str_buf_append(buf, buf_len, buf_cap, repl_ptr, repl_len)) return js_mkerr(js, "oom"); return js_mkundef();}
static ant_value_t string_replace_impl(ant_native_params_t, bool replace_all) { ant_value_t str = Ant_String_CoerceThis(js, replace_all ? "replaceAll" : "replace"); if (is_err(str)) return str; if (nargs < 1) return str;
if (is_object_type(args[0])) { if (replace_all) { ant_value_t global_val = regexp_read_flag(js, args[0], REGEXP_FLAG_INDEX_GLOBAL); if (!js_truthy(js, global_val)) return js_mkerr_typed(js, JS_ERR_TYPE, "String.prototype.replaceAll called with a non-global RegExp"); } bool called = false; ant_value_t replacement_arg = nargs > 1 ? args[1] : js_mkundef(); ant_value_t call_args[2] = { str, replacement_arg }; ant_value_t result = maybe_call_symbol_method(js, args[0], js->sym.replace_sym, args[0], call_args, 2, &called); if (is_err(result)) return result; if (called) return result; ant_value_t coerced_search = js_to_primitive(js, args[0], 1); if (is_err(coerced_search)) return coerced_search; coerced_search = js_tostring_val(js, coerced_search); if (is_err(coerced_search)) return coerced_search; args[0] = coerced_search; } if (nargs < 2) return str; ant_value_t search = args[0]; ant_value_t replacement = args[1]; if (vtype(search) != kTypeString) return str;
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off); ant_offset_t search_len, search_off = vstr(js, search, &search_len); const char *search_ptr = (char *)(uintptr_t)(search_off);
bool is_func = (vtype(replacement) == kTypeFunction); ant_offset_t repl_len = 0; const char *repl_ptr = NULL; if (!is_func) { if (vtype(replacement) != kTypeString) return str; ant_offset_t repl_off = vstr(js, replacement, &repl_len); repl_ptr = (char *)(uintptr_t)(repl_off); }
if (!replace_all) { if (search_len > str_len) return str; ant_offset_t match_pos = 0; bool found = search_len == 0; if (!found) { const char *match = find_bytes( str_ptr, str_len, search_ptr, search_len ); if (match) { match_pos = (ant_offset_t)(match - str_ptr); found = true; } } if (!found) return str;
size_t cap = str_len + repl_len + 256, len = 0; char *buf = (char *)malloc(cap); if (!buf) return js_mkerr(js, "oom"); if (!str_buf_append(&buf, &len, &cap, str_ptr, match_pos)) { free(buf); return js_mkerr(js, "oom"); } ant_value_t err = emit_str_replacement( js, replacement, is_func, repl_ptr, repl_len, str_ptr, str, match_pos, search_len, &buf, &len, &cap ); if (vtype(err) == kTypeError) { free(buf); return err; } if (!str_buf_append( &buf, &len, &cap, str_ptr + match_pos + search_len, str_len - match_pos - search_len) ) { free(buf); return js_mkerr(js, "oom"); } ant_value_t ret = js_mkstr(js, buf, len); free(buf); return ret; } else { size_t cap = str_len + repl_len + 256, len = 0; char *buf = (char *)malloc(cap); if (!buf) return js_mkerr(js, "oom"); ant_offset_t pos = 0; bool replaced = false;
if (search_len != 0) { for (;;) { const char *match = find_bytes( str_ptr + pos, str_len - pos, search_ptr, search_len ); if (!match) break; ant_offset_t match_pos = (ant_offset_t)(match - str_ptr); if (!str_buf_append( &buf, &len, &cap, str_ptr + pos, match_pos - pos )) { free(buf); return js_mkerr(js, "oom"); } replaced = true; ant_value_t err = emit_str_replacement( js, replacement, is_func, repl_ptr, repl_len, str_ptr, str, match_pos, search_len, &buf, &len, &cap ); if (vtype(err) == kTypeError) { free(buf); return err; } pos = match_pos + search_len; } } else { while (pos <= str_len) { replaced = true; ant_value_t err = emit_str_replacement( js, replacement, is_func, repl_ptr, repl_len, str_ptr, str, pos, 0, &buf, &len, &cap ); if (vtype(err) == kTypeError) { free(buf); return err; } if (pos < str_len && !str_buf_append( &buf, &len, &cap, str_ptr + pos, 1 )) { free(buf); return js_mkerr(js, "oom"); } pos++; } } if (!str_buf_append( &buf, &len, &cap, str_ptr + pos, str_len - pos) ) { free(buf); return js_mkerr(js, "oom"); } if (!replaced) { free(buf); return str; } ant_value_t ret = js_mkstr(js, buf, len); free(buf); return ret; }}
static ant_value_t builtin_string_replace(ant_params_t) { return string_replace_impl(js, args, nargs, false);}
static ant_value_t builtin_string_replaceAll(ant_params_t) { return string_replace_impl(js, args, nargs, true);}
static bool regexp_literal_replace_builtin_guard(ant_t *js) { ant_regex_state_t *state = js->regex_state; if ( !state || !js->regexp.write_guard_armed || js->regexp.replace_written || js->regexp.exec_written || !regexp_shape_flags_pristine(js, state->instance_shape.shape) ) return false;
ant_value_t replace_fn; if ( !js_try_get_own_data_prop(js, js->sym.string_proto, "replace", 7, &replace_fn) || !js_cfunc_same_entrypoint(replace_fn, builtin_string_replace)) return false;
ant_value_t regexp_proto = js->builtins.regexp_proto;
ant_offset_t sym_off = (ant_offset_t)vdata(js->sym.replace_sym); prop_meta_t meta; if (lookup_symbol_prop_meta(js, regexp_proto, sym_off, &meta) && (meta.has_getter || meta.has_setter)) return false;
ant_prop_loc_t loc = lkp_sym(regexp_proto, sym_off); if (!loc.obj || !js_cfunc_same_entrypoint(js_prop_load(loc), builtin_regexp_symbol_replace)) return false;
return true;}
ant_value_t regexp_literal_replace_call( ant_t *js, ant_value_t str, ant_value_t pattern, ant_value_t flags, ant_value_t replacement) { if ( vtype(str) == kTypeString && vtype(replacement) == kTypeString && regexp_literal_replace_builtin_guard(js) && !replacement_has_substitution(js, replacement) ) { const char *needle; ant_offset_t needle_len; if (regexp_literal_exec_fast_parts(js, pattern, flags, &needle, &needle_len)) { ant_offset_t flags_len, flags_off = vstr(js, flags, &flags_len); uint8_t flags_mask = regexp_parse_flags_mask((const char *)(uintptr_t)flags_off, flags_len); bool global = (flags_mask & REGEXP_FLAG_GLOBAL) != 0;
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (const char *)(uintptr_t)str_off; ant_offset_t repl_len, repl_off = vstr(js, replacement, &repl_len); const char *repl_ptr = (const char *)(uintptr_t)repl_off; const char *first = find_bytes(str_ptr, str_len, needle, needle_len); if (!first) return str;
size_t cap = str_len + repl_len + 256; char *buf = malloc(cap); if (!buf) return js_mkerr(js, "oom");
size_t len = 0; const char *scan = str_ptr; const char *end = str_ptr + str_len; const char *match = first; PCRE2_SIZE ovector[2];
for (;;) { if (!str_buf_append(&buf, &len, &cap, scan, (size_t)(match - scan)) || !str_buf_append(&buf, &len, &cap, repl_ptr, repl_len)) { free(buf); return js_mkerr(js, "oom"); }
ovector[0] = (PCRE2_SIZE)(match - str_ptr); ovector[1] = ovector[0] + (PCRE2_SIZE)needle_len; update_regexp_statics(js, str, ovector, 1);
scan = match + needle_len; if (!global) break; match = find_bytes(scan, (ant_offset_t)(end - scan), needle, needle_len); if (!match) break; }
if (!str_buf_append(&buf, &len, &cap, scan, (size_t)(end - scan))) { free(buf); return js_mkerr(js, "oom"); }
ant_value_t ret = js_mkstr(js, buf, len); free(buf); return ret; } }
ant_value_t regexp_obj = regexp_create_literal(js, pattern, flags); if (is_err(regexp_obj)) return regexp_obj; ant_value_t replace_fn = js_getprop_fallback(js, str, "replace"); if (is_err(replace_fn)) return replace_fn; ant_value_t call_args[2] = { regexp_obj, replacement }; return sv_vm_call(js->vm, js, replace_fn, str, call_args, 2, NULL, js_mkundef());}
static ant_value_t builtin_string_search(ant_params_t) { ant_value_t str = Ant_String_CoerceThis(js, "search"); if (is_err(str)) return str; if (nargs < 1) return tov(-1);
if (is_object_type(args[0])) { bool called = false; ant_value_t call_args[1] = { str }; ant_value_t dispatched = maybe_call_symbol_method( js, args[0], js->sym.search_sym, args[0], call_args, 1, &called ); if (is_err(dispatched)) return dispatched; if (called) return dispatched; }
ant_value_t pattern = args[0]; const char *pattern_ptr = NULL; ant_offset_t pattern_len = 0;
if (vtype(pattern) == kTypeObject) { pattern = js_to_primitive(js, pattern, 1); if (is_err(pattern)) return pattern; pattern = js_tostring_val(js, pattern); if (is_err(pattern)) return pattern; goto search_string_pattern; } else if (vtype(pattern) == kTypeString) {search_string_pattern:; ant_offset_t poff; poff = vstr(js, pattern, &pattern_len); pattern_ptr = (char *)(uintptr_t)(poff); } else return tov(-1);
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off);
compiled_regex_cache_entry_t *compiled = compiled_regex_cache_acquire( js, pattern_ptr, pattern_len, 0 ); if (!compiled) return tov(-1);
regex_match_scope_t scope; PCRE2_SIZE *ovector; uint32_t ovcount; int rc = compiled_regex_run( js, compiled, str_ptr, str_len, 0, false, &scope, &ovector, &ovcount );
if (rc < 0) { regex_match_scope_end(&scope); compiled_regex_cache_release(compiled); return tov(-1); }
double result = (double)ovector[0];
regex_match_scope_end(&scope); compiled_regex_cache_release(compiled);
return tov(result);}
static ant_value_t builtin_string_match(ant_params_t) { ant_value_t str = Ant_String_CoerceThis(js, "match"); if (is_err(str)) return str; if (nargs < 1) return js_mknull();
if (is_object_type(args[0])) { bool called = false; ant_value_t call_args[1] = { str }; ant_value_t dispatched = maybe_call_symbol_method( js, args[0], js->sym.match_sym, args[0], call_args, 1, &called ); if (is_err(dispatched)) return dispatched; if (called) return dispatched; }
ant_value_t pattern = args[0]; const char *pattern_ptr = NULL; ant_offset_t pattern_len = 0; const bool global_flag = false, ignore_case = false, multiline = false;
if (vtype(pattern) == kTypeObject) { pattern = js_to_primitive(js, pattern, 1); if (is_err(pattern)) return pattern; pattern = js_tostring_val(js, pattern); if (is_err(pattern)) return pattern; goto match_string_pattern; } else if (vtype(pattern) == kTypeString) {match_string_pattern:; ant_offset_t poff; poff = vstr(js, pattern, &pattern_len); pattern_ptr = (char *)(uintptr_t)(poff); } else return js_mknull();
ant_offset_t str_len, str_off = vstr(js, str, &str_len); const char *str_ptr = (char *)(uintptr_t)(str_off);
ant_value_t result = do_regex_match_pcre2(js, (regex_match_args_t){ .pattern_ptr = pattern_ptr, .pattern_len = pattern_len, .str_ptr = str_ptr, .str_len = str_len, .global = global_flag, .ignore_case = ignore_case, .multiline = multiline, });
if (!global_flag && vtype(result) == kTypeArray) { js_setprop(js, result, js_mkstr(js, "input", 5), str); }
return result;}
static const ant_cfunc_meta_t regexp_static_getters[REGEXP_STATIC_VALUE_COUNT] = { { regexp_static_get_d1, NULL, 0, 0 }, { regexp_static_get_d2, NULL, 0, 0 }, { regexp_static_get_d3, NULL, 0, 0 }, { regexp_static_get_d4, NULL, 0, 0 }, { regexp_static_get_d5, NULL, 0, 0 }, { regexp_static_get_d6, NULL, 0, 0 }, { regexp_static_get_d7, NULL, 0, 0 }, { regexp_static_get_d8, NULL, 0, 0 }, { regexp_static_get_d9, NULL, 0, 0 }, { regexp_static_get_last_match, NULL, 0, 0 }, { regexp_static_get_amp, NULL, 0, 0 },};
static const ant_cfunc_meta_t regexp_static_setters[REGEXP_STATIC_VALUE_COUNT] = { { regexp_static_set_d1, NULL, 1, 0 }, { regexp_static_set_d2, NULL, 1, 0 }, { regexp_static_set_d3, NULL, 1, 0 }, { regexp_static_set_d4, NULL, 1, 0 }, { regexp_static_set_d5, NULL, 1, 0 }, { regexp_static_set_d6, NULL, 1, 0 }, { regexp_static_set_d7, NULL, 1, 0 }, { regexp_static_set_d8, NULL, 1, 0 }, { regexp_static_set_d9, NULL, 1, 0 }, { regexp_static_set_last_match, NULL, 1, 0 }, { regexp_static_set_amp, NULL, 1, 0 },};
void init_regex_module(ant_t *js) { if (!js->regex_state) js->regex_state = calloc(1, sizeof(*js->regex_state)); ant_value_t glob = js->global; ant_value_t object_proto = js->sym.object_proto;
ant_value_t regexp_proto = Ant_Object_CreateBare(js); js_set_proto_init(regexp_proto, object_proto); js->builtins.regexp_proto = regexp_proto; defmethod(js, regexp_proto, "test", 4, Ant_Function_CreateWithArity(builtin_regexp_test, 1)); defmethod(js, regexp_proto, "exec", 4, Ant_Function_CreateWithArity(builtin_regexp_exec, 1)); defmethod(js, regexp_proto, "toString", 8, Ant_Function_Create(builtin_regexp_toString));
js_set_getter_desc(js, regexp_proto, "source", 6, Ant_Function_Create(builtin_regexp_source_getter), JS_DESC_C);#define REGEXP_FLAG(id, name, ch) \ js_set_getter_desc(js, regexp_proto, #name, sizeof(#name) - 1, Ant_Function_Create(builtin_regexp_get_##name), JS_DESC_C);#include "modules/regex_flags.h"
defmethod_sym(js, regexp_proto, js->sym.split_sym, Ant_Function_CreateWithArity(builtin_regexp_symbol_split, 2)); defmethod_sym(js, regexp_proto, js->sym.match_sym, Ant_Function_CreateWithArity(builtin_regexp_symbol_match, 1)); defmethod_sym(js, regexp_proto, js->sym.matchAll_sym, Ant_Function_CreateWithArity(builtin_regexp_symbol_matchAll, 1));
js->builtins.regexp_matchall_iter_proto_val = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.regexp_matchall_iter_proto_val, js->sym.iterator_proto); defmethod(js, js->builtins.regexp_matchall_iter_proto_val, "next", 4, Ant_Function_Create(regexp_matchall_next)); mkprop(js, js->builtins.regexp_matchall_iter_proto_val, js->sym.toStringTag_sym, ANT_STRING("RegExp String Iterator"), ANT_PROP_ATTR_CONFIGURABLE); defmethod_sym(js, regexp_proto, js->sym.replace_sym, Ant_Function_CreateWithArity(builtin_regexp_symbol_replace, 2)); defmethod_sym(js, regexp_proto, js->sym.search_sym, Ant_Function_CreateWithArity(builtin_regexp_symbol_search, 1)); js_set_getter_desc(js, regexp_proto, "flags", 5, Ant_Function_Create(builtin_regexp_flags_getter), JS_DESC_C); defmethod(js, regexp_proto, "compile", 7, Ant_Function_CreateWithArity(builtin_regexp_compile, 2));
ant_value_t regexp_func = Ant_Function_CreateIntrinsicConstructor(js, builtin_RegExp, regexp_proto, "RegExp", 2); ant_value_t regexp_ctor = js_as_obj(regexp_func); js_define_species_getter(js, regexp_func); js->builtins.regexp_ctor_value = regexp_func; defmethod(js, regexp_ctor, "escape", 6, Ant_Function_CreateWithArity(builtin_regexp_escape, 1));
ant_value_t empty = js_mkstr_permanent(js, "", 0); js->builtins.regexp_empty_string = empty; js->mutable_roots.regexp_static_subject = empty; for (size_t i = 0; i < sizeof(js->mutable_roots.regexp_static_values) / sizeof(js->mutable_roots.regexp_static_values[0]); i++) js->mutable_roots.regexp_static_values[i] = empty;
for (int i = 1; i <= ANT_REGEXP_STATIC_CAPTURE_COUNT; i++) { char key[3] = {'$', (char)('0' + i), '\0'}; js_set_accessor_desc( js, regexp_ctor, key, 2, js_mkfun_meta(®exp_static_getters[i - 1]), js_mkfun_meta(®exp_static_setters[i - 1]), JS_DESC_C ); } js_set_accessor_desc( js, regexp_ctor, "lastMatch", 9, js_mkfun_meta(®exp_static_getters[REGEXP_STATIC_IDX_LAST_MATCH]), js_mkfun_meta(®exp_static_setters[REGEXP_STATIC_IDX_LAST_MATCH]), JS_DESC_C ); js_set_accessor_desc( js, regexp_ctor, "$&", 2, js_mkfun_meta(®exp_static_getters[REGEXP_STATIC_IDX_AMP]), js_mkfun_meta(®exp_static_setters[REGEXP_STATIC_IDX_AMP]), JS_DESC_C ); js_set_global_builtin(js, "RegExp", regexp_func); ant_value_t string_ctor = js_get(js, glob, "String"); ant_value_t string_proto = js_get(js, string_ctor, "prototype"); defmethod(js, string_proto, "search", 6, Ant_Function_CreateWithArity(builtin_string_search, 1)); defmethod(js, string_proto, "match", 5, Ant_Function_CreateWithArity(builtin_string_match, 1)); defmethod(js, string_proto, "matchAll", 8, Ant_Function_CreateWithArity(builtin_string_matchAll, 1)); defmethod(js, string_proto, "replace", 7, Ant_Function_CreateWithArity(builtin_string_replace, 2)); defmethod(js, string_proto, "replaceAll", 10, Ant_Function_CreateWithArity(builtin_string_replaceAll, 2)); js->regexp.exec_written = false; js->regexp.replace_written = false; js->regexp.write_guard_armed = true;}
void gc_age_regex_cache(ant_t *js, bool minor) { ant_regex_state_t *state = js->regex_state; if (!state || minor) return; ant_gc_shapes_mark(js, state->instance_shape.base); compiled_regex_cache_rotate(state);}
void cleanup_regex_module(ant_t *js) { ant_regex_state_t *state = js->regex_state; if (!state) return;
compiled_regex_table_clear(&state->compiled[0]); compiled_regex_table_clear(&state->compiled[1]);
ant_shape_release(state->instance_shape.base); ant_shape_release(state->instance_shape.shape); ant_shape_release(state->result_shape.shape); ant_shape_release(state->result_indices_shape.shape); pcre2_jit_stack_free(state->jit_stack); pcre2_match_context_free(state->match_ctx); free(state); js->regex_state = NULL;}