Created
May 28, 2012 05:44
-
-
Save Heimdell/2817492 to your computer and use it in GitHub Desktop.
Ini parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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