%% Main entry point for Elixir functions. All of those functions are %% private to the Elixir compiler and reserved to be used by Elixir only. -module(elixir). -behaviour(application). -export([start_cli/0, string_to_tokens/4, tokens_to_quoted/3, 'string_to_quoted!'/4, env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3, eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]). -include("elixir.hrl"). -define(system, 'Elixir.System'). %% Top level types %% TODO: Remove char_list type by 2.0 -export_type([charlist/0, char_list/0, nonempty_charlist/0, struct/0, as_boolean/1, keyword/0, keyword/1]). -type charlist() :: string(). -type char_list() :: string(). -type nonempty_charlist() :: nonempty_string(). -type as_boolean(T) :: T. -type keyword() :: [{atom(), any()}]. -type keyword(T) :: [{atom(), T}]. -type struct() :: #{'__struct__' := atom(), atom() => any()}. %% OTP Application API -export([start/2, stop/1, config_change/3]). start(_Type, _Args) -> _ = parse_otp_release(), Encoding = file:native_name_encoding(), preload_common_modules(), set_stdio_and_stderr_to_binary_and_maybe_utf8(), check_file_encoding(Encoding), check_endianness(), Tokenizer = case code:ensure_loaded('Elixir.String.Tokenizer') of {module, Mod} -> Mod; _ -> elixir_tokenizer end, URIConfig = [ {{uri, <<"ftp">>}, 21}, {{uri, <<"sftp">>}, 22}, {{uri, <<"tftp">>}, 69}, {{uri, <<"http">>}, 80}, {{uri, <<"https">>}, 443}, {{uri, <<"ldap">>}, 389} ], CompilerOpts = #{ docs => true, ignore_module_conflict => false, debug_info => true, warnings_as_errors => false, relative_paths => true }, {ok, [[Home] | _]} = init:get_argument(home), Config = [ {at_exit, []}, {argv, []}, {bootstrap, false}, {compiler_options, CompilerOpts}, {home, unicode:characters_to_binary(Home, Encoding, Encoding)}, {identifier_tokenizer, Tokenizer} | URIConfig ], Tab = elixir_config:new(Config), case elixir_sup:start_link() of {ok, Sup} -> {ok, Sup, Tab}; {error, _Reason} = Error -> elixir_config:delete(Tab), Error end. stop(Tab) -> elixir_config:delete(Tab). config_change(_Changed, _New, _Remove) -> ok. set_stdio_and_stderr_to_binary_and_maybe_utf8() -> %% In case there is a shell, we can't really change its %% encoding, so we just set binary to true. Otherwise %% we must set the encoding as the user with no shell %% has encoding set to latin1. Opts = case init:get_argument(noshell) of {ok, _} -> [binary, {encoding, utf8}]; error -> [binary] end, ok = io:setopts(standard_io, Opts), ok = io:setopts(standard_error, [{encoding, utf8}]), ok. preload_common_modules() -> %% We attempt to load those modules here so throughout %% the codebase we can avoid code:ensure_loaded/1 checks. _ = code:ensure_loaded('Elixir.Kernel'), _ = code:ensure_loaded('Elixir.Macro.Env'), ok. parse_otp_release() -> %% Whenever we change this check, we should also change escript.build and Makefile. case string:to_integer(erlang:system_info(otp_release)) of {Num, _} when Num >= 20 -> Num; _ -> io:format(standard_error, "unsupported Erlang/OTP version, expected Erlang/OTP 20+~n", []), erlang:halt(1) end. check_endianness() -> case code:ensure_loaded(?system) of {module, ?system} -> Endianness = ?system:endianness(), case ?system:compiled_endianness() of Endianness -> ok; _ -> io:format(standard_error, "warning: Elixir is running in a system with a different endianness than the one its " "source code was compiled in. Please make sure Elixir and all source files were compiled " "in a machine with the same endianness as the current one: ~ts~n", [Endianness]) end; {error, _} -> ok end. check_file_encoding(Encoding) -> case Encoding of latin1 -> io:format(standard_error, "warning: the VM is running with native name encoding of latin1 which may cause " "Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 " "(which can be verified by running \"locale\" in your shell)~n", []); _ -> ok end. %% Boot and process given options. Invoked by Elixir's script. start_cli() -> {ok, _} = application:ensure_all_started(?MODULE), %% We start the Logger so tools that depend on Elixir %% always have the Logger directly accessible. However %% Logger is not a dependency of the Elixir application, %% which means releases that want to use Logger must %% always list it as part of its applications. _ = case code:ensure_loaded('Elixir.Logger') of {module, _} -> application:start(logger); {error, _} -> ok end, 'Elixir.Kernel.CLI':main(init:get_plain_arguments()). %% EVAL HOOKS env_for_eval(Opts) -> env_for_eval((elixir_env:new())#{ requires := elixir_dispatch:default_requires(), functions := elixir_dispatch:default_functions(), macros := elixir_dispatch:default_macros() }, Opts). env_for_eval(Env, Opts) -> Line = case lists:keyfind(line, 1, Opts) of {line, LineOpt} when is_integer(LineOpt) -> LineOpt; false -> ?key(Env, line) end, File = case lists:keyfind(file, 1, Opts) of {file, FileOpt} when is_binary(FileOpt) -> FileOpt; false -> ?key(Env, file) end, Aliases = case lists:keyfind(aliases, 1, Opts) of {aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt; false -> ?key(Env, aliases) end, Requires = case lists:keyfind(requires, 1, Opts) of {requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt); false -> ?key(Env, requires) end, Functions = case lists:keyfind(functions, 1, Opts) of {functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt; false -> ?key(Env, functions) end, Macros = case lists:keyfind(macros, 1, Opts) of {macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt; false -> ?key(Env, macros) end, Module = case lists:keyfind(module, 1, Opts) of {module, ModuleOpt} when is_atom(ModuleOpt) -> ModuleOpt; false -> nil end, FA = case lists:keyfind(function, 1, Opts) of {function, {Function, Arity}} when is_atom(Function), is_integer(Arity) -> {Function, Arity}; {function, nil} -> nil; false -> nil end, Env#{ file := File, module := Module, function := FA, macros := Macros, functions := Functions, requires := Requires, aliases := Aliases, line := Line }. %% String evaluation eval(String, Binding) -> eval(String, Binding, []). eval(String, Binding, Opts) when is_list(Opts) -> eval(String, Binding, env_for_eval(Opts)); eval(String, Binding, #{line := Line, file := File} = E) when is_list(String), is_list(Binding), is_integer(Line), is_binary(File) -> Forms = 'string_to_quoted!'(String, Line, File, []), eval_forms(Forms, Binding, E). %% Quoted evaluation eval_quoted(Tree, Binding, Opts) when is_list(Opts) -> eval_quoted(Tree, Binding, env_for_eval(Opts)); eval_quoted(Tree, Binding, #{line := Line} = E) -> eval_forms(elixir_quote:linify(Line, line, Tree), Binding, E). %% Handle forms evaluation. The main difference to %% eval_quoted is that it does not linify the given %% args. eval_forms(Tree, Binding, Opts) when is_list(Opts) -> eval_forms(Tree, Binding, env_for_eval(Opts)); eval_forms(Tree, Binding, E) -> eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)). eval_forms(Tree, Binding, Env, Scope) -> {ParsedBinding, ParsedVars, ParsedScope} = elixir_erl_var:load_binding(Binding, Scope), ParsedEnv = elixir_env:with_vars(Env, ParsedVars), {Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope), case Erl of {atom, _, Atom} -> {Atom, Binding, NewEnv, NewScope}; _ -> % Below must be all one line for locations to be the same % when the stacktrace is extended to the full stacktrace. {value, Value, NewBinding} = try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch ?WITH_STACKTRACE(Class, Exception, Stacktrace) erlang:raise(Class, Exception, get_stacktrace(Stacktrace)) end, {Value, elixir_erl_var:dump_binding(NewBinding, NewScope), NewEnv, NewScope} end. get_stacktrace(Stacktrace) -> % eval_eval and eval_bits can call :erlang.raise/3 without the full % stacktrace. When this occurs re-add the current stacktrace so that no % stack information is lost. try throw(stack) catch ?WITH_STACKTRACE(throw, stack, CurrentStack) % Ignore stack item for current function. merge_stacktrace(Stacktrace, tl(CurrentStack)) end. % The stacktrace did not include the current stack, re-add it. merge_stacktrace([], CurrentStack) -> CurrentStack; % The stacktrace includes the current stack. merge_stacktrace(CurrentStack, CurrentStack) -> CurrentStack; merge_stacktrace([StackItem | Stacktrace], CurrentStack) -> [StackItem | merge_stacktrace(Stacktrace, CurrentStack)]. %% Converts a quoted expression to Erlang abstract format quoted_to_erl(Quoted, Env) -> quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)). quoted_to_erl(Quoted, Env, Scope) -> {Expanded, NewEnv} = elixir_expand:expand(Quoted, Env), {Erl, NewScope} = elixir_erl_pass:translate(Expanded, Scope), {Erl, NewEnv, NewScope}. %% Converts a given string (charlist) into quote expression string_to_tokens(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) -> case elixir_tokenizer:tokenize(String, StartLine, [{file, File} | Opts]) of {ok, _Tokens} = Ok -> Ok; {error, {Line, _, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _SoFar} -> {error, {Line, {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; {error, {Line, _, Error, Token}, _Rest, _SoFar} -> {error, {Line, to_binary(Error), to_binary(Token)}} end. tokens_to_quoted(Tokens, File, Opts) -> handle_parsing_opts(File, Opts), try elixir_parser:parse(Tokens) of {ok, Forms} -> {ok, Forms}; {error, {Line, _, [{ErrorPrefix, ErrorSuffix}, Token]}} -> {error, {parser_line(Line), {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; {error, {Line, _, [Error, Token]}} -> {error, {parser_line(Line), to_binary(Error), to_binary(Token)}} after erase(elixir_parser_file), erase(elixir_parser_columns), erase(elixir_formatter_metadata) end. parser_line({Line, _, _}) -> Line; parser_line(Meta) -> case lists:keyfind(line, 1, Meta) of {line, L} -> L; false -> 0 end. 'string_to_quoted!'(String, StartLine, File, Opts) -> case string_to_tokens(String, StartLine, File, Opts) of {ok, Tokens} -> case tokens_to_quoted(Tokens, File, Opts) of {ok, Forms} -> Forms; {error, {Line, Error, Token}} -> elixir_errors:parse_error(Line, File, Error, Token) end; {error, {Line, Error, Token}} -> elixir_errors:parse_error(Line, File, Error, Token) end. to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List); to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8). handle_parsing_opts(File, Opts) -> FormatterMetadata = lists:keyfind(formatter_metadata, 1, Opts) == {formatter_metadata, true}, Columns = lists:keyfind(columns, 1, Opts) == {columns, true}, put(elixir_parser_file, File), put(elixir_parser_columns, Columns), put(elixir_formatter_metadata, FormatterMetadata).