diff --git a/src/hz_format.erl b/src/hz_format.erl new file mode 100644 index 0000000..d99acd8 --- /dev/null +++ b/src/hz_format.erl @@ -0,0 +1,717 @@ +%%% @doc +%%% Formatting and reading functions for Gaju and Puck quantities +%%% +%%% The numbers involved in dealing with blockchain amounts are enormous +%%% by comparison to legacy forms of currency. It isn't so much that +%%% thousands of Gajus is hard to reason about, but rather that quadrillions +%%% of Pucks is quite hard to even lock on to visually. +%%% +%%% A normal commas and underscores method of decimal formatting is provided, as +%%% `us' formatting along with two additional approaches: +%%% - Japanese traditional myriad structure (`jp' style) +%%% - An internationalized variant inspired by the Japanese technique over periods +%%% (`metric' for SI prefixes, and `legacy' for Anglicized prefixes) +%%% +%%% These are all accessible via the `amount/N' functions. +%%% +%%% The `read/1' function can accept any of the output variants as a string and +%%% will return the number of pucks indicated by the provided string, allowing for +%%% simple copy/paste functionality as well as direct input using any of the +%%% supported notations. +%%% @end + +-module(hz_format). +-vsn("0.7.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-export([amount/1, amount/2, amount/3, + approx_amount/2, approx_amount/3, + read/1, + one/1, mark/1, + price_to_string/1, string_to_price/1]). + +-spec amount(Pucks) -> Formatted + when Pucks :: integer(), + Formatted :: string(). +%% @doc +%% A convenience formatting function. +%% ``` +%% hz_format:amount(1) -> +%% 木0.000,000,000,000,000,001 +%% +%% hz_format:amount(5000) -> +%% 木0.000,000,000,000,005 +%% +%% hz_format:amount(5000000000000000000) -> +%% 木5 +%% +%% hz_format:amount(500000123000000000000000) -> +%% 木500,000.123 +%% ''' +%% @equiv amount(us, Pucks). + +amount(Pucks) -> + amount(us, Pucks). + + +-spec amount(Style, Pucks) -> Formatted + when Style :: us | jp | metric | legacy | {Separator, Span}, + Separator :: $, | $_, + Span :: 3 | 4, + Pucks :: integer(), + Formatted :: string(). +%% @doc +%% A money formatting function. +%% ``` +%% hz_format:amount(us, 100500040123000000000000000) -> +%% 木100,500,040.123 +%% +%% hz_format:amount(jp, 100500040123000000000000000) -> +%% 1億50万40木 12京3000兆本 +%% +%% hz_format:amount(metric, 100500040123000000000000000) -> +%% 木100m 500k 40 G 123p P +%% +%% hz_format:amount(legacy, 100500040123000000000000000) -> +%% 木100m 500k 40 G 123q P +%% +%% hz_format:amount({$_, 3}, 100500040123000000000000000) -> +%% 木100_500_040.123 +%% +%% hz_format:amount({$_, 4}, 100500040123000000000000000) -> +%% 木1_0050_0040.123 +%% ''' +%% @equiv amount(gaju, Style, Pucks). + +amount(Style, Pucks) -> + amount(gaju, Style, Pucks). + + +-spec amount(Unit, Style, Pucks) -> Formatted + when Unit :: gaju | puck, + Style :: us | jp | metric | legacy | {Separator, Span}, + Separator :: $, | $_, + Span :: 3 | 4, + Pucks :: integer(), + Formatted :: string(). +%% @doc +%% A simplified format function covering the most common formats desired. +%% ``` +%% hz_format:amount(gaju, us, 100500040123000004500000000) -> +%% 木100,500,040.123,000,004,5 +%% +%% hz_format:amount(puck, us, 100500040123000004500000000) -> +%% 本100,500,040,123,000,004,500,000,000 +%% +%% hz_format:amount(gaju, jp, 100500040123000004500000000) -> +%% 1億50万40木 12京3000兆45億本 +%% +%% hz_format:amount(puck, jp, 100500040123000004500000000) -> +%% 100秭5000垓4012京3000兆45億本 +%% +%% hz_format:amount(gaju, metric, 100500040123000004500000000) -> +%% 木100m 500k 40 G 123p 4g 500m P +%% +%% hz_format:amount(puck, metric, 100500040123000004500000000) -> +%% 本100y 500z 40e 123p 4g 500m P +%% +%% hz_format:amount(gaju, legacy, 100500040123000004500000000) -> +%% 木100m 500k 40 G 123q 4b 500m P +%% +%% hz_format:amount(puck, legacy, 100500040123000004500000000) -> +%% 本100y 500z 40e 123q 4b 500m P +%% ''' + +amount(gaju, us, Pucks) -> + western($,, $., 3, all, Pucks); +amount(puck, us, Pucks) -> + western($,, 3, Pucks); +amount(Unit, jp, Pucks) -> + jp(Unit, all, Pucks); +amount(Unit, metric, Pucks) -> + bestern(Unit, ranks(metric), all, Pucks); +amount(Unit, legacy, Pucks) -> + bestern(Unit, ranks(heresy), all, Pucks); +amount(gaju, {Separator, Span}, Pucks) -> + western(Separator, $., Span, all, Pucks); +amount(puck, {Separator, Span}, Pucks) -> + western(Separator, Span, Pucks). + + + +-spec approx_amount(Precision, Pucks) -> Serialized + when Precision :: all | 0..18, + Pucks :: integer(), + Serialized :: string(). +%% A formatter for decimal notation which permits a precision +%% value to be applied to the puck side of the format. +%% @equiv approx_amount(us, Precision, Pucks). +%% ``` +%% hz_format:approx_amount(3, 100500040123000004500000001) -> +%% 木100,500,040.123... +%% +%% hz_format:approx_amount(13, 100500040123000004500000001) -> +%% 木100,500,040.123,000,004,5... +%% +%% hz_format:approx_amount(all, 100500040123000004500000001) -> +%% 木100,500,040.123,000,004,500,000,001 +%% ''' + +approx_amount(Precision, Pucks) -> + approx_amount(us, Precision, Pucks). + + +-spec approx_amount(Style, Precision, Pucks) -> Serialized + when Style :: us | {Separator, Span}, + Precision :: all | 0..18, + Separator :: $, | $_, + Span :: 3 | 4, + Pucks :: integer(), + Serialized :: string(). +%% @doc +%% A formatter for decimal notation which permits a precision +%% value to be applied to the puck side of the format. +%% ``` +%% hz_format:approx_amount({$_, 3}, 3, 100500040123000004500000001) -> +%% 木100_500_040.123... +%% +%% hz_format:approx_amount({$_, 4}, 12, 100500040123000004500000001) -> +%% 木1_0050_0040.1230_0000_45... +%% ''' + +approx_amount(us, Precision, Pucks) -> + western($,, $., 3, Precision, Pucks); +approx_amount({Separator, Span}, Precision, Pucks) -> + western(Separator, $., Span, Precision, Pucks). + + +western(Separator, Span, Pucks) when Pucks >= 0 -> + western2(Separator, Span, Pucks); +western(Separator, Span, Pucks) when Pucks < 0 -> + [$- | western2(Separator, Span, Pucks * -1)]. + +western2(Separator, Span, Pucks) -> + P = lists:reverse(integer_to_list(Pucks)), + [mark(puck) | separate(Separator, Span, P)]. + + +western(Separator, Break, Span, Precision, Pucks) when Pucks >= 0 -> + western2(Separator, Break, Span, Precision, Pucks); +western(Separator, Break, Span, Precision, Pucks) when Pucks < 0 -> + [$- | western2(Separator, Break, Span, Precision, Pucks * -1)]. + + +western2(Separator, _, Span, 0, Pucks) -> + G = lists:reverse(integer_to_list(Pucks div one(gaju))), + [mark(gaju) | separate(Separator, Span, G)]; +western2(Separator, Break, Span, Precision, Pucks) -> + SP = integer_to_list(Pucks), + Length = length(SP), + Over18 = Length > 18, + NoPucks = (Pucks rem one(gaju)) =:= 0, + case {Over18, NoPucks} of + {true, true} -> + Gs = lists:reverse(lists:sublist(SP, Length - 18)), + [mark(gaju) | separate(Separator, Span, Gs)]; + {true, false} -> + {PChars, GChars} = lists:split(18, lists:reverse(SP)), + H = [mark(gaju) | separate(Separator, Span, GChars)], + {P, E} = decimal_pucks(Precision, lists:reverse(PChars)), + T = lists:reverse(separate(Separator, Span, P)), + lists:flatten([H, Break, T, E]); + {false, true} -> + [mark(gaju), $0]; + {false, false} -> + PChars = lists:flatten(string:pad(SP, 18, leading, $0)), + {P, E} = decimal_pucks(Precision, PChars), + T = lists:reverse(separate(Separator, Span, P)), + lists:flatten([mark(gaju), $0, Break, T, E]) + end. + +decimal_pucks(all, PChars) -> + RTrailing = lists:reverse(PChars), + {lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), ""}; +decimal_pucks(Precision, PChars) -> + {Significant, Rest} = lists:split(min(Precision, 18), PChars), + RTrailing = lists:reverse(Significant), + Trailing = lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), + case lists:all(fun(C) -> C =:= $0 end, Rest) of + true -> {Trailing, ""}; + false -> {Trailing, "..."} + end. + +separate(_, _, "") -> + ""; +separate(S, P, G) -> + separate(S, P, 1, G, []). + +separate(_, _, _, [H], A) -> + [H | A]; +separate(S, P, P, [H | T], A) -> + separate(S, P, 1, T, [S, H | A]); +separate(S, P, N, [H | T], A) -> + separate(S, P, N + 1, T, [H | A]). + + +bestern(gaju, Ranks, Precision, Pucks) when Pucks >= 0 -> + [mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks)]; +bestern(gaju, Ranks, Precision, Pucks) when Pucks < 0 -> + [$-, mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)]; +bestern(puck, Ranks, Precision, Pucks) when Pucks >= 0 -> + [mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks)]; +bestern(puck, Ranks, Precision, Pucks) when Pucks < 0 -> + [$-, mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks * -1)]. + +jp(Unit, Precision, Pucks) when Pucks >= 0 -> + bestern2(Unit, ranks(jp), 4, Precision, Pucks); +jp(Unit, Precision, Pucks) when Pucks < 0 -> + [$-, bestern2(Unit, ranks(jp), 4, Precision, Pucks * -1)]. + +bestern2(gaju, Ranks, Span, 0, Pucks) -> + G = lists:reverse(integer_to_list(Pucks div one(gaju))), + case Span of + 3 -> period("G", Ranks, G); + 4 -> myriad(mark(gaju), Ranks, G) + end; +bestern2(gaju, Ranks, Span, all, Pucks) -> + P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)), + Zilch = lists:all(fun(C) -> C =:= $0 end, P), + {H, T} = + case {Span, Zilch} of + {3, false} -> {bestern2(gaju, Ranks, 3, 0, Pucks), period("P", Ranks, lists:reverse(P))}; + {4, false} -> {jp(gaju, 0, Pucks), myriad(mark(puck), Ranks, lists:reverse(P))}; + {3, true} -> {bestern2(gaju, Ranks, 3, 0, Pucks), ""}; + {4, true} -> {jp(gaju, 0, Pucks), ""} + end, + lists:flatten([H, " ", T]); +bestern2(gaju, Ranks, Span, Precision, Pucks) -> + P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)), + H = + case Span of + 3 -> bestern2(gaju, Ranks, 3, 0, Pucks); + 4 -> jp(gaju, 0, Pucks) + end, + Digits = min(Precision, 18), + T = + case length(P) < Digits of + 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 -> + case Span of + 3 -> period("P", Ranks, PuckingString); + 4 -> myriad(mark(puck), Ranks, PuckingString) + end; + true -> + "" + end; + true -> + [] + end, + lists:flatten([H, " ", T]); +bestern2(puck, Ranks, Span, all, Pucks) -> + P = lists:reverse(integer_to_list(Pucks)), + case lists:all(fun(C) -> C =:= $0 end, P) of + false -> + case Span of + 3 -> period("P", Ranks, P); + 4 -> myriad(mark(puck), Ranks, P) + end; + true -> + case Span of + 3 -> [$0, " P"]; + 4 -> [$0, mark(puck)] + end + end; +bestern2(puck, Ranks, Span, Precision, Pucks) -> + Digits = min(Precision, 18), + P = lists:flatten(string:pad(integer_to_list(Pucks), 18, leading, $0)), + case length(P) < Digits of + true -> + case Span of + 3 -> [$0, " P"]; + 4 -> [$0, mark(puck)] + end; + false -> + PucksToGive = lists:sublist(P, Digits), + PuckingString = lists:flatten(string:pad(lists:reverse(PucksToGive), 18, leading, $0)), + case lists:all(fun(C) -> C =:= $0 end, PuckingString) of + false -> + case Span of + 3 -> period("P", Ranks, PuckingString); + 4 -> myriad(mark(puck), Ranks, PuckingString) + end; + true -> + case Span of + 3 -> [$0, " P"]; + 4 -> [$0, mark(puck)] + end + end + end. + + +period(Symbol, Ranks, [$0, $0, $0 | PT]) -> + rank3(Ranks, PT, [Symbol]); +period(Symbol, Ranks, [P3, $0, $0 | PT]) -> + rank3(Ranks, PT, [P3, 32, Symbol]); +period(Symbol, Ranks, [P3, P2, $0 | PT]) -> + rank3(Ranks, PT, [P2, P3, 32, Symbol]); +period(Symbol, Ranks, [P3, P2, P1 | PT]) -> + rank3(Ranks, PT, [P1, P2, P3, 32, Symbol]); +period(Symbol, _, [P3]) -> + [P3, 32, Symbol]; +period(Symbol, _, [P3, P2]) -> + [P2, P3, 32, Symbol]. + +rank3([_ | RT], [$0, $0, $0 | PT], A) -> + rank3(RT, PT, A); +rank3([RH | RT], [P3, $0, $0 | PT], A) -> + rank3(RT, PT, [P3, RH | A]); +rank3([RH | RT], [P3, P2, $0 | PT], A) -> + rank3(RT, PT, [P2, P3, RH | A]); +rank3([RH | RT], [P3, P2, P1 | PT], A) -> + rank3(RT, PT, [P1, P2, P3, RH | A]); +rank3(_, [$0, $0, $0], A) -> + A; +rank3(_, [$0, $0], A) -> + A; +rank3(_, [$0], A) -> + A; +rank3(_, [], A) -> + A; +rank3([RH | _], [P3, $0, $0], A) -> + [P3, RH | A]; +rank3([RH | _], [P3, $0], A) -> + [P3, RH | A]; +rank3([RH | _], [P3], A) -> + [P3, RH | A]; +rank3([RH | _], [P3, P2, $0], A) -> + [P2, P3, RH | A]; +rank3([RH | _], [P3, P2], A) -> + [P2, P3, RH | A]; +rank3([RH | _], [P3, P2, P1], A) -> + [P1, P2, P3, RH | A]. + + +myriad(Symbol, Ranks, [$0, $0, $0, $0 | PT]) -> + rank4(Ranks, PT, [Symbol]); +myriad(Symbol, Ranks, [P4, $0, $0, $0 | PT]) -> + rank4(Ranks, PT, [P4, Symbol]); +myriad(Symbol, Ranks, [P4, P3, $0, $0 | PT]) -> + rank4(Ranks, PT, [P3, P4, Symbol]); +myriad(Symbol, Ranks, [P4, P3, P2, $0 | PT]) -> + rank4(Ranks, PT, [P2, P3, P4, Symbol]); +myriad(Symbol, Ranks, [P4, P3, P2, P1 | PT]) -> + rank4(Ranks, PT, [P1, P2, P3, P4, Symbol]); +myriad(Symbol, _, [P4]) -> + [P4, Symbol]; +myriad(Symbol, _, [P4, P3]) -> + [P3, P4, Symbol]; +myriad(Symbol, _, [P4, P3, P2]) -> + [P2, P3, P4, Symbol]. + +rank4([_ | RT], [$0, $0, $0, $0 | PT], A) -> + rank4(RT, PT, A); +rank4([RH | RT], [P4, $0, $0, $0 | PT], A) -> + rank4(RT, PT, [P4, RH | A]); +rank4([RH | RT], [P4, P3, $0, $0 | PT], A) -> + rank4(RT, PT, [P3, P4, RH | A]); +rank4([RH | RT], [P4, P3, P2, $0 | PT], A) -> + rank4(RT, PT, [P2, P3, P4, RH | A]); +rank4([RH | RT], [P4, P3, P2, P1 | PT], A) -> + rank4(RT, PT, [P1, P2, P3, P4, RH | A]); +rank4(_, [$0, $0, $0, $0], A) -> + A; +rank4(_, [$0, $0, $0], A) -> + A; +rank4(_, [$0, $0], A) -> + A; +rank4(_, [$0], A) -> + A; +rank4(_, [], A) -> + A; +rank4([RH | _], [P4, $0, $0, $0], A) -> + [P4, RH | A]; +rank4([RH | _], [P4, $0, $0], A) -> + [P4, RH | A]; +rank4([RH | _], [P4, $0], A) -> + [P4, RH | A]; +rank4([RH | _], [P4], A) -> + [P4, RH | A]; +rank4([RH | _], [P4, P3, $0, $0], A) -> + [P3, P4, RH | A]; +rank4([RH | _], [P4, P3, $0], A) -> + [P3, P4, RH | A]; +rank4([RH | _], [P4, P3], A) -> + [P3, P4, RH | A]; +rank4([RH | _], [P4, P3, P2, $0], A) -> + [P2, P3, P4, RH | A]; +rank4([RH | _], [P4, P3, P2], A) -> + [P2, P3, P4, RH | A]; +rank4([RH | _], [P4, P3, P2, P1], A) -> + [P1, P2, P3, P4, RH | A]. + +ranks(jp) -> + "万億兆京垓秭穣溝澗正載極"; +ranks(metric) -> + ["k ", "m ", "g ", "t ", "p ", "e ", "z ", "y ", "r ", "Q "]; +ranks(heresy) -> + ["k ", "m ", "b ", "t ", "q ", "e ", "z ", "y ", "r ", "Q "]. + + +mark(gaju) -> $木; +mark(puck) -> $本. + +one(gaju) -> 1_000_000_000_000_000_000; +one(puck) -> 1. + + +-spec read(Format) -> Result + when Format :: string(), + Result :: {ok, Pucks} | error, + Pucks :: integer(). +%% @doc +%% Convert any valid string formatted representation and output a value in pucks. +%% NOTE: This function does not accept approximated values. +%% ``` +%% 1> hz_format:read("木100,500,040.123,000,004,5"). +%% {ok,100500040123000004500000000} +%% 2> hz_format:read("本100,500,040,123,000,004,500,000,000"). +%% {ok,100500040123000004500000000} +%% 3> hz_format:read("1億50万40木 12京3000兆45億本"). +%% {ok,100500040123000004500000000} +%% 4> hz_format:read("100秭5000垓4012京3000兆45億本"). +%% {ok,100500040123000004500000000} +%% 5> hz_format:read("木100m 500k 40 G 123p 4g 500m P"). +%% {ok,100500040123000004500000000} +%% 6> hz_format:read("本100y 500z 40e 123p 4g 500m P"). +%% {ok,100500040123000004500000000} +%% 7> hz_format:read("木100m 500k 40 G 123q 4b 500m P"). +%% {ok,100500040123000004500000000} +%% 8> hz_format:read("本100y 500z 40e 123q 4b 500m P"). +%% {ok,100500040123000004500000000} +%% ''' + +read([$木 | Rest]) -> + read_w_gajus(Rest, []); +read([$本 | Rest]) -> + read_w_pucks(Rest, []); +read([C | Rest]) + when C =:= $- orelse + C =:= $− orelse + C =:= $- -> + case read(Rest) of + {ok, Pucks} -> {ok, Pucks * -1}; + Error -> Error + end; +read([C | Rest]) + when C =:= 32 orelse % ASCII space + C =:= 12288 orelse % full-width space + C =:= $\t orelse + C =:= $\r orelse + C =:= $\n -> + read(Rest); +read([C | Rest]) when $0 =< C andalso C =< $9 -> + read(Rest, [C], []); +read([C | Rest]) when $0 =< C andalso C =< $9 -> + NumC = C - $0 + $0, + read(Rest, [NumC], []); +read(String) when is_binary(String) -> + read(binary_to_list(String)); +read(_) -> + error. + +read_w_gajus([C | Rest], A) when $0 =< C andalso C =< $9 -> + read_w_gajus(Rest, [C | A]); +read_w_gajus([C | Rest], A) when $0 =< C andalso C =< $9 -> + NumC = C - $0 + $0, + read_w_gajus(Rest, [NumC | A]); +read_w_gajus([$, | Rest], A) -> + read_w_gajus(Rest, A); +read_w_gajus([$_ | Rest], A) -> + read_w_gajus(Rest, A); +read_w_gajus([$. | Rest], A) -> + case read_w_pucks(Rest, []) of + {ok, P} -> + G = list_to_integer(lists:reverse(A)) * one(gaju), + {ok, G + P}; + Error -> + Error + end; +read_w_gajus([], A) -> + G = list_to_integer(lists:reverse(A)) * one(gaju), + {ok, G}; +read_w_gajus([C, 32 | Rest], A) -> + read(Rest, [], [{C, A}]); +read_w_gajus([32, $G, 32 | Rest], A) -> + read(Rest, [], [{$G, A}], []); +read_w_gajus([32, $G], A) -> + calc([{$G, A}], []); +read_w_gajus([32, $P], A) -> + calc([], [{$P, A}]); +read_w_gajus(A, B) -> + io:format("A: ~ts, B: ~p~n", [A, B]), + error. + +read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 -> + read_w_pucks(Rest, [C | A]); +read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 -> + NumC = C - $0 + $0, + read_w_pucks(Rest, [NumC | A]); +read_w_pucks([$, | Rest], A) -> + read_w_pucks(Rest, A); +read_w_pucks([$_ | Rest], A) -> + read_w_pucks(Rest, A); +read_w_pucks([C, 32 | Rest], A) -> + read(Rest, [], [], [{C, A}]); +read_w_pucks([32, $P], A) -> + calc([], [{$P, A}]); +read_w_pucks([], A) -> + Padded = lists:flatten(string:pad(lists:reverse(A), 18, trailing, $0)), + {ok, list_to_integer(Padded)}. + + +read([C | Rest], A, G) when $0 =< C andalso C =< $9 -> + read(Rest, [C | A], G); +read([C | Rest], A, G) when $0 =< C andalso C =< $9 -> + NumC = C - $0 + $0, + read(Rest, [NumC | A], G); +read([$木], A, G) -> + calc([{$G, A} | G], []); +read([$G], A, G) -> + calc([{$G, A} | G], []); +read([32, $G], A, G) -> + calc([{$G, A} | G], []); +read([32, $G, 32 | Rest], A, G) -> + read(Rest, [], [{$G, A} | G], []); +read([$木, 32 | Rest], A, G) -> + read(Rest, [], [{$G, A} | G], []); +read([$G, 32 | Rest], A, G) -> + read(Rest, [], [{$G, A} | G], []); +read([$本], A, P) -> + calc([], [{$P, A} | P]); +read([$P], A, P) -> + calc([], [{$P, A} | P]); +read([32, $P], A, P) -> + calc([], [{$P, A} | P]); +read([C, 32 | Rest], A, G) -> + read(Rest, [], [{C, A} | G]); +read([C | Rest], A, G) -> + read(Rest, [], [{C, A} | G]); +read(Rest, A, G) -> + io:format("read(\"~ts\", ~tp, ~tp) -> died!~n", [Rest, A, G]), + error. + + +read([C | Rest], A, G, P) when $0 =< C andalso C =< $9 -> + read(Rest, [C | A], G, P); +read([C | Rest], A, G, P) when $0 =< C andalso C =< $9 -> + NumC = C - $0 + $0, + read(Rest, [NumC | A], G, P); +read([$本], A, G, P) -> + calc(G, [{$P, A} | P]); +read([$P], A, G, P) -> + calc(G, [{$P, A} | P]); +read([32, $P], A, G, P) -> + calc(G, [{$P, A} | P]); +read([C, 32 | Rest], A, G, P) -> + read(Rest, [], G, [{C, A} | P]); +read([C | Rest], A, G, P) -> + read(Rest, [], G, [{C, A} | P]); +read(_, _, _, _) -> + io:format("read/4 died!~n"), + error. + +calc(G, P) -> + case calc(gaju, G, 0) of + {ok, Gajus} -> + case calc(puck, P, 0) of + {ok, Pucks} -> {ok, Gajus + Pucks}; + error -> error + end; + error -> + error + end. + +calc(U, [{_, []} | S], A) -> + calc(U, S, A); +calc(U, [{M, Cs} | S], A) -> + case magnitude(M) of + {ok, J} -> + N = list_to_integer(lists:reverse(Cs)) * J * one(U), + calc(U, S, A + N); + Error -> + Error + end; +calc(_, [], A) -> + {ok, A}. + + +magnitude($G) -> + {ok, 1}; +magnitude($P) -> + {ok, 1}; +magnitude(Mark) -> + case rank(Mark, ranks(jp), 1_0000, 1) of + {ok, J} -> + {ok, J}; + error -> + case rank([Mark, 32], ranks(metric), 1_000, 1) of + {ok, J} -> {ok, J}; + error -> rank([Mark, 32], ranks(heresy), 1_000, 1) + end + end. + +rank(Mark, [Mark | _], Magnitude, Sum) -> + {ok, Sum * Magnitude}; +rank(Mark, [_ | Rest], Magnitude, Sum) -> + rank(Mark, Rest, Magnitude, Sum * Magnitude); +rank(_, [], _, _) -> + error. + + +-spec price_to_string(Pucks) -> Gajus + when Pucks :: integer(), + Gajus :: string(). +%% @doc +%% A simplified formatting function that converts an integer value in Pucks to a string representation +%% in Gajus. Useful for formatting generic output for UI elements + +price_to_string(Pucks) -> + Gaju = one(gaju), + H = integer_to_list(Pucks div Gaju), + R = Pucks rem Gaju, + case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of + [] -> H; + T -> string:join([H, T], ".") + end. + + +-spec string_to_price(Gajus) -> Pucks + when Gajus :: string(), + Pucks :: integer(). +%% @doc +%% A simplified formatting function that converts a Gaju value represented as a string to an +%% integer value in Pucks. + +string_to_price(String) -> + case string:split(String, ".") of + [H] -> join_price(H, "0"); + [H, T] -> join_price(H, T); + _ -> {error, bad_price} + end. + +join_price(H, T) -> + try + Parts = [H, string:pad(T, 18, trailing, $0)], + Price = list_to_integer(unicode:characters_to_list(Parts)), + case Price < 0 of + false -> {ok, Price}; + true -> {error, negative_price} + end + catch + error:R -> {error, R} + end.