fewd/src/wfc_ltr.erl
Peter Harpending 5135a55081 wip
2025-09-29 13:34:08 -07:00

92 lines
2.5 KiB
Erlang

% @doc called ltr to disambiguate from "char"
%
% In "A + B + AB", this is one of the individual letters.
%
% a letter is a binary. atom is more natural representation but this is http,
% can't be spamming the atoms table.
%
% mathematically, this is a variable like "a", "b", "c", etc
-module(wfc_ltr).
-export_type([
ltr/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 ltr() :: {c, binary()}.
%%-----------------------
%% constructors/destructors
%%-----------------------
-spec validate(Ltr) -> Result
when Ltr :: ltr(),
Result :: ok
| {error, Reason :: string()}.
% @doc validate if a candidate ltr 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_ltr:validate failed; malformed ltr: ~p", [Term]),
{error, Error}.
-spec from_binary(Binary) -> Result
when Binary :: binary(),
Result :: {ok, ltr()}
| {error, Reason :: string()}.
% @doc
% make sure the binary is a legal ltr and wrap it in a {c, Binary} tuple
% @end
%% initial char must be [a-z]
from_binary(<<L:8, Rest/binary>>) when $a =< L, L =< $z ->
new2(<<L>>, Rest);
from_binary(Char = <<L:8, _/binary>>) ->
{error, wfc_utils:emsg("wfc_ltr:from_binary(~p): illegal lead character: ~p", [Char, L])};
from_binary(Char) ->
wfc_utils:err("wfc_ltr:from_binary(~p): malformed argument", [Char]).
%% rest must be [A-Za-z0-9_]
new2(Acc, <<L:8, Rest/binary>>) when ($A =< L andalso L =< $Z)
orelse ($a =< L andalso L =< $z)
orelse ($0 =< L andalso L =< $9)
orelse (L =:= $_) ->
new2(<<Acc/binary, L>>, Rest);
new2(Acc, <<>>) ->
{ok, {c, Acc}};
new2(Acc, Rest = <<BadChar:8, _/binary>>) ->
WholeChar = <<Acc/binary, Rest/binary>>,
Error = wfc_utils:emsg("wfc_ltr:new2(~p, ~p): illegal character in ltr: ~p; WholeChar: ~p", [Acc, Rest, BadChar, WholeChar]),
{error, Error}.
-spec to_binary(ltr()) -> Result
when Result :: {ok, binary()}
| {error, string()}.
to_binary({c, X}) -> {ok, X};
to_binary(Bad) -> {error, wfc_utils:str("wfc_ltr:to_binary: bad letter: ~tp", [Bad])}.