Skip to content

Instantly share code, notes, and snippets.

@chris-kruining
Last active December 3, 2025 14:17
Show Gist options
  • Select an option

  • Save chris-kruining/36b751445d0518bd46e0ad32a4397b6d to your computer and use it in GitHub Desktop.

Select an option

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 .`)
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;
}
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