From beed46a38b7567bded3f2506abd061f2ce9a44e0 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 2 Dec 2025 15:22:13 +0900 Subject: [PATCH] WIP: omg stuff --- src/hz_format.erl | 318 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 253 insertions(+), 65 deletions(-) diff --git a/src/hz_format.erl b/src/hz_format.erl index a516fb2..ad7f72e 100644 --- a/src/hz_format.erl +++ b/src/hz_format.erl @@ -4,7 +4,8 @@ -module(hz_format). --export([price/1, price/2, price/3, price/4, % read_price/1, +-export([price/1, price/2, price/3, price/4, + read/1, price_to_string/1, string_to_price/1]). -spec price(Pucks) -> Formatted @@ -19,9 +20,9 @@ price(Pucks) -> -spec price(Style, Pucks) -> Formatted - when Style :: us | ch | jp | {Separator, Period}, - Separator :: $, | $. | $_, - Period :: 3 | 4, + when Style :: us | ch | jp | {Separator, Myriad}, + Separator :: $, | $. | $_, + Myriad :: 3 | 4, Pucks :: integer(), Formatted :: string(). %% @doc @@ -33,42 +34,42 @@ price(Style, Pucks) -> -spec price(Unit, Style, Pucks) -> Formatted - when Unit :: gaju | puck, - Style :: us | ch | jp | {Separator, Period}, + when Unit :: gaju | puck, + Style :: us | ch | jp | {Separator, Myriad}, Separator :: $, | $. | $_, - Period :: 3 | 4, - Pucks :: integer(), - Formatted :: string(). + Myriad :: 3 | 4, + Pucks :: integer(), + Formatted :: string(). %% @doc %% A simplified format function covering the most common formats desired. %% %% ``` price(gaju, us, Pucks) -> - wetsern($,, $., 3, all, Pucks); + western($,, $., 3, all, Pucks); price(puck, us, Pucks) -> - wetsern($,, 3, Pucks); + western($,, 3, Pucks); price(gaju, ch, Pucks) -> - wetsern($., $,, 3, all, Pucks); + western($., $,, 3, all, Pucks); price(puck, ch, Pucks) -> - wetsern($,, 3, Pucks); + western($,, 3, Pucks); price(Unit, jp, Pucks) -> jp(Unit, all, Pucks); -price(gaju, {$., Period}, Pucks) -> - wetsern($., $,, Period, all, Pucks); -price(gaju, {Separator, Period}, Pucks) -> - wetsern(Separator, $., Period, all, Pucks); -price(puck, {Separator, Period}, Pucks) -> - wetsern(Separator, Period, Pucks). +price(gaju, {$., Myriad}, Pucks) -> + western($., $,, Myriad, all, Pucks); +price(gaju, {Separator, Myriad}, Pucks) -> + western(Separator, $., Myriad, all, Pucks); +price(puck, {Separator, Myriad}, Pucks) -> + western(Separator, Myriad, Pucks). -spec price(Unit, Style, Precision, Pucks) -> Serialized when Unit :: gaju | puck, - Style :: us | ch | jp | {Separator, Period}, + Style :: us | ch | jp | {Separator, Myriad}, Precision :: all | 0..18, Separator :: $, | $. | $_, - Period :: 3 | 4, + Myriad :: 3 | 4, Pucks :: integer(), Serialized :: string(). %% @doc @@ -110,36 +111,47 @@ price(puck, {Separator, Period}, Pucks) -> %% ''' price(gaju, us, Precision, Pucks) -> - wetsern($,, $., 3, Precision, Pucks); + western($,, $., 3, Precision, Pucks); price(gaju, ch, Precision, Pucks) -> - wetsern($., $,, 3, Precision, Pucks); + western($., $,, 3, Precision, Pucks); price(gaju, jp, Precision, Pucks) -> jp(gaju, Precision, Pucks); -price(gaju, {$., Period}, Precision, Pucks) -> - wetsern($., $,, Period, Precision, Pucks); -price(gaju, {Separator, Period}, Precision, Pucks) -> - wetsern(Separator, $., Period, Precision, Pucks); +price(gaju, {$., Myriad}, Precision, Pucks) -> + western($., $,, Myriad, Precision, Pucks); +price(gaju, {Separator, Myriad}, Precision, Pucks) -> + western(Separator, $., Myriad, Precision, Pucks); price(puck, us, _, Pucks) -> - wetsern($,, 3, Pucks); + western($,, 3, Pucks); price(puck, ch, _, Pucks) -> - wetsern($., 3, Pucks); + western($., 3, Pucks); price(puck, jp, _, Pucks) -> jp(puck, all, Pucks); -price(puck, {Separator, Period}, _, Pucks) -> - wetsern(Separator, Period, Pucks). +price(puck, {Separator, Myriad}, _, Pucks) -> + western(Separator, Myriad, Pucks). -wetsern(Separator, Period, Pucks) -> +western(Separator, Myriad, Pucks) when Pucks >= 0 -> + western2(Separator, Myriad, Pucks); +western(Separator, Myriad, Pucks) when Pucks < 0 -> + [$- | western2(Separator, Myriad, Pucks * -1)]. + +western2(Separator, Myriad, Pucks) -> P = lists:reverse(integer_to_list(Pucks)), - [puck_mark() | separate(Separator, Period, P)]. + [puck_mark() | separate(Separator, Myriad, P)]. -wetsern(Separator, _, Period, 0, Pucks) -> +western(Separator, Break, Myriad, Precision, Pucks) when Pucks >= 0 -> + western2(Separator, Break, Myriad, Precision, Pucks); +western(Separator, Break, Myriad, Precision, Pucks) when Pucks < 0 -> + [$- | western2(Separator, Break, Myriad, Precision, Pucks)]. + + +western2(Separator, _, Myriad, 0, Pucks) -> G = lists:reverse(integer_to_list(Pucks div one_gaju())), - [gaju_mark() | separate(Separator, Period, G)]; -wetsern(Separator, Break, Period, Precision, Pucks) -> + [gaju_mark() | separate(Separator, Myriad, G)]; +western2(Separator, Break, Myriad, Precision, Pucks) -> SP = integer_to_list(Pucks), Length = length(SP), Over18 = Length > 18, @@ -147,19 +159,19 @@ wetsern(Separator, Break, Period, Precision, Pucks) -> case {Over18, NoPucks} of {true, true} -> Gs = lists:reverse(lists:sublist(SP, Length - 18)), - [gaju_mark() | separate(Separator, Period, Gs)]; + [gaju_mark() | separate(Separator, Myriad, Gs)]; {true, false} -> {PChars, GChars} = lists:split(18, lists:reverse(SP)), - H = [gaju_mark() | separate(Separator, Period, GChars)], + H = [gaju_mark() | separate(Separator, Myriad, GChars)], {P, E} = decimal_pucks(Precision, lists:reverse(PChars)), - T = lists:reverse(separate(Separator, Period, P)), + T = lists:reverse(separate(Separator, Myriad, P)), lists:flatten([H, Break, T, E]); {false, true} -> [gaju_mark(), $0]; {false, false} -> PChars = lists:flatten(string:pad(SP, 18, leading, $0)), {P, E} = decimal_pucks(Precision, PChars), - T = lists:reverse(separate(Separator, Period, P)), + T = lists:reverse(separate(Separator, Myriad, P)), lists:flatten([gaju_mark(), $0, Break, T, E]) end. @@ -189,24 +201,44 @@ separate(S, P, N, [H | T], A) -> separate(S, P, N + 1, T, [H | A]). -jp(gaju, 0, Pucks) -> +jp(Unit, Precision, Pucks) when Pucks >= 0 -> + jp2(Unit, Precision, Pucks); +jp(Unit, Precision, Pucks) when Pucks < 0 -> + [$-, Formatted = jp2(Unit, Precision, Pucks * -1)]. + +jp2(gaju, 0, Pucks) -> G = lists:reverse(integer_to_list(Pucks div one_gaju())), - rank4(gaju_mark(), G); -jp(gaju, all, Pucks) -> + myriad4(gaju_mark(), h, G); +jp2(gaju, all, Pucks) -> H = jp(gaju, 0, Pucks), - T = jp(puck, all, Pucks rem one_gaju()), + P = lists:flatten(string:pad(integer_to_list(Pucks rem one_gaju()), 18, leading, $0), + T = myriad4("", l, lists:reverse(P)), lists:flatten([H, " ", T]); -jp(gaju, Precision, Pucks) -> +jp2(gaju, Precision, Pucks) -> H = jp(gaju, 0, Pucks), - T = jp(puck, Precision, Pucks rem one_gaju()), + P = lists:flatten(string:pad(integer_to_list(Pucks rem one_gaju()), 18, leading, $0), + Digits = min(Precision, 18), + T = + case length(P) > Digits of + true -> + []; + false -> + ReverseP = lists:reverse(lists:sublist(P, Digits)), + PuckingString = lists:flatten(string:pad(ReverseP, 18, leading, $0)), + case lists:all(fun(C) -> C =:= $0 end, PuckingString) of + false -> myriad4(puck_mark(), l, PuckingString); + true -> "" + end + end, + T = myriad4("", l, lists:reverse(P)), lists:flatten([H, " ", T]); -jp(puck, all, Pucks) -> +jp2(puck, all, Pucks) -> P = lists:reverse(integer_to_list(Pucks)), case lists:all(fun(C) -> C =:= $0 end, P) of - false -> rank4(puck_mark(), P); + false -> myriad4(puck_mark(), h, P); true -> [$0, puck_mark()] end; -jp(puck, Precision, Pucks) -> +jp2(puck, Precision, Pucks) -> Digits = min(Precision, 18), P = lists:flatten(string:pad(integer_to_list(Pucks), 18, leading, $0)), case length(P) < Digits of @@ -215,27 +247,27 @@ jp(puck, Precision, Pucks) -> false -> PuckingString = lists:flatten(string:pad(lists:reverse(lists:sublist(P, Digits)), 18, leading, $0)), case lists:all(fun(C) -> C =:= $0 end, PuckingString) of - false -> rank4(puck_mark(), PuckingString); + false -> myriad4(puck_mark(), h, PuckingString); true -> [$0, puck_mark()] end end. -rank4(Symbol, [$0, $0, $0, $0 | PT]) -> - rank4(ranks(jp), PT, [Symbol]); -rank4(Symbol, [P4, $0, $0, $0 | PT]) -> - rank4(ranks(jp), PT, [P4, Symbol]); -rank4(Symbol, [P4, P3, $0, $0 | PT]) -> - rank4(ranks(jp), PT, [P3, P4, Symbol]); -rank4(Symbol, [P4, P3, P2, $0 | PT]) -> - rank4(ranks(jp), PT, [P2, P3, P4, Symbol]); -rank4(Symbol, [P4, P3, P2, P1 | PT]) -> - rank4(ranks(jp), PT, [P1, P2, P3, P4, Symbol]); -rank4(Symbol, [P4]) -> +myriad4(Symbol, Segment, [$0, $0, $0, $0 | PT]) -> + rank4(ranks(Segment), PT, [Symbol]); +myriad4(Symbol, Segment, [P4, $0, $0, $0 | PT]) -> + rank4(ranks(Segment), PT, [P4, Symbol]); +myriad4(Symbol, Segment, [P4, P3, $0, $0 | PT]) -> + rank4(ranks(Segment), PT, [P3, P4, Symbol]); +myriad4(Symbol, Segment, [P4, P3, P2, $0 | PT]) -> + rank4(ranks(Segment), PT, [P2, P3, P4, Symbol]); +myriad4(Symbol, Segment, [P4, P3, P2, P1 | PT]) -> + rank4(ranks(Segment), PT, [P1, P2, P3, P4, Symbol]); +myriad4(Symbol, _, [P4]) -> [P4, Symbol]; -rank4(Symbol, [P4, P3]) -> +myriad4(Symbol, _, [P4, P3]) -> [P3, P4, Symbol]; -rank4(Symbol, [P4, P3, P2]) -> +myriad4(Symbol, _, [P4, P3, P2]) -> [P2, P3, P4, Symbol]. rank4([_ | RT], [$0, $0, $0, $0 | PT], A) -> @@ -279,8 +311,11 @@ rank4([RH | _], [P4, P3, P2], A) -> rank4([RH | _], [P4, P3, P2, P1], A) -> [P1, P2, P3, P4, RH | A]. -ranks(jp) -> - "万億兆京垓秭穣溝澗正載極". +ranks(h) -> + "万億兆京垓秭穣溝澗正載極"; +ranks(l) -> +% "曼戌弒俿咳". + "満億照鏡咳". gaju_mark() -> $木. @@ -290,6 +325,159 @@ puck_mark() -> $本. one_gaju() -> 1_000_000_000_000_000_000. +-spec read(Format) -> Result + when Format :: string(), + Result :: {ok, Pucks} | {error, Reason}, + Pucks :: integer(), + Reason :: {badarg, Partial :: string(), Rest :: term()} + | {incomplete, Partial :: string(), Rest :: binary()} + | format. +%% @doc +%% Convery any valid string formatted representation and output a value in pucks. +%% This routine can fail in the special case of `ch' style formatting with a single +%% comma and/or a single period in it, as this can trigger misinterpretation as `us' +%% style. When in doubt, always call `read/2' with a style specified. + +read(Format) -> + case assess_style(Format) of + us -> read(us, Format); + ch -> read(ch, Format); + jp -> read(jp, Format); + Error -> Error + end. + +-spec read(Style, Formatted) -> Result + when Style :: us | ch | jp | undefined, + Format :: string(), + Result :: {ok, Pucks} | {error, Reason}, + Pucks :: integer(), + Reason :: {badarg, Partial :: string(), Rest :: term()} + | {incomplete, Partial :: string(), Rest :: binary()} + | format. +%% @doc +%% Convert any valid string formatted representation and output a value in pucks. +%% Note that this function is deliberately a bit too permissive in the case of +%% western formatting, stripping all non-halfwidth digit characters on the high +%% and low sides of the format once the break (decimal) character is identified +%% and sign is determined. That is to say, there are many ways to feed this wacky +%% strings and get a number out of it, so be aware. + +read(Style, Format) -> + case unicode:characters_to_list(Format) of + String when is_list(String) -> + Trimmed = string:trim(String), + read2(Style, Trimmed); + {error, Partial, Rest} -> + {error, {badarg, Partial, Rest}}; + Incomplete -> + {error, Incomplete} + end. + +read2(us, Format) -> + read_western($., Format); +read2(ch, Format) -> + read_western($,, Format); +read2(jp, Format) -> + read_jp(Format); +read2(undefined, Format) -> + read(Format). + +read_western(Break, [$-, Rest]) -> + case read_western2(Break, Format) of + {ok, Pucks} -> {ok, Pucks * -1}; + Error -> Error + end; +read_western(Break, Format) -> + read_western2(Break, Format). + +read_western2(Break, Format) -> + case string:split(Format, [Break], all) of + [[], L] -> read_western3(0, L); + [H, []] -> read_western3(H, 0); + [H, L] -> read_western3(H, L); + [H] -> read_western3(H, 0); + _ -> {error, format} + end. + +read_western3(0, L) -> + read_l(L); +read_western3(H, 0) -> + case read_h(H) of + {ok, Gajus} -> {ok, Gajus * one_gaju()}; + Error -> Error + end; +read_western3(H, L) -> + Gajus = read_h(H), + Pucks = read_l(L), + {ok, (Gajus * one_gaju()) + Pucks}. + +read_h(S) -> + case lists:filter(fun is_numchar/1, S) of + [] -> 0; + F -> list_to_integer(Filtered) + end. + +read_l(L) -> + case lists:filter(fun is_numchar/1, S) of + [] -> 0; + F -> list_to_integer(lists:flatten(string:pad(F, 18, trailing, $0))) + end. + +is_numchar(C) -> $0 =< C andalso C =< $9. + +read_jp([$-, Format]) -> + read_jp_neg(Format); +read_jp([$-, Format]) -> + read_jp_neg(Format); +read_jp(Format) -> + read_jp2(Format). + +read_jp_neg(Format) -> + case read_jp2(Format) of + {ok, Pucks} -> {ok, Pucks * -1}; + Error -> Error + end. + +read_jp2(Format) -> + case segment_jp(Format) of + {ok, Segments} -> assemble_jp(Segments); + Error -> Error + end. + +segment_jp(Format) -> + case string:split(Format, [gaju_mark()], all) of + [Gajus, Pucks] -> + case read_segment(Gajus) of + {ok, + [Gajus] -> + case read_segment(Gajus) of + {ok, GajuSegments} -> {ok, GajuSegments, ["0"]}; + Error -> Error + end; + [] -> + {ok, ["0"], ["0"]}; + _ -> + {error, format} + end. + +assemble_jp({GajuSegments, PuckSegments}) -> + GajuString = lists:flatten(lists:map(fun expand_jp_myriad/1, GajuSegments)), + PuckString = lists:flatten(lists:map(fun expand_jp_myriad/1, PuckDegments)), + {ok, integer_to_list(lists:append(GajuString, PuckString))}. +assemble_jp(PuckSegments) -> + PuckString = lists:flatten(lists:map(fun expand_jp_myriad/1, PuckDegments)), + {ok, integer_to_list(PuckString)}. + +expand_jp_myriad(String) -> + string:pad(String, 4, leading, $0). + + +lower_jp_numchar(C) when $0 =< C andalso C =< $9 -> + C - $0; +lower_jp_numchar(C) -> + C. + + -spec price_to_string(Pucks) -> Gajus when Pucks :: integer(), Gajus :: string().