From 1ecb32bc13b01001bd16cfd6c4f2a412b6eaac19 Mon Sep 17 00:00:00 2001 From: Daniel Azuma Date: Sun, 27 Dec 2015 20:51:48 -0800 Subject: [PATCH] Support for includes --- TODO.md | 1 - lib/erl2ex/convert.ex | 6 ++--- lib/erl2ex/convert/headers.ex | 6 ++--- lib/erl2ex/erl_parse.ex | 48 +++++++++++++++++++++++++++++++--- test/files/files2/include2.hrl | 1 + test/files/include1.hrl | 2 ++ test/files/include3.hrl | 1 + test/preprocessor_test.exs | 18 +++++++++++++ 8 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 test/files/files2/include2.hrl create mode 100644 test/files/include1.hrl create mode 100644 test/files/include3.hrl diff --git a/TODO.md b/TODO.md index c51b4c3..3fde85d 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ This is a (certainly partial) list of known issues and missing capabilities. ## Preprocessor -* Support file inclusion (P1) * Check that macro defines work with guard fragments. See examples in elixir/src/elixir_tokenizer.erl (P1) * Allow defining macros from env variables, e.g. debug (P2) * Make sure defined_* attributes are properly initialized (P2) diff --git a/lib/erl2ex/convert.ex b/lib/erl2ex/convert.ex index 55f9d4a..583ab03 100644 --- a/lib/erl2ex/convert.ex +++ b/lib/erl2ex/convert.ex @@ -30,6 +30,9 @@ defmodule Erl2ex.Convert do alias Erl2ex.Convert.VarRenamer + @auto_registered_attrs [:vsn, :compile, :on_load, :behaviour, :behavior] + + def module(erl_module, opts \\ []) do context = Context.build(erl_module, opts) forms = erl_module.forms |> Enum.map(&(conv_form(context, &1))) @@ -42,9 +45,6 @@ defmodule Erl2ex.Convert do end - @auto_registered_attrs [:vsn, :compile, :on_load, :behaviour, :behavior] - - defp conv_form(context, %ErlFunc{name: name, arity: arity, clauses: clauses, comments: comments}) do mapped_name = Context.local_function_name(context, name) spec_info = Context.specs_for_func(context, name) diff --git a/lib/erl2ex/convert/headers.ex b/lib/erl2ex/convert/headers.ex index 8e16266..cb09ec7 100644 --- a/lib/erl2ex/convert/headers.ex +++ b/lib/erl2ex/convert/headers.ex @@ -12,9 +12,6 @@ defmodule Erl2ex.Convert.Headers do alias Erl2ex.Convert.Context - @import_bitwise_metadata [context: Elixir, import: Bitwise] - - def build_header(context, forms) do header = forms |> Enum.reduce(%ExHeader{}, &header_check_form/2) @@ -42,7 +39,8 @@ defmodule Erl2ex.Convert.Headers do header_check_expr(elem(expr, 1), header) defp header_check_expr(expr, header) when is_tuple(expr) and tuple_size(expr) >= 3 do - if elem(expr, 1) == @import_bitwise_metadata do + imported = expr |> elem(1) |> Keyword.get(:import, nil) + if imported == Bitwise do header = %ExHeader{header | use_bitwise: true} end expr diff --git a/lib/erl2ex/erl_parse.ex b/lib/erl2ex/erl_parse.ex index a204fbd..44392f9 100644 --- a/lib/erl2ex/erl_parse.ex +++ b/lib/erl2ex/erl_parse.ex @@ -17,7 +17,7 @@ defmodule Erl2ex.ErlParse do def from_file(path, opts \\ []) do path |> File.read! - |> from_str([{:i, Path.dirname(path)} | opts]) + |> from_str([{:cur_file_dir, Path.dirname(path)} | opts]) end @@ -42,17 +42,45 @@ defmodule Erl2ex.ErlParse do defmodule Context do @moduledoc false - defstruct include_path: [] + defstruct include_path: [], + cur_file_dir: nil, + reverse_forms: false end defp build_context(opts) do + include_path = opts + |> Keyword.get_values(:include_dir) + |> Enum.uniq %Context{ - include_path: Keyword.get_values(opts, :i) + include_path: include_path, + cur_file_dir: Keyword.get(opts, :cur_file_dir, nil), + reverse_forms: Keyword.get(opts, :reverse_forms, false) } end + defp build_opts_for_include(context) do + context.include_path + |> Enum.map(&({:include_dir, &1})) + |> Keyword.put(:reverse_forms, true) + end + + + defp find_file(context, path) do + include_path = context.include_path + if context.cur_file_dir != nil do + include_path = [context.cur_file_dir | include_path] + end + include_path = [File.cwd!() | include_path] + include_path + |> Enum.find_value(fn dir -> + full_path = Path.expand(path, dir) + if File.regular?(full_path), do: full_path, else: false + end) + end + + defp generate_token_group_stream(str) do {str, 1} |> Stream.unfold(fn {ch, pos} -> @@ -111,7 +139,10 @@ defmodule Erl2ex.ErlParse do module = form_stream |> Enum.reduce(%ErlModule{}, fn ({ast, comments}, module) -> add_form(module, ast, comments, context) end) - %ErlModule{module | forms: Enum.reverse(module.forms)} + if not context.reverse_forms do + module = %ErlModule{module | forms: Enum.reverse(module.forms)} + end + module end @@ -127,6 +158,15 @@ defmodule Erl2ex.ErlParse do } end + defp add_form(module, {:attribute, _line, :include, path}, _comments, context) do + file_path = find_file(context, path) + opts = build_opts_for_include(context) + included_module = from_file(file_path, opts) + %ErlModule{module | + forms: included_module.forms ++ module.forms + } + end + defp add_form(module, {:attribute, _line, :export, arg}, comments, _context) do %ErlModule{module | exports: module.exports ++ arg, diff --git a/test/files/files2/include2.hrl b/test/files/files2/include2.hrl new file mode 100644 index 0000000..f1c904c --- /dev/null +++ b/test/files/files2/include2.hrl @@ -0,0 +1 @@ +-define(INCLUDE2_CONST, 2). diff --git a/test/files/include1.hrl b/test/files/include1.hrl new file mode 100644 index 0000000..944bf41 --- /dev/null +++ b/test/files/include1.hrl @@ -0,0 +1,2 @@ +-define(INCLUDE1_CONST, 1). +-include("files2/include2.hrl"). diff --git a/test/files/include3.hrl b/test/files/include3.hrl new file mode 100644 index 0000000..3f51fbd --- /dev/null +++ b/test/files/include3.hrl @@ -0,0 +1 @@ +-define(INCLUDE3_CONST, 3). diff --git a/test/preprocessor_test.exs b/test/preprocessor_test.exs index 8df3b76..d4169ef 100644 --- a/test/preprocessor_test.exs +++ b/test/preprocessor_test.exs @@ -263,4 +263,22 @@ defmodule PreprocessorTest do end + test "File includes injecting forms inline" do + input = """ + -include("test/files/include1.hrl"). + -include("include3.hrl"). + """ + + expected = """ + @erlmacro_INCLUDE1_CONST 1 + + @erlmacro_INCLUDE2_CONST 2 + + @erlmacro_INCLUDE3_CONST 3 + """ + + assert Erl2ex.convert_str(input, include_dir: "test/files") == expected + end + + end