Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

510 lines (441 sloc) 17.89 kb
%%%----------------------------------------------------------------------
%%% File : yaws_compile.erl
%%% Author : Claes Wikstrom <klacke@hyber.org>
%%% Purpose :
%%% Created : 20 Feb 2002 by Claes Wikstrom <klacke@hyber.org>
%%%----------------------------------------------------------------------
-module(yaws_compile).
-author('klacke@hyber.org').
-include("../include/yaws.hrl").
-include("../include/yaws_api.hrl").
-include("yaws_debug.hrl").
%% tada !!
%% returns a CodeSpec which is:
%% a list {data, NumChars} |
%% {mod, LineNo, YawsFile, NumSkipChars, Mod, Func} |
%% {error, NumSkipChars, E}}
%% each erlang fragment inside <erl> .... </erl> is compiled into
%% its own module
-record(comp, {
gc, %% global conf
sc, %% server conf
startline = 0,
modnum = 1,
infile,
infd,
outfile,
outfd}).
-export([compile_file/1]).
%% internal exports
-export([compiler_proc/3]).
comp_opts(GC) ->
?Debug("I=~p~n", [GC#gconf.include_dir]),
I = lists:map(fun(Dir) -> {i, Dir} end, GC#gconf.include_dir),
Warnings = case get(use_yfile_name) of
true -> [return_warnings, debug_info];
_ -> []
end,
Opts = [binary, return_errors] ++ Warnings ++ I,
?Debug("Compile opts = ~p~n", [Opts]),
Opts.
compile_file(File) ->
GC=get(gc), SC=get(sc),
case get(use_yfile_name) of
true ->
%% Run by 'yaws -check'
put(yfile,filename:rootname(yaws:to_list(File)));
_ ->
put(yfile,yaws:to_list(File))
end,
%% broken erlang compiler isn't
%% reentrant, can only have one erlang compiler at a time running
global:trans({yaws, self()},
fun() ->
?Debug("Compile ~s~n", [File]),
case file_open(File) of
{ok, Fd} ->
Spec = compile_file(
#comp{infile = File,
infd = Fd, gc = GC, sc = SC},
1,
get_line(), init, 0, [], 0),
erase(yfile),
erase(yfile_data),
erase(yfile_data_orig),
?Debug("Spec: ~p~n", [Spec]),
Spec;
_Err ->
yaws:elog("can't open ~s~n", [File]),
exit(normal)
end
end,
[node()], infinity).
clump_data([{data, I}, {data, J} | Tail]) ->
clump_data([{data, I+J}|Tail]);
clump_data([H|T]) ->
[H|clump_data(T)];
clump_data([]) ->
[].
compile_file(C, _LineNo, eof, _Mode, NumChars, Ack, Errors) ->
file_close(C#comp.infd),
{ok, [{errors, Errors} |
clump_data(lists:reverse([{data, NumChars} |Ack]))]};
%% skip initial space if first thing is <erl> otherwise not
compile_file(C, LineNo, Chars, init, NumChars, Ack, Errs) ->
case Chars -- [$\s, $\t, $\n, $\r] of
[] ->
?Debug("SKIP ~p~n", [Chars]),
L=length(Chars),
compile_file(C, LineNo+1, line(), init, NumChars-L, Ack, Errs);
"<erl" ++ _ -> %% first chunk is erl, skip whistespace
compile_file(C, LineNo, Chars, html, NumChars, Ack, Errs);
_ ->
%% first chunk is html, keep whitespace
file_position_bof(),
compile_file(C,1,line(),html,0,[], Errs)
end;
compile_file(C, LineNo, Chars = "<erl" ++ Tail, html, NumChars, Ack,Es) ->
?Debug("start erl:~p",[LineNo]),
C2 = new_out_file(LineNo, C, Tail, C#comp.gc),
C3 = C2#comp{startline = LineNo},
L = length(Chars),
if
NumChars > 0 ->
compile_file(C3, LineNo+1, line() , erl,L,
[{data, NumChars} | Ack], Es);
true -> %% just ignore zero byte data segments
compile_file(C3, LineNo+1, line() , erl, L + (-NumChars),
Ack, Es) %hack
end;
compile_file(C, LineNo, Chars = "<verbatim>" ++ _Tail, html,
NumChars, Ack,Es) ->
?Debug("start verbatim:~p",[LineNo]),
Len = length(Chars),
C2 = C#comp{outfile = ["<pre>\n"]}, %% use as accumulator
compile_file(C2, LineNo+1, line() , verbatim , Len, [{data, NumChars} | Ack], Es);
compile_file(C, LineNo, Chars = "</verbatim>" ++ _Tail, verbatim,
NumChars, Ack, Es) ->
Data = list_to_binary(lists:reverse(["</pre>\n" | C#comp.outfile])),
Len = length(Chars),
compile_file(C#comp{outfile = undefined}, LineNo, line(), html, 0,
[{verbatim, NumChars+Len, Data} |Ack], Es);
compile_file(C, LineNo, Chars, verbatim, NumChars, Ack,Es) ->
case has_str(Chars, ["</verbatim>"]) of
{ok, Skipped, Chars2} ->
compile_file(C, LineNo, Chars2, verbatim,
NumChars + Skipped, Ack,Es);
false ->
C2 = C#comp{outfile = [yaws_api:htmlize(Chars) | C#comp.outfile]},
compile_file(C2, LineNo+1, line(), verbatim, NumChars +
length(Chars), Ack,Es)
end;
compile_file(C, LineNo, _Chars = "</erl>" ++ Tail, erl, NumChars, Ack, Es) ->
?Debug("stop erl:~p",[LineNo]),
file:close(C#comp.outfd),
case proc_compile_file(C#comp.outfile, comp_opts(C#comp.gc)) of
{ok, ModuleName, Binary, Warnings} ->
case get(use_yfile_name) of
true ->
file:write_file("../../ebin/" ++
filename:rootname(C#comp.outfile)++".beam",
Binary);
_ ->
ok
end,
comp_warn(C, Warnings),
case code:load_binary(ModuleName, C#comp.outfile, Binary) of
{module, ModuleName} ->
C2 = C#comp{modnum = C#comp.modnum+1},
L2 = check_exported(C, LineNo, NumChars, ModuleName),
compile_file(C2, LineNo, Tail, html, 0,
L2++[{skip, 6}|Ack], Es);
Err ->
A2 = gen_err(C, LineNo, NumChars,
?F("Cannot load module ~p: ~p",
[ModuleName, Err])),
compile_file(C, LineNo, Tail, html, 0,
[A2, {skip, 6}|Ack], Es+1)
end;
{error, Errors, Warnings} ->
%% FIXME remove outfile here ... keep while debuging
A2 = comp_err(C, LineNo, NumChars, Errors, Warnings),
compile_file(C, LineNo, Tail, html, 0, [A2, {skip, 6}|Ack], Es+1);
{error, Str} ->
%% this is boring but does actually happen
%% in order to get proper user errors here we need to catch i/o
%% or hack compiler/parser
yaws:elog("Dynamic compile error in file ~s (~s), line ~w~n~s",
[C#comp.infile, C#comp.outfile,LineNo, Str]),
A2 = {error, NumChars, ?F("<pre> Dynamic compile error in file "
" ~s line ~w~n~s </pre>",
[C#comp.infile, LineNo, Str])},
compile_file(C, LineNo, Tail, html, 0, [A2, {skip, 6}|Ack], Es+1)
end;
compile_file(C, LineNo, Chars, erl, NumChars, Ack,Es) ->
case has_str(Chars, ["</erl>"]) of
{ok, Skipped, Chars2} ->
compile_file(C, LineNo, Chars2, erl, NumChars + Skipped, Ack,Es);
false ->
?Debug("Gen: ~s", [Chars]),
io:format(C#comp.outfd, "~s", [Chars]),
compile_file(C, LineNo+1, line(), erl, NumChars +
length(Chars), Ack,Es)
end;
compile_file(C, LineNo, [], html, NumChars, Ack, Es) ->
compile_file(C, LineNo+1, line(), html, NumChars, Ack, Es);
compile_file(C, LineNo, Chars, html, NumChars, Ack,Es) ->
case has_str(Chars, ["<erl", "%%", "<verbatim>"]) of
{ok, Skipped, "<erl"++_ = Chars2} ->
compile_file(C, LineNo, Chars2, html, NumChars+Skipped, Ack, Es);
{ok, Skipped, "<verbatim>"++_ = Chars2} ->
compile_file(C, LineNo, Chars2, html, NumChars+Skipped, Ack, Es);
{ok, Skipped, "%%"++Chars2} ->
compile_file(C, LineNo, Chars2, binding, 2,
[{data, NumChars+Skipped}|Ack], Es);
false ->
compile_file(C, LineNo, tl(Chars), html, NumChars+1, Ack, Es)
end;
compile_file(C, LineNo, [], binding, NumChars, Ack, Es) ->
compile_file(C, LineNo+1, line(), html, NumChars, Ack, Es);
compile_file(C, LineNo, "%%"++Chars, binding, NumChars, Ack, Es) ->
compile_file(C, LineNo, Chars, html, 0, [{binding, NumChars+2}|Ack], Es);
compile_file(C, LineNo, [_H|T], binding, NumChars, Ack, Es) ->
compile_file(C, LineNo, T, binding, NumChars+1, Ack, Es).
has_str(L, Strs) -> has_str(L, Strs, 0).
has_str([H|T], Strs, Num) ->
case yaws:is_space(H) of
true -> has_str(T, Strs, Num+1);
false ->
case lists:any(fun(Str) -> lists:prefix(Str, [H|T]) end, Strs) of
true -> {ok, Num, [H|T]};
false -> false
end
end;
has_str(_,_,_) -> false.
check_exported(C, LineNo, NumChars, Mod) ->
case is_exported(out, 1, Mod) of
true ->
[{mod, C#comp.startline, C#comp.infile,
NumChars,Mod,out}];
false ->
?Debug("XX ~p~n", [C]),
[gen_err(C, LineNo, NumChars,
"out/1 is not defined ")]
end.
line() ->
get_line().
is_exported(Fun, A, Mod) ->
case (catch Mod:module_info()) of
List when is_list(List) ->
case lists:keysearch(exports, 1, List) of
{value, {exports, Exp}} ->
lists:member({Fun, A}, Exp);
_ ->
false
end;
_ ->
false
end.
new_out_file_module(Tail) ->
case Tail of
">" ++ _ ->
Mnum = case catch gen_server:call(yaws_server, mnum, infinity) of
{'EXIT', _} ->
1;
Other ->
Other
end,
Prefix = case get(use_yfile_name) of
true -> filename:rootname(get(yfile))++"_yaws";
_ -> "m"
end,
Prefix ++ integer_to_list(Mnum);
_ ->
case string:tokens(Tail, " =>\r\n\"") of
["module", Module] ->
Module
end
end.
new_out_file_name(Module, GC) ->
case get(use_yfile_name) of
true ->
Module ++ ".erl";
_ ->
filename:join([yaws:id_dir(GC#gconf.id), Module ++ ".erl"])
end.
%% this will generate 10 lines
new_out_file(Line, C, Tail, GC) ->
Module = new_out_file_module(Tail),
OutFile = new_out_file_name(Module, GC),
?Debug("Writing outout file~s~n", [OutFile]),
{ok, Out} = file:open(OutFile, [write]),
ok = io:format(Out, "-module(\'~s\').~n-export([out/1]).~n~n", [Module]),
ok = io:format(Out, "-yawsfile('" ++ get(yfile) ++ "').~n",[]),
io:format(Out, "%%~n%% code at line ~w from file ~s~n%%~n",
[Line, C#comp.infile]),
io:format(Out, "-import(yaws_api, [f/2, fl/1, postvar/2, queryvar/2])."
" ~n~n", []),
io:format(Out, '-include("~s/include/yaws_api.hrl").~n',
[GC#gconf.yaws_dir]),
C#comp{outfd = Out,
outfile = OutFile}.
gen_err(C, _LineNo, NumChars, Err) ->
S = io_lib:format("<p> Error in File ~s Erlang code beginning "
"at line ~w~n"
"Error is: ~p~n", [C#comp.infile, C#comp.startline,
Err]),
yaws:elog("~s~n", [S]),
{error, NumChars, S}.
comp_err(C, LineNo, NumChars, Err, Warns) ->
case get(use_yfile_name) of
true ->
report_errors(C, Err),
report_warnings(C, Warns),
{error, NumChars, ""};
_ ->
comp_err(C, LineNo, NumChars, Err)
end.
comp_err(C, _LineNo, NumChars, Err) ->
case Err of
[{_FileName, [ {Line0, Mod, E} |_]} |_] when is_integer(Line0) ->
Line = Line0 + C#comp.startline - 10,
?Debug("XX ~p~n", [{_LineNo, Line0}]),
Str = io_lib:format("~s:~w:~n ~s\ngenerated file at: ~s~n",
[C#comp.infile, Line,
apply(Mod, format_error, [E]),
C#comp.outfile
]),
HtmlStr = ?F("~n<pre>~nDynamic compile error: ~s~n</pre>~n",
[Str]),
yaws:elog("Dynamic compiler err ~s", [Str]),
{error, NumChars, HtmlStr};
_Other ->
yaws:elog("Dynamic compile error ~p", [Err]),
{error, NumChars, ?F("<pre> Compile error - "
"Other err ~p</pre>~n", [Err])}
end.
comp_warn(C, Warnings) ->
case get(use_yfile_name) of
true ->
report_warnings(C, Warnings);
_ ->
ok
end.
%% due to compiler not producing proper error
%% we NEED to catch all io produced by the compiler
proc_compile_file(F, Opts) ->
G = group_leader(),
group_leader(self(), self()),
P = proc_lib:spawn(?MODULE, compiler_proc, [self(), F, Opts]),
Res = get_compiler_data(P, []),
group_leader(G, self()),
Res.
compiler_proc(Top, F, Opts) ->
R = (catch compile:file(F, Opts)),
Top ! {self(), result, R}.
get_compiler_data(P, Ack) ->
receive
{P, result, {ok, Mod, Bin}} ->
{ok, Mod, Bin, []};
{P, result, {ok, Mod, Bin, Warnings}} ->
{ok, Mod, Bin, Warnings};
{io_request, P1, P2, {put_chars, M, F, A}} ->
P1 ! {io_reply, P2, ok},
Str = apply(M, F, A),
get_compiler_data(P, [Str|Ack]);
{P, result, {error, Errors, Warnings}} ->
{error, Errors, Warnings};
{P, result, error} ->
S = lists:map(
fun(S) -> S ++ "\n" end, lists:reverse(Ack)),
{error, S};
{P, result, {'EXIT', Reason}} ->
S = lists:flatten(io_lib:format("~p", [Reason])),
{error, S}
end.
%% This code is so that we get the \r in the line
%% when we're parsing msdos files.
file_open(Fname) ->
case file:read_file(Fname) of
{ok, Bin} ->
put(yfile_data, binary_to_list(Bin)),
put(yfile_data_orig, Bin),
{ok, yfile_data};
Err ->
Err
end.
file_close(Key) ->
erase(Key).
file_position_bof() ->
put(yfile_data, binary_to_list(get(yfile_data_orig))).
get_line() ->
case get (yfile_data) of
[] ->
eof;
Chars ->
case get_line_from_chars(Chars, []) of
{ok, Line, Tail} ->
put (yfile_data, Tail),
Line;
need_more ->
put(yfile_data, []),
Chars
end
end.
get_line_from_chars([$\r, $\n | Tail], Line) ->
{ok, lists:reverse([$\n, $\r|Line]), Tail};
get_line_from_chars([$\n | Tail], Line) ->
{ok, lists:reverse([$\n|Line]), Tail};
get_line_from_chars([], _Line) ->
need_more;
get_line_from_chars([H|T], Line) ->
get_line_from_chars(T, [H|Line]).
%% -----------------------------------------------------------------
%% From compile.erl in order to print proper error/warning messages
%% if compiled with check option.
report_errors(C, Errors) ->
Check = true,
case Check of
true ->
File = "./" ++ filename:basename(C#comp.infile),
SLine = C#comp.startline - 10,
lists:foreach(fun ({{_F,_L},Eds}) -> list_errors(File, SLine, Eds);
({_F,Eds}) -> list_errors(File, SLine, Eds)
end, Errors);
false ->
ok
end.
report_warnings(C, Ws0) ->
Check = true,
case Check of
true ->
File = "./" ++ filename:basename(C#comp.infile),
SLine = C#comp.startline - 10,
Ws1 = lists:flatmap(fun({{_F,_L},Eds}) ->
format_message(File, SLine, Eds);
({_F,Eds}) ->
format_message(File, SLine, Eds)
end,
Ws0),
Ws = ordsets:from_list(Ws1),
lists:foreach(fun({_,Str}) -> io:put_chars(Str) end, Ws);
false -> ok
end.
format_message(F, SLine, [{Line0,Mod,E}|Es]) ->
Line = Line0 + SLine,
M = {{F,Line},io_lib:format("~s:~w: Warning: ~s\n", [F,Line,Mod:format_error(E)])},
[M|format_message(F, SLine, Es)];
format_message(F, SLine, [{Mod,E}|Es]) ->
M = {none,io_lib:format("~s: Warning: ~s\n", [F,Mod:format_error(E)])},
[M|format_message(F, SLine, Es)];
format_message(_, _, []) -> [].
%% list_errors(File, StartLine, ErrorDescriptors) -> ok
list_errors(F, SLine, [{Line0,Mod,E}|Es]) ->
Line = Line0 + SLine,
io:fwrite("~s:~w: ~s\n", [F,Line,Mod:format_error(E)]),
list_errors(F, SLine, Es);
list_errors(F, SLine, [{Mod,E}|Es]) ->
io:fwrite("~s: ~s\n", [F,Mod:format_error(E)]),
list_errors(F, SLine, Es);
list_errors(_F, _SLine, []) ->
ok.
Jump to Line
Something went wrong with that request. Please try again.