168 lines
5.2 KiB
Erlang
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.
|