Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Created May 28, 2012 05:44
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/2817492 to your computer and use it in GitHub Desktop.
Save Heimdell/2817492 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").
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment