Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Created May 28, 2012 05:47
Show Gist options
  • 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

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