fewd/src/wfc_read.erl
2025-10-02 08:44:17 -07:00

168 lines
5.2 KiB
Erlang

-module(wfc_read).
-export_type([
]).
-export([
expr/1,
sexp/1,
ltr/1,
snowflake/1,
pattern/1,
val/1,
whitespace/1
]).
-type op() :: {op, '+' | '*'}.
-type val() :: {val, 0 | 1}.
-type ltr() :: wfc_ltr:ltr().
-type snowflake() :: {snowflake, binary()}.
-type pattern() :: {pattern, binary()}.
-type sexp() :: {sexp, [expr()]}.
-type expr() :: sexp() | ltr() | op() | snowflake() | pattern() | val().
-spec expr(string()) -> {ok, expr(), Rest :: string()} | {error, string()}.
expr(Str0) ->
{ok, skip, Str1} = whitespace(Str0),
attempt([fun sexp/1, fun op/1, fun ltr/1, fun snowflake/1, fun pattern/1, fun val/1], Str1).
attempt([Fun | Rest], Str) ->
case Fun(Str) of
{error, _} -> attempt(Rest, Str);
Result -> Result
end;
attempt([], Bad) ->
{error, wfc_utils:str("wfc_read:attempt: invalid expression: ~p", [Bad])}.
-spec sexp(string()) -> {ok, sexp(), Rest :: string()} | {error, string()}.
sexp(Str0) ->
{ok, skip, Str1} = whitespace(Str0),
case sexp_open(Str1) of
{ok, round, Str2} -> sexp2(round, [], Str2);
Error -> Error
end.
sexp_open("[" ++ Rest) -> {ok, square, Rest};
sexp_open("(" ++ Rest) -> {ok, round, Rest};
sexp_open(Bad) -> {error, wfc_utils:str("wfc_read:sexp_open: invalid sexp: ~p", [Bad])}.
sexp2(Shape, AccExprs, Str0) ->
{ok, skip, Str1} = whitespace(Str0),
case {Shape, attempt([fun expr/1, fun(S) -> sexp_close(Shape, S) end], Str1)} of
{round, {ok, round, Rem}} ->
{ok, {sexp, lists:reverse(AccExprs)}, Rem};
{square, {ok, square, Rem}} ->
{ok, {sexp, lists:reverse(AccExprs)}, Rem};
{round, {ok, square, Rem}} ->
{error, wfc_utils:str("wfc_read:sexp2: ( terminated by ]; parsed: ~p; rem: ~p", [lists:reverse(AccExprs), Rem])};
{square, {ok, round, Rem}} ->
{error, wfc_utils:str("wfc_read:sexp2: [ terminated by ); parsed: ~p; rem: ~p", [lists:reverse(AccExprs), Rem])};
{_, {ok, Expr, Rem}} ->
sexp2(Shape, [Expr | AccExprs], Rem);
{_, Error} ->
Error
end.
sexp_close(_, "]" ++ Rem) -> {ok, square, Rem};
sexp_close(_, ")" ++ Rem) -> {ok, round, Rem};
sexp_close(_, Bad) -> {error, wfc_utils:str("wfc_read:sexp_close: invalid closing bracket: ~p", [Bad])}.
-spec op(string()) -> {ok, op(), Rest :: string()} | {error, string()}.
op("*" ++ Rest) -> {ok, {op, '*'}, Rest};
op("+" ++ Rest) -> {ok, {op, '+'}, Rest};
op(Invalid) -> {error, wfc_utils:str("wfc_read:op: invalid op: ~p", [Invalid])}.
-spec ltr(string()) -> {ok, ltr(), Rest :: string()} | {error, string()}.
%% @doc read a 'letter': a sequence of
ltr(Str) ->
case ltr_init(Str) of
{ok, InitLetter, Rest} -> ltr_guts(<<InitLetter:8>>, Rest);
Error -> Error
end.
ltr_init([Char | Rest]) when $a =< Char, Char =< $z ->
{ok, Char, Rest};
ltr_init(Ltr) ->
{error, wfc_utils:str("wfc_read:ltr_init: bad letter: ~p", [Ltr])}.
ltr_guts(Acc, Rest) ->
case Rest of
%% valid character, consume
[L | Rest2] when ($A =< L andalso L =< $Z)
orelse ($a =< L andalso L =< $z)
orelse ($0 =< L andalso L =< $9)
orelse (L =:= $_) ->
ltr_guts(<<Acc/binary, L:8>>, Rest2);
%% nope, terminate
_ ->
case wfc_ltr:from_binary(Acc) of
{ok, Ltr} -> {ok, Ltr, Rest};
Error -> Error
end
end.
-spec whitespace(string()) -> {ok, skip, string()}.
% @doc consumes space characters
whitespace(" " ++ Rest) -> whitespace(Rest);
whitespace(Done) -> {ok, skip, Done}.
-spec snowflake(string()) -> {ok, snowflake(), string()} | {error, string()}.
% @doc A snowflake expr is ~ltr where ltr is a letter
snowflake("~" ++ Str1) ->
case ltr(Str1) of
{ok, {c, Binary}, Str2} ->
{ok, {snowflake, Binary}, Str2};
{error, BadLetter} ->
{error, wfc_utils:str("wfc_read:snowflake: error parsing \~snowflake: ~p", [BadLetter])}
end;
snowflake(S) ->
{error, wfc_utils:str("wfc_read:snowflake: bad snowflake: ~p", [S])}.
-spec pattern(string()) -> {ok, pattern(), string()} | {error, string()}.
pattern([L | Rest]) when $A =< L, L =< $Z ->
pattern(<<L:8>>, Rest);
pattern(Bad) ->
{error, wfc_utils:str("wfc_read:pattern: bad pattern: ~p", [Bad])}.
pattern(Acc, [L | Rest]) when ($A =< L andalso L =< $Z)
orelse ($a =< L andalso L =< $z)
orelse ($0 =< L andalso L =< $9)
orelse (L =:= $_) ->
pattern(<<Acc/binary, L:8>>, Rest);
pattern(Acc, Rest) ->
{ok, {pattern, Acc}, Rest}.
-spec val(string()) -> {ok, val(), string()} | {error, string()}.
val("0" ++ Rest) -> {ok, {val, 0}, Rest};
val("1" ++ Rest) -> {ok, {val, 1}, Rest};
val(X) -> {error, wfc_utils:str("wfc_read:val: bad val: ~tw", [X])}.
%
%snowflake(Str0 = "~" ++ Str1) ->
% case ltr(Str1) of
% {ok, Ltr, Str2} ->
% {ok, {snowflake, Ltr}, Str2};
% {error, BadLetter} ->
% {error, wfc_utils:str("wfc_read:snowflake: error parsing \~snowflake: ~p", [BadLetter])}
% end.