scheme.erl 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. -module(scheme).
  2. -export([compile/1, lex/1, parse/1]).
  3. %%% CONSTANTS %%%
  4. % Mostly used for built-in Scheme functions.
  5. -define(PRELUDE, ["+", "-", "*", "/", "=", "<", ">", "<=", ">=",
  6. "cons", "list", "append", "car", "cdr", "map",
  7. "filter", "member", "list-ref"]).
  8. %%% LEXING %%%
  9. % Lex a string representing Scheme code into a list.
  10. lex(S) -> lists:filter(fun(Match) -> (Match /= "") and
  11. (re:run(Match, "[ \n]+") == nomatch) end,
  12. re:split(S, "(\()|(\))|(')|([^ \n()']+)", [{return, list}])).
  13. %%% PARSING %%%
  14. % String -> Regex -> Bool
  15. % true if Regex matches the entirety of Str; false otherwise.
  16. string_matches(Str, Regex) -> case re:run(Str, Regex, [{capture, first, list}]) of
  17. {match, [S]} -> S == Str;
  18. _ -> false
  19. end.
  20. % String -> Bool
  21. % true if Str consists entirely of number characters; false otherwise.
  22. is_number_string(Str) -> string_matches(Str, "[0-9]+").
  23. % [String] -> Heterogeneous List
  24. % Returns the tuple { Parsed, Unparsed } consisting of a deep list of
  25. % parsed tokens and a flat list of unparsed tokens.
  26. parse([]) -> [];
  27. parse(["(" | Rest]) -> parse_list(Rest);
  28. parse([")" | _]) -> error;
  29. parse(["'" | Rest]) -> case parse(Rest) of
  30. {Dat, R} ->
  31. {[{atom, "quote"}, Dat], R};
  32. error ->
  33. error
  34. end;
  35. parse([S | Rest]) -> case is_number_string(S) of
  36. true ->
  37. {{number, S}, Rest};
  38. false ->
  39. {{atom, S}, Rest}
  40. end.
  41. parse_list([]) -> {[], []};
  42. parse_list([")" | Rest]) -> {[], Rest};
  43. parse_list(S) -> case parse(S) of
  44. {L, R} -> case parse_list(R) of
  45. {List, Rest} -> {lists:append([L], List),
  46. Rest};
  47. error -> error
  48. end;
  49. error -> error
  50. end.
  51. %%% COMPILATION %%%
  52. % Char -> String
  53. % converts a Scheme character to an Erlang character for identifiers; converts
  54. % certain special characters to three-character equivalents.
  55. char_convert(C) -> case C of
  56. $* -> "_ml";
  57. $+ -> "_pl";
  58. $/ -> "_dv";
  59. $- -> "_mn";
  60. $_ -> "_us";
  61. $< -> "_lt";
  62. $> -> "_gt";
  63. $= -> "_eq";
  64. $! -> "_ex";
  65. $? -> "_qs";
  66. $: -> "_cn";
  67. X -> [X]
  68. end.
  69. % a -> Bool
  70. % Well; "string" is more properly "is a list of non-lists", which also (in this case)
  71. % includes strings and lists of integers indistinguishably. With that caveat--
  72. % recogizes whether something is a "string".
  73. is_string(S) ->
  74. is_list(S) and lists:foldl(fun(X, Y) -> X and Y end, true,
  75. lists:map(fun(X) -> not(is_list(X)) end, S)).
  76. % Heterogeneous List of Strings -> String
  77. deep_string_append(L) ->
  78. string:join(lists:map(fun(M) -> case is_string(M) of
  79. true -> M;
  80. false -> deep_string_append(M)
  81. end end, L),
  82. "").
  83. % String -> String
  84. % Convert a string representing a Scheme identifier to a string representing
  85. % an Erlang function identifier or an atom, i.e. lower-case
  86. to_erlang_atom([S|XS]) ->
  87. lists:foldr(fun(X, Y) -> X ++ Y end, "",
  88. lists:map(fun(C) -> char_convert(C) end,
  89. [string:to_lower(S)|XS]));
  90. to_erlang_atom({atom, X}) -> to_erlang_atom(X).
  91. % Convert a string representing a Scheme identifier to a string representing
  92. % an Erlang variable, i.e. upper-case.
  93. to_erlang_variable([S|XS]) ->
  94. lists:foldr(fun(X, Y) -> X ++ Y end, "",
  95. lists:map(fun(C) -> char_convert(C) end,
  96. [string:to_upper(S)|XS]));
  97. to_erlang_variable({atom, X}) -> to_erlang_variable(X).
  98. % Heterogeneous List -> String
  99. generate([{atom, "define"} |
  100. [ [Name | Args] | Body]], Vars, Variadics) ->
  101. [to_erlang_atom(Name),
  102. " ( ", string:join(
  103. lists:map(fun(X) -> to_erlang_variable(X) end, Args),
  104. " , "), " ) -> ",
  105. string:join(lists:map(fun(X) -> generate(X, Vars ++ Args, Variadics) end,
  106. Body), " ; "),
  107. " . "];
  108. generate([{atom, "lambda"} | [Args | Body]], Vars, Variadics) ->
  109. ["fun (",
  110. string:join(
  111. lists:map(fun(X) -> to_erlang_variable(X) end, Args),
  112. " , "),
  113. ") -> ",
  114. string:join(lists:map(fun(X) -> generate(X, Vars ++ Args, Variadics) end,
  115. Body), " ; "),
  116. "end"];
  117. generate([{atom, "if"}
  118. | [ Condition
  119. | [ Then_Case
  120. | [ Else_Case ]]]], Vars, Variadics) ->
  121. ["case scheme_prelude:is_true( ",
  122. generate(Condition, Vars, Variadics),
  123. ") of true -> ",
  124. generate(Then_Case, Vars, Variadics),
  125. " ; false -> ",
  126. generate(Else_Case, Vars, Variadics),
  127. " end "];
  128. generate([{atom, "quote"} | [Rest]], _, _) ->
  129. generate_quoted(Rest);
  130. generate([{atom, Func} | Args], Vars, Variadics) ->
  131. case lists:member(Func, Variadics) of
  132. true -> [generate({atom, Func}, Vars, Variadics),
  133. " ([ ",
  134. string:join(lists:map(fun(X) -> generate(X, Vars, Variadics) end, Args),
  135. [" , "]),
  136. " ]) "];
  137. false -> [generate({atom, Func}, Vars, Variadics),
  138. " ( ",
  139. string:join(lists:map(fun(X) -> generate(X, Vars, Variadics) end, Args),
  140. [" , "]),
  141. " ) "]
  142. end;
  143. generate({number, X}, _, _) -> [X];
  144. generate({atom, X}, Vars, _) ->
  145. [case lists:member(X, Vars) of
  146. true -> to_erlang_variable(X);
  147. false -> case lists:member(X, ?PRELUDE) of
  148. true -> ["scheme_prelude:", to_erlang_variable(X)];
  149. false -> to_erlang_variable(X)
  150. end
  151. end].
  152. generate_quoted({atom, X}) ->
  153. [to_erlang_atom(X)];
  154. generate_quoted({number, X}) ->
  155. [X];
  156. generate_quoted(List) ->
  157. [" [ ", string:join(lists:map(fun(X) -> generate_quoted(X) end,
  158. List), [" , "]),
  159. " ] "].
  160. % String -> String
  161. % Compiles a string representing Scheme code into a string representing an
  162. % Erlang module.
  163. compile(S) -> case parse_list(lex(S)) of
  164. {[X|_], _} -> deep_string_append(generate(X,
  165. [],
  166. ["-", "+", "*", "list"]));
  167. _ -> "NOTHING"
  168. end.