Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Created May 28, 2012 05:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Heimdell/2817501 to your computer and use it in GitHub Desktop.
Save Heimdell/2817501 to your computer and use it in GitHub Desktop.
Ini parser
-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").
@gliush
Copy link

gliush commented May 28, 2012

мне не нравится запись функции safe(),
safe(Plus) ->
fun (A, none) -> A;
(none, B) -> B;
(A, B) -> Plus(A, B)
end.

@gliush
Copy link

gliush commented May 28, 2012

черт, форматирование сбилось.
Короче,функция может занимать во вполне читаемом виде 5 строчек, а занимает в два раза больше.
Не нужно такого.

@gliush
Copy link

gliush commented May 28, 2012

Еще мне кажется, что можно много кода сократить, если делать не:

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 если ты добавляешь/убавляешь хоть одну локальную функцию в модуле. Т.е. если у тебя происходит обновление кода, все старые потоки управления ломаются тут же.

@gliush
Copy link

gliush commented May 28, 2012

tuple() - нужно переделать. У тебя сейчас очень неправильное ограничение с размером тьюпла.
Я бы переделал с использованием списков, чтобы целиком обрабатывать

erlang:list_to_tuple([a,b,c,d]).
{a,b,c,d}

Ну или по крайней мере используй библиотечную функцию

erlang:append_element({a,b,c}, d).
{a,b,c,d}

@Heimdell
Copy link
Author

Heimdell commented Jun 4, 2012

К сожалению, без анонимных функций никак нельзя эффективно сделать замыкания вроде drop(many(char($\n))), которые, кстати, уменьшают размер кода "клиента" библиотеки. Делать же их через кортежи - значит изобретать велосипед. Кортежами они были изначально, пока разработчики эрланга не вняли мольбам сделать этот механизм не таким медленным.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment