Skip to content

Commit

Permalink
Add initial version.
Browse files Browse the repository at this point in the history
  • Loading branch information
meddle0x53 committed Jun 9, 2018
0 parents commit f9dc79d
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
elixir_lfe-*.tar

60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Mix LFE Compiler

A very simple Mix task that compiles LFE (lisp (flavoured (erlang))).

It is a Mix compiler which uses the Erlang Mix compiler.

Lisp is a great language to get into the functional way of thinking and LFE is Lisp 2+ which runs on the greatest platform (personal opinion).
Elixir's Mix is a great (or the greatest) configuration/package/build manager, so why not use it to compile LFE?
Also Elixir developers should try LFE! This little project has the purpose to make that an easier.

## Installation

You can create a Mix project with `mix new <project_name>` and add this as dependency:

```elixir
def deps do
[
{:mix_lfe, "~> 0.1.0"}
]
end
```

The project uses the rebar3 `lfe` package to compile LFE source files, so the following will be necessary:

1. Navigate to the new project root.
2. Run `mix deps.get`.
3. Run `mix local.rebar` (if you don't have rebar3 installed).
4. Compile LFE : `(cd deps/lfe && ~/.mix/rebar3 compile)`. You can replace `~/.mix/rebar3` here with the location it is installed on your machine.
5. Create a `src` folder and add your `*.lfe` sources there.
6. Run `mix compile.lfe` to compile them. Now you'll be able to use the compiled modules with `iex -S mix`.

To use the compiled modules with the LFE REPL, you can run:

```bash
./deps/lfe/bin/lfe -pa _build/dev/lib/*/ebin
```

Also if you want to just run `mix compile` add `compilers: Mix.compilers() ++ [:lfe]` to the list returned by `project/0` which is defined in your `mix.exs`.

## Example projects

TODO

## TODO

The tests of this project mirror the ones for the Erlang Mix compiler.
For now the source is very simple and uses an [idea](https://github.com/elixir-lang/elixir/blob/e1c903a5956e4cb9075f0aac00638145788b0da4/lib/mix/lib/mix/compilers/erlang.ex#L20) from the Erlang Mix compiler.
All works well, but requires some manual work and doesn't support LFE compiler fine tunning, so that's what we'll be after next.

1. Add task for running LFE tests. What kind of tests? Will see...
2. Automate the installation & setup process in a way. Maybe by using something similar to the Phoenix generator tasks.
3. Pass more options to the LFE compiler, using mix configuration.
4. Use LFE syntax for configuration (not sure this is needed, really).
5. More and more examples.
6. A mix task or binary running the LFE REPL in the context of the compiled artifacts.
7. Add CI to this project.

## License

Same as Elixir.
28 changes: 28 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :elixir_lfe, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:elixir_lfe, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
68 changes: 68 additions & 0 deletions lib/mix/tasks/compile.lfe.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule Mix.Tasks.Compile.Lfe do
use Mix.Task.Compiler
import Mix.Compilers.Erlang

@recursive true
@manifest "compile.lfe"
@switches [force: :boolean, all_warnings: :boolean]

@moduledoc """
Compiles LFE source files.
Uses an [idea](https://github.com/elixir-lang/elixir/blob/e1c903a5956e4cb9075f0aac00638145788b0da4/lib/mix/lib/mix/compilers/erlang.ex#L20) from the Erlang Mix compiler to do so.
It supports the options of the Erlang Mix compiler under the covers at it is used.
This means that these options are supported:
## Command line options
* `--force` - forces compilation regardless of modification times
* `--all-warnings` - prints warnings even from files that do not need to be recompiled
## Configuration
The [Erlang compiler configuration](https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/tasks/compile.erlang.ex#L31) is supported.
Specific configuration options for the LFE compiler will be supported in future.
"""

@doc """
Runs this task.
"""
def run(args) do
{opts, _, _} = OptionParser.parse(args, switches: @switches)
do_run(opts)
end

defp do_run(opts) do
dest = Mix.Project.compile_path()

callback = fn input, output ->
module = input |> Path.basename(".lfe") |> String.to_atom()
:code.purge(module)
:code.delete(module)

outdir = output |> Path.dirname() |> to_erl_file()

compile_result(:lfe_comp.file(to_erl_file(input), [{:outdir, outdir}, :return, :report]))
end

compile(manifest(), [{"src", dest}], :lfe, :beam, opts, callback)
end

@doc """
Returns LFE manifests.
"""
def manifests, do: [manifest()]

@doc """
Cleans up compilation artifacts.
"""
def clean, do: clean(manifest())

defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)

defp compile_result({:error, [{:error, [{file, [error | _]}], []}], [], []}) do
{:error, [{file, [error]}], []}
end

defp compile_result(result), do: result
end
33 changes: 33 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule MixLfe.MixProject do
use Mix.Project

def project do
[
app: :mix_lfe,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
description: "A LFE compiler for Mix",
package: package(),
deps: deps()
]
end

def application do
[extra_applications: [:logger]]
end

def package do
%{
licenses: ["Apache 2"],
links: %{"GitHub" => "https://github.com/meddle0x53/mix_lfe"},
maintainers: ["Nikolay Tsvetinov (Meddle)"]
}
end

def deps do
[
{:lfe, "~> 1.2", override: true, manager: :rebar}
]
end
end
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
%{
"lfe": {:hex, :lfe, "1.2.0", "257708859c0a6949f174cecee6f08bb5d6f08c0f97ec7b75c07072014723ecbc", [:rebar3], [], "hexpm"},
}
7 changes: 7 additions & 0 deletions test/fixtures/sample/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Sample.Mixfile do
use Mix.Project

def project do
[app: :sample, version: "1.0.0"]
end
end
6 changes: 6 additions & 0 deletions test/fixtures/sample/src/tut4.lfe
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(defmodule tut4
(export (convert 2)))

(defun convert
((m 'inch) (/ m 2.54))
((n 'centimeter) (* n 2.54)))
6 changes: 6 additions & 0 deletions test/fixtures/sample/src/tut5.lfe
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(defmodule tut5
(export (convert-length 1)))

(defun convert-length
(((tuple 'centimeter x)) (tuple 'inch (/ x 2.54)))
(((tuple 'inch y)) (tuple 'centimeter (* y 2.54))))
Loading

0 comments on commit f9dc79d

Please sign in to comment.