char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
5553f00
main
1import { registerUncloneable } from "ext:web/serialization.js" ; 2import { 3ObjectCreate , 4ObjectDefineProperty , 5RegExp , 6RegExpPrototypeExec , 7TypeError , 8WeakMap , 9WeakMapPrototypeGet , 10WeakMapPrototypeSet , 11} from "ext:core/primordials" ; 12import { isObject , usv } from "ext:web/idl.js" ; 13import { installInterface } from "ext:web/interface.js" ; 14import { op_urlpattern_match_input , op_urlpattern_parse } from "ext:core/ops" ; 15import type { NativeComponent , PatternComponent , PatternInit , PatternInput } from "ext:core/ops" ; 16 17interface Component { 18pattern :string ; 19regexp :RegExp ; 20names :string []; 21} 22interface PatternState { 23components :Component []; 24hasRegExpGroups :boolean ; 25} 26type PatternResult = Record < PatternComponent , { 27groups :Record < string , string | undefined >; 28input :string ; 29}> & { 30inputs :[ PatternInput ] | [ PatternInput , string ]; 31}; 32 33const patterns = new WeakMap < object , PatternState >(); 34registerUncloneable ( patterns ); 35// Order matches the native parser's component array; dictionaries convert in lexicographic order. 36const components :PatternComponent [] = [ 37"protocol" , 38"username" , 39"password" , 40"hostname" , 41"port" , 42"pathname" , 43"search" , 44"hash" , 45]; 46const members :( PatternComponent | "baseURL" )[] = [ 47"baseURL" , 48"hash" , 49"hostname" , 50"password" , 51"pathname" , 52"port" , 53"protocol" , 54"search" , 55"username" , 56]; 57 58function convertInput ( value :unknown ) :PatternInput { 59if ( value !== null && value !== undefined && ! isObject ( value )) { 60return usv ( value ); 61} 62const input :PatternInit = ObjectCreate ( null ); 63if ( value !== null && value !== undefined ) { 64for ( let index = 0 ; index < members . length ; index ++ ) { 65const name = members [ index ]; 66const member = value [ name ]; 67if ( member !== undefined ) input [ name ] = usv ( member ); 68} 69} 70return input ; 71} 72function state ( receiver :unknown ) :PatternState { 73const value = WeakMapPrototypeGet ( patterns , receiver ); 74if ( ! value ) throw new TypeError ( "Illegal invocation" ); 75return value ; 76} 77function define < T extends object , K extends keyof T > ( object :T , name :K , value :NoInfer < T [ K ]>) :void { 78ObjectDefineProperty ( object , name , { 79 value, 80writable :true , 81enumerable :true , 82configurable :true , 83}); 84} 85function match ( receiver :unknown , input :unknown , base :unknown , exec :false ) :boolean ; 86function match ( receiver :unknown , input :unknown , base :unknown , exec :true ) :PatternResult | null ; 87function match ( receiver :unknown , rawInput :unknown , rawBase :unknown , exec :boolean ) :PatternResult | null | boolean { 88const data = state ( receiver ); 89let input = convertInput ( rawInput ); 90const base = rawBase === undefined ?undefined :usv ( rawBase ); 91const parsed = op_urlpattern_match_input ([ input , base ?? null ]); 92if ( parsed [ 1 ] !== null ) throw new TypeError ( parsed [ 1 ]); 93const values = parsed [ 0 ]; 94if ( values === null ) return exec ?null :false ; 95const result :Partial < PatternResult > | null = exec 96 ?{ 97hash :undefined , 98hostname :undefined , 99inputs :undefined , 100password :undefined , 101pathname :undefined , 102port :undefined , 103protocol :undefined , 104search :undefined , 105username :undefined , 106} 107 :null ; 108for ( let index = 0 ; index < components . length ; index ++ ) { 109const name = components [ index ]; 110const component = data . components [ index ]; 111const captures = RegExpPrototypeExec ( component . regexp , values [ name ]); 112if ( captures === null ) return exec ?null :false ; 113if ( exec ) { 114const groups :Record < string , string | undefined > = {}; 115for ( let group = 0 ; group < component . names . length ; group ++ ) { 116define ( groups , component . names [ group ], captures [ group + 1 ]); 117} 118define ( result ! , name , { groups, input :values [ name ] }); 119} 120} 121if ( ! exec ) return true ; 122if ( typeof input !== "string" ) { 123const dictionary :PatternInit = {}; 124for ( let index = 0 ; index < members . length ; index ++ ) { 125const name = members [ index ]; 126if ( input [ name ] !== undefined ) define ( dictionary , name , input [ name ]); 127} 128input = dictionary ; 129} 130result ! . inputs = base === undefined ?[ input ] :[ input , base ]; 131// Every component was defined above; partial results never escape. 132return result as PatternResult ; 133} 134 135interface URLPattern extends Readonly < Record < PatternComponent , string >> {} 136 137class URLPattern { 138constructor ( 139rawInput :unknown = undefined , 140baseOrOptions :unknown = undefined , 141options :unknown = undefined , 142) { 143const withBase = arguments . length >= 3 || 144( baseOrOptions !== null && baseOrOptions !== undefined && 145! isObject ( baseOrOptions )); 146const input = convertInput ( rawInput ); 147const base = withBase ?usv ( baseOrOptions ) :null ; 148options = withBase ?options :baseOrOptions ; 149if ( options !== null && options !== undefined && ! isObject ( options )) { 150throw new TypeError ( "Invalid URLPattern options" ); 151} 152const ignoreCase = !! options ?. ignoreCase ; 153const parsed = op_urlpattern_parse ([ input , base , ignoreCase ]); 154if ( parsed [ 1 ] !== null ) throw new TypeError ( parsed [ 1 ]); 155// Reuse the native array: new indexed writes could hit inherited setters. 156const compiled :( NativeComponent | Component )[] = parsed [ 0 ][ 0 ]; 157for ( let index = 0 ; index < compiled . length ; index ++ ) { 158const component = compiled [ index ] as NativeComponent ; 159compiled [ index ] = { 160pattern :component [ 0 ], 161regexp :new RegExp ( component [ 1 ], component [ 3 ]), 162names :component [ 2 ], 163}; 164} 165WeakMapPrototypeSet ( patterns , this , { 166components :compiled as Component [], 167hasRegExpGroups :parsed [ 0 ][ 1 ], 168}); 169} 170test ( input :unknown = undefined , base :unknown = undefined ) :boolean { 171return match ( this , input , base , false ); 172} 173exec ( input :unknown = undefined , base :unknown = undefined ) :PatternResult | null { 174return match ( this , input , base , true ); 175} 176get hasRegExpGroups () :boolean { 177return state ( this ). hasRegExpGroups ; 178} 179} 180for ( let index = 0 ; index < components . length ; index ++ ) { 181const name = components [ index ]; 182const getter = { 183get ( this :unknown ) :string { 184return state ( this ). components [ index ]. pattern ; 185}, 186}. get ; 187ObjectDefineProperty ( getter , "name" , { 188value :`get ${ name } ` , 189configurable :true , 190}); 191ObjectDefineProperty ( URLPattern . prototype , name , { 192get :getter , 193enumerable :true , 194configurable :true , 195}); 196} 197installInterface ( "URLPattern" , URLPattern ); 198export { URLPattern };