Last active
December 3, 2025 14:17
-
-
Save chris-kruining/36b751445d0518bd46e0ad32a4397b6d to your computer and use it in GitHub Desktop.
A quick and dirty implementation of a url parser and switch expression (I run this with `nix eval --json --file ./url.nix | jq .`)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| let | |
| inherit (import <nixpkgs> {system = builtins.currentSystem;}) lib; | |
| inherit (lib) isFunction isString isInt isAttrs attrsToList any all isList imap0 length elemAt; | |
| P = { | |
| # Wildcard | |
| _ = { | |
| __type = "pattern"; | |
| __kind = "any wildcard"; | |
| __functor = _: value: value != null; | |
| }; | |
| string = { | |
| __type = "pattern"; | |
| __kind = "any string"; | |
| __functor = _: value: lib.isString value; | |
| }; | |
| int = { | |
| __kind = "pattern"; | |
| __type = "any int"; | |
| __functor = _: value: isInt value; | |
| }; | |
| # string | |
| match = pattern: { | |
| __type = "pattern"; | |
| __kind = "regex string match"; | |
| __functor = _: value: ((isString value) && (lib.match pattern value) != null); | |
| }; | |
| # Escape hatch / custom matcher | |
| when = predicate: { | |
| __type = "pattern"; | |
| __kind = "predicate"; | |
| __functor = _: value: predicate value; | |
| }; | |
| # Logical combinators | |
| not = pattern: { | |
| __type = "pattern"; | |
| __kind = "not pattern"; | |
| __functor = _: value: (isMatch pattern value) == false; | |
| }; | |
| optional = pattern: { | |
| __type = "pattern"; | |
| __kind = "optional / orNull pattern"; | |
| __functor = _: value: isNull value || isMatch pattern value; | |
| }; | |
| any = patternList: { | |
| __type = "pattern"; | |
| __kind = "any of patterns"; | |
| __functor = _: value: (any (pattern: isMatch pattern value) patternList); | |
| }; | |
| all = patternList: { | |
| __type = "pattern"; | |
| __kind = "all of patterns"; | |
| __functor = _: value: (all (pattern: (isMatch pattern value)) patternList); | |
| }; | |
| # Subpatterns | |
| list = pattern: { | |
| __type = "pattern"; | |
| __kind = "permissive sublist"; | |
| __functor = _: value: | |
| pattern | |
| |> imap0 (index: pattern: {inherit index pattern;}) | |
| |> all ( | |
| { | |
| index, | |
| pattern, | |
| }: | |
| isMatch pattern (elemAt value index) | |
| ); | |
| }; | |
| record = namePattern: valuePattern: { | |
| __type = "pattern"; | |
| __kind = "record"; | |
| __functor = _: value: throw "Not yet implemented"; | |
| }; | |
| }; | |
| while = predicate: callback: var: | |
| if predicate var | |
| then while predicate callback (callback var) | |
| else var; | |
| isMatcher = subject: isFunction subject || (isAttrs subject && subject?__kind && subject.__type == "pattern"); | |
| resolve = callbackOrLiteral: value: | |
| if isFunction callbackOrLiteral | |
| then callbackOrLiteral value | |
| else callbackOrLiteral; | |
| isMatch = pattern: value: | |
| if isMatcher pattern | |
| then pattern value | |
| # When a pattern is a set this assumes the value is a set as well | |
| else if isAttrs pattern && isAttrs value | |
| then | |
| pattern | |
| |> attrsToList | |
| |> all ( | |
| pair: isMatch pair.value (value.${pair.name} or null) | |
| ) | |
| # When a pattern is a list this assumes the value is a set as well | |
| else if isList pattern && isList value | |
| then (length pattern == length value) && ((P.list pattern) value) | |
| else pattern == value; | |
| in { | |
| inherit P isMatch while; | |
| switch = source: { | |
| inherit source; | |
| state = "running"; | |
| }; | |
| case = pattern: callback: context: | |
| if (context.state != "matched" && isMatch pattern context.source) | |
| then | |
| context | |
| // { | |
| inherit callback; | |
| state = "matched"; | |
| } | |
| else context; | |
| exhaustive = context: | |
| if context.state != "matched" | |
| then throw "Switch failed to match on '${builtins.toJSON context.source}'" | |
| else resolve context.callback context.source; | |
| otherwise = callbackOrLiteral: context: | |
| if context.state != "matched" | |
| then resolve callbackOrLiteral context.source | |
| else resolve context.callback context.source; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| let | |
| inherit (import <nixpkgs> {system = builtins.currentSystem;}) lib; | |
| inherit (lib) stringLength substring recursiveUpdate elemAt splitString foldl nameValuePair listToAttrs toInt; | |
| my-lib = import ./my-lib.nix; | |
| parse = str: let | |
| inherit (my-lib) while switch case exhaustive P; | |
| when = kind: pattern: callback: let | |
| _p = "^${pattern}.*$"; | |
| in | |
| case { | |
| input = P.match _p; | |
| result = {${kind} = null;}; | |
| } ({ | |
| input, | |
| result, | |
| }: let | |
| matches = lib.match _p input; | |
| in { | |
| input = substring (stringLength (elemAt matches 0)) (-1) input; | |
| result = recursiveUpdate result (foldl (cb: v: cb v) callback matches); | |
| }); | |
| in | |
| { | |
| input = str; | |
| result = {href = str;}; | |
| } | |
| |> while (state: (stringLength state.input) > 0) ( | |
| state: | |
| switch state | |
| |> when "scheme" "(([a-z]+):/{0,2})" (_: scheme: {inherit scheme;}) | |
| |> when "user" "(([^@:]+)(:([^@]+))?@)" (_: user: _: password: {inherit user password;}) | |
| |> when "host" "((.+):([0-9]+)|(.+)*?)(/|\\?|#|$)" (host: hostname1: port: hostname2: _: { | |
| inherit host; | |
| hostName = | |
| if hostname1 != null | |
| then hostname1 | |
| else if hostname2 != null | |
| then hostname2 | |
| else null; | |
| port = | |
| if port != null | |
| then toInt port | |
| else null; | |
| }) | |
| |> when "path" "((/[^/?#]+)*)" (path: _: {inherit path;}) | |
| |> when "query" "(\\?([^=#&]+=[^=#&]+)*(&[^=#]+=[^=#&]+)*)" (query: _: _: { | |
| inherit query; | |
| queryParams = | |
| query | |
| |> substring 1 (-1) | |
| |> splitString "&" | |
| |> map (pair: pair |> splitString "=" |> (foldl (fn: arg: fn arg) nameValuePair)) | |
| |> listToAttrs; | |
| }) | |
| |> when "fragment" "(#.*)" (fragment: {inherit fragment;}) | |
| |> exhaustive | |
| ) | |
| |> ({result, ...}: result); | |
| in | |
| [ | |
| "http://example.org" | |
| "https://user:pass@[2001:db8::1]:8443/some/path?x=1&y=2&z=3#frag" | |
| "https://user:password123@[2001:db8::1]:8443/some/path?x=1&y=2&z=3#frag" | |
| "mailto:[email protected]" | |
| "ssh://[email protected]" | |
| "postgres://localhost:5432/database_name?sslmode=disable" | |
| "postgres://127.0.0.1:5432/database_name?sslmode=disable" | |
| "postgres://[::1]:5432/database_name?sslmode=disable" | |
| "bogus://user:[email protected]" | |
| "bogus://user:[email protected]" | |
| "bogus://[email protected]" | |
| ] | |
| |> map (url: parse url) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment