%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% -module(ic_pp). -export([run/2]). -define(is_number(X), X >= $0, X =< $9). -define(is_upper(X), X >= $A, X =< $Z). -define(is_lower(X), X >= $a, X =< $z). -define(is_underline(X), X == $_). -define(is_tab(X), X == 9). -define(is_space(X), X == 32). -define(tab, 9). -define(space, 32). %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Preprocessor %% %% This preprocessor is equivalent to the gcc-preprocessor. It takes a file name and %% a list of preprocessor flags as an input and returns a processed text file. %% %% The processing is done in two phases. %% In the first phase the input file is tokenised into a list where all comments are %% replaced by a space and all "backslash-newline" sequences are removed. %% %% In the second phase all macros are expanded. %% %% %% NOTE: #if, #else, and #elif are not yet implemented. %% Only '#if 0' is implemented to be possible to keep old code as a comment for %% future refence by putting '#if 0' before it and '#endif' after it. %% %%====================================================================================== %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Variables which are used throughout the program: %% ------------------------------------------------ %% %% Command A preprocessor command %% Current Temporary variable used when tokenising the file %% Defs The currently valid macro definitions %% Err The current list of errors = [{file, line number, error text}, ...] %% File The tokenised file (or what remains of it when expanding the macros) %% Flags The preprocessor flags %% FN or FileName Tbe name of the current file %% IfCou Used for ifdef/ifndef/endif values: check_all | {endif, Endif, IfLine} %% Endif = number of matching endif's yet to be found %% Ifline = the line number for the the first found ifdef/ifndef %% IncDir Directories to be searched for included files %% IncFile Stack of included files %% IncLine The line numer of an include %% L The current line number %% Name Name of a macro %% Nl Number of encountered newlines %% No_of_para Numer of parameters of the currently expanded macro %% Out The result of the second step %% Parameters The parameters of the currently expanded macro %% PrevFile The name of the "parent" file which includes the currently expanded file %% Rem Remaining of the file currently being expanded %% Removed The tokens removed, used when removing tokens to the end of a line %% Result The current result of something %% SelfRef List of variables which shoud not be expanded at the rescan to avoid %% endless loops due to self referencing %% Str Temporary string %% Text A variable used for string handling, e.g at error handling %% Tokens Temoprary list when tokenising %% War The current list of warnings = [{file, line number, warning text}, ...] %% X Temporary variable used when the value is not important %% Y Temporary variable used when the value is not important %% %%====================================================================================== %% Multiple Include Optimization %% %% Algorithm described at: %% http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html -record(mio, {valid = true, %% multiple include valid cmacro, %% controlling macro of the current conditional directive depth = 0, %% conditional directive depth included = []}). %%====================================================================================== %%====================================================================================== %%====================================================================================== %% The main entry for the preprocessor %% %% %% Output {ok, Out, War} | {error, Err} %%====================================================================================== %%====================================================================================== %%====================================================================================== run(FileName, Flags) when is_atom(FileName) -> run(atom_to_list(FileName), Flags); run(FileName, Flags) -> IncDir = include_dir(Flags), case catch file:read_file(FileName) of {ok, Bin} -> FileList = binary_to_list(Bin), run(FileList, FileName, IncDir, Flags); {error, _} -> Text = "No such file or directory", {error, [FileName ++ ": " ++ Text]} end. run(FileList, FileName, IncDir, Flags) -> %%---------------------------------------------------------- %% Run the first phase, i.e tokenise the file %%---------------------------------------------------------- File = tokenise(FileList, FileName), %%---------------------------------------------------------- %% Run the second phase, i.e expand macros %%---------------------------------------------------------- {Out, Err, War, _Defs, _Mio, IfCou} = expand(File, FileName, IncDir, Flags), %%---------------------------------------------------------- %% Check if all #if #ifdef #ifndef have a matching #endif %%---------------------------------------------------------- IfError = case IfCou of {endif, Endif, IfLine} when Endif > 0 -> [{FileName, IfLine, "unterminated `#if' conditional"}]; _ -> [] end, Err2 = Err++IfError, case Err2 of [] -> {ok, lists:flatten(lists:reverse(Out)), lists:reverse(War)}; _ -> {error, lists:reverse(Err2)} end. %%====================================================================================== %% The entry for all included files %% %% %% Output {Out, Err, War, Defs, MultipleIncludeValid} %%====================================================================================== run_include(FileName, FileList, _Out, Defs, Err, War, IncLine, IncFile, IncDir, Mio) -> %%---------------------------------------------------------- %% Run the first phase, i.e tokenise the file %%---------------------------------------------------------- [PrevFile | _T] = IncFile, {File, FileInfoStart, FileInfoEnd} = tokenise(FileList, FileName, IncLine, PrevFile), %%---------------------------------------------------------- %% Run the second phase, i.e expand macros %%---------------------------------------------------------- {Out2, Err2, War2, Defs2, Mio2, IfCou2} = expand([FileInfoStart|File]++FileInfoEnd, Defs, Err, War, [FileName|IncFile], IncDir, #mio{included=Mio#mio.included}), MergeIncluded = sets:to_list(sets:from_list(Mio#mio.included ++ Mio2#mio.included)), Mio3 = case {Mio2#mio.valid, Mio2#mio.cmacro} of {V, Macro} when V == false; Macro == undefined -> update_mio(Mio#mio{included=MergeIncluded}); {true, _} -> update_mio({include, FileName}, Mio#mio{included=MergeIncluded}) end, %%---------------------------------------------------------- %% Check if all #if #ifdef #ifndef have a matching #endif %%---------------------------------------------------------- IfError = case IfCou2 of {endif, Endif, IfLine} when Endif > 0 -> [{FileName, IfLine, "unterminated `#if' conditional"}]; _ -> [] end, {Out2, Defs2, Err2++IfError, War2, Mio3}. %%=================================================================================== %%=================================================================================== %%=================================================================================== %% Tokenise the file %% %% %% Output: File %% %% Description: %% The input file is tokenised into a list where all comments are replaced %% by a space and all "backslash-newline" sequences are removed. %% %% A file information is added at start and end of an included file to set the %% current file name and line number. %% %% %% A token consists of: %% -------------------- %% %% {char, Char} special characters like ()[]{},!%& etc %% {command,Command} a macro command %% {expanded,Str} an expanded variable, used to prevent infinite loops %% at self reference %% {file_info,FI} start and end information of a file %% FI is a string of the following format: %% "# Line FileName Int" were Int is %% 1 if start of an included file, %% 2 when returning to "parent" file %% {nl, L} newline %% {number,Num) variable, a string starting with a number %% {self_ref,Var} to allow reference to a variable again, used when expanding %% self refering macros %% space a space %% space_exp a space, special notation to prevent not wanted concatination %% {string, Str} a (tail of a) string constant %% {string_part, Str} a head of a string constant defined on several consecutive lines %% {sys_head, Str} (tail of) the file name of included system file %% {sys_head_part , Str} the file name of included system file %% {var,Var} variable, a string starting with minuscular or capital letter or %% an underline %% %% Note, comments are not removed within a character or string constant %% or inside an include-definition where the file name is delimited with < > %%=================================================================================== %%=================================================================================== %%=================================================================================== tokenise(File, FileName) -> {Result, _L} = token(File, 2, [], not_set, 0), FI_start = lists:reverse(lists:flatten(io_lib:format("# 1 ~p~n",[FileName]))), FileInfoStart = {file_info, FI_start}, [FileInfoStart | Result]. tokenise(File, FileName, IncLine, PrevFile) -> {Result, _L} = token(File, 2, [], not_set, 0), FI_start = lists:reverse(lists:flatten(io_lib:format("# 1 ~p 1~n",[FileName]))), FileInfoStart = {file_info, FI_start}, FI_end = lists:reverse(lists:flatten(io_lib:format("# ~p ~p 2~n~n",[IncLine-1,PrevFile]))), FileInfoEnd = [{file_info, FI_end}], {Result, FileInfoStart, FileInfoEnd}. % [FileInfoStart | Result] ++ FileInfoEnd. %%=================================================================================== %% token(InputFile, L, Result, Gen) %% Gen information of the first token on the line, default = not_set %% %% Output: File %%=================================================================================== %%================================================================== %% Normal line %%================================================================== %%--------------------------------------- %% All file tokenised %%--------------------------------------- token([], L, [{nl,NL}|Result], _Gen, _BsNl) when L == NL+1-> {lists:reverse([{nl,NL}|Result]), L}; token([], L, Result, _Gen, _BsNl) -> {lists:reverse([{nl,L-1}|Result]), L}; %%--------------------------------------- %% String %%--------------------------------------- token(File, L, Result, string, BsNl) -> case token_string(File, []) of {Rem, Str, nl} -> Result1 = [{nl, L}, {string,Str} | Result], token(Rem, L+1, Result1, string, BsNl); {Rem, Str} -> token(Rem, L, [{string,Str}|Result], not_set, BsNl) end; token([$"|File], L, Result, Gen, BsNl) -> case token_string(File, []) of {Rem, Str, nl} -> Result1 = [{nl, L}, {string_part,Str} | Result], token(Rem, L+1, Result1, string, BsNl); {Rem, Str} -> token(Rem, L, [{string,Str}|Result], Gen, BsNl) end; %%--------------------------------------- %% Include with < > %%--------------------------------------- token(File, L, Result, include, BsNl) -> case token_include(File, []) of {Rem, Str, nl} -> Result1 = [{nl, L}, {sys_head,Str} | Result], token(Rem, L+1, Result1, include, BsNl); {Rem, Str} -> token(Rem, L, [{sys_head,Str}|Result], not_set, BsNl) end; token([$<|File], L, [space,{command,"include"}|Result], Gen, BsNl) -> case token_include(File, []) of {Rem, Str, nl} -> Result1 = [{nl, L}, {sys_head_part,Str}, space, {command,"include"} |Result], token(Rem, L+1,Result1, include, BsNl); {Rem, Str} -> Result1 = [{sys_head,Str}, space, {command,"include"} |Result], token(Rem, L, Result1, Gen, BsNl) end; token([$<|File], L, [{command,"include"}|Result], Gen, BsNl) -> case token_include(File, []) of {Rem, Str, nl} -> Result1 = [{nl, L}, {sys_head_part,Str}, space, {command,"include"} |Result], token(Rem, L+1,Result1, include, BsNl); {Rem, Str} -> Result1 = [{sys_head,Str}, space, {command,"include"} |Result], token(Rem, L, Result1, Gen, BsNl) end; %%--------------------------------------- %% CR (just remove these) %%--------------------------------------- token([$\r|File], L, Result, Gen, BsNl) -> % Bs = lists:duplicate(BsNl+1,{nl,L}), token(File, L, Result, Gen, BsNl); %% Bs or BsNl? %%--------------------------------------- %% Newline %%--------------------------------------- token([$\n|File], L, Result, _Gen, BsNl) -> Bs = lists:duplicate(BsNl+1,{nl,L}), token(File, L+1, Bs++Result, not_set, 0); token([$\\,$\n|File], L, Result, Gen, BsNl) -> token(File, L, Result, Gen, BsNl+1); %%--------------------------------------- %% Comments %%--------------------------------------- token([$/,$/|File], L, Result, not_set, BsNl) -> Rem = skip_to_nl(File), token(Rem, L+1,[{nl, L} | Result], not_set, BsNl); token([$/,$/|File], L, Result, _Gen, BsNl) -> Rem = skip_to_nl(File), token(Rem, L+1,[{nl, L} | Result], not_set, BsNl); token([$/,$*|File], L, Result, not_set, BsNl) -> case token_comment(File) of {Rem, nl} -> token(Rem, L+1, [{nl, L} | Result], not_set, BsNl); Rem -> token(Rem, L, Result, not_set, BsNl) end; token([$/,$*|File], L, Result, Gen, BsNl) -> case token_comment(File) of {Rem, nl} -> token(Rem, L+1, [{nl, L}, space | Result], not_set, BsNl); Rem -> token(Rem, L, [space|Result], Gen, BsNl) end; %%--------------------------------------- %% Variable %%--------------------------------------- token([X|File], L, Result, Gen, BsNl) when ?is_upper(X) -> GenNew = case Gen of not_set -> var; _ -> Gen end, {Rem, Var} = tok_var(File, [X]), token(Rem, L, [{var,Var}|Result], GenNew, BsNl); token([X|File], L, Result, Gen, BsNl) when ?is_lower(X) -> GenNew = case Gen of not_set -> var; _ -> Gen end, {Rem, Var} = tok_var(File, [X]), token(Rem, L, [{var,Var}|Result], GenNew, BsNl); token([X|File], L, Result, Gen, BsNl) when ?is_underline(X) -> GenNew = case Gen of not_set -> var; _ -> Gen end, {Rem, Var} = tok_var(File, [X]), token(Rem, L, [{var,Var}|Result], GenNew, BsNl); %%--------------------------------------- %% Number %%--------------------------------------- token([X|File], L, Result, Gen, BsNl) when ?is_number(X) -> GenNew = case Gen of not_set -> number; _ -> Gen end, {Rem, Tokens} = tok_number(File, [X]), token(Rem, L, [{number,Tokens}|Result], GenNew, BsNl); %%--------------------------------------- %% Space %%--------------------------------------- token([X|File], L, [Y|Result], Gen, BsNl) when ?is_space(X) -> case Y of space -> Rem = remove_leading_spaces(File), token(Rem, L, [Y|Result], Gen, BsNl); {nl,_,_} -> Rem = remove_leading_spaces(File), token(Rem, L, Result, Gen, BsNl); _ -> Rem = remove_leading_spaces(File), token(Rem, L, [space, Y |Result], Gen, BsNl) end; token([X|File], L, [Y|Result], Gen, BsNl) when ?is_tab(X) -> case Y of space -> Rem = remove_leading_spaces(File), token(Rem, L, [Y|Result], Gen, BsNl); {nl,_,_} -> Rem = remove_leading_spaces(File), token(Rem, L, Result, Gen, BsNl); _ -> Rem = remove_leading_spaces(File), token(Rem, L, [space, Y |Result], Gen, BsNl) end; %%--------------------------------------- %% Command %%--------------------------------------- token([$#|File], L, Result, not_set, BsNl) -> {Rem, Command} = token_pp_com(File), case catch list_to_integer(Command) of {'EXIT', _} -> token(Rem, L, [{command,Command}|Result], not_set, BsNl); _Int -> Result1 = [{number,Command}, {command,"line"}| Result], token(Rem, L, Result1, not_set, BsNl) end; %%--------------------------------------- %% Char %%--------------------------------------- token([X|File], L, Result, Gen, BsNl) -> GenNew = case Gen of not_set -> char; _ -> Gen end, token(File, L, [{char,X}|Result], GenNew, BsNl). %%================================================================== %% Scan to the end of a token %%================================================================== %%--------------------------------------- %% Number %%--------------------------------------- tok_number([], Str) -> {[], lists:reverse(Str)}; tok_number([X|File], Str) when ?is_upper(X) -> tok_number(File, [X|Str]); tok_number([X|File], Str) when ?is_lower(X) -> tok_number(File, [X|Str]); tok_number([X|File], Str) when ?is_underline(X) -> tok_number(File, [X|Str]); tok_number([X|File], Str) when ?is_number(X) -> tok_number(File, [X|Str]); tok_number(File, Str) -> {File, lists:reverse(Str)}. %%--------------------------------------- %% Variable %%--------------------------------------- tok_var([], Str) -> {[], lists:reverse(Str)}; tok_var([X|File], Str) when ?is_upper(X) -> tok_var(File, [X|Str]); tok_var([X|File], Str) when ?is_lower(X) -> tok_var(File, [X|Str]); tok_var([X|File], Str) when ?is_underline(X) -> tok_var(File, [X|Str]); tok_var([X|File], Str) when ?is_number(X) -> tok_var(File, [X|Str]); tok_var(File, Str) -> {File, lists:reverse(Str)}. %%--------------------------------------- %% Preprocessor command %%--------------------------------------- token_pp_com([X|File]) when ?is_upper(X) -> tok_var(File, [X]); token_pp_com([X|File]) when ?is_lower(X) -> tok_var(File, [X]); token_pp_com([X|File]) when ?is_underline(X) -> tok_var(File, [X]); token_pp_com([X|File]) when ?is_number(X) -> tok_var(File, [X]); token_pp_com(File) -> Rem = remove_leading_spaces(File), {Rem, "null"}. %%--------------------------------------- %% Comment %%--------------------------------------- token_comment([]) -> []; token_comment([$*,$/|File]) -> File; token_comment([$\n|File]) -> {[$/,$*|File], nl}; token_comment([$\r,$\n|File]) -> {[$/,$*|File], nl}; token_comment([$\\,$\n|File]) -> {[$/,$*|File], nl}; %token_comment([$\\,$\n|File]) -> % token_comment(File); token_comment([_|File]) -> token_comment(File). %%--------------------------------------- %% String %%--------------------------------------- token_string([], Str) -> {[], lists:reverse(Str)}; token_string([$"|File], Str) -> {File, lists:reverse(Str)}; token_string([$\n|File], Str) -> {File, lists:reverse(Str), nl}; token_string([$\r,$\n|File], Str) -> {File, lists:reverse(Str), nl}; token_string([$\\,$\n|File], Str) -> token_string(File, Str); token_string([X|File], Str) -> token_string(File, [X|Str]). %%--------------------------------------- %% Include %%--------------------------------------- token_include([], Str) -> {[], lists:reverse(Str)}; token_include([$>|File], Str) -> {File, lists:reverse(Str)}; token_include([$\n|File], Str) -> {File, lists:reverse(Str), nl}; token_include([$\r,$\n|File], Str) -> {File, lists:reverse(Str), nl}; token_include([$\\,$\n|File], Str) -> token_include(File, Str); token_include([X|File], Str) -> token_include(File, [X|Str]). %%=================================================================================== %% detokenise a list of tokens, until next newline %% %% Output: a string %%=================================================================================== detokenise(Tokens) -> detokenise(Tokens, []). detokenise([], Result) -> lists:flatten(Result); detokenise([space], Result) -> lists:flatten(Result); detokenise([space_exp], Result) -> lists:flatten(Result); detokenise([space|Rem], Result) -> detokenise(Rem, Result++[?space]); detokenise([space_exp|Rem], Result) -> detokenise(Rem, Result++[?space]); detokenise([nl|Rem], Result) -> detokenise(Rem, Result++[$\n]); detokenise([{_, String}|Rem], Result) -> detokenise(Rem, Result++[String]). detokenise_pragma(Tokens) -> detokenise_pragma(Tokens, []). detokenise_pragma([], Result) -> lists:flatten(Result); detokenise_pragma([space], Result) -> lists:flatten(Result); detokenise_pragma([space|Rem], Result) -> detokenise_pragma(Rem, Result++[?space]); detokenise_pragma([nl|Rem], Result) -> detokenise_pragma(Rem, Result++[$\n]); detokenise_pragma([{string, String}|Rem], Result) -> detokenise_pragma(Rem, Result++[$"|String]++[$"]); detokenise_pragma([{_, String}|Rem], Result) -> detokenise_pragma(Rem, Result++[String]). %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Expand macros. %% %% %% Output: A text file %% %% Description: Expands all macros. All macro definitions are logged in a list 'Defs' %% and all found errors and warnings are logged in a list 'Err' and 'War', %% respectively. %% %% When a macro name is found in a source line it is expanded according %% to the current 'Defs'-list. The macro must agree both to the name %% and number of parameters, otherwise an error is reported. %%====================================================================================== %%====================================================================================== %%====================================================================================== expand(List, FileName, IncDir, Flags) -> %% Get all definitions from preprocessor commnads %% and merge them on top of the file collected. CLDefs = get_cmd_line_defs(Flags), expand(List, [], [], CLDefs, [FileName], IncDir, #mio{}, check_all, [], [], 1, FileName). expand(List, Defs, Err, War, [FileName|IncFile], IncDir, Mio) -> expand(List, [], [], Defs, [FileName|IncFile], IncDir, Mio, check_all, Err, War, 1, FileName). %%======================================================= %% Main loop for the expansion %%======================================================= expand([], Out, _SelfRef, Defs, _IncFile, _IncDir, Mio, IfCou, Err, War, _L, _FN) -> % io:format("~n ===============~n"), % io:format(" definitions ~p~n",[lists:reverse(Defs)]), % io:format(" found warnings ~p~n",[lists:reverse(War)]), % io:format(" found errors ~p~n",[lists:reverse(Err)]), % io:format(" ===============~n~n~n"), {Out, Err, War, Defs, Mio, IfCou}; expand([{file_info, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, Str++Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); %%--------------------------------------- %% Searching for endif, %% i.e skip all source lines until matching %% end if is encountered %%--------------------------------------- expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "ifdef" -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif+1, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "ifndef" -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif+1, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "if" -> case pp_command(Command, Rem, Defs, IncDir, Mio, Err, War, L, FN) of {{'if', true}, Rem2, Err2, War2, Nl} -> IfCou2 = {endif, Endif+1, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); %% {{'if', false}, Rem2, Err2, War2, Nl} -> Not implemented yet {{'if', error}, Rem2, Err2, War2, Nl} -> IfCou2 = {endif, Endif, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN) end; expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) when Command == "endif" -> {_Removed, Rem2, Nl} = read_to_nl(Rem), case Endif of 1 -> Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L+Nl, FN); _ -> IfCou2 = {endif, Endif-1, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L+Nl, FN) end; expand([{command,_Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> {_Removed, Rem2, _Nl} = read_to_nl(Rem), IfCou2 = {endif, Endif, IfLine}, expand(Rem2, Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err, War, L, FN); %% Solves a bug when spaces in front of hashmark ! expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); expand([{nl,_Nl} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); expand([_X | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN) -> {_Removed, Rem2, Nl} = read_to_nl(Rem), Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, {endif, Endif, IfLine}, Err, War, L, FN); %%--------------------------------------- %% Check all tokens %%--------------------------------------- expand([{nl, _N} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [$\n | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L+1, FN); expand([space | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); expand([space_exp | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [?space | Out], SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN); expand([{command,Command} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L, FN) -> case pp_command(Command, Rem, Defs, IncDir, Mio, Err, War, L, FN) of {define, Rem2, Defs2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); {undef, Rem2, Defs2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs2, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); {{include, ok}, FileName, FileCont, Rem2, Nl, Err2, War2} -> {Out3, Defs3, Err3, War3, Mio2} = run_include(FileName, FileCont, Out, Defs, Err2, War2, L+Nl, IncFile, IncDir, Mio), Nls = [], Out4 = Out3++Nls++Out, expand(Rem2, Out4, SelfRef, Defs3, IncFile, IncDir, Mio2, check_all, Err3, War3, L+Nl, FN); {{include, error}, Rem2, Nl, Err2, War2} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err2, War2, L+Nl, FN); {{include, skip}, Rem2} -> Out2 = [$\n|Out], expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+1, FN); {{ifdef, true}, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); {{ifdef, false}, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Mio2 = update_mio(ifdef, Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {{ifndef, true}, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); {{ifndef, false}, Macro, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Mio2 = update_mio({ifndef, Macro}, Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {endif, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Mio2 = update_mio(endif, Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {{'if', true}, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, IfCou2 = {endif, 1, L}, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio, IfCou2, Err2, War2, L+Nl, FN); %% {{'if', false}, Removed, Rem2, Nl} -> Not implemented at present {{'if', error}, Rem2, Err2, War2, Nl} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Mio2 = update_mio('if', Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err2, War2, L+Nl, FN); {'else', {_Removed, Rem2, Nl}} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Err2 = {FN, L, "`else' command is not implemented at present"}, Mio2 = update_mio('else', Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN); {'elif', {_Removed, Rem2, Nl}} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, Err2 = {FN, L, "`elif' command is not implemented at present"}, Mio2 = update_mio('elif', Mio), expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN); {warning, {WarningText, Rem2, Nl}} -> [FileName|_More] = IncFile, War2 = {FileName, L, "warning: #warning "++detokenise(WarningText)}, Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, [War2|War], L+Nl, FN); {error, {ErrorText, Rem2, Nl}} -> [FileName|_More] = IncFile, Err2 = {FileName, L, detokenise(ErrorText)}, Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, [Err2|Err], War, L+Nl, FN); {{line, ok}, {_Removed, Rem2, Nl}, L2, FN2, LineText} -> Out2 = lists:duplicate(Nl,$\n)++LineText++Out, [_X|IF] = IncFile, IncFile2 = [FN2|IF], expand(Rem2, Out2, SelfRef, Defs, IncFile2, IncDir, update_mio(Mio), check_all, Err, War, L2, FN2); {{line, error}, {_Removed, Rem2, Nl}, Err2} -> Out2 = lists:duplicate(Nl,$\n) ++ Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, [Err2|Err], War, L+Nl, FN); hash_mark -> expand(Rem, Out, SelfRef, Defs, IncFile, IncDir, Mio, check_all, Err, War, L, FN); {pragma, Rem2, Nl, Text} -> Out2 = lists:duplicate(Nl,$\n)++Text++Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+Nl, FN); {ident, Rem2, Nl, Text} -> Out2 = lists:duplicate(Nl,$\n)++Text++Out, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, update_mio(Mio), check_all, Err, War, L+Nl, FN); {not_recognised, {Removed, Rem2, Nl}} -> Text = lists:reverse([$#|Command]), RemovedS = lists:reverse([?space|detokenise(Removed)]), Out2 = [$\n|RemovedS]++Text++Out, Mio2 = update_mio(Mio), case Command of [X|_T] when ?is_upper(X) -> expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); [X|_T] when ?is_lower(X) -> expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); [X|_T] when ?is_underline(X) -> expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, Err, War, L+Nl, FN); _ -> Err2 = {FN, L, "invalid preprocessing directive name"}, expand(Rem2, Out2, SelfRef, Defs, IncFile, IncDir, Mio2, check_all, [Err2|Err], War, L+Nl, FN) end; Else -> % io:format(" %%%%Else%%%%%% ~p~n",[Else]), exit(Else) end; expand([{var, "__LINE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> LL = io_lib:format("~p",[L]), expand(Rem, [LL | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, "__FILE__"}|Rem], Out, SelfRef, Defs, IncFile, Mio, IncDir, IfCou, Err, War, L, FN) -> expand(Rem, [$",FN,$" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, "__DATE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {{Y,M,D},{_H,_Mi,_S}} = calendar:universal_time(), Date = io_lib:format("\"~s ~p ~p\"",[month(M),D,Y]), expand(Rem, [Date | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, "__TIME__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {{_Y,_M,_D},{H,Mi,S}} = calendar:universal_time(), HS = if H < 10 -> "0"++integer_to_list(H); true -> integer_to_list(H) end, MiS = if Mi < 10 -> "0"++integer_to_list(Mi); true -> integer_to_list(Mi) end, SS = if S < 10 -> "0"++integer_to_list(S); true -> integer_to_list(S) end, Time = io_lib:format("\"~s:~s:~s\"",[HS,MiS,SS]), expand(Rem, [Time | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, "__INCLUDE_LEVEL__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> IL = io_lib:format("~p",[length(IncFile)-1]), expand(Rem, [IL | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, "__BASE_FILE__"}|Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> [BF|_T] = lists:reverse(IncFile), expand(Rem, [$",BF,$" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{var, Var} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {Out2, Err2, War2, Rem2, SelfRef2} = source_line(Var, Rem, SelfRef, Defs, Err, War, L, FN), expand(Rem2, [Out2 | Out], SelfRef2, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err2, War2, L, FN); expand([{char, Char} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [Char | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{number, Number} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [Number | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{expanded, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [Str | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{self_ref, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> SelfRef2 = lists:delete(Str,SelfRef), expand(Rem, Out, SelfRef2, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{string, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> expand(Rem, [$", Str, $" | Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L, FN); expand([{string_part, Str} | Rem], Out, SelfRef, Defs, IncFile, IncDir, Mio, IfCou, Err, War, L, FN) -> {Str2, Rem2, Nl} = expand_string_part([$"|Str], Rem), expand(Rem2, [Str2| Out], SelfRef, Defs, IncFile, IncDir, update_mio(Mio), IfCou, Err, War, L+Nl, FN). %%======================================================================== %% Expand a line starting as a partial string %%======================================================================== expand_string_part(Str, File) -> expand_string_part(File, Str, 0). expand_string_part([{string, Str_part} | Rem], Str, Nl) -> {Str++Str_part++[$"], Rem, Nl}; expand_string_part([space | Rem], Str, Nl) -> expand_string_part(Rem, Str, Nl); expand_string_part([nl| Rem], Str, Nl) -> expand_string_part(Rem, Str++[$\n], Nl); expand_string_part([{string_part, Str_part} | Rem], Str, Nl) -> expand_string_part(Rem, Str++Str_part, Nl). %%======================================================================== %% Parse and integrate command line macro directives %% At this momment, only -D and -U are supported (gcc like) %%======================================================================== %% Collect all command line macro definitions get_cmd_line_defs(Flags) -> Adjusted = parse_cmd_line(Flags,[]), {_Out, _Err, _War, Defs, _IfCou, _Mio} = expand(tokenise(Adjusted,""), [], [], [], [], [], #mio{}, check_all, [], [], 1, ""), Defs. %% Parse command line macros parse_cmd_line([],Found) -> lists:flatten(lists:reverse(Found)); parse_cmd_line([45,68|Rest],Found) -> {Collected,RestCmds} = collect_define(Rest,[]), parse_cmd_line(RestCmds,[Collected|Found]); parse_cmd_line([45,85|Rest],Found) -> {Collected,RestCmds} = collect_undefine(Rest,[]), parse_cmd_line(RestCmds,[Collected|Found]); parse_cmd_line([_|Rest],Found) -> parse_cmd_line(Rest,Found). %% Collect defines and translate them %% into a text format collect_define([],Found) -> { "#define "++lists:reverse(Found)++"\n", [] }; collect_define([32|Rest],Found) -> { "#define "++lists:reverse(Found)++"\n", Rest }; collect_define([61|Rest],[]) -> { "", Rest }; collect_define([61|Rest],Found) -> collect_define(Rest,[32|Found]); collect_define([C|Rest],Found) -> collect_define(Rest,[C|Found]). %% Collect undefines and translate them %% into a text format collect_undefine([],Found) -> { "#undef "++lists:reverse(Found)++"\n", [] }; collect_undefine([32|Rest],Found) -> { "#undef "++lists:reverse(Found)++"\n", Rest }; collect_undefine([C|Rest],Found) -> collect_undefine(Rest,[C|Found]). %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Read a preprocessor command %% %% %% Output: Depending of the command, typically = {Command, Rem, Err, War, Nl} %% %%====================================================================================== %%====================================================================================== %%====================================================================================== pp_command(Command, [space|File], Defs, IncDir, Mio, Err, War, L, FN) -> pp_command(Command, File, Defs, IncDir, Mio, Err, War, L, FN); pp_command(Command, File, Defs, IncDir, Mio, Err, War, L, FN) -> case Command of %%---------------------------------------- %% #define %%---------------------------------------- "define" -> case define(File, Err, War, L, FN) of {error, Rem, Err2, War2, Nl} -> {define, Rem, Defs, Err2, War2, Nl}; {warning, Rem, Name, No_of_para, Parameters, Macro, Err2, War2, Nl} -> case is_define_ok(Name, No_of_para, Parameters, Macro, Defs) of {yes, Defs2} -> {define, Rem, Defs2, Err2, War2, Nl}; {no, Defs2} -> Text = lists:flatten(io_lib:format("`~s' redefined",[Name])), {define, Rem, Defs2, Err2, [{FN, L, Text}|War2], Nl}; {error, Text, Defs2} -> {define, Rem, Defs2, [{FN, L, Text}|Err2], War2, Nl} end; {ok, Rem, Name, No_of_para, Parameters, Macro, Err2, War2, Nl} -> case is_define_ok(Name, No_of_para, Parameters, Macro, Defs) of {yes, Defs2} -> {define, Rem, Defs2, Err2, War2, Nl}; {no, Defs2} -> Text = lists:flatten(io_lib:format("`~s' redefined",[Name])), {define, Rem, Defs2, Err2, [{FN, L, Text}|War2], Nl}; {error, Text, Defs2} -> {define, Rem, Defs2, [{FN, L, Text}|Err2], War2, Nl} end end; %%---------------------------------------- %% #undef %%---------------------------------------- "undef" -> case undef(File, Err, War, L, FN) of {error, Rem, Err2, War2, Nl} -> {undef, Rem, Defs, Err2, War2, Nl}; {ok, Rem, Name, Err2, War2, Nl} -> Defs2 = lists:keydelete(Name, 1, Defs), {undef, Rem, Defs2, Err2, War2, Nl} end; %%---------------------------------------- %% #include %%---------------------------------------- "include" -> case include(File, IncDir, Mio) of {error, Rem, Nl, Err2} -> {{include, error}, Rem, Nl, [{FN, L, Err2}|Err], War}; {error, Rem, Nl, Err2, NameNl} -> {{include, error}, Rem, Nl, [{FN, L+ NameNl, Err2}|Err], War}; {ok, FileNamePath, FileCont, Rem, Nl} -> {{include, ok}, FileNamePath, FileCont, Rem, Nl, Err, War}; {skip, Rem} -> {{include, skip}, Rem} end; %%---------------------------------------- %% #ifdef %%---------------------------------------- "ifdef" -> case define(File, Err, War, L, FN) of {error, Rem, Err2, War2, Nl} -> {{ifdef, false}, Rem, Defs, Err2, War2, Nl}; {warning, Rem, Name, No_of_para, _Parameters, _Macro, Err2, War2, Nl} -> case is_defined_before(Name, No_of_para, Defs) of yes -> {{ifdef, false}, Rem, Err2, War2, Nl}; no -> {{ifdef, true}, Rem, Err2, War2, Nl} end; {ok, Rem, Name, No_of_para, _Parameters, _Macro, Err2, War2, Nl} -> case is_defined_before(Name, No_of_para, Defs) of yes -> {{ifdef, false}, Rem, Err2, War2, Nl}; no -> {{ifdef, true}, Rem, Err2, War2, Nl} end end; %%---------------------------------------- %% #ifndef %%---------------------------------------- "ifndef" -> case define(File, Err, War, L, FN) of {error, Rem, Err2, War2, Nl} -> {{ifndef, false}, Rem, Defs, Err2, War2, Nl}; {warning, Rem, Name, No_of_para, _Parameters, _Macro, Err2, War2, Nl} -> case is_defined_before(Name, No_of_para, Defs) of yes -> {{ifndef, true}, Rem, Err2, War2, Nl}; no -> {{ifndef, false}, Name, Rem, Err2, War2, Nl} end; {ok, Rem, Name, No_of_para, _Parameters, _Macro, Err2, War2, Nl} -> case is_defined_before(Name, No_of_para, Defs) of yes -> {{ifndef, true}, Rem, Err2, War2, Nl}; no -> {{ifndef, false}, Name, Rem, Err2, War2, Nl} end end; %%---------------------------------------- %% #endif %%---------------------------------------- "endif" -> {Removed, Rem, Nl} = read_to_nl(File), case Removed of [] -> {endif, Rem, Err, War, 1}; _ -> Text = "ignoring the tail of the line", {ok, Rem, Err, [{FN, L, Text}|War], Nl} end; %%---------------------------------------- %% #if %%---------------------------------------- "if" -> case if_zero(File, Err, War, L, FN) of {error, Rem2, _Removed, Nl} -> Err2 = {FN, L, "only '#if 0' is implemented at present"}, {{'if', error}, Rem2, [Err2 | Err], War, Nl}; {ok, Rem2, 0, _Removed, Nl} -> {{'if', true}, Rem2, Err, War, Nl}; {ok, Rem2, _Num, _Removed, Nl} -> Err2 = {FN, L, "only '#if 0' is implemented at present"}, {{'if', error}, Rem2, [Err2 | Err], War, Nl} end; %%---------------------------------------- %% #else %%---------------------------------------- "else" -> {'else', read_to_nl(File)}; %%---------------------------------------- %% #elif %%---------------------------------------- "elif" -> {'elif', read_to_nl(File)}; %%---------------------------------------- %% #pragma %%---------------------------------------- "pragma" -> {Removed, Rem, Nl} = read_to_nl(File), {pragma, Rem, Nl, lists:reverse("#pragma " ++ detokenise_pragma(Removed))}; %%---------------------------------------- %% #ident %%---------------------------------------- "ident" -> {Removed, Rem, Nl} = read_to_nl(File), {ident, Rem, Nl, lists:reverse("#ident " ++ detokenise_pragma(Removed))}; %%---------------------------------------- %% #warning %%---------------------------------------- "warning" -> {warning, read_to_nl(File)}; %%---------------------------------------- %% #error %%---------------------------------------- "error" -> {error, read_to_nl(File)}; %%---------------------------------------- %% #line %%---------------------------------------- "line" -> line(File, L, FN); %%---------------------------------------- %% # %%---------------------------------------- "null" -> hash_mark; %%---------------------------------------- %% not recognised preprocessor commands %%---------------------------------------- _Else -> {not_recognised, read_to_nl(File)} end. %%=============================================================== %%=============================================================== %%=============================================================== %% if %% %% Only #if 0 is implemented at the time to be able to use if %% to comment some code parts. %%=============================================================== %%=============================================================== %%=============================================================== if_zero(File, _Err, _War, _L, _FN) -> case if_zero(File) of {ok, Remain, Num, Removed, Nl} -> case catch list_to_integer(Num) of {'EXIT', _} -> {Removed2, Rem2, Nl2} = read_to_nl(File), {error, Rem2, Removed2, Nl2}; Int -> {ok, Remain, Int, Removed, Nl} end; E -> E end. if_zero([{number,Num}]) -> {ok, [], Num, [], 0}; if_zero([{number,Num}, space]) -> {ok, [], Num, [], 0}; if_zero([{number,Num} | Rem]) -> {Removed, Rem2, Nl} = read_to_nl(Rem), {ok, Rem2, Num, Removed, Nl}; %if_zero([{number,Num}, {nl,_X} | Rem]) -> % {ok, Rem, Num, [], 1}; if_zero(Rem) -> {Removed, Rem2, Nl} = read_to_nl(Rem), {error, Rem2, Removed, Nl}. %%=============================================================== %%=============================================================== %%=============================================================== %% Define macro %% %% Check the syntax of the macro, extract the parameters if any. %% If valid macro it is added to the Defs-list. %% If a macro is redefined, a warning will be given, the latest %% definition is always the valid one. %%=============================================================== %%=============================================================== %%=============================================================== define(File, Err, War, L, FN) -> case define_name(File) of {ok, Rem, Name, No_of_para, Parameters, Macro, Nl} -> {ok, Rem, Name, No_of_para, Parameters, Macro, Err, War, Nl}; {{warning,no_space}, Rem, Name, No_of_para, Parameters, Macro, Nl} -> Text = lists:flatten(io_lib:format("missing white space after `#define ~s'",[Name])), {warning, Rem, Name, No_of_para, Parameters, Macro, Err, [{FN, L, Text}|War], Nl}; {error, invalid_name, Nl} -> Text = "invalid macro name", {_Removed, Rem, Nl2} = read_to_nl(File), {error, Rem, [{FN, L, Text}|Err], War, Nl+Nl2}; {error, invalid_name, Name, Nl} -> Text = lists:flatten(io_lib:format("invalid macro name `~s'",[Name])), {_Removed, Rem, Nl2} = read_to_nl(File), {error, Rem, [{FN, L, Text}|Err], War, Nl+Nl2}; {error, illegal_arg} -> {Removed, Rem, Nl} = read_to_nl(File), RemovedS = detokenise(Removed), Text = lists:flatten(io_lib:format("Invalid argument list ~s",[RemovedS])), {error, Rem, [{FN, L, Text}|Err], War, Nl} end. %%=========================================================== %% Check if valid macro %%=========================================================== define_name([]) -> {warning, no_macro}; define_name([space]) -> {warning, no_macro}; %% Macro with parameters define_name([{var,Name},{char,$(}|Rem]) -> case read_para([{char,$(}|Rem]) of {ok, Rem2, Para, NoOfPara} -> {Removed, Rem3, _Nl} = read_to_nl(Rem2), {ok, Rem3, Name, NoOfPara, Para, Removed, 1}; Error -> Error end; %% Macro without parameters define_name([{var,Name}]) -> {ok, [], Name, 0, [], [], 0}; define_name([{var,Name}, space | Rem]) -> {Removed, Rem2, Nl} = read_to_nl(Rem), {ok, Rem2, Name, 0, [], Removed, Nl}; define_name([{var,Name}, {nl,_X} | Rem]) -> {ok, Rem, Name, 0, [], [], 1}; define_name([{var,Name} | Rem]) -> {Removed, Rem2, Nl} = read_to_nl(Rem), {{warning,no_space}, Rem2, Name, 0, [], Removed, Nl}; %% Invalid macro name define_name([{number, Name} | _Rem]) -> {error, invalid_name, Name, 0}; define_name(_Rem) -> {error, invalid_name, 0}. %%=============================================================== %%=============================================================== %%=============================================================== %% Undefine macro %% %% If it is a valid undef command the macro name will be deleted %% from the Defs-list %%=============================================================== %%=============================================================== %%=============================================================== undef(File, Err, War, L, FN) -> case undef(File) of {ok, Rem, Name, Nl} -> {ok, Rem, Name, Err, War, Nl}; {warning, Rem, Name, Nl} -> Text = "ignoring the tail of the line", {ok, Rem, Name, Err, [{FN, L, Text}|War], Nl}; {error, invalid_name} -> Text = "invalid macro name", {_Removed, Rem, Nl} = read_to_nl(File), {error, Rem, [{FN, L, Text}|Err], War, Nl}; {error, invalid_name, Name} -> Text = lists:flatten(io_lib:format("invalid macro name `~s'",[Name])), {_Removed, Rem, Nl} = read_to_nl(File), {error, Rem, [{FN, L, Text}|Err], War, Nl} end. %%------------------------------------------------- %% Check if valid macro name %%------------------------------------------------- undef([]) -> {error, invalid_name, []}; %% Valid name undef([{var,Name}]) -> {ok, [], Name, 0}; undef([{var,Name}, {nl,_X} | Rem]) -> {ok, Rem, Name, 1}; undef([{var,Name}, space, {nl,_X} | Rem]) -> {ok, Rem, Name, 1}; undef([{var,Name} | Rem]) -> {_Removed, Rem2, Nl} = read_to_nl(Rem), {warning, Rem2, Name, Nl}; %% Invalid macro name undef([{number, Name} | _Rem]) -> {error, invalid_name, Name}; undef(_Rem) -> {error, invalid_name}. %%=============================================================== %%=============================================================== %%=============================================================== %% Include macro %% %% Read the included file %%=============================================================== %%=============================================================== %%=============================================================== include(File, IncDir, Mio) -> case include2(File) of {ok, FileName, Rem, Nl, FileType} -> Result = read_inc_file(FileName, IncDir, Mio), case {Result, Nl, FileType} of {{ok, FileNamePath, FileCont}, _, _} -> {ok, FileNamePath, FileCont, Rem, Nl}; {skip, _, _} -> {skip, Rem}; {{error, Text}, _, own_file} -> NameNl = count_nl(FileName,0), Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), {error, Rem, Nl, Error, NameNl}; {{error, Text}, 1, sys_file} -> NameNl = count_nl(FileName,0), Error = lists:flatten(io_lib:format("~s: ~s",[FileName,Text])), {error, Rem, Nl, Error, NameNl}; {{error, _Text}, _, sys_file} -> {error, Rem, Nl, "`#include' expects \"FILENAME\" or "} end; {error, {_Removed, Rem, Nl}} -> {error, Rem, Nl, "`#include' expects \#FILENAME\" or "} end. count_nl([],Nl) -> Nl; count_nl([$\n|T],Nl) -> count_nl(T,Nl+1); count_nl([_H|T],Nl) -> count_nl(T,Nl). %%================================================= %% Extract the file name from the token list %%================================================= include2([space|Rem]) -> include2(Rem); include2([{string, FileName}]) -> {ok, FileName, [], 1, own_file}; include2([{string, FileName}, space]) -> {ok, FileName, [], 1, own_file}; include2([{string, FileName}, {nl, _X} | Rem]) -> {ok, FileName, Rem, 1, own_file}; include2([{string, FileName}, space, {nl, _X} | Rem]) -> {ok, FileName, Rem, 1, own_file}; include2([{string, _FileName}, _No_nl | Rem]) -> {error, read_to_nl(Rem)}; include2([{string_part, File_part}, {nl, _X} | Rem]) -> case include_read_string_file_name(File_part++[$\n], Rem, 1) of {ok, FileName, Rem2, Nl} -> {ok, FileName, Rem2, Nl, own_file}; error -> {error, read_to_nl([{string_part,File_part} | Rem])} end; include2([{sys_head, FileName}]) -> {ok, FileName, [], 1, sys_file}; include2([{sys_head, FileName}, space]) -> {ok, FileName, [], 1, sys_file}; include2([{sys_head, FileName}, {nl, _X} | Rem]) -> {ok, FileName, Rem, 1, sys_file}; include2([{sys_head, FileName}, space, {nl, _X} | Rem]) -> {ok, FileName, Rem, 1, sys_file}; include2([{sys_head, _FileName}, _No_nl | Rem]) -> {error, read_to_nl(Rem)}; include2([{sys_head_part ,File_part}, {nl, _X} | Rem]) -> case include_read_sys_file_name(File_part++[$\n], Rem, 1) of {ok, FileName, Rem2, Nl} -> {ok, FileName, Rem2, Nl, sys_file}; error -> {error, read_to_nl([{sys_head_part, File_part} | Rem])} end; include2(Rem) -> {error, read_to_nl(Rem)}. %%------------------------------------------------- %% File name framed by " " %%------------------------------------------------- include_read_string_file_name(File, [{string, File_part}, {nl,_X} | Rem], Nl) -> {ok, File++File_part, Rem, Nl+1}; include_read_string_file_name(File, [{string_part, File_part}, {nl,_X} | Rem], Nl) -> include_read_string_file_name(File++File_part++[$\n], Rem, Nl+1); include_read_string_file_name(_File, _X, _Nl) -> error. %%------------------------------------------------- %% File name framed by < > %%------------------------------------------------- include_read_sys_file_name(File, [{sys_head, File_part}, {nl,_X} | Rem], Nl) -> {ok, File++File_part, Rem, Nl+1}; include_read_sys_file_name(File, [{sys_head_part, File_part}, {nl,_X} | Rem], Nl) -> include_read_sys_file_name(File++File_part++[$\n], Rem, Nl+1); include_read_sys_file_name(_File, _X, _Nl) -> error. %%=============================================================== %%=============================================================== %%=============================================================== %% Line macro %% %% The line macro may redefine both the current line number and %% the current file name: #line ' new_line_nr' 'new_file_name' %%=============================================================== %%=============================================================== %%=============================================================== line(File, L, FN) -> line(File, L, FN, not_defined, not_defined). line([], L, FN, _Line, _File) -> {{line, error}, {[],[],0}, {FN,L,"invalid format `#line' directive"}}; line([space|Rem], L, FN, Line, File) -> line(Rem, L, FN, Line, File); %%------------------------------ %% Line number expected %%------------------------------ line([{number,Number}|Rem], L, FN, not_defined, File) -> case catch list_to_integer(Number) of {'EXIT', _} -> {{line, error}, read_to_nl(Rem), {FN,L,"invalid format `#line' directive"}}; Int -> line(Rem, L, FN, Int, File) end; line(Rem, L, FN, not_defined, _File) -> {{line, error}, read_to_nl(Rem), {FN,L,"invalid format `#line' directive"}}; %%------------------------------ %% File name or newline expected %%------------------------------ line([{nl, _NL}|Rem], _L, FN, Line, not_defined) -> {{line, ok}, {[],Rem,1}, Line, FN, io_lib:format("~n~p ~p #",[FN, Line-1])}; line([{string,NewFN}|Rem], _L, _FN, Line, not_defined) -> {{line, ok}, read_to_nl(Rem), Line, NewFN, io_lib:format("~n~p ~p #",[NewFN, Line-1])}; line(Rem, L, FN, _Line, _File) -> {{line, error}, read_to_nl(Rem), {FN,L,"invalid format `#line' directive"}}. %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Source line %% %% %% Output: {Str, Err, War, Rem, SelfRef} %% %% Description: The input source line is searched for macros. If a macro is found it %% is expanded. The result of an expansion is rescanned for more macros. %% To prevent infinite loops if the macro is self referring %% an extra token is put into the Rem list. The variable SelfRef %% contains all the macros which are inhibited to be expanded. %% A special specae token is also inserted to prevent not wanted %% concatinations if one of the variables to be concatinated is expanded. %%====================================================================================== %%====================================================================================== %%====================================================================================== source_line(Str, Rem, SelfRef, Defs, Err, War, L, FN) -> {Rem2, Para, No_of_para} = case read_para(Rem) of {ok, RemT, ParaT, No_of_paraT} -> {RemT, ParaT, No_of_paraT}; {error, illegal_arg} -> {[], [], 0} end, %%------------------------------------------------- %% Check if a valid macro %%------------------------------------------------- case lists:keysearch(Str, 1, Defs) of %% a macro without parameters {value, {Str, 0, _MacroPara, Macro}} -> case lists:member(Str, SelfRef) of true -> {[Str], Err, War, Rem, SelfRef}; false -> ExpandedRes2 = sl_mark_expanded(Macro, Str), {[], Err, War, ExpandedRes2 ++ [{self_ref,Str}|Rem], [Str|SelfRef]} end; %% a macro with parameters {value, {Str, N, _MacroPara, Macro}} when N == No_of_para -> case lists:member(Str, SelfRef) of true -> {[Str], Err, War, Rem, SelfRef}; false -> ExpandedRes = sl_macro_expand(Macro, Para, Defs), ExpandedRes2 = sl_mark_expanded(ExpandedRes, Str), {[], Err, War, ExpandedRes2 ++ [{self_ref,Str}|Rem2], [Str|SelfRef]} end; %% a variable, because it doesn't have any parameters {value, {Str, _N, _MacroPara, _Macro}} when No_of_para == 0 -> {Str, Err, War, Rem, SelfRef}; %% illegal no of parameters {value, {Str, N, _MacroPara, _Macro}} when No_of_para < N -> Text = io_lib:format(" macro `~s' used with just ~p arg",[Str,No_of_para]), Err2 = {FN, L, lists:flatten(Text)}, {Str, [Err2|Err], War, Rem, SelfRef}; {value, {Str, _N, _MacroPara, _Macro}} -> Text = io_lib:format(" macro `~s' used with too many (~p) args",[Str,No_of_para]), Err2 = {FN, L, lists:flatten(Text)}, {Str, [Err2|Err], War, Rem, SelfRef}; %% no macro false -> {Str, Err, War, Rem, SelfRef} end. %%================================================= %% Expand a macro %%================================================= sl_macro_expand(Macro, Para, Defs) -> sl_macro_expand(Macro, Para, Defs, []). %%................... %% End %%................... sl_macro_expand([], _Para, _Defs, Res) -> lists:reverse(Res); %%................... %% Concatination %%................... %% para ## para sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, Res) -> Exp = sl_para_para({para, N},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% para## para sl_macro_expand([{para, N}, {char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, Res) -> Exp = sl_para_para({para, N},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% para ##para sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, {para,M} | T], Para, Defs, Res) -> Exp = sl_para_para({para, N},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% para##para sl_macro_expand([{para, N}, {char,$#}, {char,$#}, {para,M} | T], Para, Defs, Res) -> Exp = sl_para_para({para, N},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% para ## var sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, space, {var, Var}|T], Para, Defs, Res) -> Exp = sl_para_var({para, N}, {var, Var}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% para## var sl_macro_expand([{para, N}, {char,$#}, {char,$#}, space, {var, Var} | T], Para, Defs, Res) -> [{var, VarN}] = lists:nth(N,Para), sl_macro_expand(T, Para, Defs, [{expanded,Var},{expanded,VarN}, space |Res]); %% para ##var sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, {var, Var} | T], Para, Defs, Res) -> [{var, VarN}] = lists:nth(N,Para), sl_macro_expand(T, Para, Defs, [{expanded,Var},{expanded,VarN}, space |Res]); %% para##var sl_macro_expand([{para, N}, {char,$#}, {char,$#}, {var, Var} | T], Para, Defs, Res) -> [{var, VarN}] = lists:nth(N,Para), sl_macro_expand(T, Para, Defs, [{expanded,Var},{expanded,VarN}, space |Res]); %% var ## para sl_macro_expand([{var, Var}, space, {char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, Res) -> Exp = sl_var_para({var, Var},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% var## para sl_macro_expand([{var, Var}, {char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, Res) -> Exp = sl_var_para({var, Var},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% var ##para sl_macro_expand([{var, Var}, space, {char,$#}, {char,$#}, {para,M} | T], Para, Defs, Res) -> Exp = sl_var_para({var, Var},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% var##para sl_macro_expand([{var, Var}, {char,$#}, {char,$#}, {para,M} | T], Para, Defs, Res) -> Exp = sl_var_para({var, Var},{para, M}, Para), sl_macro_expand(Exp++T, Para, Defs, [space |Res]); %% expanded ## para sl_macro_expand([space, {char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, [{expanded, Var}|Res]) -> [{var, VarM}] = lists:nth(M,Para), sl_macro_expand(T, Para, Defs, [{expanded,VarM},{expanded,Var} |Res]); %% expanded## para sl_macro_expand([{char,$#}, {char,$#}, space, {para,M} | T], Para, Defs, [{expanded, Var}|Res]) -> [{var, VarM}] = lists:nth(M,Para), sl_macro_expand(T, Para, Defs, [{expanded,VarM},{expanded,Var} |Res]); %% expanded ##para sl_macro_expand([space, {char,$#}, {char,$#}, {para,M} | T], Para, Defs, [{expanded, Var}|Res]) -> [{var, VarM}] = lists:nth(M,Para), sl_macro_expand(T, Para, Defs, [{expanded,VarM},{expanded,Var} |Res]); %% expanded##para sl_macro_expand([{char,$#}, {char,$#}, {para,M} | T], Para, Defs, [{expanded, Var}|Res]) -> [{var, VarM}] = lists:nth(M,Para), sl_macro_expand(T, Para, Defs, [{expanded,VarM},{expanded,Var} |Res]); %% para ## ? sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, space, X | T], Para, Defs, Res) -> Reexp = sl_macro_reexpand(lists:nth(N,Para), Defs, []), sl_macro_expand([X, space|T], Para, Defs, lists:flatten([Reexp, space|Res])); %% para## ? sl_macro_expand([{para, N}, {char,$#}, {char,$#}, space, X | T], Para, Defs, Res) -> Reexp = sl_macro_reexpand(lists:nth(N,Para), Defs, []), sl_macro_expand([X, space|T], Para, Defs, lists:flatten([Reexp, space|Res])); %% para ##? sl_macro_expand([{para, N}, space, {char,$#}, {char,$#}, X | T], Para, Defs, Res) -> Reexp = sl_macro_reexpand(lists:nth(N,Para), Defs, []), sl_macro_expand([X, space|T], Para, Defs, lists:flatten([Reexp, space|Res])); %% para##? sl_macro_expand([{para, N}, {char,$#}, {char,$#}, X | T], Para, Defs, Res) -> Reexp = sl_macro_reexpand(lists:nth(N,Para), Defs, []), sl_macro_expand([X, space|T], Para, Defs, lists:flatten([Reexp, space|Res])); sl_macro_expand([{char,$#}, {char,$#}, space |T], Para, Defs, [space|Res]) -> sl_macro_expand(T, Para, Defs, Res); sl_macro_expand([{char,$#}, {char,$#} |T], Para, Defs, [space|Res]) -> sl_macro_expand(T, Para, Defs, Res); sl_macro_expand([{char,$#}, {char,$#}, space |T], Para, Defs, Res) -> sl_macro_expand(T, Para, Defs, Res); sl_macro_expand([{char,$#}, {char,$#} |T], Para, Defs, Res) -> sl_macro_expand(T, Para, Defs, Res); %%................... %% Stringification %%................... sl_macro_expand([{char,$#}, {para, N}|T], Para, Defs, Res) -> Nth = lists:nth(N,Para), Tokens = detokenise(Nth), sl_macro_expand(T, Para, Defs, [{string,Tokens}|Res]); sl_macro_expand([{char,$#}, space, {para, N}|T], Para, Defs, Res) -> Nth = lists:nth(N,Para), Tokens = detokenise(Nth), sl_macro_expand(T, Para, Defs, [{string,Tokens}|Res]); %%................... %% A parameter %%................... sl_macro_expand([{para, N}|T], Para, Defs, Res) -> Reexp = sl_macro_reexpand(lists:nth(N,Para), Defs, []), sl_macro_expand(T, Para, Defs, lists:flatten([Reexp|Res])); %%................... %% No parameter %%................... sl_macro_expand([H|T], Para, Defs, Res) -> sl_macro_expand(T, Para, Defs, [H|Res]). %%------------------------------------------------- %% Expand parameters %%------------------------------------------------- sl_para_para({para, N}, {para, M}, Para) -> case sl_para_1st(lists:nth(N,Para)) of {ok, Para1st} -> Para1st ++ sl_para_2nd(lists:nth(M,Para)); {exp, Para1st} -> Para1st ++ sl_para_2nd(lists:nth(M,Para)) ++ [space_exp]; {space, Para1st} -> Para1st ++ [space_exp | sl_para_2nd(lists:nth(M,Para))] end. sl_var_para(Var, {para, M}, Para) -> [Var|sl_para_2nd(lists:nth(M,Para))]. sl_para_var({para, N}, Var, Para) -> case sl_para_1st(lists:nth(N,Para)) of {ok, Para1st} -> Para1st ++ [Var]; {exp, Para1st} -> Para1st ++ [Var | space_exp]; {space, Para1st} -> Para1st ++ [space_exp | Var] end. sl_para_1st([{var, Var}]) -> {ok,[{expanded,Var}]}; sl_para_1st([{var, Var}, space]) -> {ok,[{expanded,Var}]}; sl_para_1st([{var, Var}, space_exp]) -> {exp, [{expanded,Var}]}; sl_para_1st(L) -> {space, L}. sl_para_2nd([{var, Var}]) -> [{expanded,Var}]; sl_para_2nd([{var, Var}, space_exp]) -> [{expanded,Var}]; sl_para_2nd([space, {var, Var}]) -> [{expanded,Var}]; sl_para_2nd([space_exp, {var, Var}]) -> [{expanded,Var}]; sl_para_2nd(L) -> L++[space]. %%------------------------------------------------- %% Check if the expansion is a valid macro, %% do not reexpand if concatination %%------------------------------------------------- sl_macro_reexpand([], _Defs, Result) -> Result; sl_macro_reexpand([{var,Var}|Rem], Defs, Result) -> case lists:keysearch(Var, 1, Defs) of {value, {Var, 0, _MacroPara, Macro}} -> Rem2 = case Rem of [space | RemT] -> [space_exp | RemT]; _ -> [space_exp | Rem] end, sl_macro_reexpand(Macro++Rem2, Defs, Result); _ -> sl_macro_reexpand(Rem, Defs, [{var,Var}|Result]) end; sl_macro_reexpand([H|Rem], Defs, Result) -> sl_macro_reexpand(Rem, Defs, [H|Result]). %%------------------------------------------------- %% Self referring macros are marked not be reexpanded %%------------------------------------------------- sl_mark_expanded(QQ, Str) -> sl_mark_expanded(QQ, Str, []). sl_mark_expanded([], _Str, Res) -> lists:reverse(Res); sl_mark_expanded([H|T], Str, Res) -> case H of {_,Str} -> sl_mark_expanded(T, Str, [{expanded, Str}|Res]); _ -> sl_mark_expanded(T, Str, [H|Res]) end. %%====================================================================================== %%====================================================================================== %%====================================================================================== %% Misceleaneous functions %%====================================================================================== %%====================================================================================== %%====================================================================================== %%=============================================================== %% Check the Flags for include directories %%=============================================================== include_dir(Flags) when is_list(Flags)-> include_dir(Flags,[]); include_dir(_Flags) -> []. include_dir(Flags,IncDir) -> case string:str(Flags,"-I") of 0 -> lists:reverse(IncDir); X -> Rem2 = string:sub_string(Flags, X+2), Rem = string:strip(Rem2, left), Y = string:str(Rem," "), case string:str(Rem," ") of 0 -> lists:reverse([string:sub_string(Rem, Y+1)|IncDir]); Y -> include_dir(string:sub_string(Rem, Y+1), [string:sub_string(Rem,1,Y-1)|IncDir]) end end. %%=============================================================== %% Read a included file. Try current dir first then the IncDir list %%=============================================================== read_inc_file(FileName, IncDir, Mio) -> case find_inc_file(FileName, IncDir) of {ok, AbsFile} -> %% is included before? case lists:member(FileName, Mio#mio.included) of false -> case catch file:read_file(AbsFile) of {ok, Bin} -> FileList = binary_to_list(Bin), {ok, AbsFile, FileList}; {error, Text} -> {error, Text} end; true -> skip end; {error, Text} -> {error, Text} end. find_inc_file(FileName, IncDir) -> case catch file:read_file_info(FileName) of {ok, _} -> {ok, FileName}; {error, _} -> find_inc_file2(FileName, IncDir) end. find_inc_file2(_FileName, []) -> {error, "No such file or directory"}; find_inc_file2(FileName, [D|Rem]) -> Dir = case lists:last(D) of $/ -> D; _ -> D++"/" end, case catch file:read_file_info(Dir++FileName) of {ok, _} -> {ok, Dir++FileName}; {error, _} -> find_inc_file2(FileName, Rem) end. %%=============================================================== %% Read parameters of a macro or a variable in a source line %%=============================================================== read_para([{char,$(} | Rem]) -> read_para(Rem, 1, [], [], 1); read_para([space,{char,$(} | Rem]) -> read_para(Rem, 1, [], [], 1); read_para(_Rem) -> {ok, [], [], 0}. %% Abrupt end of the list read_para([], _NoOfParen, _Current, _Para, _NoOfPara) -> {error, illegal_arg}; %% All parameters checked read_para([{char,$)}|Rem], 1, [], Para, NoOfPara) -> {ok, Rem, lists:reverse(Para), NoOfPara}; read_para([{char,$)}|Rem], 1, Current, Para, NoOfPara) -> {ok, Rem, lists:reverse([Current|Para]), NoOfPara}; %% Continue reading read_para([{char,$)}|Rem], NoOfParen, Current, Para, NoOfPara) -> read_para(Rem, NoOfParen-1, Current++[{char,$)}], Para, NoOfPara); read_para([{char,$(}|Rem], NoOfParen, Current, Para, NoOfPara) -> read_para(Rem, NoOfParen+1, Current++[{char,$(}], Para, NoOfPara); read_para([{char,$,}|Rem], NoOfParen, Current, Para, NoOfPara) when NoOfParen == 1 -> read_para(Rem, NoOfParen, [], [Current|Para], NoOfPara+1); read_para([space|Rem], NoOfParen, [], Para, NoOfPara) -> read_para(Rem, NoOfParen, [], Para, NoOfPara); read_para([X|Rem], NoOfParen, Current, Para, NoOfPara) -> read_para(Rem, NoOfParen, Current++[X], Para, NoOfPara). %%=================================================================================== %% check if a macro is already defined %%=================================================================================== is_define_ok(Name, No_of_para, Parameters, Macro, Defs) -> case lists:keysearch(Name, 1, Defs) of {value, {Name, No_of_para, _MacroPara, Macro}} -> {yes, Defs}; {value, _} -> Defs2 = lists:keydelete(Name, 1, Defs), NewMacro = is_define_ok_check_para(Parameters, Macro, []), case is_stringify_ok(NewMacro) of yes -> {no, [{Name, No_of_para, Parameters, NewMacro}|Defs2]}; no -> ErrorText = "`#' operator is not followed by a macro argument name", {error, ErrorText, [{Name, No_of_para, Parameters, NewMacro}|Defs2]} end; false -> NewMacro = is_define_ok_check_para(Parameters, Macro, []), case is_stringify_ok(NewMacro) of yes -> {yes, [{Name, No_of_para, Parameters, NewMacro}|Defs]}; no -> ErrorText = "`#' operator is not followed by a macro argument name", {error, ErrorText, [{Name, No_of_para, Parameters, NewMacro}|Defs]} end end. is_define_ok_check_para(_Para, [], Result) -> lists:reverse(Result); is_define_ok_check_para(Para, [H|T], Result) -> case define_arg_para_number(1, Para, H) of no_para -> is_define_ok_check_para(Para, T, [H|Result]); N -> is_define_ok_check_para(Para, T, [{para,N}|Result]) end. define_arg_para_number(_N, [], _Current) -> no_para; define_arg_para_number(N, [H|_Para], Current) when H == [Current] -> N; define_arg_para_number(N, [_H|Para], Current) -> define_arg_para_number(N+1, Para, Current). is_stringify_ok([]) -> yes; is_stringify_ok([{char,$#},{char,$#}|T]) -> is_stringify_ok(T); is_stringify_ok([{char,$#},space,{para,_X}|T]) -> is_stringify_ok(T); is_stringify_ok([{char,$#},{para,_X}|T]) -> is_stringify_ok(T); is_stringify_ok([{char,$#},space,{var,_X}|T]) -> is_stringify_ok(T); is_stringify_ok([{char,$#},{var,_X}|T]) -> is_stringify_ok(T); is_stringify_ok([{char,$#},space,{nl,_X}|_T]) -> no; is_stringify_ok([{char,$#},{nl,_X}|_T]) -> no; is_stringify_ok([{char,$#}|_T]) -> no; is_stringify_ok([_H|T]) -> is_stringify_ok(T). %%=================================================================================== %% check if a macro is already defined %%=================================================================================== is_defined_before(Name, No_of_para, Defs) -> case lists:keysearch(Name, 1, Defs) of {value, {Name, No_of_para, _MacroPara, _Macro}} -> yes; {value, _} -> no; false -> no end. %%=================================================================================== %% read_to_nl(File) %%=================================================================================== read_to_nl([space|Rem]) -> read_to_nl(Rem, [], 1); read_to_nl(Rem) -> read_to_nl(Rem, [], 1). read_to_nl([], Result, Nl) -> {lists:reverse(Result), [], Nl}; read_to_nl([{nl, _N}|Rem], [{string_part,String} | Result], Nl) -> read_to_nl(Rem, [nl, {string_part,String}|Result], Nl+1); read_to_nl([{nl, _N}|Rem], [{sys_head_part,String} | Result], Nl) -> read_to_nl(Rem, [nl, {sys_head_part,String}|Result], Nl+1); read_to_nl([{nl, _N}|Rem], Result, Nl) -> {lists:reverse(Result), Rem, Nl}; read_to_nl([space|Rem], Result, Nl) -> read_to_nl(Rem, [space|Result], Nl); read_to_nl([{X,String}|Rem], Result, Nl) -> read_to_nl(Rem, [{X,String}|Result], Nl). %%=========================================================== %% Read characters until next newline %%=========================================================== %read_to_nl2(Str) -> read_to_nl2([],Str). %read_to_nl2(Line, []) -> {Line,[]}; %read_to_nl2(Line, [$\n|Str]) -> {Line, Str}; %read_to_nl2(Line, [X|Str]) -> read_to_nl2([X|Line], Str). %%=========================================================== %% Remove leading spaces from a list %%=========================================================== remove_leading_spaces([?space|List]) -> remove_leading_spaces(List); remove_leading_spaces([?tab|List]) -> remove_leading_spaces(List); remove_leading_spaces(List) -> List. %%=========================================================== %% Skip characters until next newline %%=========================================================== skip_to_nl([]) -> []; skip_to_nl([$\n | Str]) -> Str; skip_to_nl([$\\,$\n | Str]) -> [$/,$/|Str]; skip_to_nl([_|Str]) -> skip_to_nl(Str). month(1) -> "Jan"; month(2) -> "Feb"; month(3) -> "Mar"; month(4) -> "Apr"; month(5) -> "May"; month(6) -> "Jun"; month(7) -> "Jul"; month(8) -> "Aug"; month(9) -> "Sep"; month(10) -> "Oct"; month(11) -> "Nov"; month(12) -> "Dec". %% Multiple Include Optimization %% %% Algorithm described at: %% http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html update_mio({include, FileName}, #mio{included=Inc}=Mio) -> Mio#mio{valid=false, included=[FileName|Inc]}; %% valid=false & cmacro=undefined indicates it is already decided this file is %% not subject to MIO update_mio(_, #mio{valid=false, depth=0, cmacro=undefined}=Mio) -> Mio; %% if valid=true, there is no non-whitespace tokens before this ifndef update_mio({'ifndef', Macro}, #mio{valid=true, depth=0, cmacro=undefined}=Mio) -> Mio#mio{valid=false, cmacro=Macro, depth=1}; %% detect any tokens before top level #ifndef update_mio(_, #mio{valid=true, depth=0, cmacro=undefined}=Mio) -> Mio#mio{valid=false}; %% If cmacro is alreay set, this is after the top level #endif update_mio({'ifndef', _}, #mio{valid=true, depth=0}=Mio) -> Mio#mio{valid=false, cmacro=undefined}; %% non-top level conditional, just update depth update_mio({'ifndef', _}, #mio{depth=D}=Mio) when D > 0 -> Mio#mio{depth=D+1}; update_mio('ifdef', #mio{depth=D}=Mio) -> Mio#mio{depth=D+1}; update_mio('if', #mio{depth=D}=Mio) -> Mio#mio{depth=D+1}; %% top level #else #elif invalidates multiple include optimization update_mio('else', #mio{depth=1}=Mio) -> Mio#mio{valid=false, cmacro=undefined}; update_mio('else', Mio) -> Mio; update_mio('elif', #mio{depth=1}=Mio) -> Mio#mio{valid=false, cmacro=undefined}; update_mio('elif', Mio) -> Mio; %% AT exit to top level, if the controlling macro is not set, this could be the %% end of a non-ifndef conditional block, or there were tokens before entering %% the #ifndef block. In either way, this invalidates the MIO %% %% It doesn't matter if `valid` is true at the time of exiting, it is set to %% true. This will be used to detect if more tokens are following the top %% level #endif. update_mio('endif', #mio{depth=1, cmacro=undefined}=Mio) -> Mio#mio{valid=false, depth=0}; update_mio('endif', #mio{depth=1}=Mio) -> Mio#mio{valid=true, depth=0}; update_mio('endif', #mio{depth=D}=Mio) when D > 1 -> Mio#mio{valid=true, depth=D-1}; %%if more tokens are following the top level #endif. update_mio('endif', #mio{depth=1, cmacro=undefined}=Mio) -> Mio#mio{valid=false, depth=0}; update_mio('endif', #mio{depth=D}=Mio) when D > 0 -> Mio#mio{valid=true, depth=D-1}; update_mio(_, Mio) -> Mio#mio{valid=false}. %% clear `valid`, this doesn't matter since #endif will restore it if %% appropriate update_mio(Mio) -> Mio#mio{valid=false}.