From 5e559b540bce807177fa44d75a55423cbea3cc48 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Fri, 19 Sep 2025 22:24:38 -0700 Subject: [PATCH] wfc stuff --- src/wfc_utils.erl | 10 ++++++ src/wfc_wfchar.erl | 83 ++++++++++++++++++++++++++++++++++++++++++++++ src/wfc_word.erl | 82 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/wfc_utils.erl create mode 100644 src/wfc_wfchar.erl create mode 100644 src/wfc_word.erl diff --git a/src/wfc_utils.erl b/src/wfc_utils.erl new file mode 100644 index 0000000..454494e --- /dev/null +++ b/src/wfc_utils.erl @@ -0,0 +1,10 @@ +% @doc misc utility functions +-module(wfc_utils). + +-export([err/2, emsg/2]). + +err(Fmt, Pat) -> + {error, emsg(Fmt, Pat)}. + +emsg(Fmt, Pat) -> + unicode:characters_to_list(io_lib:format(Fmt, Pat)). diff --git a/src/wfc_wfchar.erl b/src/wfc_wfchar.erl new file mode 100644 index 0000000..78b39e7 --- /dev/null +++ b/src/wfc_wfchar.erl @@ -0,0 +1,83 @@ +% @doc called wfchar to disambiguate from +% +% mathematically, this is a variable like "a", "b", "c", etc +-module(wfc_wfchar). + +-export_type([ + wfchar/0 +]). + +-export([ + validate/1, + from_binary/1, to_binary/1 +]). + +% @doc +% a character is binary data that would be a valid atom +% +% [a-z][A-Za-z0-9_]+ +% +% - leads with lowercase letter +% - underscores ok +% - numbers ok +% - no non-ascii chars + +-opaque wfchar() :: {c, binary()}. + +%%----------------------- +%% constructors/destructors +%%----------------------- + +-spec validate(WfChar) -> Result + when WfChar :: wfchar(), + Result :: ok + | {error, Reason :: string()}. +% @doc validate if a candidate wfchar is well-formed and the inner binary is +% legal + +validate({c, Binary}) -> + case from_binary(Binary) of + {ok, {c, X}} when X =:= Binary -> + ok; + Error -> + Error + end; +validate(Term) -> + Error = wfc_utils:emsg("wfc_wfchar:validate failed; malformed wfchar: ~p", [Term]), + {error, Error}. + + +-spec from_binary(Binary) -> Result + when Binary :: binary(), + Result :: {ok, wfchar()} + | {error, Reason :: string()}. +% @doc +% make sure the binary is a legal wfchar and wrap it in a {c, Binary} tuple +% @end + +%% initial char must be [a-z] +from_binary(<>) when $a =< L, L =< $z -> + new2(<>, Rest); +from_binary(Char = <>) -> + {error, wfc_utils:emsg("wfc_wfchar:from_binary(~p): illegal lead character: ~p", [Char, L])}; +from_binary(Char) -> + wfc_utils:err("wfc_wfchar:from_binary(~p): malformed argument", [Char]). + +%% rest must be [A-Za-z0-9_] +new2(Acc, <>) when ($A =< L andalso L =< $Z) + orelse ($a =< L andalso L =< $z) + orelse ($0 =< L andalso L =< $9) + orelse (L =:= $_) -> + new2(<>, Rest); +new2(Acc, <<>>) -> + {ok, {c, Acc}}; +new2(Acc, Rest = <>) -> + WholeChar = <>, + Error = wfc_utils:emsg("wfc_wfchar:new2(~p, ~p): illegal character in wfchar: ~p; WholeChar: ~p", [Acc, Rest, BadChar, WholeChar]), + {error, Error}. + + + +-spec to_binary(wfchar()) -> binary(). + +to_binary({c, X}) -> X. diff --git a/src/wfc_word.erl b/src/wfc_word.erl new file mode 100644 index 0000000..53bbb47 --- /dev/null +++ b/src/wfc_word.erl @@ -0,0 +1,82 @@ +% @doc a word is an ordset of wfchars +% +% multiplication is implied in a word +% +% empty word is 1 +% +% anything times itself equals itself, so duplication is ignored +-module(wfc_word). + +-export_type([ + word/0 +]). + +-export([ + %% constructors + one/0, + validate/1, + from_wfchars/1, to_wfchars/1, + %% ops + mul/2, mul/1 +]). + +-opaque word() :: {w, ordsets:ordset(wfc_wfchar:wfchar())}. + +%%---------------------------- +%% constructor +%%---------------------------- + +-spec one() -> word(). + +one() -> + {w, []}. + + +validate(_) -> error(nyi). +to_wfchars(_) -> error(nyi). + +-spec from_wfchars(WfChars) -> Result + when WfChars :: list(wfc_char:wfchar()), + Result :: {ok, word()} + | {error, Reason :: string()}. + +from_wfchars(Chars) -> + from_wfchars(ordsets:from_list(Chars), []). + + +%% validate each char +from_wfchars([WfChar | Rest], Acc) -> + case wfc_wfchar:validate(WfChar) of + ok -> from_wfchars(Rest, [WfChar | Acc]); + Error -> Error + end; +% done, all good +from_wfchars([], Acc) -> + {ok, {w, lists:reverse(Acc)}}. + + +%%---------------------------- +%% ops +%%---------------------------- + +-spec mul(word(), word()) -> Result + when Result :: {ok, word()} + | {error, Reason :: string()}. +% @doc product of two words + +mul({w, X}, {w, Y}) -> + case from_wfchars(ordsets:union(X, Y)) of + Result = {ok, _} -> Result; + Error -> Error + end. + + +-spec mul(Words) -> Result + when Words :: [word()], + Result :: {ok, word()} + | {error, Reason}, + Reason :: string(). +% @doc multiply a list of words together + +mul([Word | Rest]) -> mul(Word, mul(Rest)); +mul([]) -> {ok, one()}.