├── Makefile ├── dynamic_compile.epm ├── ebin ├── dynamic_compile.app └── dynamic_compile.app.in └── src └── dynamic_compile.erl /Makefile: -------------------------------------------------------------------------------- 1 | PKGNAME=dynamic_compile 2 | VERSION=0.1 3 | LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell` 4 | ERL_SOURCES := $(wildcard src/*.erl) 5 | ERL_OBJECTS := $(ERL_SOURCES:%.erl=./%.beam) 6 | 7 | all: app $(ERL_OBJECTS) 8 | 9 | app: 10 | sh ebin/$(PKGNAME).app.in $(VERSION) 11 | 12 | clean: 13 | rm -rf ebin/*.beam erl_crash.dump 14 | 15 | install: 16 | mkdir -p $(prefix)/$(LIBDIR)/dynamic_compile-$(VERSION)/ebin 17 | for i in ebin/*; do install $$i $(prefix)/$(LIBDIR)/dynamic_compile-$(VERSION)/$$i ; done 18 | rm -rf ebin/*.beam erl_crash.dump ebin/*.app 19 | 20 | package: clean 21 | @mkdir $(PKGNAME)-$(VERSION)/ && cp -rf ebin Makefile src $(PKGNAME)-$(VERSION) 22 | @COPYFILE_DISABLE=true tar zcf $(PKGNAME)-$(VERSION).tgz $(PKGNAME)-$(VERSION) 23 | @rm -rf $(PKGNAME)-$(VERSION)/ 24 | 25 | ./%.beam: %.erl 26 | @mkdir -p ebin 27 | erlc +debug_info -I include -o ebin $< -------------------------------------------------------------------------------- /dynamic_compile.epm: -------------------------------------------------------------------------------- 1 | [{deps, []}]. -------------------------------------------------------------------------------- /ebin/dynamic_compile.app: -------------------------------------------------------------------------------- 1 | {application, dynamic_compile, [ 2 | {description, "dynamic_compile"}, 3 | {vsn, "0.1"}, 4 | {modules, [ 5 | dynamic_compile 6 | ]}, 7 | {registered, []}, 8 | {applications, [compiler, kernel, stdlib]} 9 | ]}. 10 | -------------------------------------------------------------------------------- /ebin/dynamic_compile.app.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=${1} 4 | MODULES=`ls -1 src/*.erl | awk -F[/.] '{ print "\t\t" $2 }' | sed '$q;s/$/,/g'` 5 | 6 | cat > ebin/dynamic_compile.app << EOF 7 | {application, dynamic_compile, [ 8 | {description, "dynamic_compile"}, 9 | {vsn, "${VERSION}"}, 10 | {modules, [ 11 | ${MODULES} 12 | ]}, 13 | {registered, []}, 14 | {applications, [compiler, kernel, stdlib]} 15 | ]}. 16 | EOF 17 | -------------------------------------------------------------------------------- /src/dynamic_compile.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2007 2 | %% Mats Cronqvist 3 | %% Chris Newcombe 4 | %% Jacob Vorreuter 5 | %% 6 | %% Permission is hereby granted, free of charge, to any person 7 | %% obtaining a copy of this software and associated documentation 8 | %% files (the "Software"), to deal in the Software without 9 | %% restriction, including without limitation the rights to use, 10 | %% copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | %% copies of the Software, and to permit persons to whom the 12 | %% Software is furnished to do so, subject to the following 13 | %% conditions: 14 | %% 15 | %% The above copyright notice and this permission notice shall be 16 | %% included in all copies or substantial portions of the Software. 17 | %% 18 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | %% OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | %%%------------------------------------------------------------------- 28 | %%% File : dynamic_compile.erl 29 | %%% Description : 30 | %%% Authors : Mats Cronqvist 31 | %%% Chris Newcombe 32 | %%% Jacob Vorreuter 33 | %%% TODO : 34 | %%% - add support for limit include-file depth (and prevent circular references) 35 | %%% prevent circular macro expansion set FILE correctly when -module() is found 36 | %%% -include_lib support $ENVVAR in include filenames 37 | %%% substitute-stringize (??MACRO) 38 | %%% -undef/-ifdef/-ifndef/-else/-endif 39 | %%% -file(File, Line) 40 | %%%------------------------------------------------------------------- 41 | -module(dynamic_compile). 42 | 43 | %% API 44 | -export([load_from_string/1, load_from_string/2]). 45 | -export([from_string/1, from_string/2]). 46 | 47 | -import(lists, [reverse/1, keyreplace/4]). 48 | 49 | %%==================================================================== 50 | %% API 51 | %%==================================================================== 52 | %%-------------------------------------------------------------------- 53 | %% Function: 54 | %% Description: 55 | %% Compile module from string and load into VM 56 | %%-------------------------------------------------------------------- 57 | load_from_string(CodeStr) -> 58 | load_from_string(CodeStr, []). 59 | 60 | load_from_string(CodeStr, CompileFormsOptions) -> 61 | {Mod, Bin} = from_string(CodeStr, CompileFormsOptions), 62 | code:load_binary(Mod, [], Bin). 63 | 64 | %%-------------------------------------------------------------------- 65 | %% Function: 66 | %% Description: 67 | %% Returns a binary that can be used with 68 | %% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary). 69 | %%-------------------------------------------------------------------- 70 | from_string(CodeStr) -> 71 | from_string(CodeStr, []). 72 | 73 | % takes Options as for compile:forms/2 74 | from_string(CodeStr, CompileFormsOptions) -> 75 | %% Initialise the macro dictionary with the default predefined macros, 76 | %% (adapted from epp.erl:predef_macros/1 77 | Filename = "compiled_from_string", 78 | %%Machine = list_to_atom(erlang:system_info(machine)), 79 | Ms0 = dict:new(), 80 | % Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0), 81 | % Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this 82 | % Ms3 = dict:store('MODULE', {[], undefined}, Ms2), 83 | % Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3), 84 | % Ms5 = dict:store('MACHINE', {[], Machine}, Ms4), 85 | % InitMD = dict:store(Machine, {[], true}, Ms5), 86 | InitMD = Ms0, 87 | 88 | %% From the docs for compile:forms: 89 | %% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories: 90 | %% 1. ".", the current working directory of the file server; 91 | %% 2. the base name of the compiled file; 92 | %% 3. the directories specified using the i option. The directory specified last is searched first. 93 | %% In this case, #2 is meaningless. 94 | IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])], 95 | {RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath), 96 | Forms = [{attribute, 0, file, {"compiled_from_string", 0}}|reverse([{eof, 0}|RevForms])], 97 | 98 | %% note: 'binary' is forced as an implicit option, whether it is provided or not. 99 | case compile:forms(Forms, CompileFormsOptions) of 100 | {ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) -> 101 | {ModuleName, CompiledCodeBinary}; 102 | {ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list 103 | {ModuleName, CompiledCodeBinary}; 104 | {ok, _ModuleName, _CompiledCodeBinary, Warnings} -> 105 | throw({?MODULE, warnings, Warnings}); 106 | Other -> 107 | throw({?MODULE, compile_forms, Other}) 108 | end. 109 | 110 | %%==================================================================== 111 | %% Internal functions 112 | %%==================================================================== 113 | %%% Code from Mats Cronqvist 114 | %%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html 115 | %%%## 'scan_and_parse' 116 | %%% 117 | %%% basically we call the OTP scanner and parser (erl_scan and 118 | %%% erl_parse) line-by-line, but check each scanned line for (or 119 | %%% definitions of) macros before parsing. 120 | %% returns {ReverseForms, FinalMacroDict} 121 | scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) -> 122 | {RevForms, MacroDict}; 123 | 124 | scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) -> 125 | case scanner(RemainingText, CurrLine, MacroDict) of 126 | {tokens, NLine, NRemainingText, Toks} -> 127 | {ok, Form0} = erl_parse:parse_form(Toks), 128 | {Form, Forms} = normalize_record(Form0), 129 | scan_and_parse(NRemainingText, CurrFilename, NLine, [ Form | Forms ++ RevForms], MacroDict, IncludeSearchPath); 130 | {macro, NLine, NRemainingText, NMacroDict} -> 131 | scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms,NMacroDict, IncludeSearchPath); 132 | {include, NLine, NRemainingText, IncludeFilename} -> 133 | IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath), 134 | %%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]), 135 | %% Modify the FILE macro to reflect the filename 136 | %%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict), 137 | IncludeMacroDict = MacroDict, 138 | 139 | %% Process the header file (inc. any nested header files) 140 | {RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath), 141 | %io:format("include file results = ~p~n", [R]), 142 | %% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file) 143 | %%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict), 144 | NMacroDict = IncludedMacroDict, 145 | 146 | %% Continue with the original file 147 | scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath); 148 | done -> 149 | scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) 150 | end. 151 | 152 | scanner(Text, Line, MacroDict) -> 153 | case erl_scan:tokens([],Text,Line) of 154 | {done, {ok,Toks,NLine}, LeftOverChars} -> 155 | case pre_proc(Toks, MacroDict) of 156 | {tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks}; 157 | {macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict}; 158 | {include, Filename} -> {include, NLine, LeftOverChars, Filename} 159 | end; 160 | {more, _Continuation} -> 161 | %% This is supposed to mean "term is not yet complete" (i.e. a '.' has 162 | %% not been reached yet). 163 | %% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file. 164 | %% So we check to see if Text only consists of comments. 165 | case is_only_comments(Text) of 166 | true -> 167 | done; 168 | false -> 169 | throw({incomplete_term, Text, Line}) 170 | end 171 | end. 172 | 173 | is_only_comments(Text) -> is_only_comments(Text, not_in_comment). 174 | 175 | is_only_comments([], _) -> true; 176 | is_only_comments([$ |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment 177 | is_only_comments([$\t |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment 178 | is_only_comments([$\n |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment 179 | is_only_comments([$% |T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment 180 | is_only_comments(_, not_in_comment) -> false; 181 | % found any significant char NOT in a comment 182 | is_only_comments([$\n |T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment 183 | is_only_comments([_ |T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars 184 | 185 | %%%## 'pre-proc' 186 | %%% 187 | %%% have to implement a subset of the pre-processor, since epp insists 188 | %%% on running on a file. 189 | %%% only handles 2 cases; 190 | %% -define(MACRO, something). 191 | %% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}). 192 | pre_proc([{'-',_},{atom,_,define},{'(',_},{_,_,Name}|DefToks],MacroDict) -> 193 | false = dict:is_key(Name, MacroDict), 194 | case DefToks of 195 | [{',',_} | Macro] -> 196 | {macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)}; 197 | [{'(',_} | Macro] -> 198 | {macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)} 199 | end; 200 | 201 | pre_proc([{'-',_}, {atom,_,include}, {'(',_}, {string,_,Filename}, {')',_}, {dot,_}], _MacroDict) -> 202 | {include, Filename}; 203 | 204 | pre_proc(Toks,MacroDict) -> 205 | {tokens, subst_macros(Toks, MacroDict)}. 206 | 207 | macro_params_body_def([{')',_},{',',_} | Toks], RevParams) -> 208 | {reverse(RevParams), macro_body_def(Toks, [])}; 209 | macro_params_body_def([{var,_,Param} | Toks], RevParams) -> 210 | macro_params_body_def(Toks, [Param | RevParams]); 211 | macro_params_body_def([{',',_}, {var,_,Param} | Toks], RevParams) -> 212 | macro_params_body_def(Toks, [Param | RevParams]). 213 | 214 | macro_body_def([{')',_}, {dot,_}], RevMacroBodyToks) -> 215 | reverse(RevMacroBodyToks); 216 | macro_body_def([Tok|Toks], RevMacroBodyToks) -> 217 | macro_body_def(Toks, [Tok | RevMacroBodyToks]). 218 | 219 | subst_macros(Toks, MacroDict) -> 220 | reverse(subst_macros_rev(Toks, MacroDict, [])). 221 | 222 | %% returns a reversed list of tokes 223 | subst_macros_rev([{'?',_}, {_,LineNum,'LINE'} | Toks], MacroDict, RevOutToks) -> 224 | %% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file 225 | subst_macros_rev(Toks, MacroDict, [{integer,LineNum,LineNum}] ++ RevOutToks); 226 | 227 | subst_macros_rev([{'?',_}, {_,_,Name}, {'(',_} = Paren | Toks], MacroDict, RevOutToks) -> 228 | case dict:fetch(Name, MacroDict) of 229 | {[], MacroValue} -> 230 | %% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff" 231 | %% Recursively expand any macro calls inside this macro's value 232 | %% TODO: avoid infinite expansion due to circular references (even indirect ones) 233 | RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), 234 | subst_macros_rev([Paren|Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); 235 | ParamsAndBody -> 236 | %% This macro does have vars. 237 | %% Collect all of the passe arguments, in an ordered list 238 | {NToks, Arguments} = subst_macros_get_args(Toks, []), 239 | %% Expand the varibles 240 | ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments), 241 | %% Recursively expand any macro calls inside this macro's value 242 | %% TODO: avoid infinite expansion due to circular references (even indirect ones) 243 | RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []), 244 | subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks) 245 | end; 246 | 247 | subst_macros_rev([{'?',_}, {_,_,Name} | Toks], MacroDict, RevOutToks) -> 248 | %% This macro invocation does not have arguments. 249 | %% Therefore the definition should not have parameters 250 | {[], MacroValue} = dict:fetch(Name, MacroDict), 251 | 252 | %% Recursively expand any macro calls inside this macro's value 253 | %% TODO: avoid infinite expansion due to circular references (even indirect ones) 254 | RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), 255 | subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); 256 | 257 | subst_macros_rev([Tok|Toks], MacroDict, RevOutToks) -> 258 | subst_macros_rev(Toks, MacroDict, [Tok|RevOutToks]); 259 | subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks. 260 | 261 | subst_macros_get_args([{')',_} | Toks], RevArgs) -> 262 | {Toks, reverse(RevArgs)}; 263 | subst_macros_get_args([{',',_}, {var,_,ArgName} | Toks], RevArgs) -> 264 | subst_macros_get_args(Toks, [ArgName| RevArgs]); 265 | subst_macros_get_args([{var,_,ArgName} | Toks], RevArgs) -> 266 | subst_macros_get_args(Toks, [ArgName | RevArgs]). 267 | 268 | subst_macros_subst_args_for_vars({[], BodyToks}, []) -> 269 | BodyToks; 270 | subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg|Args]) -> 271 | NBodyToks = keyreplace(Param, 3, BodyToks, {var,1,Arg}), 272 | subst_macros_subst_args_for_vars({Params, NBodyToks}, Args). 273 | 274 | read_include_file(Filename, IncludeSearchPath) -> 275 | case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of 276 | {ok, IoDevice, FullName} -> 277 | {ok, Data} = file:read(IoDevice, filelib:file_size(FullName)), 278 | file:close(IoDevice), 279 | binary_to_list(Data); 280 | {error, Reason} -> 281 | throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath}) 282 | end. 283 | 284 | normalize_record({attribute, La, record, {Record, Fields}} = Form) -> 285 | case epp:normalize_typed_record_fields(Fields) of 286 | {typed, NewFields} -> 287 | {{attribute, La, record, {Record, NewFields}}, 288 | [{attribute, La, type, 289 | {{record, Record}, Fields, []}}]}; 290 | not_typed -> 291 | {Form, []} 292 | end; 293 | normalize_record(Form) -> 294 | {Form, []}. 295 | 296 | --------------------------------------------------------------------------------