-
-
Save Heimdell/2817501 to your computer and use it in GitHub Desktop.
| -module(parse_ini). | |
| -compile(export_all). | |
| % Parser fun :: s() -> {'ok', {a(), s()}} | {'error', reason()} | |
| return(A) -> fun (S) -> {ok, {A, S}} end. | |
| what(Msg, P) -> | |
| fun (S) -> | |
| {ok, {PS, S1}} = P(S), | |
| io:format("~s: ~p~n", [Msg, PS]), | |
| {ok, {PS, S1}} | |
| end. | |
| char(C) -> | |
| fun (S) -> | |
| case S of | |
| [C | S1] -> {ok, {C, S1}}; | |
| _ -> {error, {expected, C, but_found, S}} | |
| end | |
| end. | |
| modify(P, Fun) -> | |
| fun (S) -> | |
| case P(S) of | |
| {ok, {D, S1}} -> {ok, {Fun(D), S1}}; | |
| Error -> Error | |
| end | |
| end. | |
| join(Plus, P) -> | |
| fun (C) -> | |
| modify(P, fun (D) -> Plus(C, D) end) | |
| end. | |
| safe(Plus) -> | |
| fun | |
| (A, none) -> | |
| A; | |
| (none, B) -> | |
| B; | |
| (A, B) -> | |
| Plus(A, B) | |
| end. | |
| list() -> | |
| safe(fun(A, B) -> | |
| lists:flatten([A, B]) | |
| end). | |
| list_many() -> | |
| safe(fun(A, B) -> | |
| [A | B] | |
| end). | |
| tuple() -> | |
| safe( | |
| fun | |
| (A, {B, C, D, E, F, G, H}) -> | |
| {A, B, C, D, E, F, G, H}; | |
| (A, {B, C, D, E, F, G}) -> | |
| {A, B, C, D, E, F, G}; | |
| (A, {B, C, D, E, F}) -> | |
| {A, B, C, D, E, F}; | |
| (A, {B, C, D, E}) -> | |
| {A, B, C, D, E}; | |
| (A, {B, C, D}) -> | |
| {A, B, C, D}; | |
| (A, {B, C}) -> | |
| {A, B, C}; | |
| (A, B) -> | |
| {A, B} | |
| end). | |
| % (>>=) :: Parser a s -> (a -> Parser a s) -> Parser a s | |
| % | |
| bind(P, FP) -> | |
| fun (S) -> | |
| case P(S) of | |
| {ok, {C, S1}} -> | |
| FPC = FP(C), | |
| FPC(S1); | |
| Error -> Error | |
| end | |
| end. | |
| seq(Plus, PList) -> | |
| RPList = lists:reverse(PList), | |
| lists:foldl( | |
| fun (P, Acc) -> | |
| bind(P, join(Plus, Acc)) | |
| end, | |
| hd(RPList), | |
| tl(RPList)). | |
| enlist(PList) -> seq(list(), PList). | |
| enlist_plain(PList) -> seq(list_many(), PList). | |
| entuple(PList) -> seq(tuple(), PList). | |
| any([PLast]) -> | |
| fun (S) -> | |
| case PLast(S) of | |
| {ok, Result} -> {ok, Result}; | |
| Error -> Error | |
| end | |
| end; | |
| any([P | PList]) -> | |
| fun (S) -> | |
| case P(S) of | |
| {ok, Result} -> {ok, Result}; | |
| _ -> | |
| Rest = any(PList), | |
| Rest(S) | |
| end | |
| end. | |
| many(P) -> | |
| fun (S) -> | |
| case P(S) of | |
| {ok, {C, S1}} -> | |
| Next = join(list_many(), many(P)), | |
| NextC = Next(C), | |
| NextC(S1); | |
| _ -> {ok, {[], S}} | |
| end | |
| end. | |
| many1(P) -> | |
| bind(P, join(list_many(), many(P))). | |
| % TODO: Implement `is({PRED, ARGS})`-style. | |
| % | |
| in_set({Name, Predicat}) -> | |
| fun | |
| ([]) -> | |
| {error, {expected, {being_in_set, Name}, but_found, 'EOF'}}; | |
| ([C | S]) -> | |
| case Predicat(C) of | |
| true -> {ok, {C, S}}; | |
| false -> {error, {expected, {being_in_set, Name}, but_found, [C | S]}} | |
| end | |
| end. | |
| is({'in_range', L, H}) -> | |
| fun (C) -> | |
| (C >= L) and (C =< H) | |
| end; | |
| is({'equal_to', D}) -> | |
| fun (C) -> | |
| (C == D) | |
| end; | |
| is({'or', Preds}) -> | |
| fun (C) -> | |
| lists:any(fun (P) -> IP = is(P), IP(C) end, Preds) | |
| end; | |
| is({'and', Preds}) -> | |
| fun (C) -> | |
| lists:all(fun (P) -> IP = is(P), IP(C) end, Preds) | |
| end; | |
| is({'not', P}) -> | |
| fun (C) -> | |
| IP = is(P), | |
| not (IP(C)) | |
| end. | |
| that(Pred) -> | |
| IP = is(Pred), | |
| fun | |
| ([]) -> | |
| {error, {expected, Pred, but_found, 'EOF'}}; | |
| ([C | S]) -> | |
| case IP(C) of | |
| true -> {ok, {C, S}}; | |
| false -> {error, {expected, Pred, but_found, [C | S]}} | |
| end | |
| end. | |
| % Decorator. | |
| % | |
| parsing(Expected, P) -> | |
| fun (S) -> | |
| case P(S) of | |
| {ok, Result} -> | |
| {ok, Result}; | |
| {error, {expected, E, but_found, S1}} -> | |
| {error, {expected, {Expected, means, E}, but_found, S1}} | |
| end | |
| end. | |
| drop(P) -> | |
| modify(P, fun (_) -> none end). | |
| null() -> | |
| fun (S) -> | |
| {ok, {none, S}} | |
| end. | |
| listSepBy(P, SP) -> | |
| enlist([ | |
| P, | |
| many(enlist([SP, P])) | |
| ]). | |
| % pseudocode: inside(is $[, is "section", is $]) | |
| % | |
| inside(B, P) -> | |
| inside(B, P, B). | |
| inside(B, P, A) -> | |
| enlist([ | |
| B, | |
| P, | |
| A | |
| ]). | |
| % Whole string | |
| % | |
| chunk(String) -> | |
| enlist(lists:map(fun char/1, String)). | |
| is_letter() -> | |
| {'or', [{'in_range', $A, $Z}, | |
| {'in_range', $a, $z}, | |
| {'equal_to', $_}]}. | |
| is_number() -> | |
| {'in_range', $0, $9}. | |
| spaces() -> many(drop(char($ ))). | |
| token(Pred) -> many(that(Pred)). | |
| word(Item) -> inside(Item, spaces()). | |
| name() -> | |
| enlist([ | |
| spaces(), | |
| that(is_letter()), | |
| token({'or', [is_letter(), is_number()]}), | |
| spaces() | |
| ]). | |
| %=============================================================================== | |
| config() -> | |
| enlist_plain([ | |
| return("[default]"), | |
| many( | |
| any([ | |
| key_value_pair(), | |
| section(), | |
| drop_line() | |
| ]) | |
| ) | |
| ]). | |
| section() -> | |
| enlist([ | |
| drop(spaces()), | |
| char($[), | |
| word(token(is_letter())), | |
| char($]), | |
| drop(spaces()), | |
| drop(char($\n)) | |
| ]). | |
| key_value_pair() -> | |
| entuple([ | |
| name(), | |
| drop(char($=)), | |
| multiline(word(token({'or', [is_letter(), is_number()]}))), | |
| drop(many(char($\n))) | |
| ]). | |
| drop_line() -> | |
| enlist([ | |
| drop(what( | |
| 'Cannot parse', | |
| many(that( | |
| {'not', {'equal_to', $\n}} | |
| )) | |
| )), | |
| drop(char($\n)) | |
| ]). | |
| multiline(P) -> | |
| listSepBy( | |
| P, | |
| enlist([ | |
| drop(chunk("\\\n")), | |
| return($ ) | |
| ]) | |
| ). | |
| selftest() -> | |
| Test = config(), | |
| Test("loose_key = one\nloose_keys = one\nloose_kiy = one\n [General] \n Resolution = 640x480\\\n 800x600\n driver = open_gl\\\n dirX\n 1vsync = enabled\nhoho=yaya"). |
черт, форматирование сбилось.
Короче,функция может занимать во вполне читаемом виде 5 строчек, а занимает в два раза больше.
Не нужно такого.
Еще мне кажется, что можно много кода сократить, если делать не:
A() -> fun(V) -> V + 1 end.
И вызов A() создат анонимную функцию.
А лучше делать
A(V) -> V + 1.
И в месте вызова подставлять fun A/1
Пример:
f(A), A = fun() -> fun(V) -> V + 1 end end.
Fun<erl_eval.20.67289768>
lists:map(A(), [1,2,3]).
[2,3,4]
f(B), B = fun(V) -> V+1 end.Fun<erl_eval.6.13229925>
lists:map(B, [1,2,3]).
[2,3,4]
Результат один и тот же, а кода меньше.
Чем плохи анонимные функции - они не переживают hot code reload если ты добавляешь/убавляешь хоть одну локальную функцию в модуле. Т.е. если у тебя происходит обновление кода, все старые потоки управления ломаются тут же.
tuple() - нужно переделать. У тебя сейчас очень неправильное ограничение с размером тьюпла.
Я бы переделал с использованием списков, чтобы целиком обрабатывать
erlang:list_to_tuple([a,b,c,d]).
{a,b,c,d}
Ну или по крайней мере используй библиотечную функцию
erlang:append_element({a,b,c}, d).
{a,b,c,d}
К сожалению, без анонимных функций никак нельзя эффективно сделать замыкания вроде drop(many(char($\n))), которые, кстати, уменьшают размер кода "клиента" библиотеки. Делать же их через кортежи - значит изобретать велосипед. Кортежами они были изначально, пока разработчики эрланга не вняли мольбам сделать этот механизм не таким медленным.
мне не нравится запись функции safe(),
safe(Plus) ->
fun (A, none) -> A;
(none, B) -> B;
(A, B) -> Plus(A, B)
end.