Skip to content

Commit c37a244

Browse files
author
José Valim
committed
Add Code.eval_file/2
1 parent bdab804 commit c37a244

File tree

11 files changed

+130
-121
lines changed

11 files changed

+130
-121
lines changed

lib/elixir/lib/code.ex

Lines changed: 77 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ defmodule Code do
5959
end
6060

6161
@doc """
62-
Evaluate the contents given by `string`.
62+
Evaluate the contents given by `string`.
63+
64+
The `binding` argument is a keyword list of variable bindings.
65+
The `opts` argument is a keyword list of environment options.
6366
64-
The `binding` argument is a keyword list of variable bindings.
65-
The `opts` argument is a keyword list of environment options.
66-
6767
Those options can be:
6868
6969
* `:file` - the file to be considered in the evaluation
@@ -118,15 +118,12 @@ defmodule Code do
118118
def eval_string(string, binding \\ [], opts \\ [])
119119

120120
def eval_string(string, binding, Macro.Env[] = env) do
121-
do_eval_string(string, binding, env.to_keywords)
121+
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
122+
{ value, binding }
122123
end
123124

124125
def eval_string(string, binding, opts) when is_list(opts) do
125126
validate_eval_opts(opts)
126-
do_eval_string(string, binding, opts)
127-
end
128-
129-
defp do_eval_string(string, binding, opts) when is_list(binding) do
130127
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
131128
{ value, binding }
132129
end
@@ -153,15 +150,12 @@ defmodule Code do
153150
def eval_quoted(quoted, binding \\ [], opts \\ [])
154151

155152
def eval_quoted(quoted, binding, Macro.Env[] = env) do
156-
do_eval_quoted(quoted, binding, env.to_keywords)
153+
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
154+
{ value, binding }
157155
end
158156

159157
def eval_quoted(quoted, binding, opts) when is_list(opts) do
160158
validate_eval_opts(opts)
161-
do_eval_quoted(quoted, binding, opts)
162-
end
163-
164-
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
165159
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
166160
{ value, binding }
167161
end
@@ -204,8 +198,8 @@ defmodule Code do
204198
end
205199

206200
@doc """
207-
Convert the given string to its quoted form.
208-
201+
Convert the given string to its quoted form.
202+
209203
Returns `{ :ok, quoted_form }`
210204
if it succeeds, `{ :error, { line, error, token } }` otherwise.
211205
@@ -228,17 +222,12 @@ defmodule Code do
228222
def string_to_quoted(string, opts \\ []) when is_list(opts) do
229223
file = Keyword.get opts, :file, "nofile"
230224
line = Keyword.get opts, :line, 1
231-
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
232-
233-
case res do
234-
{ :ok, forms } -> { :ok, forms }
235-
_ -> res
236-
end
225+
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
237226
end
238227

239228
@doc """
240-
Convert the given string to its quoted form.
241-
229+
Convert the given string to its quoted form.
230+
242231
It returns the ast if it succeeds,
243232
raises an exception otherwise. The exception is a `TokenMissingError`
244233
in case a token is missing (usually because the expression is incomplete),
@@ -253,18 +242,31 @@ defmodule Code do
253242
end
254243

255244
@doc """
256-
Load the given file.
257-
258-
Accepts `relative_to` as an argument to tell where
259-
the file is located. If the file was already required/loaded, loads it again.
260-
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
261-
module defined in the file.
262-
263-
Notice that if `load_file` is invoked by different processes
264-
concurrently, the target file will be invoked concurrently
265-
many times. I.e. if `load_file` is called N times with
266-
a given file, the given file will be loaded N times. Check
267-
`require_file/2` if you don't want a file to be loaded concurrently.
245+
Evals the given file.
246+
247+
Accepts `relative_to` as an argument to tell where the file is located.
248+
249+
While `load_file` loads a file and returns the loaded modules and their
250+
byte code, `eval_file` simply evalutes the file contents and returns the
251+
evaluation result and its bindings.
252+
"""
253+
def eval_file(file, relative_to \\ nil) do
254+
file = find_file(file, relative_to)
255+
eval_string File.read!(file), [], []
256+
end
257+
258+
@doc """
259+
Load the given file.
260+
261+
Accepts `relative_to` as an argument to tell where the file is located.
262+
If the file was already required/loaded, loads it again.
263+
264+
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
265+
each module defined in the file.
266+
267+
Notice that if `load_file` is invoked by different processes concurrently,
268+
the target file will be loaded concurrently many times. Check `require_file/2`
269+
if you don't want a file to be loaded concurrently.
268270
"""
269271
def load_file(file, relative_to \\ nil) when is_binary(file) do
270272
file = find_file(file, relative_to)
@@ -275,19 +277,19 @@ defmodule Code do
275277
end
276278

277279
@doc """
278-
Require the given `file`.
279-
280-
Accepts `relative_to` as an argument to tell where
281-
the file is located. The return value is the same as that of `load_file/2`. If
282-
the file was already required/loaded, doesn't do anything and returns `nil`.
280+
Requires the given `file`.
281+
282+
Accepts `relative_to` as an argument to tell where the file is located.
283+
The return value is the same as that of `load_file/2`. If the file was already
284+
required/loaded, doesn't do anything and returns `nil`.
283285
284286
Notice that if `require_file` is invoked by different processes concurrently,
285287
the first process to invoke `require_file` acquires a lock and the remaining
286288
ones will block until the file is available. I.e. if `require_file` is called
287289
N times with a given file, it will be loaded only once. The first process to
288290
call `require_file` will get the list of loaded modules, others will get `nil`.
289291
290-
Check `load_file/2` if you want a file to be loaded concurrently.
292+
Check `load_file/2` if you want a file to be loaded multiple times.
291293
"""
292294
def require_file(file, relative_to \\ nil) when is_binary(file) do
293295
file = find_file(file, relative_to)
@@ -305,7 +307,7 @@ defmodule Code do
305307
end
306308

307309
@doc """
308-
Load the compilation options from the code server.
310+
Gets the compilation options from the code server.
309311
310312
Check `compiler_options/1` for more information.
311313
"""
@@ -314,7 +316,7 @@ defmodule Code do
314316
end
315317

316318
@doc """
317-
Set compilation options.
319+
Sets compilation options.
318320
319321
These options are global since they are stored by Elixir's Code Server.
320322
@@ -338,11 +340,10 @@ defmodule Code do
338340
end
339341

340342
@doc """
341-
Compile the given string.
343+
Compiles the given string.
342344
343-
Returns a list of tuples where
344-
the first element is the module name and the second one is its
345-
binary.
345+
Returns a list of tuples where the first element is the module name
346+
and the second one is its byte code (as a binary).
346347
347348
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
348349
"""
@@ -351,26 +352,23 @@ defmodule Code do
351352
end
352353

353354
@doc """
354-
Compile the quoted expression.
355-
356-
Returns a list of tuples where
357-
the first element is the module name and the second one is its
358-
binary.
355+
Compiles the quoted expression.
356+
357+
Returns a list of tuples where the first element is the module name and
358+
the second one is its byte code (as a binary).
359359
"""
360360
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
361361
:elixir_compiler.quoted quoted, file
362362
end
363363

364364
@doc """
365-
Ensure the given module is loaded.
366-
367-
If the module is already
368-
loaded, this works as no-op. If the module was not yet loaded,
369-
it tries to load it.
365+
Ensures the given module is loaded.
366+
367+
If the module is already loaded, this works as no-op. If the module
368+
was not yet loaded, it tries to load it.
370369
371-
If it succeeds loading the module, it returns
372-
`{ :module, module }`. If not, returns `{ :error, reason }` with
373-
the error reason.
370+
If it succeeds loading the module, it returns `{ :module, module }`.
371+
If not, returns `{ :error, reason }` with the error reason.
374372
375373
## Code loading on the Erlang VM
376374
@@ -385,47 +383,46 @@ defmodule Code do
385383
module uses this function to check if a specific parser exists for a given
386384
URI scheme.
387385
388-
## Code.ensure_compiled
386+
## `Code.ensure_compiled/1`
389387
390388
Elixir also contains an `ensure_compiled/1` function that is a
391389
superset of `ensure_loaded/1`.
392390
393391
Since Elixir's compilation happens in parallel, in some situations
394-
you may need to use a module but that was not yet compiled, therefore
392+
you may need to use a module that was not yet compiled, therefore
395393
it can't even be loaded.
396394
397395
`ensure_compiled/1` halts the current process until the
398396
module we are depending on is available.
399397
400-
In most cases, `ensure_loaded` is enough. `ensure_compiled`
401-
must be used in some rare cases, usually involving macros
402-
that need to invoke a module for callback information.
398+
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
399+
must be used in rare cases, usually involving macros that need to
400+
invoke a module for callback information.
403401
"""
404402
def ensure_loaded(module) when is_atom(module) do
405403
:code.ensure_loaded(module)
406404
end
407405

408406
@doc """
409-
Ensure the given module is loaded.
407+
Ensures the given module is loaded.
410408
411409
Similar to `ensure_loaded/1`, but returns `true` if the module
412-
is already loaded or was successfully loaded. Returns `false` otherwise.
410+
is already loaded or was successfully loaded. Returns `false`
411+
otherwise.
413412
"""
414413
def ensure_loaded?(module) do
415414
match?({ :module, ^module }, ensure_loaded(module))
416415
end
417416

418417
@doc """
419-
Ensure the given module is compiled and loaded.
420-
421-
If the module
422-
is already loaded, it works as no-op. If the module was not
423-
loaded yet, it checks if it needs to be compiled first and
424-
then tries to load it.
418+
Ensures the given module is compiled and loaded.
419+
420+
If the module is already loaded, it works as no-op. If the module was
421+
not loaded yet, it checks if it needs to be compiled first and then
422+
tries to load it.
425423
426-
If it succeeds loading the module, it returns
427-
`{ :module, module }`. If not, returns `{ :error, reason }` with
428-
the error reason.
424+
If it succeeds loading the module, it returns `{ :module, module }`.
425+
If not, returns `{ :error, reason }` with the error reason.
429426
430427
Check `ensure_loaded/1` for more information on module loading
431428
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
@@ -448,10 +445,10 @@ defmodule Code do
448445
end
449446

450447
@doc """
451-
Ensure the given module is compiled and loaded.
448+
Ensures the given module is compiled and loaded.
452449
453450
Similar to `ensure_compiled/1`, but returns `true` if the module
454-
is already loaded or was successfully loaded and compiled.
451+
is already loaded or was successfully loaded and compiled.
455452
Returns `false` otherwise.
456453
"""
457454
def ensure_compiled?(module) do
@@ -461,6 +458,7 @@ defmodule Code do
461458
## Helpers
462459

463460
# Finds the file given the relative_to path.
461+
#
464462
# If the file is found, returns its path in binary, fails otherwise.
465463
defp find_file(file, relative_to) do
466464
file = if relative_to do

lib/elixir/src/elixir.erl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,11 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
158158
{ ok, _Line, Tokens } ->
159159
try elixir_parser:parse(Tokens) of
160160
{ ok, Forms } -> { ok, Forms };
161-
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
161+
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
162162
catch
163-
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
163+
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
164164
end;
165-
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
165+
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
166166
end.
167167

168168
'string_to_quoted!'(String, StartLine, File, Opts) ->
@@ -172,3 +172,6 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
172172
{ error, { Line, Error, Token } } ->
173173
elixir_errors:parse_error(Line, File, Error, Token)
174174
end.
175+
176+
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
177+
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).

lib/elixir/src/elixir_errors.erl

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,18 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
4444

4545
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
4646

47-
parse_error(Meta, File, Error, []) ->
47+
parse_error(Meta, File, Error, <<>>) ->
4848
Message = case Error of
49-
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
50-
_ -> iolist_to_binary(Error)
49+
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
50+
_ -> Error
5151
end,
5252
raise(Meta, File, 'Elixir.TokenMissingError', Message);
5353

54-
parse_error(Meta, File, "syntax error before: ", "'end'") ->
54+
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
5555
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
5656

57-
parse_error(Meta, File, Error, Token) ->
58-
BinError = if
59-
is_atom(Error) -> atom_to_binary(Error, utf8);
60-
true -> iolist_to_binary(Error)
61-
end,
62-
63-
BinToken = if
64-
Token == [] -> <<>>;
65-
true -> elixir_utils:characters_to_binary(Token)
66-
end,
67-
68-
Message = <<BinError / binary, BinToken / binary >>,
57+
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
58+
Message = <<Error / binary, Token / binary >>,
6959
raise(Meta, File, 'Elixir.SyntaxError', Message).
7060

7161
%% Shows a deprecation message

lib/elixir/test/elixir/code_test.exs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ defmodule CodeTest do
5959
assert Code.eval_quoted(quote(do: MyList.flatten [[1, 2, 3]]), [], __ENV__) == { [1, 2, 3],[] }
6060
end
6161

62+
test :eval_file do
63+
assert Code.eval_file(fixture_path("code_sample.exs")) == { 3, [var: 3] }
64+
end
65+
6266
test :require do
6367
Code.require_file fixture_path("code_sample.exs")
6468
assert fixture_path("code_sample.exs") in Code.loaded_files
@@ -70,12 +74,18 @@ defmodule CodeTest do
7074
end
7175

7276
test :string_to_quoted do
73-
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
74-
assert { :error, _ } = Code.string_to_quoted("a.1")
77+
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
78+
assert Code.string_to_quoted!("1 + 2") == { :+, [line: 1], [1, 2] }
79+
80+
assert Code.string_to_quoted("a.1") ==
81+
{ :error, { 1, "syntax error before: ", "1" } }
82+
83+
assert_raise SyntaxError, fn ->
84+
Code.string_to_quoted!("a.1")
85+
end
7586
end
7687

7788
test :string_to_quoted_existing_atoms_only do
78-
assert :badarg = catch_error(Code.string_to_quoted(":thereisnosuchatom", existing_atoms_only: true))
7989
assert :badarg = catch_error(Code.string_to_quoted!(":thereisnosuchatom", existing_atoms_only: true))
8090
end
8191

0 commit comments

Comments
 (0)