compile 6.7 KB

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