-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(<>, 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(<>, 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(<>, 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(<>, 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.