Compare commits

..
14 Commits
Author SHA1 Message Date
hermes-agent c345dab9c1 Merge pull request 'adapter: support OpenAI streaming (SSE) in /v1/chat/completions' (#1) from feat/adapter-streaming into main 2026-09-12 12:51:34 +02:00
hermes-agent 2d46eda619 adapter: support OpenAI streaming (SSE) in /v1/chat/completions
OpenCode's @ai-sdk/openai-compatible sends stream:true and would render an
empty response because the adapter always returned a single non-streaming
chat.completion JSON body. Now when stream:true, emit OpenAI-compatible SSE
chat.completion.chunk events (role, content, [DONE]) so streaming clients
render text. Non-streaming path unchanged.

Adds a local EchoServer test that exercises the streaming route end-to-end.
2026-09-12 10:50:47 +00:00
hermes-agent beb9b1b3ef Add temporary chat-request logging (diagnose SwiftChat error) 2026-09-11 18:44:45 +00:00
hermes-agent cb512a7f17 admin: serve the page without requiring the Bearer header
A browser opening /admin can't send an Authorization header, so the
admin page was unreachable (401 blank). Serve the HTML form openly — it
exposes no data — and let the in-page ADMIN_API_KEY field drive the
auth'd /admin/agents CRUD calls.
2026-09-11 15:36:15 +00:00
hermes-agent e4fdeb6b79 nixos-module: set RELEASE_COOKIE so the release starts
The Elixir release's start script reads releases/COOKIE which isn't baked
in, so the service crashed on boot (cat: releases/COOKIE: No such file).
Set RELEASE_COOKIE in the systemd Environment to fix startup.
2026-09-10 14:35:28 +00:00
hermes-agent 6b22171018 flake: fill mixFodDeps hash 2026-09-10 07:21:42 +00:00
hermes-agent f0112289e6 flake: add mixFodDeps (fetchMixDeps) for Hex deps 2026-09-10 07:17:56 +00:00
hermes-agent 72e18a0a0c Remove AGENTS env seeding; agents managed only via admin API
The store now starts empty and agents are added/removed exclusively through
the web admin page / admin API, persisted to AGENTS_FILE. No AGENTS env var
needed in the sops secret.
2026-09-10 07:06:26 +00:00
hermes-agent e2be3f652e Add web admin page to manage agents
GET /admin serves a self-contained HTML page (ADMIN_API_KEY protected)
that lists agents and lets you add/update/remove them via the admin API —
no redeploy needed to add an agent.
2026-09-10 06:59:08 +00:00
hermes-agent 490bd32322 Add admin API to manage agents at runtime
- AgentRegistry is now file-backed (AGENTS_FILE, default
  /var/lib/n8n-openai/agents.json): agents persist across restarts and
  can be added/removed without a redeploy.
- New admin endpoints (separate ADMIN_API_KEY):
    GET    /admin/agents
    POST   /admin/agents   {model, webhook}
    DELETE /admin/agents/:model
- AGENTS env only seeds the store on first boot; the file is authoritative.
- NixOS module sets AGENTS_FILE under the writable StateDirectory.
2026-09-10 06:46:05 +00:00
hermes-agent 1bfcba133a Add NixOS module for the adapter service
Export nixosModules.default so the service (systemd unit, service user,
sops secret) is defined in the flake, not re-declared in each host config.
Consume with imports = [ inputs.n8n-openai-adapter.nixosModules.default ]
+ services.n8n-openai-adapter = { enable = true; domain = ...; port = ...; }.
2026-09-10 06:36:44 +00:00
hermes-agent c3d024c16c Apply mix format 2026-09-09 21:46:16 +00:00
hermes-agent 1530a761d5 Add flake.lock pinning nixpkgs 2026-09-09 21:44:55 +00:00
hermes-agent 5197b6ece6 OpenAI-compatible adapter for n8n chat agents (Elixir)
Exposes self-hosted n8n chat agents behind /v1/chat/completions and
/v1/models. Model -> n8n webhook routing via a GenServer registry, so
multiple agents map to multiple models. Plug + Bandit, req for the
n8n webhook call, Bearer auth (ADAPTER_API_KEY). Ships a flake.nix
(beamPackages.mixRelease) so it can be consumed as a NixOS flake input.
2026-09-09 21:43:21 +00:00
281 changed files with 1182 additions and 32857 deletions
+11 -13
View File
@@ -1,13 +1,11 @@
/.eunit/*
/.full
/lib/*/ebin/*
/lib/*/tmp
/lib/*/test/tmp
/lib/elixir/src/elixir.app.src
/lib/elixir/src/*_lexer.erl
/lib/elixir/src/*_parser.erl
/lib/elixir/test/ebin
/deps/*
/ebin
/rel/elixir
erl_crash.dump
.formatter.exs
/_build/
/cover/
/deps/
/doc/
/.fetch
erl_crash.dump
*.ez
n8n_openai_adapter-*.tar
/tmp/
/result
-9
View File
@@ -1,9 +0,0 @@
language: erlang
script: "make compile && make .full test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
otp_release:
- R15B01
- R15B
-53
View File
@@ -1,53 +0,0 @@
# v0.6.0 (2012-08-01)
* incompatible changes
* [Kernel] Compiled files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace;
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`;
* [Module] Removed data functions in favor of unifying the attributes API;
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior;
* [Kernel] loop and recur were removed in favor of recursion with named functions;
* deprecations
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access;
* [Module] `Module.add_compile_callback(module, target, callback)` was deprecated in favor of `Module.add_attribute(module, :before_compile, { target, callback })`;
* [Module] `Module.function_defined?` was deprecated in favor of `Module.defines?`;
* [Module] `Module.defined_functions` was deprecated in favor of `Module.definitions_in`;
* [File] `File.read_info` was deprecated in favor of `File.stat`;
* [IO] `IO.print` was deprecated in favor of `IO.write`;
* [Kernel] Deprecated `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`;
* [Kernel] Deprecated `in_guard` in favor of `__CALLER__.in_guard?`;
* [Kernel] `refer` is deprecated in favor of `alias`;
* [ExUnit] Some assertions were deprecated in favor of simply using `assert()`;
* enhancements
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing;
* [Kernel] Operator `!` is now allowed in guard clauses;
* [IEx] IEx now provides autocomplete if the OS supports tty;
* [IEx] IEx now supports remsh;
* [Mix] First Mix public release;
* [Regex] Back references are now properly supported;
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation;
* [ExUnit] Support setup and teardown callbacks;
* [Kernel] Introduced operator `=~` for regular expression matches;
* [Kernel] Compiled docs now include the function signature;
* [Kernel] `defmodule` do not start a new variable scope, this improves meta-programming capabilities;
* [Range] Added a Range module with support to `in` operator (`x in 1..3`) and iterators;
* [Enum] Enhanced Enum protocol to support `Enum.count`;
* [Module] Added support to `@before_compile` and `@after_compile` callbacks. The first receives the module name while the latter receives the module name and its object code;
* [Kernel] quote special form now supports line and unquote as options;
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang;
* [IEx] Functions `c` and `m` are available in IEx to compile and print available module information. Functions `h` and `v` are available to show history and print previous commands values;
* [Enum] Optimized functions when a list is given as collection;
* [System] Added `System.find_executable`
* [Kernel] Document the macro `@` and allow attributes to be read inside functions;
* [IO/File] Many improvements to `File` and `IO` modules;
* [Macro] Added `Macro.expand`, useful for debugging what a macro expands to;
* [Enum] Added `find_index`;
* [Record] Records now provide a `to_keywords` function;
* [Kernel] Added support to the `%R` sigil. The same as `%r`, but without interpolation or escaping. Both implementations were also optimized to generate the regex at compilation time;
* [Kernel] Added `__ENV__` which returns a `Macro.Env` record with information about the compilation environment;
* [Kernel] Added `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site;
# v0.5.0 (2012-05-24)
* First official release
-10
View File
@@ -1,10 +0,0 @@
LEGAL NOTICE INFORMATION
------------------------
All the files in this distribution are covered under either Elixir's
license (see the file LICENSE) except the files mentioned below that
contains sections that are under Erlang's License (EPL):
lib/elixir/src/elixir_glob.erl
lib/elixir/src/elixir_parser.erl (generated by build scripts)
-13
View File
@@ -1,13 +0,0 @@
Copyright 2012 Plataformatec.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-103
View File
@@ -1,103 +0,0 @@
REBAR:=$(shell echo `pwd`/rebar)
ELIXIRC:=bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC:=erlc -I lib/elixir/include
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
FULLFLAG:=.full
VERSION:=0.6.0
.PHONY: 1
.NOTPARALLEL: compile
#==> Templates
define TASK_TEMPLATE
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app:
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
@ echo "==> $(1) (compile)"
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs"
endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
@ rm -rf lib/elixir/src/elixir.app.src
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
erlang:
@ cd lib/elixir && $(REBAR) compile
# We need to compile only EEx (without the app)
# file so we can compile Mix
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex
kernel: $(KERNEL)
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
@ if [ -f $(KERNEL) ]; then \
echo "==> kernel (compile)"; \
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
else \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ rm -rf lib/elixir/ebin/elixir.app
@ cd lib/elixir && $(REBAR) compile
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
$(eval $(call TASK_TEMPLATE,eex,EEx))
$(eval $(call TASK_TEMPLATE,mix,Mix))
clean:
@ rm -rf .full
@ rm -rf lib/*/ebin
@ cd lib/elixir && $(REBAR) clean
#==> Release tasks
$(FULLFLAG): $(wildcard lib/*/ebin/*)
make ELIXIRC_OPTS="--debug-info" FORCE=1
touch $(FULLFLAG)
zip: $(FULLFLAG)
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
docs: $(FULLFLAG)
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../exdoc/bin/exdoc
rm -rf ebin
release_docs: docs
cd ../elixir-lang.github.com && git checkout master
rm -rf ../elixir-lang.github.com/docs/master
mv output ../elixir-lang.github.com/docs/master
release_erl: $(FULLFLAG)
@ rm -rf rel/elixir
@ cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
test_erlang: compile
@ echo "==> elixir (eunit)"
@ mkdir -p lib/elixir/test/ebin
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
@ time $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt
@ echo
test_elixir: test_kernel test_mix test_ex_unit test_eex
test_kernel: compile
@ echo "==> kernel (exunit)"
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"
+73 -31
View File
@@ -1,52 +1,94 @@
![Elixir](https://github.com/elixir-lang/elixir-lang.github.com/raw/master/images/logo/logo.png)
=========
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.png?branch=master "Build Status")](http://travis-ci.org/elixir-lang/elixir)
# n8n-openai-adapter
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
An OpenAI-compatible HTTP adapter that exposes self-hosted **n8n chat agents**
behind a standard `/v1/chat/completions` API, so any OpenAI client (Cursor,
LibreChat, the `openai` SDK, a custom app) can talk to your n8n agents as if
they were OpenAI models.
# Usage
n8n itself does **not** ship an inbound OpenAI-compatible endpoint (its "AI
Gateway" is an outbound proxy to n8n Cloud). This small Elixir service is the
bridge: one `/v1/chat/completions` endpoint, routed to whichever n8n agent you
name in the `model` field.
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
## How it works
$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ make test
```
Your OpenAI client
POST /v1/chat/completions {"model":"scholar-agent","thread_id":"abc","messages":[...]}
|
v
n8n-openai-adapter (Plug + Bandit)
- authorize (Bearer <ADAPTER_API_KEY>)
- look up "scholar-agent" -> n8n chat webhook URL (AgentRegistry GenServer)
- take the last user message
- forward to the n8n webhook {sessionId: thread_id, action: sendMessage, chatInput}
|
v
n8n agent (its MCP tools, memory, etc. run as usual)
|
v
returns OpenAI-shaped {"choices":[{"message":{"role":"assistant","content":...}}]}
```
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
Multiple agents = multiple `model` names, each mapped to a different n8n webhook
in the `AGENTS` env var.
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
## Configuration (env vars)
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
| Var | Required | Purpose |
|------------------|----------|---------------------------------------------------------------------|
| `ADAPTER_API_KEY`| yes | Bearer key that OpenAI clients send. |
| `ADMIN_API_KEY` | yes | Bearer key for the admin API / web admin page. |
| `AGENTS_FILE` | no | Path to the JSON store (default `/var/lib/n8n-openai/agents.json`). |
| `PORT` | no | HTTP port (default `8000`). |
| `CHAT_WEBHOOK_BASIC` | no | `"user:password"` if your n8n Chat Trigger is Basic-auth protected. |
If you have the correct version and tests still fail, feel free to [open an issue][2].
Agents are **not** configured via env — they're managed at runtime through the
web admin page / admin API and persisted to `AGENTS_FILE`. The store starts
empty; add agents after boot.
# Contributing
## Admin API (manage agents at runtime)
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
Agents are persisted to `AGENTS_FILE` and can be added/removed without a
redeploy, using the `ADMIN_API_KEY`:
* `elixir` - Contains Elixir's kernel and stdlib;
```bash
# list
curl -H "Authorization: Bearer $ADMIN_API_KEY" https://openai.bueso.eu/admin/agents
* `eex` - Template engine that allows you to embed Elixir;
# add / update an agent
curl -X POST -H "Authorization: Bearer $ADMIN_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"media-agent","webhook":"https://n8n.bueso.eu/webhook/<id>/chat"}' \
https://openai.bueso.eu/admin/agents
* `ex_unit` - Simple test framework that ships with Elixir;
# remove
curl -X DELETE -H "Authorization: Bearer $ADMIN_API_KEY" \
https://openai.bueso.eu/admin/agents/media-agent
```
We usually keep a list of features and bugs [in the issue tracker][2].
The store is authoritative and persists across restarts; no env config needed.
# Important links
## Building & running
* #elixir-lang on freenode IRC
* [Website][1]
* [Issue tracker][2]
* [Mailing list][3]
```bash
mix deps.get
mix compile
ADAPTER_API_KEY=secret AGENTS='{"scholar-agent":"https://n8n.bueso.eu/webhook/<id>/chat"}' \
PORT=8000 mix run --no-halt
```
[1]: http://elixir-lang.org
[2]: https://github.com/elixir-lang/elixir/issues
[3]: http://groups.google.com/group/elixir-lang-core
## Testing
# License
```bash
MIX_ENV=test mix test
```
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
## Nix
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
The repo ships a `flake.nix` exporting `overlays.default` and a `packages.default`
(the packaged BEAM release), so it can be consumed as a flake input from your
NixOS config just like any other flake — e.g.:
Check LEGAL and LICENSE files for more information.
```nix
inputs.n8n-openai-adapter.url = "git+https://gitea.bueso.eu/<owner>/n8n-openai-adapter";
```
-25
View File
@@ -1,25 +0,0 @@
## Release process
This document simply outlines the release process:
1) Remove .dev extension from current versions
2) Run `make clean test` to ensure all tests pass from scratch
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above and tag new version on Git
5) Release new docs, update elixir-lang.org
6) Push new zip to Elixir's downloads page
7) After release, bump versions and add .dev back
## Places where version is mentioned
* src/elixir.app.src
* lib/elixir/lib/system.ex
* rel/reltool.config
* Makefile
* CHANGELOG
-71
View File
@@ -1,71 +0,0 @@
#!/bin/sh
if [ $# -eq 0 ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version and exit
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script (*)
-pr \"file\" Requires the given files/patterns in parallel (*)
-pa \"path\" Prepends the given path to Erlang code path (*)
-pz \"path\" Appends the given path to Erlang code path (*)
--erl \"switches\" Switches to be passed down to erlang
--name \"name\" Makes and assigns a name to the distributed node
--sname \"name\" Makes and assigns a short name to the distributed node
--remsh \"name\" Connects to a node using a remote shell (with iex)
--no-halt Does not halt the Erlang VM after execution
** Options marked with (*) can be given more than once
** Options given after the .exs file or -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
exit 1
fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
ERL=""
I=1
while [ $I -le $# ]; do
S=1
eval "PEEK=\${$I}"
case "$PEEK" in
-v|--compile|--no-halt)
;;
-e|-r|-pr|-pa|-pz|--remsh|-S)
S=2
;;
--sname|--name)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL `echo $PEEK | cut -c 2-` "$VAL""
;;
--erl)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL "$VAL""
;;
*)
break
;;
esac
I=$(expr $I + $S)
done
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
"$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
else
erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
fi
-22
View File
@@ -1,22 +0,0 @@
@echo off
if "%*" == "" (
goto documentation
) else (
goto run
)
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r command Requires the given file/pattern (*)
echo -pr command Requires the given file/pattern in parallel (*)
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-halt Do not halt the Erlang VM after execution
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after the .exs file or -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
:run
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
-27
View File
@@ -1,27 +0,0 @@
#!/bin/sh
if [ $# -eq 0 ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
--no-docs Do not attach documentation with compiled code
--debug-info Attach debug info to compiled modules
--ignore-module-conflict
** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
exit 1
fi
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
"$SCRIPT_PATH"/elixir --compile "$@"
-22
View File
@@ -1,22 +0,0 @@
@echo off
if "%*" == "" (
goto documentation
) else (
goto run
)
:documentation
echo Usage: %~nx0 [switches] [.ex files]
echo.
echo -v Prints version and exit
echo -o The directory to output compiled files
echo -pa path Prepend the given path to Erlang code path (*)
echo -pz path Append the given path to Erlang code path (*)
echo --no-docs Do not attach documentation with compiled code
echo --debug-info Attach debug info to compiled modules
echo --ignore-module-conflict
echo.
echo ** Options marked with (*) can be given more than once
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
:run
call "%~dp0\elixir.bat" --compile %*
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
readlink_f () {
cd "$(dirname "$1")" > /dev/null
local filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
"$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
-2
View File
@@ -1,2 +0,0 @@
@echo off
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.run
-2
View File
@@ -1,2 +0,0 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+7
View File
@@ -0,0 +1,7 @@
import Config
import_config "#{config_env()}.exs"
if config_env() == :test do
config :logger, level: :warning
end
+3
View File
@@ -0,0 +1,3 @@
import Config
# Dev: no special config — all runtime settings come from env vars.
+5
View File
@@ -0,0 +1,5 @@
import Config
# Production: no hardcoded values here. All runtime config (PORT, AGENTS,
# ADAPTER_API_KEY, CHAT_WEBHOOK_BASIC) comes from the systemd EnvironmentFile
# in the NixOS service module.
+7
View File
@@ -0,0 +1,7 @@
import Config
# Test environment: the app starts with an empty agent store (no AGENTS env
# seeding — agents are managed via the admin API). ADAPTER_API_KEY /
# ADMIN_API_KEY are set in test/test_helper.exs. AGENTS_FILE must be set HERE
# (config loads before the app boots) to a writable tmp path.
System.put_env("AGENTS_FILE", Path.join(System.tmp_dir!(), "n8n-openai-test-agents.json"))
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1788881743,
"narHash": "sha256-2V9GZGvPfrNzxFozhI9dcqV+c3QdA8YZrvAAzqEB+dI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d6524aaca2ff07876657ae2b323f24be4874944b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+48
View File
@@ -0,0 +1,48 @@
{
description = "OpenAI-compatible adapter exposing n8n chat agents behind /v1/chat/completions";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{ self, nixpkgs, ... }:
let
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in
{
packages = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
beamPackages = pkgs.beamPackages;
in
{
default = beamPackages.mixRelease {
pname = "n8n-openai-adapter";
version = "0.1.0";
src = self;
mixFodDeps = beamPackages.fetchMixDeps {
pname = "n8n-openai-adapter";
version = "0.1.0";
src = self;
hash = "sha256-sdAhpZUeF33V9xjEa/z/aTmCllfetMjO/1XyfJfUNao=";
};
};
}
);
overlays.default = final: prev: {
n8n-openai-adapter = self.packages.${final.stdenv.system}.default;
};
# Proper NixOS module: consume with
# imports = [ inputs.n8n-openai-adapter.nixosModules.default ];
# services.n8n-openai-adapter = { enable = true; domain = "..."; port = 8134; };
nixosModules.default = import ./nixos-module.nix;
};
}
-201
View File
@@ -1,201 +0,0 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %B"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
## API
This module provides 3 main APIs for you to use:
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
directly. This is the simplest API to use but also the
slowest, since the code is evaluated and not compiled before;
2) Define a function from a string (`function_from_string`)
or a file (`function_from_file`). This allows you to embed
the template as a function inside a module which will then
be compiled. This is the preferred API if you have access
to the template at compilation time;
3) Compile a string (`compile_string`) or a file (`compile_file`)
into Elixir syntax tree. This is the API used by both functions
above and is available to you if you want to provide your own
ways of handling the compiled template.
## Engine
EEx has the concept of engines which allows you to modify or
transform the code extracted from the given string or file.
By default, `EEx` uses the `EEx.SmartEngine` that provides some
conveniences on top of the simple `EEx.Engine`.
### Tags
`EEx.SmartEngine` supports the following tags:
<% Elixir expression - inline with output %>
<%= Elixir expression - replace with result %>
All expressions that output something to the template
**must** use the equals sign (`=`). Since everything in
Elixir is a macro, there are no exceptions for this rule.
For example, while some template languages would special-
case `if` clauses, they are treated the same in EEx and
also require `=` in order to have their result printed:
<%= if true do %>
It is obviously true
<% else %>
This will never appear
<% end %>
Notice that different engines may have different rules
for each tag. Other tags may be added in future versions.
### Macros
`EEx.SmartEngine` also adds some macros to your template.
An example is the `@` macro which allows easy data access
in a template:
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
#=> 1
In other words, <%= @foo %> is simply translated to:
<%= Keyword.get assigns, :foo %>
The assigns extension is useful when the number of variables
required by the template is not specified at compilation time.
"""
@doc """
Generates a function definition from the string.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
## Examples
defmodule Sample do
require EEx
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
end
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_string(kind, name, source, args // [], options // []) do
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
quote do
info = Keyword.merge unquote(info), unquote(options)
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_string(unquote(source), info), info)
end
end
@doc """
Generates a function definition from the file contents.
The kind (`:def` or `:defp`) must be given, the
function name, its arguments and the compilation options.
This function is useful in case you have templates but
you want to precompile inside a module for speed.
## Examples
# sample.eex
<%= a + b %>
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:a, :b]
end
# iex
Sample.sample(1, 2) #=> "3"
"""
defmacro function_from_file(kind, name, filename, args // [], options // []) do
quote do
file = unquote(filename)
info = Keyword.merge unquote(options), [file: file, line: 1]
@file file
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
unquote(args), EEx.compile_file(file, info), info)
end
end
@doc """
Get a string `source` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_string(source, options // []) do
EEx.Compiler.compile(source, options)
end
@doc """
Get a `filename` and generate a quoted expression
that can be evaluated by Elixir or compiled to a function.
"""
def compile_file(filename, options // []) do
options = Keyword.merge options, [file: filename, line: 1]
compile_string(File.read!(filename), options)
end
@doc """
Get a string `source` and evaluate the values using the `bindings`.
## Examples
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
#=> "foo baz"
"""
def eval_string(source, bindings // [], options // []) do
compiled = compile_string(source, options)
do_eval(compiled, bindings, options)
end
@doc """
Get a `filename` and evaluate the values using the `bindings`.
## Examples
# sample.ex
foo <%= bar %>
# iex
EEx.eval_file "sample.ex", [bar: "baz"]
#=> "foo baz"
"""
def eval_file(filename, bindings // [], options // []) do
options = Keyword.put options, :file, filename
compiled = compile_file(filename, options)
do_eval(compiled, bindings, options)
end
### Helpers
@doc false
def function_from_quoted(module, kind, name, args, source, info) do
args = Enum.map args, fn arg -> { arg, 0, nil } end
quote = quote do
unquote(kind).(unquote(name).(unquote_splicing(args)), do: unquote(source))
end
Module.eval_quoted module, quote, [], info
end
defp do_eval(compiled, bindings, options) do
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
result
end
end
-117
View File
@@ -1,117 +0,0 @@
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
defmodule EEx.Compiler do
@moduledoc false
@doc """
This is the compilation entry point. It glues the tokenizer
and the engine together by handling the tokens and invoking
the engine every time a full expression or text is received.
"""
def compile(source, options) do
line = Keyword.get(options, :line, 1)
tokens = EEx.Tokenizer.tokenize(source, line)
state = EEx.State.new(options)
generate_buffer(tokens, "", [], state)
end
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, chars)
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
expr = maybe_block Erlang.elixir_translator.forms(chars, line, state.file)
buffer = state.engine.handle_expr(buffer, mark, expr)
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
buffer = state.engine.handle_expr(buffer, mark, contents)
generate_buffer(t, buffer, scope, state.dict([]))
end
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
generate_buffer(t, "", [wrapped|scope], state.line(line))
end
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
tuples = maybe_block Erlang.elixir_translator.forms(wrapped, state.start_line, state.file)
buffer = insert_quotes(tuples, state.dict)
{ buffer, t }
end
defp generate_buffer([{ :end_expr, line, _, chars }|_], _buffer, [], _state) do
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
end
defp generate_buffer([], buffer, [], _state) do
buffer
end
defp generate_buffer([], _buffer, _scope, _state) do
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
end
# Creates a placeholder and wrap it inside the expression block
defp wrap_expr(current, line, buffer, chars, state) do
new_lines = List.duplicate(?\n, line - state.line)
if state.dict == [] and is_empty?(buffer) do
{ current ++ new_lines ++ chars, state }
else
key = length(state.dict)
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
{ current ++ placeholder ++ new_lines ++ chars, state.prepend_dict([{key, buffer}]) }
end
end
# Check if the syntax node represents an empty string
defp is_empty?(bin) when is_binary(bin) do
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
end
defp is_empty?({ :<>, _, [left, right] }) do
is_empty?(left) and is_empty?(right)
end
defp is_empty?(_) do
false
end
# Block wrapping
defp maybe_block([]), do: nil
defp maybe_block([h]), do: h
defp maybe_block(other), do: { :__block__, 0, other }
# Changes placeholder to real expression
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
{ ^key, value } = List.keyfind dict, key, 1
value
end
defp insert_quotes({ left, line, right }, dict) do
{ insert_quotes(left, dict), line, insert_quotes(right, dict) }
end
defp insert_quotes({ left, right }, dict) do
{ insert_quotes(left, dict), insert_quotes(right, dict) }
end
defp insert_quotes(list, dict) when is_list(list) do
Enum.map list, insert_quotes(&1, dict)
end
defp insert_quotes(other, _dict) do
other
end
end
-57
View File
@@ -1,57 +0,0 @@
defmodule EEx.Engine do
@moduledoc %B"""
This is the basic EEx engine that ships with Elixir.
An engine needs to implement two functions:
* `handle_text(buffer, text)` - it receives the buffer,
the text and must return a new quoted expression;
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
the marker, the expr and must return a new quoted expression;
The marker is what follows exactly after `<%`. For example,
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
as marker. The allowed markers so far are:
* `''`
* `'='`
Read `handle_expr/3` below for more information about the markers
implemented by default by this engine.
"""
@doc """
The default implementation simply concatenates text to the buffer.
"""
def handle_text(buffer, text) do
quote do: unquote(buffer) <> unquote(text)
end
@doc """
Implements expressions according to the markers.
<% Elixir expression - inline with output %>
<%= Elixir expression - replace with result %>
All other markers are not implemented by this engine.
"""
def handle_expr(buffer, '=', expr) do
quote do
tmp_1 = unquote(buffer)
tmp_2 = to_binary(unquote(expr))
tmp_1 <> tmp_2
end
end
def handle_expr(buffer, '', expr) do
quote do
tmp = unquote(buffer)
unquote(expr)
tmp
end
end
def behaviour_info(:callbacks) do
[handle_text: 2, handle_expr: 3]
end
end
-97
View File
@@ -1,97 +0,0 @@
defmodule EEx.TransformerEngine do
@moduledoc """
An abstract engine that is meant to be used and
built upon in other modules. This engine implements
the `EEx.Engine` behavior and provides a `transform`
overridable directive that allows a developer to
customize the expression returned by the engine.
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
examples of using this module.
"""
@doc false
defmacro __using__(_) do
quote do
@behavior EEx.Engine
def handle_text(buffer, text) do
EEx.Engine.handle_text(buffer, text)
end
def handle_expr(buffer, mark, expr) do
EEx.Engine.handle_expr(buffer, mark, transform(expr))
end
defp transform({ a, b, c }) do
{ transform(a), b, transform(c) }
end
defp transform({ a, b }) do
{ transform(a), transform(b) }
end
defp transform(list) when is_list(list) do
lc i inlist list, do: transform(i)
end
defp transform(other) do
other
end
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
end
end
end
defmodule EEx.AssignsEngine do
@moduledoc """
An abstract engine that, when used with the
`TransformerEngine`, allows a developer to access
assigns using `@` as syntax.
This engine is included by default on the SmartEngine.
## Examples
defmodule MyEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
end
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
#=> 1
In the example above, we can access the value `foo` under
the binding `assigns` using `@foo`. This is useful when
a template, after compiled, may receive different assigns
and the developer don't want to recompile it for each
variable set.
"""
@doc false
defmacro __using__(_) do
quote unquote: false do
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
quote(do: Keyword.get var!(assigns), unquote(name))
end
defp transform(_) do
super
end
defoverridable [transform: 1]
end
end
end
defmodule EEx.SmartEngine do
use EEx.TransformerEngine
use EEx.AssignsEngine
@moduledoc """
An engine meant for end-user usage that includes
`AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
-151
View File
@@ -1,151 +0,0 @@
defmodule EEx.Tokenizer do
@moduledoc false
@doc """
Tokenizes the given char list or binary.
It returns 4 different types of tokens as result:
* { :text, line, contents }
* { :expr, line, marker, contents }
* { :start_expr, line, marker, contents }
* { :end_expr, line, marker, contents }
"""
def tokenize(bin, line) when is_binary(bin) do
tokenize(binary_to_list(bin), line)
end
def tokenize(list, line) do
List.reverse(tokenize(list, line, line, [], []))
end
defp tokenize([?<,?%|t], current_line, line, buffer, acc) do
{ marker, t } = retrieve_marker(t)
{ expr, new_line, rest } = tokenize_expr t, line, []
token = token_name(expr)
acc = tokenize_text(current_line, buffer, acc)
final = { token, line, marker, List.reverse(expr) }
tokenize rest, new_line, new_line, [], [final | acc]
end
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
tokenize t, current_line, line + 1, [?\n|buffer], acc
end
defp tokenize([h|t], current_line, line, buffer, acc) do
tokenize t, current_line, line, [h|buffer], acc
end
defp tokenize([], current_line, _line, buffer, acc) do
tokenize_text(current_line, buffer, acc)
end
# Retrieve marker for <%
defp retrieve_marker('=' ++ t) do
{ '=', t }
end
defp retrieve_marker(t) do
{ '', t }
end
# Tokenize an expression until we find %>
defp tokenize_expr([?%,?>|t], line, buffer) do
{ buffer, line, t }
end
defp tokenize_expr('\n' ++ t, line, buffer) do
tokenize_expr t, line + 1, [?\n|buffer]
end
defp tokenize_expr([h|t], line, buffer) do
tokenize_expr t, line, [h|buffer]
end
# Raise an error if the expected token is not found
defp tokenize_expr([], _line, _buffer) do
raise EEx.SyntaxError, message: "missing token: %>"
end
# Receive an expression content and check
# if it is a start, middle or an end token.
#
# Start tokens finish with `do` and `fn ->`
# Middle tokens are marked with `->` or keywords
# End tokens contain only the end word
defp token_name([h|t]) when h in [?\s, ?\t] do
token_name(t)
end
defp token_name('od' ++ [h|_]) when h in [?\s, ?\t, ?)] do
:start_expr
end
defp token_name('>-' ++ rest) do
rest = List.reverse(rest)
# Tokenize the remaining passing "__internal__" as file,
# which relax the tokenizer to not error on unmatched
# pairs. Then, we check if there is a "fn" token and,
# if so, it is not followed by an "end" token. If this
# is the case, we are on a start expr.
case :elixir_tokenizer.tokenize(rest, 1, "__internal__") do
{ :ok, tokens } ->
tokens = List.reverse(tokens)
fn_index = fn_index(tokens)
if fn_index && end_index(tokens) > fn_index do
:start_expr
else
:middle_expr
end
error ->
:middle_expr
end
end
defp token_name('esle' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
defp token_name(_) do
:expr
end
defp fn_index(tokens) do
Enum.find_index(tokens, function do
{ :fn_paren, _ } -> true
{ :fn, _ } -> true
_ -> false
end)
end
defp end_index(tokens) do
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
end
defp check_spaces(string, token) do
if only_spaces?(string), do: token, else: :expr
end
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
defp only_spaces?(other), do: other == []
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text(_line, [], acc) do
acc
end
defp tokenize_text(line, buffer, acc) do
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
end
end
-7
View File
@@ -1,7 +0,0 @@
defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version]
end
end
-22
View File
@@ -1,22 +0,0 @@
Code.require_file "../../test_helper", __FILE__
defmodule EEx.SmartEngineTest do
use ExUnit.Case, async: true
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
end
test "evaluates with assigns" do
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
end
test "evaluates with loops" do
assert_eval "1\n2\n3\n", "<%= lc x inlist [1,2,3] do %><%= x %>\n<% end %>"
end
defp assert_eval(expected, actual, binding // []) do
result = EEx.eval_string(actual, binding, file: __FILE__)
assert result == expected
end
end
-81
View File
@@ -1,81 +0,0 @@
Code.require_file "../../test_helper", __FILE__
defmodule EEx.TokenizerTest do
use ExUnit.Case, async: true
require EEx.Tokenizer, as: T
test "simple chars lists" do
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
end
test "simple strings" do
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, [], ' bar ' } ]
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, '=', ' bar ' } ]
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" },{ :expr, 2, '=', ' bar ' } ]
end
test "strings with more than one line and expression with more than one line" do
string = '''
foo <%= bar
baz %>
<% foo %>
'''
assert T.tokenize(string, 1) == [
{:text, 1, "foo "},
{:expr, 1, '=', ' bar\n\nbaz '},
{:text, 3, "\n"},
{:expr, 4, [], ' foo '},
{:text, 4, "\n"}
]
end
test "strings with embedded do end" do
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :text, 1, "bar" },
{ :end_expr, 1, '', ' end ' }
]
end
test "strings with embedded -> end" do
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' cond do ' },
{ :middle_expr, 1, '', ' false -> ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' true -> ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
]
end
test "strings with embedded keywords blocks" do
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
{ :text, 1, "foo " },
{ :start_expr, 1, '', ' if true do ' },
{ :text, 1, "bar" },
{ :middle_expr, 1, '', ' else ' },
{ :text, 1, "baz" },
{ :end_expr, 1, '', ' end ' }
]
end
test "raise syntax error when there is start mark and no end mark" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
T.tokenize('foo <% :bar', 1)
end
end
end
-342
View File
@@ -1,342 +0,0 @@
Code.require_file "../test_helper", __FILE__
require EEx
defmodule EExText.Compiled do
def before_compile do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
{ :erlang, 1, 2 }.tuple_to_list
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
def file_sample(arg), do: private_file_sample(arg)
def after_compile do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file "unknown"
def unknown do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
@file __ENV__
def other do
fill_in_stacktrace
{ __ENV__.line, hd(tl(System.stacktrace)) }
end
defp fill_in_stacktrace do
try do
Erlang.erlang.error "failed"
catch
:error, _, stack -> stack
end
end
end
defmodule Clause do
defmacro defclause(expr, block) do
quote do
def unquote(expr), unquote(block)
end
end
end
defmodule EExTest do
use ExUnit.Case, async: true
test "evaluates simple string" do
assert_eval "foo bar", "foo bar"
end
test "evaluates with embedded" do
assert_eval "foo bar", "foo <%= :bar %>"
end
test "evaluates with embedded and the binding" do
assert EEx.eval_string("foo <%= bar %>", [bar: 1]) == "foo 1"
end
test "evaluates with embedded do end" do
assert_eval "foo bar", "foo <%= if true do %>bar<% end %>"
end
test "evaluates with embedded do end and eval the expression" do
assert_eval "foo ", "foo <%= if false do %>bar<% end %>"
end
test "evaluates with embedded do end and nested print expression" do
assert_eval "foo bar", "foo <%= if true do %><%= :bar %><% end %>"
end
test "evaluates with embedded do end and nested expressions" do
assert_eval "foo bar baz", "foo <%= if true do %>bar <% Process.put(:eex_text, 1) %><%= :baz %><% end %>"
assert Process.get(:eex_text) == 1
end
test "evaluates with embedded middle expression" do
assert_eval "foo bar", "foo <%= if true do %>bar<% else %>baz<% end %>"
end
test "evaluates with embedded middle expression and eval the expression" do
assert_eval "foo baz", "foo <%= if false do %>bar<% else %>baz<% end %>"
end
test "evaluates with nested start expression" do
assert_eval "foo bar", "foo <%= if true do %><%= if true do %>bar<% end %><% end %>"
end
test "evaluates with nested middle expression" do
assert_eval "foo baz", "foo <%= if true do %><%= if false do %>bar<% else %>baz<% end %><% end %>"
end
test "evaluates with defined variable" do
assert_eval "foo 1", "foo <% bar = 1 %><%= bar %>"
end
test "evaluates with require code" do
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1,2,3], \",\" %>"
end
test "evaluates with end of token" do
assert_eval "foo bar %>", "foo bar %>"
end
test "raises a syntax error when the token is invalid" do
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
EEx.compile_string "foo <%= bar"
end
end
test "raises a syntax error when end expression is found without a start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
EEx.compile_string "foo <% end %>"
end
end
test "raises a syntax error when start expression is found without an end expression" do
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
EEx.compile_string "foo <% if true do %>"
end
end
test "raises a syntax error when nested end expression is found without an start expression" do
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
EEx.compile_string "foo <% if true do %><% end %><% end %>"
end
end
test "respects line numbers" do
expected = """
foo
2
"""
string = """
foo
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "respects line numbers inside nested expressions" do
expected = """
foo
3
5
"""
string = """
foo
<%= if true do %>
<%= __ENV__.line %>
<% end %>
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "respects line numbers inside start expression" do
expected = """
foo
true
5
"""
string = """
foo
<%= if __ENV__.line == 2 do %>
<%= true %>
<% end %>
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "respects line numbers inside middle expression with ->" do
expected = """
foo
true
7
"""
string = """
foo
<%= cond do %>
<% false -> %> false
<% __ENV__.line == 4 -> %>
<%= true %>
<% end %>
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "respects line number inside middle expressions with keywords" do
expected = """
foo
5
7
"""
string = """
foo
<%= if false do %>
<%= __ENV__.line %>
<% else %>
<%= __ENV__.line %>
<% end %>
<%= __ENV__.line %>
"""
assert_eval expected, string
end
test "properly handle functions" do
expected = """
Number 1
Number 2
Number 3
"""
string = """
<%= Enum.map [1,2,3], fn x -> %>
Number <%= x %>
<% end %>
"""
assert_eval expected, string
end
test "do not consider already finished functions" do
expected = """
foo
true
"""
string = """
foo
<%= cond do %>
<% false -> %> false
<% fn -> 1 end -> %>
<%= true %>
<% end %>
"""
assert_eval expected, string
end
test "evaluates the source from a given file" do
filename = File.expand_path("../fixtures/eex_template.eex", __FILE__)
result = EEx.eval_file(filename)
assert result == "foo bar.\n"
end
test "evaluates the source from a given file with bindings" do
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
result = EEx.eval_file(filename, [bar: 1])
assert result == "foo 1\n"
end
test "raises an Exception when there's an error with the given file" do
assert_raise File.Error, "could not read file non-existent.eex: no such file or directory", fn ->
filename = "non-existent.eex"
EEx.compile_file(filename)
end
end
test "defined from string" do
assert EExText.Compiled.string_sample(1, 2) == "3"
end
test "defined from file" do
assert EExText.Compiled.file_sample(1) == "foo 1\n"
end
test "defined from file do not affect backtrace" do
assert EExText.Compiled.before_compile ==
{ 8,
{ EExText.Compiled,
:before_compile,
0,
[file: binary_to_list(__FILE__), line: 7]
}
}
assert EExText.Compiled.after_compile ==
{ 19,
{ EExText.Compiled,
:after_compile,
0,
[file: binary_to_list(__FILE__), line: 18]
}
}
assert EExText.Compiled.unknown ==
{ 25,
{ EExText.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
}
}
assert EExText.Compiled.other ==
{ 31,
{ EExText.Compiled,
:other,
0,
[file: binary_to_list(__FILE__), line: 30]
}
}
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
assert result == expected
end
end
-1
View File
@@ -1 +0,0 @@
foo <%= if true do %>bar.<% end %>
-1
View File
@@ -1 +0,0 @@
foo <%= bar %>
-2
View File
@@ -1,2 +0,0 @@
# Configure ExUnit, no options supported yet.
ExUnit.start []
-39
View File
@@ -1,39 +0,0 @@
-define(ELIXIR_WRAP_CALL(Line, Module, Function, Args),
{ call, Line,
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
Args
}).
-define(ELIXIR_ATOM_CONCAT(Atoms), list_to_atom(lists:concat(Atoms))).
-define(ELIXIR_MACRO(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
noname=false, %% when true, don't add new names (used by try)
check_clauses=true, %% when true, check def clauses ordering
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
name_args=false, %% when true, it means arguments should be named
macro=[], %% a stack with macros nesting
module=nil, %% the current module
function=nil, %% the current function
recur=nil, %% the current loop function to be recurred
vars=dict:new(), %% a dict of defined variables and their alias
temp_vars=dict:new(), %% a dict of all variables defined in a particular assign
clause_vars=dict:new(), %% a dict of all variables defined in a particular clause
quote_vars=dict:new(), %% a dict of all quoted variables
extra_guards=nil, %% extra guards from args expansion
counter=0, %% a counter for the variables defined
file=(<<"nofile">>), %% the current scope filename
local=nil, %% the scope to evaluate local functions against
aliases=[], %% an orddict with aliases by new -> old names
requires=elixir_dispatch:default_requires(), %% a set with modules required
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
scheduled=[]}). %% scheduled modules to be loaded
-record(elixir_quote, {
line=0,
marker=quoted,
unquote=true
}).
-66
View File
@@ -1,66 +0,0 @@
import Kernel, except: [access: 2]
defprotocol Access do
@moduledoc """
The Access protocol is the underlying protocol invoked
when the brackets syntax is used. For instance, `foo[bar]`
is translated to `access foo, bar` which, by default,
invokes `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, tuples, atoms and
functions.
"""
@only [List, Function, Record, Atom]
@doc """
Receives the element being accessed and the access item.
"""
def access(element, qualifier)
end
defimpl Access, for: List do
@doc """
Access the given key in a keywords list.
## Examples
keywords = [a: 1, b: 2]
keywords[:a] #=> 1
"""
def access(list, atom) when is_atom(atom) do
atom_access(list, atom)
end
defp atom_access([{k, _}|_], key) when key < k, do: nil
defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
defp atom_access([{_k, value}|_], _key), do: value
defp atom_access([], _), do: nil
end
defimpl Access, for: Atom do
@doc """
The access protocol can only be accessed by atoms
at compilation time. If we reach this, we should raise
an exception.
"""
def access(atom, _) do
raise "The access protocol can only be invoked for atoms at " <>
"compilation time, tried to invoke it for #{inspect atom}"
end
end
defimpl Access, for: Function do
@doc """
The Access protocol for functions simply invokes
the function passing the item as argument. This
is useful because it allows a function to be
passed as argument in places a dict would also fit.
"""
def access(function, item) do
function.(item)
end
end
-218
View File
@@ -1,218 +0,0 @@
defmodule Binary do
@moduledoc """
Functions for working with binaries.
"""
@doc %B"""
Receives a char list and escapes all special chars (like \n)
and interpolation markers. A last argument is given and wraps
the whole char list given.
## Examples
Binary.escape "foo", ?'
#=> "'foo'"
"""
def escape(other, char) do
<<char>> <> do_escape(other, char)
end
@doc """
Check if a binary is printable considering it is encoded
as UTF-8. Returns true if so, false otherwise.
## Examples
Binary.printable?("abc") #=> true
"""
# Allow basic ascii chars
def printable?(<<c, t|:binary>>) when c in ?\s..?~ do
printable?(t)
end
# From 16#A0 to 16#BF
def printable?(<<194, c, t|:binary>>) when c in 160..191 do
printable?(t)
end
# From 16#C0 to 16#7FF
def printable?(<<m, o1, t|:binary>>) when m in 195..223 and o1 in 128..191 do
printable?(t)
end
# From 16#800 to 16#CFFF
def printable?(<<m, o1, o2, t|:binary>>) when m in 224..236 and
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# From 16#D000 to 16#D7FF
def printable?(<<237, o1, o2, t|:binary>>) when
o1 >= 128 and o1 < 160 and o2 >= 128 and o2 < 192 do
printable?(t)
end
# Reject 16#FFFF and 16#FFFE
def printable?(<<239, 191, o>>) when o == 190 or o == 191 do
false
end
# From 16#E000 to 16#EFFF
def printable?(<<m, o1, o2, t|:binary>>) when (m == 238 or m == 239) and
o1 in 128..191 and o2 in 128..191 do
printable?(t)
end
# From 16#F000 to 16#FFFD
def printable?(<<239, o1, o2, t|:binary>>) when
o1 in 128..191 and o2 in 128..191 do
printable?(t)
end
# From 16#10000 to 16#3FFFF
def printable?(<<240, o1, o2, o3, t|:binary>>) when
o1 in 144..191 and o2 in 128..191 and o3 in 128..191 do
printable?(t)
end
# Reject 16#110000 onwards
def printable?(<<244, o1, _, _, _|:binary>>) when o1 >= 144 do
false
end
# From 16#4000 to 16#10FFFF
def printable?(<<m, o1, o2, o3, t|:binary>>) when m in 241..244 and
o1 in 128..191 and o2 in 128..191 and o3 in 128..191 do
printable?(t)
end
def printable?(<<?\n, t|:binary>>), do: printable?(t)
def printable?(<<?\r, t|:binary>>), do: printable?(t)
def printable?(<<?\t, t|:binary>>), do: printable?(t)
def printable?(<<?\v, t|:binary>>), do: printable?(t)
def printable?(<<?\b, t|:binary>>), do: printable?(t)
def printable?(<<?\f, t|:binary>>), do: printable?(t)
def printable?(<<?\e, t|:binary>>), do: printable?(t)
def printable?(<<>>), do: true
def printable?(_), do: false
@doc %B"""
Unescape the given chars. The unescaping is driven by the same
rules as single- and double-quoted strings. Check `unescape/2`
for information on how to customize the escaping map.
In this setup, Elixir will escape the following: `\b`, `\d`,
`\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
escaped according to the latin1 set they represent.
## Examples
Binary.unescape "example\\n"
#=> "example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
def unescape(chars) do
Erlang.elixir_interpolation.unescape_chars(chars)
end
@doc %B"""
Unescape the given chars according to the map given.
Check `unescape/1` if you want to use the same map as Elixir
single- and double-quoted strings.
## Map
The map must be a function. The function receives an integer
representing the number of the characters it wants to unescape.
Here is the default mapping function implemented by Elixir:
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e), do: e
If the `unescape_map` function returns false. The char is
not escaped and `\` is kept in the char list.
## Octals
Octals will by default be escaped unless the map function
returns false for ?0.
## Examples
Using the unescape_map defined above is easy:
Binary.unescape "example\\n", unescape_map(&1)
"""
def unescape(chars, map) do
Erlang.elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape/1` and `unescape/2` for more information
about unescaping. Only tokens that are char lists are
unescaped, all others are ignored. This method is useful
when implementing your own sigils. Check the implementation
of `Kernel.__b__` for examples.
"""
def unescape_tokens(tokens) do
Erlang.elixir_interpolation.unescape_tokens(tokens)
end
@doc """
Unescape the given tokens according to the given map.
Check `unescape_tokens/1` and `unescaped/2` for more information.
"""
def unescape_tokens(tokens, map) do
Erlang.elixir_interpolation.unescape_tokens(tokens, map)
end
## Helpers
defp do_escape(<<char, t|:binary>>, char) do
<<?\\, char, do_escape(t, char)|:binary>>
end
defp do_escape(<<h, t|:binary>>, char) when
h == ?# or h == ?\b or
h == ?\d or h == ?\e or
h == ?\f or h == ?\n or
h == ?\r or h == ?\\ or
h == ?\t or h == ?\v do
<<?\\, escape_map(h), do_escape(t, char)|:binary>>
end
defp do_escape(<<h, t|:binary>>, char) do
<<h, do_escape(t,char)|:binary>>
end
defp do_escape(<<>>, char) do
<<char>>
end
defp escape_map(?#), do: ?#
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
defp escape_map(?\f), do: ?f
defp escape_map(?\n), do: ?n
defp escape_map(?\r), do: ?r
defp escape_map(?\\), do: ?\\
defp escape_map(?\t), do: ?t
defp escape_map(?\v), do: ?v
end
-74
View File
@@ -1,74 +0,0 @@
import Kernel, except: [to_binary: 1]
defprotocol Binary.Chars do
@moduledoc %B"""
The Binary.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_binary` which does the conversion.
The `to_binary` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_binary in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_binary(bar)`.
"""
@only [BitString, List, Number, Atom, Record]
def to_binary(thing)
end
defimpl Binary.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_binary(nil) do
""
end
def to_binary(atom) do
atom_to_binary(atom, :utf8)
end
end
defimpl Binary.Chars, for: BitString do
@doc """
Simply returns the binary itself.
"""
def to_binary(thing) when is_binary(thing) do
thing
end
end
defimpl Binary.Chars, for: List do
@doc """
Consider the list is an iolist and converts it
to a binary. This allows a list of binaries, or
a charlist, or a mix of both, to be converted
successfully.
## Examples
to_binary 'foo' #=> "foo"
to_binary ["foo", 'bar'] #=> "foobar"
"""
def to_binary(thing) do
iolist_to_binary(thing)
end
end
defimpl Binary.Chars, for: Number do
@doc """
Simply converts the number (integer or a float) to a binary.
"""
def to_binary(thing) when is_integer(thing) do
list_to_binary integer_to_list(thing)
end
def to_binary(thing) do
list_to_binary float_to_list(thing)
end
end
-301
View File
@@ -1,301 +0,0 @@
import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a Binary for textual
representation. All basic data structures (tuple,
list, function, pid, etc) implement the inspect
protocol. Other structures are adviced to implement
the protocol in order to provide pretty printing.
"""
@only [BitString, List, Record, Tuple, Atom, Number, Any]
def inspect(thing)
end
defimpl Binary.Inspect, for: Atom do
require Macro
@doc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
inspect(:foo) #=> ":foo"
inspect(nil) #=> "nil"
inspect(Foo.Bar) #=> "Foo.Bar"
"""
def inspect(false), do: "false"
def inspect(true), do: "true"
def inspect(nil), do: "nil"
def inspect(:""), do: ":\"\""
def inspect(Elixir), do: "Elixir"
def inspect(atom) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
"Elixir-" <> rest = binary
bc <<r>> inbits rest, do: <<to_dot(r)>>
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> Binary.escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
defp to_dot(?-), do: ?.
defp to_dot(l), do: l
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t|:binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t|:binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t|:binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
@doc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
inspect("bar") #=> "bar"
inspect("f\"oo") #=> "f\"oo"
"""
def inspect(thing) when is_binary(thing) do
if Binary.printable?(thing) do
Binary.escape(thing, ?")
else
as_bitstring(thing)
end
end
def inspect(thing) do
as_bitstring(thing)
end
## Helpers
defp as_bitstring(thing) do
erlang = Erlang.io_lib.format('~p', [thing])
list_to_binary List.reverse(replace(erlang, []))
end
defp replace([?:|t], acc), do: replace(t, [?||acc])
defp replace([h|t], acc) when is_list(h), do: replace(t, replace(h, acc))
defp replace([h|t], acc), do: replace(t, [h|acc])
defp replace([], acc), do: acc
end
defimpl Binary.Inspect, for: List do
@doc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
Inspecting a list is conservative as it does not try
to guess how the list is encoded. That said, `'josé'`
will likely be inspected as `[106,111,115,195,169]`
because we can't know if it is encoded in utf-8
or iso-5569-1, which is common in Erlang libraries.
## Examples
inspect('bar') #=> 'bar'
inspect([0|'bar']) #=> "[0,98,97,114]"
inspect([:foo,:bar]) #=> "[:foo, :bar]"
"""
def inspect([]), do: "[]"
def inspect(thing) do
if printable?(thing) do
Binary.escape(list_to_binary(thing), ?')
else
container_join(thing, "[", "]")
end
end
## Helpers
def container_join([h], acc, last) do
acc <> Binary.Inspect.inspect(h) <> last
end
def container_join([h|t], acc, last) when is_list(t) do
acc = acc <> Binary.Inspect.inspect(h) <> ","
container_join(t, acc, last)
end
def container_join([h|t], acc, last) do
acc <> Binary.Inspect.inspect(h) <> "|" <> Binary.Inspect.inspect(t) <> last
end
def container_join([], acc, last) do
acc <> last
end
## printable?
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
printable?(cs)
end
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e] do
printable?(cs)
end
defp printable?([]), do: true
defp printable?(_), do: false
end
defimpl Binary.Inspect, for: Tuple do
@doc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
inspect({1,2,3}) #=> "{1,2,3}"
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
"""
def inspect({}), do: "{}"
def inspect(exception) when is_exception(exception) do
[name,_|tail] = tuple_to_list(exception)
[_|fields] = lc { field, _ } inlist name.__record__(:fields), do: field
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
end
def inspect(thing) do
list = tuple_to_list(thing)
[name|tail] = list
if is_record?(name) do
fields = lc { field, _ } inlist name.__record__(:fields), do: field
if length(fields) != size(thing) - 1 do
Binary.Inspect.List.container_join(list, "{", "}")
else
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
end
else
Binary.Inspect.List.container_join(list, "{", "}")
end
end
## Helpers
defp is_record?(name) do
is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name, :utf8)) and
function_exported?(name, :__record__, 1)
end
defp records_join([f], [v], acc, last) do
acc <> atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v) <> last
end
defp records_join([fh|ft], [vh|vt], acc, last) do
acc = acc <> atom_to_binary(fh, :utf8) <> ": " <> Binary.Inspect.inspect(vh) <> ", "
records_join(ft, vt, acc, last)
end
defp records_join([], [], acc, last) do
acc <> last
end
end
defimpl Binary.Inspect, for: Number do
@doc """
Represents the number as a binary.
## Examples
inspect(1) #=> "1"
"""
def inspect(thing) when is_integer(thing) do
list_to_binary integer_to_list(thing)
end
def inspect(thing) do
list_to_binary float_to_list(thing)
end
end
defimpl Binary.Inspect, for: Regex do
@doc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
"""
def inspect(thing) do
"%r" <> Binary.Inspect.inspect(Regex.source(thing)) <> Regex.opts(thing)
end
end
defimpl Binary.Inspect, for: Any do
@doc """
For all other terms not implemented, we use the default
Erlang representation.
## Examples
inspect Process.self #=> "<0.35.0>"
"""
def inspect(thing) do
iolist_to_binary Erlang.io_lib.format('~p', [thing])
end
end
-126
View File
@@ -1,126 +0,0 @@
defmodule Bitwise do
@moduledoc """
This module provide macros and operators for bitwise operators.
These macros can be used in guards.
The easiest way to use is to simply import them into
your module:
use Bitwise
bnot 1 #=> -2
1 &&& 1 #=> 1
You can select to include only or skip operators by passing options:
use Bitwise, only_operators: true
1 &&& 1 #=> 1
"""
@doc """
Allow a developer to use this module in their programs with
the following options:
* `:only_operators` - Include only operators;
* `:skip_operators` - Skip operators;
"""
defmacro __using__(options) do
except = cond do
Keyword.get(options, :only_operators) ->
[:bnot, :band, :bor, :bxor, :bsl, :bsr]
Keyword.get(options, :skip_operators) ->
[:~~~, :&&&, :|||, :^^^, :<<<, :>>>]
true -> []
end
quote do
import Bitwise, except: unquote(except)
end
end
@doc """
Bitwise not.
"""
defmacro bnot(expr) do
quote do: __op__ :bnot, unquote(expr)
end
@doc """
Bitwise not as operator.
"""
defmacro ~~~expr do
quote do: __op__ :bnot, unquote(expr)
end
@doc """
Bitwise and.
"""
defmacro band(left, right) do
quote do: __op__ :band, unquote(left), unquote(right)
end
@doc """
Bitwise and as operator.
"""
defmacro left &&& right do
quote do: __op__ :band, unquote(left), unquote(right)
end
@doc """
Bitwise or.
"""
defmacro bor(left, right) do
quote do: __op__ :bor, unquote(left), unquote(right)
end
@doc """
Bitwise or as operator.
"""
defmacro left ||| right do
quote do: __op__ :bor, unquote(left), unquote(right)
end
@doc """
Bitwise xor.
"""
defmacro bxor(left, right) do
quote do: __op__ :bxor, unquote(left), unquote(right)
end
@doc """
Bitwise xor as operator.
"""
defmacro left ^^^ right do
quote do: __op__ :bxor, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift left.
"""
defmacro bsl(left, right) do
quote do: __op__ :bsl, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift left as operator.
"""
defmacro left <<< right do
quote do: __op__ :bsl, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift right.
"""
defmacro bsr(left, right) do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
@doc """
Arithmetic bitshift right as operator.
"""
defmacro left >>> right do
quote do: __op__ :bsr, unquote(left), unquote(right)
end
end
-314
View File
@@ -1,314 +0,0 @@
defmodule Code do
@moduledoc """
The Code module is responsible to manage code compilation,
code evaluation and code loading.
It complements (Erlang's code module)[1] to add behavior
which is specific to Elixir.
[1]: (www.erlang.org/doc/man/code.html)
"""
@doc """
Returns all the loaded files.
"""
def loaded_files do
server_call :loaded
end
@doc """
Appends a path to Erlang VM code path.
The path is expanded with `File.expand_path` before added.
"""
def append_path(path) do
Erlang.code.add_pathz(File.expand_path to_char_list(path))
end
@doc """
Prepends a path to Erlang VM code path.
The path is expanded with `File.expand_path` before added.
"""
def prepend_path(path) do
Erlang.code.add_patha(File.expand_path to_char_list(path))
end
@doc """
Evalutes the contents given by string. The second argument is the binding
(which should be a Keyword) followed by a keyword list of options. The
options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
otherwise functions are evaluated inside Erlang's default scope.
## Examples
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keywords lists:
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval(string, binding // [], opts // []) do
{ value, binding, _scope } =
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
{ value, binding }
end
@doc """
Evalutes the quoted contents.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
contents = quote hygiene: false, do: a + b
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
#=> { 3, [ {:a, 1}, {:b, 2} ] }
When passing the __ENV__'s file and line, we could simply get
the location which already returns both fields as a keywords lists:
Code.eval_quoted contents, [a: 1, b: 2], __ENV__.location
#=> { 3, [ {:a, 1}, {:b, 2} ] }
"""
def eval_quoted(quoted, binding // [], opts // []) do
{ value, binding, _scope } =
Erlang.elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
@doc """
Converts the given string to AST. It returns { :ok, ast }
if it succeeds, { :error, { line, error, token } } otherwise.
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
"""
def string_to_ast(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.raw_forms(:unicode.characters_to_list(string), line, file)
case res do
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
_ -> res
end
end
@doc """
Converts the given string to AST. It returns the ast if it succeeds,
raises an exception otherwise. The exception is a TokenMissingError
in case a token is missing (usually because the expression is incomplete),
SyntaxError otherwise.
## Options
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
"""
def string_to_ast!(string, opts // []) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file)
unpack_ast(line, res)
end
defp unpack_ast(_line, []), do: nil
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
defp unpack_ast(line, forms), do: { :__block__, line, forms }
@doc """
Loads the given `file`. Accepts `relative_to` as an argument to tell
where the file is located. If the file was already required/loaded,
loads it again. It returns the full path of the loaded file.
When loading a file, you may skip passing .exs as extension as Elixir
automatically adds it for you.
"""
def load_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
server_call { :loaded, file }
Erlang.elixir_compiler.file file
file
end
@doc """
Requires the given `file`. Accepts `relative_to` as an argument to tell
where the file is located. If the file was already required/loaded,
returns nil, otherwise the full path of the loaded file.
When requiring a file, you may skip passing .exs as extension as
Elixir automatically adds it for you.
"""
def require_file(file, relative_to // nil) when is_binary(file) do
file = find_file(file, relative_to)
case server_call({ :loaded, file }) do
:ok -> Erlang.elixir_compiler.file file
:duplicated -> []
end
end
@doc """
Loads the compilation options from the code server.
Check compiler_options/1 for more information.
"""
def compiler_options do
server_call :compiler_options
end
@doc """
Sets compilation options. Those options are global
since they are stored by Elixir's Code Server.
Available options are:
* docs - when true, retain documentation in the compiled module.
True by default;
* debug_info - when true, retain debug information in the compiled module.
This allows a developer to reconstruct the original source
code, for such reasons, false by default;
* ignore_module_conflict - when true, override modules that were already defined
without raising errors, false by default;
"""
def compiler_options(opts) do
server_call { :compiler_options, opts }
end
@doc """
Compiles the given string and returns a list of tuples where
the first element is the module name and the second one is its
binary.
For compiling many files at once, check `Kernel.ParallelCompiler`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
Erlang.elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
end
@doc """
Ensures the given module is loaded. If the module is already
loaded, it works as no-op. If the module was not loaded yet,
it tries to load it.
If it succeeds loading the module anyhow, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
## Code loading on the Erlang VM
Erlang has two modes to load code: interactive and embedded.
By default, the Erlang VM runs on interactive mode, where modules
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
Therefore, this function is useful to check if a module is loaded
before using it and react accordingly. For example, the `URI` module
uses this function to check if a specific parser exists and is for the
given URI scheme.
## Code.ensure_compiled
Elixir also contains an `ensure_compiled/1` function that is a
superset of `ensure_loaded/1`.
Since Elixir's compilation happens in parallel, in some situations
you may need to use a module but it was not compiled yet, therefore
it can't even be loaded.
`ensure_compiled/1` puts a halt in the current process until the
module we are depending on is available.
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
must be used just in same rare conditions, usually involving macros
that needs to invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
Erlang.code.ensure_loaded(module)
end
@doc """
Ensures the given module is compiled and loaded. If the module
is already loaded, it works as no-op. If the module was not
loaded yet, it checks if it needs to be compiled first and just
then tries to load it.
If it succeeds loading the module anyhow, it returns
`{ :module, module }`. If not, returns `{ :error, reason }` with
the error reason.
Check `ensure_loaded/1` for more information on module loading
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
"""
def ensure_compiled(module) when is_atom(module) do
case Erlang.code.ensure_loaded(module) do
{ :error, :nofile } = error ->
case :erlang.get(:elixir_compiler_pid) do
:undefined -> error
_ ->
try do
module.__info__(:self)
{ :module, module }
rescue
UndefinedFunctionError -> error
end
end
other -> other
end
end
## Helpers
# Finds the file given the relative_to path.
# If the file is found, returns its path in binary, fails otherwise.
defp find_file(file, relative_to) do
file = to_binary(file)
file = if relative_to do
File.expand_path(file, relative_to)
else
File.expand_path(file)
end
if File.regular?(file) do
file
else
prefix = "#{file}.exs"
if File.regular?(prefix) do
prefix
else
raise ArgumentError, message: "could not load #{file}"
end
end
end
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
end
end
-152
View File
@@ -1,152 +0,0 @@
defprotocol Dict do
@only [Record]
@moduledoc """
This module provides the Dict protocol
with the goal of being a common API
to work with dictionaries.
"""
@doc """
Returns a list containing all dict's keys.
The keys are not guaranteed to be sorted, unless
the underlying dict implementation defines so.
## Examples
Dict.keys [a: 1, b: 2] #=> [:a,:b]
"""
def keys(dict)
@doc """
Returns a list containing all dict's values.
## Examples
Dict.values [a: 1, b: 2] #=> [1,2]
"""
def values(dict)
@doc """
Returns the number of elements in `dict`.
## Examples
Dict.size [a: 1, b: 2] #=> 2
"""
def size(dict)
@doc """
Returns whether the given key exists in the given dict.
## Examples
Dict.has_key?([a: 1], :a) #=> true
Dict.has_key?([a: 1], :b) #=> false
"""
def has_key?(dict, key)
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, returns `default` (or nil if not provided).
## Examples
Dict.get [a: 1], :a #=> 1
Dict.get [a: 1], :b #=> nil
Dict.get [a: 1], :b, 3 #=> 3
"""
def get(dict, key)
def get(dict, key, default)
@doc """
Stores the given `value` under `key` in `dict`.
If `dict` already has `key`, the stored value is replaced by the new one.
## Examples
Dict.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
"""
def put(dict, key, val)
@doc """
Removes the entry stored under the given key from `dict`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
Dict.delete [a: 1, b: 2], :a #=> [b: 2]
Dict.delete [b: 2], :a #=> [b: 2]
"""
def delete(dict, key)
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the one
given as second argument wins.
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
"""
def merge(dict1, dict2)
@doc """
Merges two dicts into one. If the dicts have duplicated entries, the given
function is invoked to solve conflicts.
## Examples
Dict.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
v1 + v2
end
#=> [a: 4, b: 2, d: 4]
"""
def merge(dict1, dict2, fun)
@doc """
Update a value in `dict` by calling `fun` on the value to get a new
value. An exception is generated if `key` is not present in the dict.
## Examples
Dict.update [a: 1, b: 2], :a, fn val -> -val end
#=> [a: -1, b: 2]
"""
def update(dict, key, fun)
@doc """
Update a value in `dict` by calling `fun` on the value to get a new value. If
`key` is not present in `dict` then `initial` will be stored as the first
value.
## Examples
Dict.update [a: 1, b: 2], :c, 3, fn val -> -val end
#=> [a: 1, b: 2, c: 3]
"""
def update(dict, key, initial, fun)
@doc """
Returns an empty dict of the same type as `dict`.
"""
def empty(dict)
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
def to_list(dict)
end
-45
View File
@@ -1,45 +0,0 @@
defmodule Dict.Common do
@moduledoc false
defmacro __using__(ref) do
quote do
@doc """
Creates a new empty dict.
"""
def new do
unquote(ref).empty(nil)
end
@doc """
Creates a new dict from a list of pairs.
## Examples
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
#=> [a: 1, b: 2]
"""
def new(pairs) do
Enum.reduce pairs, new, fn { k, v }, dict ->
unquote(ref).put(dict, k, v)
end
end
@doc """
Creates a new dict from a list of elements with the
help of the transformation function.
## Examples
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
#=> ["a": "a", "b": "b"]
"""
def new(list, transform) when is_function(transform) do
Enum.reduce list, new(), fn i, dict ->
{ k, v } = transform.(i)
unquote(ref).put(dict, k, v)
end
end
end
end
end
File diff suppressed because it is too large Load Diff
-213
View File
@@ -1,213 +0,0 @@
defmodule Exception do
@moduledoc """
Several convenience functions to work and pretty print
exceptions and backtraces.
"""
# Normalize an exception converting Erlang exceptions
# to Elixir style exceptions. This is meant to be used
# internally.
@doc false
def normalize(exception) when is_exception(exception) do
exception
end
def normalize(:badarg) do
ArgumentError.new
end
def normalize(:badarith) do
ArithmeticError.new
end
def normalize(:system_limit) do
SystemLimitError.new
end
def normalize({ :badarity, { fun, args } }) do
BadArityError.new(function: fun, args: args)
end
def normalize({ :badfun, actual }) do
BadFunctionError.new(actual: actual)
end
def normalize({ :badmatch, actual }) do
MatchError.new(actual: actual)
end
def normalize({ :case_clause, actual }) do
CaseClauseError.new(actual: actual)
end
def normalize(:undef) do
UndefinedFunctionError.new from_stacktrace(System.stacktrace)
end
def normalize(:function_clause) do
FunctionClauseError.new from_stacktrace(System.stacktrace)
end
def normalize({ :badarg, payload }) do
ArgumentError.new message: "argument error: #{inspect(payload)}"
end
def normalize(other) do
ErlangError.new original: other
end
# Check the given module is a valid record.
@doc false
def check!(module) do
unless :erlang.function_exported(module, :message, 1) do
raise "Expected #{inspect module} to implement message/1"
end
end
@doc """
Receives a module, fun and arity and returns a string
representing such invocation. Arity may also be a list
of arguments. It follows the same syntax as in stacktraces.
"""
def format_module_fun_arity(module, fun, arity) do
<< ?:, fun | :binary >> = inspect(fun)
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect module}.#{fun}/#{arity}"
end
end
@doc """
Formats each line in the stacktrace.
"""
def format_stacktrace({module, fun, arity, file_line}) do
"#{format_file_line(file_line)}#{format_module_fun_arity(module, fun, arity)}"
end
@doc """
Formats file and line information present in stacktraces.
Expect them to be given in a keywords list.
"""
def format_file_line(file_line) do
format_file_line(Keyword.get(file_line, :file), Keyword.get(file_line, :line))
end
@doc """
Formats the given file and line.
"""
def format_file_line(file, line) do
if file do
file = to_binary(file)
if line && line != 0 do
"#{file}:#{line}: "
else
"#{file}: "
end
else
""
end
end
## Helpers
defp from_stacktrace([{ module, function, arity, _ }|_]) do
[module: module, function: function, arity: arity]
end
defp from_stacktrace(_), do: []
end
defexception RuntimeError, message: "runtime error"
defexception ArgumentError, message: "argument error"
defexception ArithmeticError, message: "bad argument in arithmetic expression"
defexception SystemLimitError, message: "a system limit has been reached"
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
def message(exception) do
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
end
end
defexception BadFunctionError, [actual: nil] do
def message(exception) do
"bad function: #{inspect(exception.actual)}"
end
end
defexception MatchError, [actual: nil] do
def message(exception) do
"no match of right hand side value: #{inspect(exception.actual)}"
end
end
defexception CaseClauseError, [actual: nil] do
def message(exception) do
"no case clause matching: #{inspect(exception.actual)}"
end
end
defexception BadArityError, [function: nil, args: nil] do
def message(exception) do
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
end
end
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, to_arity(exception.arity)
"undefined function: #{formatted}"
else
"undefined function"
end
end
defp to_arity(arity) when is_integer(arity), do: arity
defp to_arity(list) when is_list(list), do: length(list)
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
"no function clause matching: #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
def message(exception) do
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception Keyword.KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
File diff suppressed because it is too large Load Diff
-48
View File
@@ -1,48 +0,0 @@
defmodule GenServer.Behavior do
@doc """
By using this module, you get default GenServer callbacks
for `handle_call`, `handle_info`, `handle_cast`, `terminate`
and `code_change`. `init` still needs to be implemented by the
developer. Since these functions are defined as overridable,
they can be partially customized and have a global clause
that simply invokes `super`. See `ExUnit.Server` for some
code examples.
This module also tags the behavior as :gen_server. For more
information on gen_server, please refer to the Erlang
documentation:
http://www.erlang.org/doc/man/gen_server.html
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
"""
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
def handle_call(_request, _from, state) do
{ :reply, :undef, state }
end
def handle_info(_msg, state) do
{ :noreply, state }
end
def handle_cast(_msg, state) do
{ :noreply, state }
end
def terminate(reason, state) do
:error_logger.error_report('#{inspect __MODULE__} crashed:\n#{inspect reason}')
:error_logger.error_report('#{inspect __MODULE__} snapshot:\n#{inspect state}')
:ok
end
def code_change(_old, state, _extra) do
{ :ok, state }
end
defoverridable [handle_call: 3, handle_info: 2, handle_cast: 2, terminate: 2, code_change: 3]
end
end
end
-81
View File
@@ -1,81 +0,0 @@
defimpl Dict, for: HashDict do
defmacrop dict(data) do
quote do
{ HashDict, unquote(data) }
end
end
def keys(dict(data)) do
:dict.fetch_keys data
end
def values(dict(data)) do
:dict.fold fn _key, value, acc ->
[value|acc]
end, [], data
end
def size(dict(data)) do
:dict.size data
end
def has_key?(dict(data), key) do
:dict.is_key key, data
end
def get(dict(data), key, default // nil) do
case :dict.find(key, data) do
{:ok, value} ->
value
:error ->
default
end
end
def put(dict(data), key, value) do
dict(:dict.store key, value, data)
end
def delete(dict(data), key) do
dict(:dict.erase key, data)
end
def merge(dict(d1), dict(d2)) do
dict(:dict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
end
def merge(dict(d1), dict(d2), fun) do
dict(:dict.merge fun, d1, d2)
end
def update(dict(data), key, fun) do
dict(:dict.update key, fun, data)
end
def update(dict(data), key, initial, fun) do
dict(:dict.update key, fun, initial, data)
end
def empty(_) do
dict(:dict.new)
end
def to_list(dict(data)) do
:dict.to_list data
end
end
defimpl Enum.Iterator, for: HashDict do
def iterator({ HashDict, data }), do: :dict.to_list(data)
def count({ HashDict, data }), do: :dict.size(data)
end
defmodule HashDict do
@moduledoc """
This module implements a dictionary based on hashing of the keys.
It is a simple wrapper around [Erlang's dict module](http://www.erlang.org/doc/man/dict.html)
and exposed via the `Dict` protocol.
"""
use Dict.Common, Dict.HashDict
end
-212
View File
@@ -1,212 +0,0 @@
defrecord IEx.Config, io: nil, binding: nil, cache: '', counter: 1, scope: nil, result: nil
defmodule IEx do
@moduledoc """
This module implements interactive Elixir. It provides a main
function, `start` which will either delegate to `tty` or `simple`.
The former is meant for systems where tty is available and relies
on it in order to work properly. This makes all control commands
available in tty available to the developer.
In case `tty` is not available (for example, Windows), a
developer may invoke `simple` which starts a stripped
down version.
"""
import Exception, only: [format_stacktrace: 1]
@doc """
Interface to start IEx from CLI.
"""
def cli do
run([remsh: get_remsh(:init.get_plain_arguments)])
end
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
defp get_remsh([_|t]), do: get_remsh(t)
defp get_remsh([]), do: nil
@doc """
Runs IEx checking if tty is available or not.
If so, invoke tty, otherwise go with the simple iex.
"""
def run(opts // []) when is_list(opts) do
case :os.type do
{ :unix, _ } -> tty(opts)
_ -> simple(opts)
end
end
@doc """
Starts IEx using a tty server.
"""
def tty(opts // []) when is_list(opts) do
config = boot_config(opts)
remote =
if remsh = opts[:remsh] do
if node() == :nonode@nohost do
raise ArgumentError, message: "In order to use --remsh, you need to name the node"
end
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
end
function = fn ->
# We are inside the new tty and in a new process,
# reattach it the error logger.
attach_error_logger
start config
end
# Dettach the error logger because we are going to unregister
# the user process and start a new tty which will get control
# over the standardio. Dettaching it here allows us to get rid
# of warnings. We reattach it again when we get the new tty.
dettach_error_logger
# Unregister the user process, user_drv command below
# will register the new one.
unregister_user_process
# Close the default io port, user_drv start command below
# will take control over the io.
close_io_port
args =
if remote do
{ remote, :erlang, :apply, [function, []] }
else
{ :erlang, :apply, [function, []] }
end
Erlang.user_drv.start([:"tty_sl -c -e", args])
end
@doc """
Starts IEx simply using the current stdio.
"""
def simple(opts // []) when is_list(opts) do
start boot_config(opts)
end
# This is a callback invoked by Erlang shell utilities.
@doc false
def start(config // nil) do
spawn fn ->
config = config || boot_config([])
gl = :erlang.group_leader
glnode = node gl
if glnode != node do
ensure_module_exists glnode, IEx.Remsh
expand_fun = IEx.Remsh.expand node
else
expand_fun = IEx.Autocomplete.expand &1
end
:io.setopts gl, [expand_fun: expand_fun]
start_loop(config)
end
end
## Boot Helpers
defp boot_config(opts) do
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit"
scope = Erlang.elixir.scope_for_eval(
file: 'iex',
delegate_locals_to: IEx.Helpers
)
IEx.Config[
io: opts[:io] || IEx.UnicodeIO,
binding: opts[:binding] || [],
scope: scope
]
end
defp dettach_error_logger do
:error_logger.delete_report_handler(:error_logger_tty_h)
end
defp attach_error_logger do
:error_logger.add_report_handler(:error_logger_tty_h)
end
defp unregister_user_process do
if is_pid(Process.whereis(:user)), do: Process.unregister :user
end
defp close_io_port do
if port = Enum.find(Port.list, io_port?(&1)) do
Port.close(port)
end
end
defp io_port?(port) do
Port.info(port, :name) == {:name,'0/1'} && port
end
## Loop helpers
defp start_loop(config) do
Process.put :iex_history, []
{ _, _, scope } = Erlang.elixir.eval('import IEx.Helpers', [], 0, config.scope)
do_loop(config.scope(scope))
end
defp do_loop(config) do
io = config.io
counter = config.counter
cache = config.cache
code = cache ++ io.get(config)
new_config =
try do
{ result, new_binding, scope } =
Erlang.elixir.eval(code, config.binding, counter, config.scope)
io.put result
config = config.result(result)
update_history(config.cache(code).scope(nil))
config.increment_counter.cache('').binding(new_binding).scope(scope)
rescue
TokenMissingError ->
config.cache(code)
exception ->
stacktrace = System.stacktrace
io.error "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace io, stacktrace
config.cache('')
catch
kind, error ->
stacktrace = System.stacktrace
io.error "** (#{kind}) #{inspect(error)}"
print_stacktrace io, stacktrace
config.cache('')
end
do_loop(new_config)
end
defp update_history(config) do
current = Process.get :iex_history
Process.put :iex_history, [config|current]
end
defp print_stacktrace(io, stacktrace) do
Enum.each stacktrace, fn s -> io.error " #{format_stacktrace(s)}" end
end
## Code injection helper
defp ensure_module_exists(node, mod) do
unless :rpc.call node, :code, :is_loaded, [mod] do
{m,b,f} = :code.get_object_code mod
{:module, mod} = :rpc.call node, :code, :load_binary, [m,f,b]
end
end
end
-266
View File
@@ -1,266 +0,0 @@
defmodule IEx.Autocomplete do
@moduledoc """
Autocompletion for the Elixir shell.
"""
defrecord Mod, name: nil, type: nil
defrecord Fun, name: nil, arities: []
defprotocol Entry do
@moduledoc false
def to_entries(entry)
def to_hint(entry, hint)
end
defimpl Entry, for: Mod do
def to_entries(mod) do
[mod.name]
end
def to_hint(Mod[name: name], hint) do
:lists.nthtail(length(hint), name) ++ '.'
end
end
defimpl Entry, for: Fun do
def to_entries(fun) do
lc a inlist fun.arities, do: '#{fun.name}/#{a}'
end
def to_hint(Fun[name: name], hint) do
:lists.nthtail(length(hint), name)
end
end
def expand([]) do
funs = module_funs(IEx.Helpers) ++ module_funs(Kernel)
mods = [Mod[name: 'Elixir', type: :elixir], Mod[name: 'Erlang', type: :elixir]]
format_expansion mods ++ funs
end
def expand([h|t]=expr) do
cond do
h === ?. ->
expand_dot reduce(t)
h === ?: ->
expand_erlang_modules
(h in ?a..?z) or (h in ?A..?Z) or h === ?_ ->
expand_expr reduce(expr)
h in '(+[' ->
expand ''
true ->
no_match
end
end
defp expand_dot(expr) do
case Code.string_to_ast expr do
{:ok, atom} when is_atom(atom) ->
expand_module_funs atom
{:ok, {:__aliases__,_,[:Erlang]}} ->
expand_erlang_modules
{:ok, {:__aliases__,_,list}} ->
expand_elixir_modules list
{:ok, {{:.,_,[{:__aliases__,_,[:Erlang]},mod]},_,[]}} when is_atom(mod) ->
expand_module_funs mod
_ ->
no_match
end
end
defp expand_expr(expr) do
case Code.string_to_ast expr do
{:ok, atom} when is_atom(atom) ->
expand_erlang_modules atom_to_list(atom)
{:ok, { atom, _, nil }} when is_atom(atom) ->
expand_module_funs Kernel, atom_to_list(atom)
{:ok, {:__aliases__,_,[root]}} ->
expand_elixir_modules [], atom_to_list(root)
{:ok, {:__aliases__,_,list}} ->
hint = atom_to_list(List.last(list))
list = :lists.sublist(list, length(list)-1)
expand_elixir_modules list, hint
{:ok, {{:., _, [mod,fun]},_,[]}} when is_atom(fun) ->
expand_call mod, atom_to_list(fun)
_ -> no_match
end
end
defp reduce(expr) do
last_token(List.reverse(expr), [' ', '(', '[', '+', '-'])
end
defp last_token(s, []) do
s
end
defp last_token(s, [h|t]) do
last_token(List.last(:string.tokens(s, h)), t)
end
defp no_match, do: { :no, '', [] }
## Formatting
defp format_expansion(list, hint // '')
defp format_expansion([], _) do
no_match
end
defp format_expansion([uniq], hint) do
{ :yes, Entry.to_hint(uniq, hint), [] }
end
defp format_expansion([first|_]=entries, hint) do
binary = Enum.map entries, fn e -> list_to_binary(e.name) end
length = length hint
prefix = :binary.longest_common_prefix(binary)
if prefix == 0 or (prefix == length) do
{:yes, '',
Enum.reduce entries, [], fn e, acc -> Entry.to_entries(e) ++ acc end }
else
{:yes, :lists.sublist(first.name, 1 + length, prefix-length), [] }
end
end
## Root Modules
defp root_modules do
Enum.reduce :code.all_loaded, [], fn {m,_}, acc ->
mod = atom_to_list(m)
case mod do
'Elixir' ++ _ ->
tokens = :string.tokens(mod, '-')
if length(tokens) === 2 do
[Mod.new(name: List.last(tokens), type: :elixir)|acc]
else
acc
end
_ ->
[Mod.new(name: mod, type: :erlang)|acc]
end
end
end
## Expand calls
# :atom.fun
defp expand_call(mod, hint) when is_atom(mod) do
expand_module_funs mod, hint
end
# Erlang.mod.fun
defp expand_call({ { :., _, [{ :__aliases__, _, [:Erlang] }, mod] }, _, [] }, hint) when is_atom(mod) do
expand_module_funs mod, hint
end
# Erlang.mod
defp expand_call({ :__aliases__, _, [:Erlang] }, hint) do
expand_erlang_modules hint
end
# Elixir.fun
defp expand_call({ :__aliases__, _, list }, hint) do
expand_module_funs Module.concat(list), hint
end
defp expand_call(_, _) do
no_match
end
## Erlang modules
defp expand_erlang_modules(hint // '') do
format_expansion match_erlang_modules(hint), hint
end
defp match_erlang_modules('') do
Enum.filter root_modules, fn m -> m.type === :erlang end
end
defp match_erlang_modules(hint) do
Enum.filter root_modules, fn m -> :lists.prefix(hint, m.name) end
end
## Elixir modules
defp expand_elixir_modules(list, hint // '') do
mod = Module.concat(list)
format_expansion elixir_submodules(mod, hint, list == []) ++ module_funs(mod, hint), hint
end
defp elixir_submodules(mod, hint, root) do
modname = atom_to_list(mod)
depth = length(:string.tokens(modname, '-')) + 1
base = modname ++ [?-|hint]
Enum.reduce modules_as_lists(root), [], fn(m, acc) ->
if :lists.prefix(base, m) do
tokens = :string.tokens(m, '-')
if length(tokens) == depth do
name = List.last(tokens)
[Mod.new(type: :elixir, name: name)|acc]
else
acc
end
else
acc
end
end
end
defp modules_as_lists(true) do
['Elixir-Elixir', 'Elixir-Erlang'] ++ modules_as_lists(false)
end
defp modules_as_lists(false) do
Enum.map(:code.all_loaded, fn({ m, _ }) -> atom_to_list(m) end)
end
## Functions
defp expand_module_funs(mod, hint // '') do
format_expansion module_funs(mod, hint), hint
end
defp module_funs(mod, hint // '') do
case ensure_loaded(mod) do
{ :module, _ } ->
falist = get_funs(mod)
list = Enum.reduce falist, [], fn {f,a}, acc ->
case :lists.keyfind(f, 1, acc) do
{f,aa} -> :lists.keyreplace(f, 1, acc, {f, [a|aa]})
false -> [{f, [a]}|acc]
end
end
lc {fun, arities} inlist list, name = atom_to_list(fun), is_prefix?(hint, name) do
Fun[name: name, arities: arities]
end
_ ->
[]
end
end
## Generic Helpers
defp get_funs(mod) do
if function_exported?(mod, :__info__, 1) do
if docs = mod.__info__(:docs) do
lc { pair, _line, _kind, _sign, doc } inlist docs, doc != false, do: pair
else
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
end
else
mod.module_info(:exports)
end
end
defp is_prefix?('', _), do: true
defp is_prefix?(hint, name), do: :lists.prefix(hint, name)
defp ensure_loaded(Elixir), do: { :error, :nofile }
defp ensure_loaded(mod), do: Code.ensure_loaded(mod)
end
-244
View File
@@ -1,244 +0,0 @@
defmodule IEx.Helpers do
@moduledoc """
A bunch of helpers available in IEx.
* `c` - compiles a file in the given path
* `d` - prints documentation
* `h` - prints history
* `m` - prints loaded modules
* `r` - recompiles and reloads the given module's source file
* `v` - retrieves nth value from console
Documentation for functions in this module can be consulted
directly from the command line, as an example, try:
d(:c, 1)
"""
@doc """
Expects a list of files to compile and a path
to write their object code to. It returns the name
of the compiled modules.
## Examples
c ["foo.ex"], "ebin"
#=> Foo
"""
def c(files, path // ".") do
tuples = Kernel.ParallelCompiler.files_to_path List.wrap(files), path
Enum.map tuples, elem(&1, 1)
end
@doc """
Returns the name and module of all modules loaded.
"""
def m do
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
sorted = List.sort(all)
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
format = "~-#{size}s ~s~n"
Enum.each sorted, fn({ mod, file }) ->
:io.format(format, [mod, file])
end
end
@doc """
Prints commands history and their result.
"""
def h do
history = List.reverse(Process.get(:iex_history))
Enum.each(history, print_history(&1))
end
defp print_history(config) do
IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
end
@doc """
Shows the documentation for IEx.Helpers.
"""
def d() do
d(IEx.Helpers, :all)
end
@doc """
Shows the documentation for the given module
or for the given function/arity pair.
## Examples
d(Enum)
#=> Prints documentation for Enum
It also accepts functions in the format `fun/arity`
and `module.fun/arity`, for example:
d receive/1
d Enum.all?/2
"""
defmacro d({ :/, _, [{ fun, _, nil }, arity] }) do
quote do
d(unquote(fun), unquote(arity))
end
end
defmacro d({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
quote do
d(unquote(mod), unquote(fun), unquote(arity))
end
end
defmacro d(other) do
quote do
d(unquote(other), :all)
end
end
@doc """
Prints the documentation for the given function and arity.
The function may either be a function defined inside `IEx.Helpers`
or in `Kernel`. To see functions from other module, use
`d/3` instead.
## Examples
d(:d, 2)
#=> Prints documentation for this function
"""
def d(:d, 1) do
d(__MODULE__, :d, 1)
end
def d(function, arity) when is_atom(function) and is_integer(arity) do
if function_exported?(__MODULE__, function, arity) do
d(__MODULE__, function, arity)
else
d(Kernel, function, arity)
end
end
def d(module, :all) when is_atom(module) do
case Code.ensure_loaded(module) do
{ :module, _ } ->
case module.__info__(:moduledoc) do
{ _, binary } when is_binary(binary) ->
IO.puts "# #{inspect module}\n"
IO.write binary
{ _, _ } ->
IO.puts "No docs for #{inspect module}"
_ ->
IO.puts "#{inspect module} was not compiled with docs"
end
{ :error, reason } ->
IO.puts "Could not load module #{inspect module}: #{reason}"
end
end
@doc """
Shows the documentation for the `function/arity` in `module`.
"""
def d(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
if docs = module.__info__(:docs) do
doc =
if tuple = List.keyfind(docs, { function, arity }, 1) do
print_signature(tuple)
end
if doc do
IO.write "\n" <> doc
else
IO.puts "No docs for #{function}/#{arity}"
end
else
IO.puts "#{inspect module} was not compiled with docs"
end
end
# Get the full signature from a function.
defp print_signature({ _info, _line, _kind, _args, false }) do
false
end
defp print_signature({ { name, _arity }, _line, kind, args, docs }) do
args = Enum.map_join(args, ", ", signature_arg(&1))
IO.puts "* #{kind} #{name}(#{args})"
docs
end
defp signature_arg({ ://, _, [left, right] }) do
signature_arg(left) <> " // " <> Macro.to_binary(right)
end
defp signature_arg({ var, _, _ }) do
atom_to_binary(var)
end
@doc """
Retrieves nth query's value from the history. Use negative
values to lookup query's value from latest to earliest.
For instance, v(-1) returns the latest result.
"""
def v(n) when n < 0 do
history = Process.get(:iex_history)
Enum.nth!(history, abs(n)).result
end
def v(n) do
history = Process.get(:iex_history) /> List.reverse
Enum.nth!(history, n).result
end
@doc """
Reloads all modules that were already reloaded
at some point with `r/1`.
"""
def r do
Enum.map iex_reloaded, r(&1)
end
@doc """
Recompiles and reloads the specified module's source file.
Please note that all the modules defined in the specified
files are recompiled and reloaded.
"""
def r(module) do
if source = source(module) do
Process.put(:iex_reloaded, :ordsets.add_element(module, iex_reloaded))
{ module, Code.load_file source }
else
:nosource
end
end
defp iex_reloaded do
Process.get(:iex_reloaded) || :ordsets.new
end
defp source(module) do
compile = module.module_info(:compile)
# Get the source of the compiled module. Due to a bug in Erlang
# R15 and before, we need to look for the source first in the
# options and then into the real source.
options =
case List.keyfind(compile, :options, 1) do
{ :options, opts } -> opts
_ -> []
end
source = List.keyfind(options, :source, 1) || List.keyfind(compile, :source, 1)
case source do
{ :source, source } -> list_to_binary(source)
_ -> nil
end
end
end
-24
View File
@@ -1,24 +0,0 @@
defmodule IEx.Remsh do
@moduledoc """
Helper function injected into connecting remote nodes
to properly handle autocompletion. Elixir supports:
* remsh from an elixir node to an elixir node
* remsh from a plain erlang node to an elixir node (through the ^G menu)
* remsh from an elixir node to a plain erlang node (and get an erl shell there)
In order to get an Elixir shell from the ^G menu,
you need to use 'Elixir-IEx' as the shell name.
Connecting an Elixir shell to a remote node without
Elixir is **not** supported.
"""
def expand(node) do
fn e ->
case :rpc.call node, Elixir.IEx.Autocomplete, :expand, [e] do
{:badrpc, _} -> {:no, '', []}
r -> r
end
end
end
end
-45
View File
@@ -1,45 +0,0 @@
defmodule IEx.UnicodeIO do
@moduledoc """
This module implements the API used by IEX to
interact with the console. This API may change
in the future without warnings.
"""
@doc """
Implements the get IO API used by IEx. It receives the
code cache, the instructions counter and needs to
return a list with the new characters inserted.
"""
def get(config) do
prefix = case config.cache do
[] -> "iex"
_ -> "..."
end
prompt = case node do
:nonode@nohost ->
"#{prefix}(#{config.counter})> "
n ->
"#{prefix}(#{n})#{config.counter}> "
end
case IO.gets(prompt) do
{ :error, _ } -> ''
data -> :unicode.characters_to_list(data)
end
end
@doc """
Implements the put IO API used by IEx. It receives the
result and prints it.
"""
def put(result) do
IO.inspect result
end
@doc """
Implements the error IO API used by IEx. It prints error
messages.
"""
def error(result) do
IO.puts result
end
end
-135
View File
@@ -1,135 +0,0 @@
defmodule IO do
@moduledoc """
Module responsible for doing IO. The function in this
module expects an iodata as argument encoded in UTF-8.
An iodata can be:
* A list of integers representing a string. Any unicode
character must be represented with one entry in the list,
this entry being an integer with the codepoint value;
* A binary in which unicode characters are represented
with many bytes (Elixir's default representation);
* A list of binaries or a list of char lists (as described above);
* If none of the above, `to_binary` is invoked in the
given argument;
"""
@doc """
Reads `count` bytes from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def read(device // :stdio, count) do
Erlang.io.get_chars(map_dev(device), "", count)
end
@doc """
Read a line from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
This function does the same as `gets/2`,
except the prompt is not required as argument.
"""
def readline(device // :stdio) do
Erlang.io.get_line(map_dev(device), "")
end
@doc """
Writes the given argument to the given device.
By default the device is the standard output.
The argument is expected to be a chardata (i.e.
a char list or an unicode binary).
It returns `:ok` if it succeeds.
## Examples
IO.write "sample"
#=> "sample"
IO.write :stderr, "error"
#=> "error"
"""
def write(device // :stdio, item) do
Erlang.io.put_chars map_dev(device), to_iodata(item)
end
def print(device // :stdio, item) do
IO.puts "IO.print is deprecated in favor of IO.write"
Erlang.io.put_chars map_dev(device), to_iodata(item)
end
@doc """
Writes the argument to the device, similarly to write
but adds a new line at the end. The argument is expected
to be a chardata.
"""
def puts(device // :stdio, item) do
erl_dev = map_dev(device)
Erlang.io.put_chars erl_dev, to_iodata(item)
Erlang.io.nl(erl_dev)
end
@doc """
Inspects and writes the given argument to the device
followed by a new line. Returns the item given.
"""
def inspect(device // :stdio, item) do
puts device, Kernel.inspect(item)
item
end
@doc """
Gets `count` bytes from the IO device. It returns:
* `data` - The input characters.
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def getb(device // :stdio, prompt, count // 1) do
Erlang.io.get_chars(map_dev(device), to_iodata(prompt), count)
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The characters in the line terminated
by a LF (or end of file).
* :eof - End of file was encountered.
* {:error, reason} - Other (rare) error condition,
for instance {:error, :estale} if reading from an
NFS file system.
"""
def gets(device // :stdio, prompt) do
Erlang.io.get_line(map_dev(device), to_iodata(prompt))
end
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_iodata(io) when is_list(io) or is_binary(io), do: io
defp to_iodata(other), do: to_binary(other)
end
File diff suppressed because it is too large Load Diff
-230
View File
@@ -1,230 +0,0 @@
defrecord Kernel.CLI.Config, commands: [], output: ".",
compile: [], halt: true, compiler_options: []
defmodule Kernel.CLI do
@moduledoc false
import Exception, only: [format_stacktrace: 1]
# Invoked directly from erlang boot process. It parses all argv
# options and execute them in the order they are specified.
def process_argv(options) do
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
argv = lc arg inlist argv, do: list_to_binary(arg)
Erlang.gen_server.call(:elixir_code_server, { :argv, argv })
all_commands = List.reverse(config.commands)
try do
Enum.map all_commands, process_command(&1, config)
if config.halt do
at_exit(0)
halt(0)
end
rescue
exception ->
at_exit(1)
stacktrace = System.stacktrace
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(stacktrace)
halt(1)
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
halt(reason)
:exit, :normal ->
at_exit(0)
halt(0)
kind, reason ->
at_exit(1)
stacktrace = System.stacktrace
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
print_stacktrace(stacktrace)
halt(1)
end
end
## Private
defp at_exit(status) do
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
lc hook inlist hooks do
try do
hook.(status)
rescue
exception ->
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
print_stacktrace(System.stacktrace)
catch
kind, reason ->
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
print_stacktrace(System.stacktrace)
end
end
end
defp invalid_option(option) do
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
halt(1)
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|t], _ } when h == hd(list) ->
invalid_option h
{ new_list, new_config } ->
callback.(new_list, new_config)
end
end
defp print_stacktrace(stacktrace) do
Enum.each stacktrace, fn s -> IO.puts :stderr, " #{format_stacktrace(s)}" end
end
# Process shared options
defp process_shared(['-v'|t], config) do
IO.puts "Elixir #{System.version}"
process_shared t, config
end
defp process_shared(['--no-halt'|t], config) do
process_shared t, config.halt(false)
end
defp process_shared(['-e',h|t], config) do
process_shared t, config.prepend_commands [eval: h]
end
defp process_shared(['-pa',h|t], config) do
Enum.each File.wildcard(File.expand_path(h)), Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(['-pz',h|t], config) do
Enum.each File.wildcard(File.expand_path(h)), Code.append_path(&1)
process_shared t, config
end
defp process_shared(['-r',h|t], config) do
h = list_to_binary(h)
config = Enum.reduce File.wildcard(h), config, fn path, config ->
config.prepend_commands [require: path]
end
process_shared t, config
end
defp process_shared(['-pr',h|t], config) do
h = list_to_binary(h)
process_shared t, config.prepend_commands [parallel_require: h]
end
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
end
# Process init options
def process_options(['--'|t], config) do
{ config, t }
end
def process_options(['--compile'|t], config) do
process_compiler t, config
end
def process_options(['-S',h|t], config) do
exec = System.find_executable(h)
if exec do
{ config.prepend_commands([require: list_to_binary(exec)]), t }
else
IO.puts(:stderr, "Could not find executable #{h}")
halt(1)
end
end
def process_options([h|t] = list, config) do
case h do
'-' ++ _ ->
shared_option? list, config, process_options(&1, &2)
_ ->
h = list_to_binary(h)
{ config.prepend_commands([require: h]), t }
end
end
def process_options([], config) do
{ config, [] }
end
# Process compiler options
defp process_compiler(['--'|t], config) do
{ config, t }
end
defp process_compiler(['-o',h|t], config) do
process_compiler t, config.output(list_to_binary(h))
end
defp process_compiler(['--no-docs'|t], config) do
process_compiler t, config.merge_compiler_options(docs: false)
end
defp process_compiler(['--debug-info'|t], config) do
process_compiler t, config.merge_compiler_options(debug_info: true)
end
defp process_compiler(['--ignore-module-conflict'|t], config) do
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
end
defp process_compiler([h|t] = list, config) do
case h do
'-' ++ _ ->
shared_option? list, config, process_compiler(&1, &2)
_ ->
h = list_to_binary(h)
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.prepend_compile [pattern]
end
end
defp process_compiler([], config) do
{ config.prepend_commands([compile: config.compile]), [] }
end
# Process commands
defp process_command({:eval, expr}, _config) when is_list(expr) do
Erlang.elixir.eval(expr, [])
end
defp process_command({:require, file}, _config) when is_binary(file) do
Code.require_file(file)
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = File.wildcard(pattern)
files = List.uniq(files)
files = Enum.filter files, File.regular?(&1)
Kernel.ParallelRequire.files(files)
end
defp process_command({:compile, patterns}, config) do
File.mkdir_p(config.output)
files = Enum.map patterns, File.wildcard(&1)
files = List.uniq(List.concat(files))
files = Enum.filter files, File.regular?(&1)
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
fn file -> IO.puts "Compiled #{file}" end)
end
end
-27
View File
@@ -1,27 +0,0 @@
# Implement error_handler pattern for Erlang
# which is integrated with Kernel.ParallelCompiler
defmodule Kernel.ErrorHandler do
@moduledoc false
def undefined_function(module, fun, args) do
ensure_loaded(module)
Erlang.error_handler.undefined_function(module, fun, args)
end
def undefined_lambda(module, fun, args) do
ensure_loaded(module)
Erlang.error_handler.undefined_lambda(module, fun, args)
end
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{ :module, _ } -> []
{ :error, _ } ->
parent = Process.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
receive do
{ :release, ^parent } -> :ok
end
end
end
end
-117
View File
@@ -1,117 +0,0 @@
defmodule Kernel.ParallelCompiler do
alias Erlang.orddict, as: Orddict
@moduledoc """
A module responsible for compiling files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Compiles the given files.
Those files are compiled in parallel and can automatically
detect dependencies between them. Once a dependency is found,
the current file stops being compiled until the dependency is
resolved.
A callback that is invoked every time a file is compiled
with its name can be optionally given as argument.
"""
def files(files, callback // default_callback) do
files_to_path(files, nil, callback)
end
@doc """
Compiles the given files to the given path.
Read files/2 for more information.
"""
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
Code.ensure_loaded(Kernel.ErrorHandler)
spawn_compilers(files, path, callback, [], [], [])
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, output, callback, waiting, queued, result) when
length(queued) - length(waiting) >= 4 do
wait_for_messages(files, output, callback, waiting, queued, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], output, callback, waiting, queued, result) do
parent = self()
child = spawn_link fn ->
Process.put(:elixir_compiler_pid, parent)
Process.flag(:error_handler, Kernel.ErrorHandler)
try do
if output do
Erlang.elixir_compiler.file_to_path(h, output)
else
Erlang.elixir_compiler.file(h)
end
parent <- { :compiled, self(), h }
catch
kind, reason ->
parent <- { :failure, self(), kind, reason, System.stacktrace }
end
end
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _output, _callback, [], [], result), do: result
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], output, callback, waiting, queued, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], output, callback, waiting, queued, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], output, callback, waiting, queued, result) do
wait_for_messages([], output, callback, waiting, queued, result)
end
# Wait for messages from child processes
defp wait_for_messages(files, output, callback, waiting, queued, result) do
receive do
{ :compiled, child, file } ->
callback.(file)
new_queued = List.keydelete(queued, child, 1)
# Sometimes we may have spurious entries in the waiting
# list because someone invoked try/rescue UndefinedFunctionError
new_waiting = List.keydelete(waiting, child, 1)
spawn_compilers(files, output, callback, new_waiting, new_queued, result)
{ :module_available, child, module, binary } ->
new_waiting = release_waiting_processes(module, waiting)
new_result = [{module, binary}|result]
wait_for_messages(files, output, callback, new_waiting, queued, new_result)
{ :waiting, child, on } ->
new_waiting = Orddict.store(child, on, waiting)
spawn_compilers(files, output, callback, new_waiting, queued, result)
{ :failure, child, kind, reason, stacktrace } ->
extra = if match?({^child, module}, List.keyfind(waiting, child, 1)) do
" (undefined module #{inspect module})"
end
{^child, file} = List.keyfind(queued, child, 1)
IO.puts "== Compilation error on file #{file}#{extra} =="
Erlang.erlang.raise(kind, reason, stacktrace)
end
end
# Release waiting processes that are waiting for the given module
defp release_waiting_processes(module, waiting) do
Enum.filter waiting, fn { child, waiting_module } ->
if waiting_module == module do
child <- { :release, self() }
false
else
true
end
end
end
end
-51
View File
@@ -1,51 +0,0 @@
defmodule Kernel.ParallelRequire do
@moduledoc """
A module responsible for requiring files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Requires the given files.
A callback that is invoked every time a file is required
can be optionally given as argument.
"""
def files(files, callback // default_callback) do
spawn_requires(files, [], callback, [])
end
defp spawn_requires([], [], _callback, result), do: result
defp spawn_requires([], waiting, callback, result), do: wait_for_messages([], waiting, callback, result)
defp spawn_requires(files, waiting, callback, result) when length(waiting) >= 4 do
wait_for_messages(files, waiting, callback, result)
end
defp spawn_requires([h|t], waiting, callback, result) do
parent = self
child = spawn_link fn ->
try do
new = Code.require_file(h)
result = new ++ result
callback.(h)
parent <- { :required, self }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
end
end
spawn_requires(t, [child|waiting], callback, result)
end
defp wait_for_messages(files, waiting, callback, result) do
receive do
{ :required, child } ->
spawn_requires(files, List.delete(waiting, child), callback, result)
{ :failure, _child, kind, reason, stacktrace } ->
Erlang.erlang.raise(kind, reason, stacktrace)
end
end
end
-450
View File
@@ -1,450 +0,0 @@
defmodule Kernel.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Those are called
special forms because they cannot be overridden by the developer
and sometimes have lexical scope (like `alias`, `import`, etc).
This module also documents Elixir's pseudo variables (`__MODULE__`,
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
information about Elixir's compilation environment and can only
be read, never assigned to.
Finally, it also documents 3 special forms (`__block__`,
`__scope__` and `__aliases__`), which are not intended to be
called directly by the developer but they appear in quoted
contents since they are essential in Elixir's constructions.
"""
@doc """
Defines a new tuple.
## Examples
:{}.(1,2,3)
{ 1, 2, 3 }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
:[].(1,2,3)
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
## Examples
:<<>>.(1,2,3)
<< 1, 2, 3 >>
"""
defmacro :<<>>.(args)
@doc """
`alias` is used to setup atom aliases, often useful with modules names.
## Examples
`alias` can be used to setup an alias for any module:
defmodule Math do
alias MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be alias
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing Elixir:
Keyword.values #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values
Notice that calling `alias` without the `as:` option automatically
sets an alias based on the last part of the module. For example:
alias Foo.Bar.Baz
Is the same as:
alias Foo.Bar.Baz, as: Baz
## Lexical scope
`import`, `require` and `alias` are called directives and all
have lexical scope. This means you can set up aliases inside
specific functions and it won't affect the overall scope.
"""
defmacro alias(module, opts)
@doc """
`require` is used to require the presence of external
modules so macros can be invoked.
## Examples
Notice that usually modules should not be required before usage,
the only exception is if you want to use the macros from a module.
In such cases, you need to explicitly require them.
Let's suppose you created your own `if` implementation in the module
`MyMacros`. If you want to invoke it, you need to first explicitly
require the `MyMacros`:
defmodule Math do
require MyMacros
MyMacros.if do_something, it_works
end
An attempt to call a macro that was not loaded will raise an error.
## Alias shortcut
`require` also accepts `as:` as an option so it automatically sets
up an alias. Please check `alias` for more information.
"""
defmacro require(module, opts)
@doc """
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
## Examples
If you want to use the `values` function from `Keyword` several times
in your module and you don't want to always type `Keyword.values`,
you can simply import it:
defmodule Math do
import Keyword, only: [values: 1]
def some_function do
# call values(orddict)
end
end
In this case, we are importing only the function `values` (with arity 1)
from `Keyword`. Although `only` is optional, its usage is recommended.
`except` could also be given as an option. If no option is given, all
functions and macros are imported.
In case you want to import only functions or macros, you can pass a
first argument selecting the scope:
import :macros, MyMacros
And you can then use `only` or `except` to filter the macros being
included.
## Lexical scope
It is important to notice that `import` is lexical. This means you
can import specific macros inside specific functions:
defmodule Math do
def some_function do
# 1) Disable `if/2` from Kernel
import Kernel, except: [if: 2]
# 2) Require the new `if` macro from MyMacros
import MyMacros
# 3) Use the new macro
if do_something, it_works
end
end
In the example above, we imported macros from `MyMacros`, replacing
the original `if/2` implementation by our own during that
specific function. All other functions in that module will still
be able to use the original one.
## Alias/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `alias` for more information.
"""
defmacro import(module, opts)
@doc """
Returns the current environment information as a `Macro.Env`
record. In the environment you can access the current filename,
line numbers, set up aliases, the current function and others.
"""
defmacro __ENV__
@doc """
Returns the current module name as an atom or nil otherwise.
Although the module can be accessed in the __ENV__, this macro
is a convenient shortcut.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
Although the file can be accessed in the __ENV__, this macro
is a convenient shortcut.
"""
defmacro __FILE__
@doc """
Allows you to get the representation of any expression.
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, 0, [1, 2, 3] }
## Homoiconicity
Elixir is an homoiconic language. Any Elixir program can be
represented using its own data structures. The building block
of Elixir homoiconicity is a tuple with three elements, for example:
{ :sum, 1, [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, meaning
that it may be a variable.
## Macro literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1,2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
Elixir macros are hygienic regarding to variables. This means
a variable defined in a macro cannot affect the scope where
the macro is included. Consider the following example:
defmodule Hygiene do
defmacro no_interference do
quote do: a = 1
end
end
require Hygiene
a = 10
Hygiene.no_interference
a #=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because the variables defined
in the macro does not affect the context the macro is
executed. If you want to set or get a variable, you can do
it with the help of the `var!` macro:
defmodule NoHygiene do
defmacro interference do
quote do: var!(a) = 1
end
end
require NoHygiene
a = 10
NoHygiene.interference
a #=> 11
Notice that aliases are not hygienic in Elixir, ambiguity
must be solved by prepending Elixir:
quote do
Elixir.Foo #=> Access the root Foo
Foo #=> Access the Foo alias in the current module
(if any is set), then fallback to Elixir.Foo
end
## Options
* `:hygiene` - When false, disables hygiene;
* `:unquote` - When false, disables unquoting. Useful when you have a quote
inside another quote and want to control which quote is able to unquote;
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
Read the Stacktrace information section below for more information;
## Stacktrace information
One of Elixir goals is to provide proper stacktrace whenever there is an
exception. In order to work properly with macros, the default behavior
in quote is to set the line to 0. When a macro is invoked and the quoted
expressions is expanded, 0 is replaced by the line of the call site.
This is a good behavior for the majority of the cases, except if the macro
is defining new functions. Consider this example:
defmodule MyServer do
use GenServer.Behavior
end
`GenServer.Behavior` defines new functions in our `MyServer` module.
However, if there is an exception in any of these functions, we want
the stacktrace to point to the `GenServer.Behavior` and not the line
that calls `use GenServer.Behavior`. For this reason, there is an
option called `:location` that when set to `:keep` keeps these proper
semantics:
quote location: :keep do
def handle_call(request, _from, state) do
{ :reply, :undef, state }
end
end
It is important to warn though that `location: :keep` evaluates the
code as if it was defined inside `GenServer.Behavior` file, in
particular, the macro `__FILE__` will always point to
`GenServer.Behavior` file.
"""
defmacro quote(opts, do: contents)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, 0, [1, { :value, 0, quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, 0, [1, 13, 3] }
"""
defmacro unquote(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, 0, [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@doc """
List comprehensions allow you to quickly build a list from another list:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
A comprehension accepts many generators and also filters. Generators
are defined using both `inlist` and `inbits` operators, allowing you
to loop lists and bitstrings:
# A list generator:
lc n inlist [1,2,3,4], do: n * 2
#=> [2,4,6,8]
# A bit string generator:
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
#=> [2,4,6,8]
# A generator from a variable:
list = [1,2,3,4]
lc n inlist list, do: n * 2
#=> [2,4,6,8]
# A comprehension with two generators
lc x inlist [1,2], y inlist [2,3], do: x*y
#=> [2,3,4,6]
Filters can also be given:
# A comprehension with a generator and a filter
lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
#=> [2,4,6]
Bit string generators are quite useful when you need to
organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r:8,g:8,b:8>> inbits pixels, do: {r,g,b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
"""
defmacro lc(args)
@doc """
Defines a bit comprehension. It follows the same syntax as
a list comprehension but expects each element returned to
be a bitstring. For example, here is how to remove all
spaces from a string:
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
"helloworld"
"""
defmacro bc(args)
@doc """
This is the special form used whenever we have a block
of expressions in Elixir. This special form is private
and should not be invoked directly:
quote do: (1; 2; 3)
#=> { :__block__, 0, [1,2,3] }
"""
defmacro __block__(args)
@doc """
This is the special form used whenever we have to temporarily
change the scope information of a block. Used when `quote` is
invoked with `location: :keep` to execute a given block as if
it belonged to another file.
quote location: :keep, do: 1
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
Check `quote/1` for more information.
"""
defmacro __scope__(opts, args)
@doc """
This is the special form used to hold aliases information.
At compilation time, it is usually compiled to an atom:
quote do: Foo.Bar
{ :__aliases__, 0, [:Foo,:Bar] }
"""
defmacro __aliases__(args)
end
-244
View File
@@ -1,244 +0,0 @@
defmodule Kernel.Typespec do
@moduledoc """
This is the module that converts Elixir typespecs
to Erlang typespecs syntax. Everytime @spec, @type
and @typep are used they proxy to the functions
in this module.
"""
defmacro deftype(name, options // []) do
_deftype(name, true, __CALLER__, options)
end
defmacro deftypep(name) do
_deftype(name, false, __CALLER__, [])
end
defmacro defspec(spec, block) do
_defspec(:spec, __CALLER__, spec, block)
end
defmacro defcallback(spec, block) do
_defspec(:callback, __CALLER__, spec, block)
end
@doc """
Get the types defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_types(module) do
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
end
@doc """
Get the specs defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_specs(module) do
specs = :ets.tab2list(spec_table_for(module))
keys = :lists.ukeysort(1, specs)
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
end
## Typespec conversion
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = List.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
# Handle binaries
defp typespec({:<<>>, line, []}, _,_) do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
end
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
end
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
end
# Handle ranges
defp typespec({:"..", line, args}, vars, caller) do
typespec({:range, line, args}, vars, caller)
end
# Handle aliases
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
end
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var,1)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line, [left, right] }
end
# Handle unary ops
defp typespec({op, line, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line, op, {:integer, line, integer} }
end
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
end
# Handle tuples
defp typespec({:tuple, line, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, line, []}, vars, caller)
end
defp typespec({:{}, line, []}, _, _) do
{ :type, line, :tuple, :any }
end
defp typespec({:{}, line, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line, :tuple, args }
end
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case List.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, List.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
{ :type, line, :fun, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
else
typespec({name, line, []}, vars, caller)
end
end
# Handle local calls
defp typespec({name, line, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
end
# Handle literals
defp typespec(atom, _, _) when is_atom(atom) do
{ :atom, 0, atom }
end
defp typespec(integer, _, _) when is_integer(integer) do
{ :integer, 0, integer }
end
defp typespec([], vars, caller) do
typespec({ nil, 0, [] }, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, 0, [spec] }, vars, caller)
end
defp typespec(l, _, _) when is_list(l) do
raise(ArgumentError, message: "Unexpected list #{inspect l}")
end
defp typespec(t, vars, caller) when is_tuple(t) do
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
{ :type, 0, :tuple, args }
end
## Helpers
defp remote_type({remote, line, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line, [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
{ :type, line, :any }
end
defp fn_args(line, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line, :product, args }
end
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
_deftype(name, definition, export, caller, opts)
end
def _deftype(name, export, caller, opts) do
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
end
defp _deftype({name, _, args}, definition, export, caller, options) do
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
vars = lc {:var, _, var} inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc ({:var, _, _} = var) inlist args, do: var
attr = if options[:opaque], do: :opaque, else: :type
export = if export do
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
else
nil
end
quote do
name = unquote(name)
spec = unquote(Macro.escape(spec))
vars = unquote(Macro.escape(vars))
type = { name, spec, vars }
Module.compile_type __MODULE__, unquote(attr), type
unquote(export)
{ unquote(attr), type }
end
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = Macro.escape { {type, { name, length(args) }}, [spec] }
table = spec_table_for(caller.module)
quote do
code = unquote(code)
:ets.insert(unquote(table), code)
code
end
end
defp spec_table_for(module) do
table = list_to_atom Erlang.lists.concat([:s, module])
unless table == :ets.info(table, :name), do:
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
table
end
defp variable({name, line, _}) do
{:var, line, name}
end
end
-240
View File
@@ -1,240 +0,0 @@
defmodule Keyword do
@moduledoc """
A keyword is a list of tuples where the first element
of the tuple is an atom and the second element can be
any value. The list is sorted by the first element of
each tuple.
A keyword may have duplicated keys, so it is not strictly
a dictionary. However most of the functions in this module
allows it to behave exactly as a dictionary. For example,
`Keyword.get` will get the first entry matching the given
key, regardless if duplicated entries exist. Similarly,
`Keyword.put` and `Keyword.delete` ensure all duplicated
entries for a given key are removed when invoked.
This module uses `==` as operator to check if two keys
are equal or not.
"""
@doc """
Creates a Keyword from enum. Differently from `Keyword.new`
that behaves as a dict, `Keyword.from_enum` do not remove
duplicated entries.
"""
def from_enum(enum) do
Enum.qsort(enum)
end
@doc """
Returns an empty keywords list, i.e. an empty list.
"""
def new do
[]
end
@doc """
Creates a Keyword from an enumerable. Similarly to dicts,
duplicated entries are removed, the latest one prevails.
## Examples
Keyword.new [{:b,1},{:a,2}]
#=> [a: 2, b: 1]
"""
def new(pairs) do
Enum.reduce pairs, [], fn {k, v}, keywords ->
put(keywords, k, v)
end
end
@doc """
Creates a Keyword from an enumerable with the
help of the transformation function. Duplicated
entries are removed, the latest one prevails.
## Examples
Keyword.new [:a, :b], fn x -> {x,x} end
#=> [a: :a, b: :b]
"""
def new(pairs, transform) do
Enum.reduce pairs, [], fn i, keywords ->
{ k, v } = transform.(i)
put(keywords, k, v)
end
end
@doc """
Gets the value for specific key.
If key not exist return default value (nil if no default value)
exists.
If duplicated entries exist, the first one is returned.
Use get_values/2 to retrieve all entries.
## Examples
Keyword.get [a: 1], :a #=> 1
Keyword.get [a: 1], :b #=> nil
Keyword.get [a: 1], :b, 3 #=> 3
"""
def get(keywords, key, default // nil)
def get([{k, _}|_], key, default) when key < k, do: default
def get([{k, _}|d], key, default) when key > k, do: get(d, key, default)
def get([{_, value}|_], _key, _default), do: value
def get([], _, default), do: default
@doc """
Gets the value for specific key. If key does not exist,
an error is raised.
## Examples
Keyword.get! [a: 1], :a #=> 1
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
"""
def get!([{k, _}|_], key) when key < k, do: raise(Keyword.KeyError, key: key)
def get!([{k, _}|d], key) when key > k, do: get!(d, key)
def get!([{_, value}|_], _key), do: value
def get!([], key), do: raise(Keyword.KeyError, key: key)
@doc """
Gets all values for a specific key.
## Examples
Keyword.get_values [a: 1, a: 2], :a
#=> [1,2]
"""
def get_values([{k, _}|_], key) when key < k, do: []
def get_values([{k, _}|d], key) when key > k, do: get_values(d, key)
def get_values([{_, value}|d], key), do: [value|get_values(d, key)]
def get_values([], _), do: []
@doc """
Returns all keys from the keywords list. Duplicated
keys appear duplicated in the final list of keys.
## Examples
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
"""
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@doc """
Returns all values.
## Examples
Keyword.values [a: 1, b: 2] #=> [1,2]
"""
def values(keywords) do
lc { _, value } inlist keywords, do: value
end
@doc """
Deletes all entries in the keywords list for a specific key.
If the key does not exist, returns the keywords list unchanged.
Use `delete_first` to delete just the first entry in case of
duplicated keys.
## Examples
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
Keyword.delete [b: 2], :a #=> [b: 2]
"""
def delete([{k, _}|_] = keywords, key) when key < k, do: keywords
def delete([{k, _} = e|tail], key) when key > k, do: [e|delete(tail, key)]
def delete([{_, _}|tail], key), do: delete(tail, key)
def delete([], _), do: []
@doc """
Sets the given `value` under `key`.
If a previous value is already stored, all entries are
removed and the value is overriden.
Use `put_other/3` to add a new value for an existing key
without removing previous ones.
## Examples
Keyword.put [a: 1, b: 2], :a, 3
#=> [a: 3, b: 2]
"""
def put([{k, _} = e|keywords], key, value) when key < k and is_atom(key) do
[{key, value},e|keywords]
end
def put([{k, _} = e|keywords], key, value) when key > k do
[e|put(keywords, key, value)]
end
def put([{key, _}|keywords], key, value) when is_atom(key) do
[{key, value}|delete(keywords, key)]
end
def put([], key, value) when is_atom(key) do
[{key, value}]
end
@doc """
Merges two keywords lists into one. If they have duplicated
entries, the one given as second argument wins.
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
#=> [a:3, b:2, d: 4]
"""
def merge(d1, d2) do
merge(d1, d2, fn _k, _v1, v2 -> v2 end)
end
@doc """
Merges two keywords lists into one. If they have duplicated
entries, the given function is invoked to solve conflicts.
## Examples
Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
v1 + v2
end
#=> [a:4, b:2, d: 4]
"""
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 < k2 and is_atom(k1) do
[e1|merge(d1, [e2|d2], fun)]
end
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 > k2 and is_atom(k2) do
[e2|merge([e1|d1], d2, fun)]
end
def merge([{k1, v1}|d1], [{k1, v2}|d2], fun) do
[{k1, fun.(k1, v1, v2)}|merge(d1, d2, fun)]
end
def merge([], d2, _fun), do: d2
def merge(d1, [], _fun), do: d1
@doc """
Returns whether a given key exists in the given keywords.
### Examples
Keyword.key?([a: 1], :a)
#=> true
Keyword.key?([a: 1], :b)
#=> false
"""
def key?([{k, _}|_], key) when key < k, do: false
def key?([{k, _}|d], key) when key > k, do: key?(d, key)
def key?([{_, _}|_], _key), do: true
def key?([], _), do: false
end
-447
View File
@@ -1,447 +0,0 @@
defmodule List do
@moduledoc """
Implements functions that only make sense for lists
and cannot be part of the Enum protocol. In general,
favor using the Enum API instead of List.
A decision was taken to delegate most functions to
Erlang's standard lib but following Elixir's convention
of receiving the target (in this case, a list) as the
first argument.
"""
@doc """
Given a list of lists, concatenates the sublists into a single list.
## Examples
List.concat [[1,[2],3], [4], [5,6]]
#=> [1,[2],3,4,5,6]
"""
def concat(list) when is_list(list) do
Erlang.lists.append(list)
end
@doc """
Concatenates the list on the right with the list on the left.
This function produces the same result the `++` operator. The only difference
is a minor optimization: when the first list contains only one element, we
simply add it as a head to the second list.
## Examples
List.concat [1,2,3], [4,5,6]
#=> [1,2,3,4,5,6]
"""
def concat(list, elements) when is_list(list) and is_list(elements) do
list ++ elements
end
@doc """
Deletes the given item from the list. Returns a list without the item.
If the item occurs more than once in the list, just the first occurrence
is removed.
## Examples
List.delete([1,2,3], 1)
#=> [2,3]
"""
def delete(list, item) do
Erlang.lists.delete(item, list)
end
@doc """
Flattens the given `list` of nested lists. An optional
tail can be given that will be added at the end of
the flattened list.
## Examples
List.flatten [1,[[2],3]]
#=> [1,2,3]
List.flatten [1,[[2],3]], [4,5]
#=> [1,2,3,4,5]
"""
def flatten(list) do
Erlang.lists.flatten(list)
end
def flatten(list, tail) do
Erlang.lists.flatten(list, tail)
end
@doc """
Folds (reduces) the given list to the left with
a function. Requires an accumulator.
## Examples
List.foldl [5,5], 10, fn x, acc -> x + acc end
#=> 20
List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
#=> 2
"""
def foldl(list, acc, function) when is_list(list) and is_function(function) do
Erlang.lists.foldl(function, acc, list)
end
@doc """
Folds (reduces) the given list to the right with
a function. Requires an accumulator.
## Examples
List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
#=> -2
"""
def foldr(list, acc, function) when is_list(list) and is_function(function) do
Erlang.lists.foldr(function, acc, list)
end
@doc """
Reverses the given list. This function simply delegates
to `lists:reverse` which is implemented in C for performance.
## Examples
List.reverse [1,2,3]
#=> [3,2,1]
"""
def reverse(list) do
:lists.reverse(list)
end
@doc """
Returns the last element in `list` or nil if the `list` is empty.
## Examples
List.last []
#=> nil
List.last [1]
#=> 1
List.last [1, 2, 3]
#=> 3
"""
def last([]), do: nil
def last(list) do
:lists.last(list)
end
@doc """
Checks if the given `term` is included in the list.
This function simply delegates to `lists:member`
which is implemented in C for performance.
## Examples
List.member? [1,2,3], 1
#=> true
List.member? [1,2,3], 0
#=> false
"""
def member?(list, term) do
Erlang.lists.member(term, list)
end
@doc """
Receives a list of tuples and returns the first tuple
where the item at position `posistion` matches with the
given `item`.
## Examples
List.keyfind([a: 1, b: 2], :a, 1)
#=> { :a, 1 }
List.keyfind([a: 1, b: 2], 2, 2)
#=> { :b, 2 }
List.keyfind([a: 1, b: 2], :c, 1)
#=> nil
"""
def keyfind(list, item, position, default // nil) do
Erlang.lists.keyfind(item, position, list) || default
end
@doc """
Receives a list of tuples and returns true if there is
a tuple where the item at position `posistion` matches
with the given `item`.
## Examples
List.keymember?([a: 1, b: 2], :a, 1)
#=> true
List.keymember?([a: 1, b: 2], 2, 2)
#=> true
List.keymember?([a: 1, b: 2], :c, 1)
#=> false
"""
def keymember?(list, item, position) do
Erlang.lists.keymember(item, position, list)
end
@doc """
Receives a list of tuples and deletes the first tuple
where the item at position `posistion` matches with the
given `item`. Returns the new tuple.
## Examples
List.keydelete([a: 1, b: 2], :a, 1)
#=> [{ :b, 2 }]
List.keydelete([a: 1, b: 2], 2, 2)
#=> [{ :a, 1 }]
List.keydelete([a: 1, b: 2], :c, 1)
#=> [{ :a, 1 }, { :b, 2 }]
"""
def keydelete(list, item, position) do
Erlang.lists.keydelete(item, position, list)
end
@doc """
Returns a list of integers in the given range (both ends included when
possible). An optional step can be provided as well (defaults to 1).
If first > last and no step is provided, the numbers will be in descending
order.
## Examples
List.range 1, 3 #=> [1,2,3]
List.range 1, 8, 2 #=> [1,3,5,7]
List.range 1, 0 #=> []
List.range 3, 1 #=> [3,2,1]
List.range 5, 1, -2 #=> [5, 3, 1]
"""
def range(first, last, step // nil)
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
step = case step do
nil ->
Erlang.lists.seq(first, last, 1)
x when x < 0 ->
[]
_ ->
Erlang.lists.seq(first, last, step)
end
end
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
step = case step do
nil ->
Erlang.lists.seq(first, last, -1)
x when x > 0 ->
[]
_ ->
Erlang.lists.seq(first, last, step)
end
end
@doc """
Sorts the list by comparing each term. For an alternative
sorting algorithm, check `Enum.qsort`.
## Examples
List.sort [3, 4, 2, 1, 7]
#=> [1, 2, 3, 4, 7]
"""
def sort(list) do
:lists.sort list
end
@doc """
Sorts the list according to an ordering function. fun(a, b) should
return true if `a` compares less than or equal to `b`, `false` otherwise.
## Examples
List.sort [3, 4, 2, 1, 7], fn a, b -> b <= a end
#=> [7, 4, 3, 2, 1]
"""
def sort(list, fun) do
:lists.sort fun, list
end
@doc """
Returns a list without duplicated items.
## Examples
List.uniq [1,2,3,2,1]
#=> [1,2,3]
"""
def uniq(list) when is_list(list) do
do_uniq(list, [])
end
@doc """
Duplicates the given element n times in a list.
## Examples
List.duplicate "hello", 3
#=> ["hello","hello","hello"]
List.duplicate [1,2], 2
#=> [[1,2],[1,2]]
"""
def duplicate(elem, n) do
Erlang.lists.duplicate(n, elem)
end
@doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
If the argument is nil, returns an empty list.
## Examples
List.wrap [1,2,3] #=> [1,2,3]
"""
def wrap(list) when is_list(list) do
list
end
def wrap(nil) do
[]
end
def wrap(other) do
[other]
end
@doc """
Zips corresponding elements from two lists (or tuples) into one list of tuples. The
number of elements in the resulting list is equal to the length of the
shortest list among the given ones.
## Examples
List.zip [1, 2, 3], [4, 5, 6]
#=> [{1, 4}, {2, 5}, {3, 6}]
List.zip [1, 2], [4, 5, 6]
#=> [{1, 4}, {2, 5}]
"""
def zip(item1, item2) do
do_zip(to_list(item1), to_list(item2), [])
end
@doc """
Zips corresponding elements from each list in `list_of_lists`.
## Examples
List.zip [[1, 2], [3, 4], [5, 6]]
#=> [{1, 3, 5}, {2, 4, 6}]
List.zip [[1, 2], [3], [5, 6]]
#=> [{1, 3, 5}]
"""
def zip(list_of_lists) when is_list(list_of_lists) do
do_zip(list_of_lists, [])
end
@doc """
Unzips the given list of lists or tuples into separate lists and returns a
list of lists.
## Examples
List.unzip [{1, 2}, {3, 4}]
#=> [[1, 3], [2, 4]]
List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
#=> [[1, 2, 3], [:a, :b, :c]]
"""
def unzip(list) when is_list(list) do
:lists.map tuple_to_list(&1), zip(list)
end
## Private
# uniq
defp do_uniq([h|t], acc) do
case Erlang.lists.member(h, acc) do
true ->
do_uniq(t, acc)
false ->
[h|do_uniq(t, [h|acc])]
end
end
defp do_uniq([], _acc) do
[]
end
# zip
defp do_zip([h1|t1], [h2|t2], acc) do
do_zip t1, t2, [{h1, h2}|acc]
end
defp do_zip(_, _, acc) do
reverse acc
end
defp do_zip(list, acc) do
converter = fn x, acc -> do_zip_each(to_list(x), acc) end
{mlist, heads} = :lists.mapfoldl converter, [], list
case heads do
nil ->
:lists.reverse acc
_ ->
do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
end
end
defp do_zip_each(_, nil) do
{ nil, nil }
end
defp do_zip_each([h|t], acc) do
{ t, [h|acc] }
end
defp do_zip_each([], _) do
{ nil, nil }
end
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
defp to_list(list) when is_list(list), do: list
end
-32
View File
@@ -1,32 +0,0 @@
defprotocol List.Chars do
@moduledoc %B"""
The List.Chars protocol is responsible for
converting a structure to a list (only if applicable).
The only function required to be implemented is
`to_char_list` which does the conversion.
The `to_char_list` function automatically imported
by Kernel invokes this protocol.
"""
@only [BitString, List, Atom, Number, Record]
def to_char_list(thing)
end
defimpl List.Chars, for: Atom do
def to_char_list(atom), do: atom_to_list(atom)
end
defimpl List.Chars, for: BitString do
def to_char_list(bitstring), do: bitstring_to_list(bitstring)
end
defimpl List.Chars, for: List do
def to_char_list(list), do: list
end
defimpl List.Chars, for: Number do
def to_char_list(integer) when is_integer(integer), do: integer_to_list(integer)
def to_char_list(float) when is_float(float), do: float_to_list(float)
end
-370
View File
@@ -1,370 +0,0 @@
import Kernel, except: [to_binary: 1]
defmodule Macro do
@moduledoc """
This module provides conveniences for working with macros.
"""
@doc """
Returns a list of binary operators. This is available
as a macro so it can be used in guard clauses.
"""
defmacro binary_ops do
[
:===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
:<, :>,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
]
end
@doc """
Returns a list of unary operators. This is available
as a macro so it can be used in guard clauses.
"""
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-]
end
@doc """
Recursively escapes the given value so it can be inserted
into a syntax tree. Structures that are valid syntax nodes
(like atoms, integers, binaries) are represented by themselves.
## Examples
Macro.escape(:foo)
#=> :foo
Macro.escape({ :a, :b, :c })
#=> { :{}, 0, [:a, :b, :c] }
"""
def escape({ left, right }) do
{ escape(left), escape(right) }
end
def escape(tuple) when is_tuple(tuple) do
{ :{}, 0, escape(tuple_to_list(tuple)) }
end
def escape(list) when is_list(list) do
lc item inlist list, do: escape(item)
end
def escape(other), do: other
@doc """
Converts the given expression to a binary.
## Examples
Macro.to_binary(quote do: foo.bar(1, 2, 3))
#=> "foo.bar(1, 2, 3)"
"""
def to_binary(tree)
# Variables
def to_binary({ var, _, atom }) when is_atom(atom) do
atom_to_binary(var, :utf8)
end
# Aliases
def to_binary({ :__aliases__, _, refs }) do
Enum.map_join(refs, ".", call_to_binary(&1))
end
# Blocks
def to_binary({ :__block__, _, [expr] }) do
to_binary(expr)
end
def to_binary({ :__block__, _, _ } = expr) do
block = adjust_new_lines block_to_binary(expr), "\n "
"(\n " <> block <> "\n)"
end
# Bits containers
def to_binary({ :<<>>, _, args }) do
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
end
# Tuple containers
def to_binary({ :{}, _, args }) do
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
end
# List containers
def to_binary({ :[], _, args }) do
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
end
# Fn keyword
def to_binary({ :fn, _, [[do: block]] }) do
"fn " <> block_to_binary(block) <> "\nend"
end
# Partial call
def to_binary({ :&, _, [num] }) do
"&#{num}"
end
# Binary ops
def to_binary({ op, _, [left, right] }) when op in binary_ops do
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
end
# Unary ops
def to_binary({ op, _, [arg] }) when op in unary_ops do
atom_to_binary(op, :utf8) <> to_binary(arg)
end
# All other calls
def to_binary({ target, _, args }) when is_list(args) do
{ list, last } = Erlang.elixir_tree_helpers.split_last(args)
case is_kw_blocks?(last) do
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
false -> call_to_binary_with_args(target, args)
end
end
# Two-item tuples
def to_binary({ left, right }) do
to_binary({ :{}, 0, [left, right] })
end
# Lists
def to_binary(list) when is_list(list) do
to_binary({ :[], 0, list })
end
# All other structures
def to_binary(other), do: Binary.Inspect.inspect(other)
# Block keywords
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
defp is_kw_blocks?([_|_] = kw), do: Enum.all?(kw, fn({x,_}) -> x in kw_keywords end)
defp is_kw_blocks?(_), do: false
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
defp call_to_binary(other), do: to_binary(other)
defp call_to_binary_with_args(target, args) do
args = Enum.map_join(args, ", ", to_binary(&1))
call_to_binary(target) <> "(" <> args <> ")"
end
defp kw_blocks_to_binary(kw) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.key?(kw, x) do
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
false -> acc
end
end) <> "end"
end
defp kw_block_to_binary(key, value) do
block = adjust_new_lines block_to_binary(value), "\n "
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
end
defp block_to_binary({ :->, _, exprs }) do
Enum.map_join(exprs, "\n", fn({ left, right }) ->
left = Enum.map_join(left, ", ", to_binary(&1))
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
end)
end
defp block_to_binary({ :__block__, _, exprs }) do
Enum.map_join(exprs, "\n", to_binary(&1))
end
defp block_to_binary(other), do: to_binary(other)
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
"(" <> to_binary(expr) <> ")"
end
defp op_to_binary(expr), do: to_binary(expr)
defp adjust_new_lines(block, replacement) do
bc <<x>> inbits block do
<< case x == ?\n do
true -> replacement
false -> <<x>>
end | :binary >>
end
end
@doc """
Receives an expression representation and expands it. The following
contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (__FILE__, __MODULE__, etc);
In case the expression cannot be expanded, it returns the expression itself.
Notice that `Macro.expand` is not recursive and it does not
expand child expressions. For example, `!some_macro` will expand as:
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
case some_macro do
false -> true
nil -> true
_ -> false
end
Notice that the `!` operator is a macro that expands to a case.
Even though `some_macro` is also a macro, it is not expanded
because it is a child expression given to `!` as argument.
## Examples
In the example below, we have a macro that generates a module
with a function named `name_length` that returns the length
of the module name. The value of this function will be calculated
at compilation time and not at runtime.
Consider the implementation below:
defmacro defmodule_with_length(name, do: block) do
length = length(atom_to_list(name))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
When invoked like this:
defmodule_with_length My.Module do
def other_function, do: ...
end
The compilation will fail because `My.Module` when quoted
is not an atom, but a syntax tree as follow:
{:__aliases__, 0, [:My, :Module] }
That said, we need to expand the aliases node above to an
atom, so we can retrieve its length. Expanding the node is
not straight-forward because we also need to expand the
caller aliases. For example:
alias MyHelpers, as: My
defmodule_with_length My.Module do
def other_function, do: ...
end
The final module name will be `MyHelpers.Module` and not
`My.Module`. With `Macro.expand`, such aliases are taken
into consideration. Local and remote macros are also
expanded. We could rewrite our macro above to use this
function as:
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(atom_to_list(expanded))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end
"""
def expand(aliases, env)
# The first case we handle is __aliases__. In case
# aliases just contain one item, we are sure it is
# an atom, so we just expand it based on the aliases
# dict.
def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
expand_alias(h, env)
end
# In case aliases contains more than one item, we need
# to loop them checking if they are all atoms or not.
# Macros and pseudo-variables are then expanded.
def expand({ :__aliases__, _, [h|t] }, env) do
aliases = case h do
x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
_ -> [h|t]
end
aliases = lc alias inlist aliases, do: expand(alias, env)
:lists.all(is_atom(&1), aliases) && Erlang.elixir_aliases.concat(aliases)
end
# Expand Erlang.foo calls
def expand({ { :., _, [{ :__aliases__, _, [:Erlang] }, atom] }, _, args }, _env) when
is_atom(atom) and (is_atom(args) or args == []), do: atom
# Expand pseudo-variables
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
# Expand possible macro import invocation
def expand({ atom, line, args } = original, env) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
end
case not is_partial?(args) do
false -> original
true ->
expand = Erlang.elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, env.function, env.requires, env.macros, env)
case expand do
{ :ok, _, expanded } -> expanded
{ :error, _ } -> original
end
end
end
# Expand possible macro require invocation
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
receiver = expand(left, env)
case is_atom(receiver) and not is_partial?(args) do
false -> original
true ->
expand = Erlang.elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, env.function, env.requires, env)
case expand do
{ :ok, expanded } -> expanded
{ :error, _ } -> original
end
end
end
# Anything else is just returned
def expand(other, _env), do: other
## Helpers
defp is_partial?(args) do
:lists.any(match?({ :&, _, [_] }, &1), args)
end
defp expand_alias(h, env) do
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
Erlang.elixir_aliases.lookup(atom, env.aliases)
end
end
-90
View File
@@ -1,90 +0,0 @@
# We generate this record using a raw module due to
# bootstrap constraints. Notice the fields are not
# represented by a keywords list because we want
# to keep control over the order.
defmodule Macro.Env do
@doc """
A record that contains compile time environment information,
It can be accessed at any time by calling __ENV__.
"""
def __access__(caller, args) do
Record.access(caller, __MODULE__, __record__(:fields), args)
end
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: Macro.Env
# When adding removing new fields,
# src/elixir_tree_helpers.erl needs to be changed as well.
def __record__(:fields) do
[
{:module,nil},
{:file,nil},
{:line,nil},
{:function,nil},
{:aliases,nil},
{:context,nil},
{:requires,nil},
{:macros,nil}
]
end
@doc """
Returns the current module name.
"""
def module(record), do: elem(record, 2)
@doc """
Returns the current file name as a binary.
"""
def file(record), do: elem(record, 3)
@doc """
Returns the current line as an integer.
"""
def line(record), do: elem(record, 4)
@doc """
Returns a tuple as { Atom, Integer }, where the first element
is the function name and the seconds its arity. Returns `nil`
if not inside a function.
"""
def function(record), do: elem(record, 5)
@doc """
Returns a list of two item tuples, where the first
item is the aliased name and the second the actual name.
"""
def aliases(record), do: elem(record, 6)
@doc """
Returns wether the compilation environment is currently
inside a guard.
"""
def in_guard?(record), do: elem(record, 7) == :guard
@doc """
Returns wether the compilation environment is currently
inside a match clause.
"""
def in_match?(record), do: elem(record, 7) == :assign
@doc """
Returns the list of required modules.
"""
def requires(record), do: elem(record, 8)
@doc """
Returns a list of macros imported from each module.
"""
def macros(record), do: elem(record, 9)
@doc """
Returns a keywords list containing the file and line
information as keys.
"""
def location(record) do
[file: file(record), line: line(record)]
end
end
-563
View File
@@ -1,563 +0,0 @@
defmodule Module do
require Erlang.ets, as: ETS
@moduledoc """
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After the module is compiled, using many of the functions in
this module will raise errors, since it is out of their purpose
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
"""
@doc """
Evalutes the quotes contents in the given module context.
Raises an error if the module was already compiled.
## Options
This function accepts a list of options. The supported
options are:
* `:file` - The filename to be used in stacktraces
and the file reported in the __ENV__ variable.
* `:line` - The line reported in the __ENV__ variable.
## Examples
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, []
end
Foo.sum(1, 2) #=> 3
This function also accepts a `Macro.Env` as first argument. This
is useful to evalute the quoted contents inside an existing environment:
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __ENV__, contents, []
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(env, quoted, binding // [], opts // [])
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
eval_quoted(module, quoted, binding, Keyword.merge(env.location, opts))
end
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
binding = Erlang.elixir_module.binding_for_eval(module, binding)
scope = Erlang.elixir_module.scope_for_eval(module, opts)
Erlang.elixir_def.reset_last(module)
line = Keyword.get opts, :line, 1
{ value, binding, _scope } = Erlang.elixir.eval_quoted([quoted], binding, line, scope)
{ value, binding }
end
@doc """
Concatenates the list of aliases and returns a new alias.
It handles char lists, binaries and atoms.
## Examples
Module.concat [Foo, Bar] #=> Foo.Bar
Module.concat [Foo, "Bar"] #=> Foo.Bar
Module.concat [Foo, 'Bar'] #=> Foo.Bar
"""
def concat(list) when is_list(list) do
Erlang.elixir_aliases.concat(list)
end
@doc """
Concatenates the two given aliases and returns a new alias.
It handles char lists, binaries and atoms.
## Examples
Module.concat Foo, Bar #=> Foo.Bar
Module.concat Foo, "Bar" #=> Foo.Bar
Module.concat Foo, 'Bar' #=> Foo.Bar
"""
def concat(left, right) do
Erlang.elixir_aliases.concat([left, right])
end
@doc """
Concatenates the list aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
It handles char lists, binaries and atoms.
## Examples
Module.safe_concat [Unknown, Module]
#=> ArgumentError
Module.safe_concat [List, Chars]
#=> List.Chars
"""
def safe_concat(list) when is_list(list) do
Erlang.elixir_aliases.safe_concat(list)
end
@doc """
Concatenates the two aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with ArgumentError.
It handles char lists, binaries and atoms.
## Examples
Module.safe_concat Unknown, Module
#=> ArgumentError
Module.safe_concat List, Chars
#=> List.Chars
"""
def safe_concat(left, right) do
Erlang.elixir_aliases.safe_concat([left, right])
end
@doc """
Attaches documentation to a given function. It expects
the module the function belongs to, the line (a non negative
integer), the kind (def or defmacro), a tuple representing
the function and its arity and the documentation, which should
be either a binary or a boolean.
## Examples
defmodule MyModule do
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
def version, do: 1
end
"""
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
if doc, do: { :error, :private_doc }, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
is_binary(doc) or is_boolean(doc) or doc == nil do
assert_not_compiled!(:add_doc, module)
table = docs_table_for(module)
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
{ simplify_signature(x, line, acc), acc + 1 }
end
case { ETS.lookup(table, tuple), doc } do
{ [], _ } ->
ETS.insert(table, { tuple, line, kind, signature, doc })
:ok
{ [{ tuple, line, kind, old, doc }], nil } ->
ETS.insert(table, { tuple, line, kind, merge_signatures(old, signature, 1), doc })
:ok
_ ->
{ :error, :existing_doc }
end
end
# Simplify signatures to be stored in docs
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
{ ://, defline, [simplify_signature(left, line, i), right] }
end
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
case atom_to_list(var) do
[?_|_] -> { var, line, :guess }
_ -> { var, line, nil }
end
end
defp simplify_signature({ :=, _, [_, right] }, line, i) do
simplify_signature(right, line, i)
end
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
# Merge signatures
defp merge_signatures([h1|t1], [h2|t2], i) do
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
end
defp merge_signatures([], [], _) do
[]
end
defp merge_signature({ ://, line, [left, right] }, newer, i) do
{ ://, line, [merge_signature(left, newer, i), right] }
end
defp merge_signature(older, { ://, _, [left, _] }, i) do
merge_signature(older, left, i)
end
# The older signature, when given, always have higher precedence
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
# Both are a guess, so check if they are the same guess
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for an specific type.
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 } #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 } #=> true
end
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
ETS.lookup(table, tuple) != []
end
@doc """
Checks if the module defines a function or macro with the
given `kind`. `kind` can be either `:def`, `:defp`,
`:defmacro` or `:defmacrop`.
## Examples
defmodule Example do
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
def version, do: 1
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
end
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = function_table_for(module)
case ETS.lookup(table, tuple) do
[{ _, ^kind, _, _, _, _, _, _ }] -> true
_ -> false
end
end
@doc false
def function_defined?(module, tuple) do
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
defines?(module, tuple)
end
@doc false
def function_defined?(module, tuple, kind) do
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
defines?(module, tuple, kind)
end
@doc false
def defined_functions(module) do
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
definitions_in(module)
end
@doc false
def defined_functions(module, kind) do
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
definitions_in(module, kind)
end
@doc """
Return all functions defined in the given module.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__ #=> [{:version,1}]
end
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
end
@doc """
Returns all functions defined in te given module according
to its kind.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
Module.definitions_in __MODULE__, :defp #=> []
end
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = function_table_for(module)
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
end
@doc """
Makes the given functions in the given module overridable.
An overridable function is lazily defined, allowing a
developer to customize it.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
table = function_table_for(module)
lc tuple inlist tuples do
case ETS.lookup(table, tuple) do
[clause] ->
ETS.delete(table, tuple)
old = Module.read_attribute(module, :__overridable)
new = [ { tuple, { 1, [clause] } } ]
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
Module.add_attribute(module, :__overridable, merged)
_ ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
end
end
end
def add_compile_callback(module, target, fun // :__compiling__) do
IO.puts "Module.add_compile_callback(module, target, fun) is deprecated in favor of " <>
"Module.add_attribute(module, :before_compile, { target, fun })"
assert_not_compiled!(:add_compile_callback, module)
add_attribute(module, :before_compile, { target, fun })
end
@doc """
Adds an Erlang attribute to the given module with the given
key and value. The semantics of adding the attribute depends
if the attribute was registered or not via `register_attribute/2`.
## Examples
defmodule MyModule do
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
end
"""
def add_attribute(module, key, value) when is_atom(key) do
assert_not_compiled!(:add_attribute, module)
table = data_table_for(module)
value = normalize_attribute(key, value)
acc = ETS.lookup_element(table, :__acc_attributes, 2)
new =
if List.member?(acc, key) do
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
[] -> [value]
end
else
value
end
ETS.insert(table, { key, new })
end
@doc """
Reads the given attribute from a module. If the attribute
was marked as accumulate with `Module.register_attribute`,
a list is always returned.
## Examples
defmodule Foo do
Module.add_attribute __MODULE__, :value, 1
Module.read_attribute __MODULE__, :value #=> 1
Module.register_attribute __MODULE__, :value, accumulate: true
Module.add_attribute __MODULE__, :value, 1
Module.read_attribute __MODULE__, :value #=> [1]
end
"""
def read_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:read_attribute, module)
table = data_table_for(module)
case ETS.lookup(table, key) do
[{^key,old}] -> old
[] ->
acc = ETS.lookup_element(table, :__acc_attributes, 2)
if List.member?(acc, key), do: [], else: nil
end
end
@doc """
Deletes all attributes that matches the given key.
## Examples
defmodule MyModule do
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
end
"""
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
ETS.delete(table, key)
end
@doc """
Registers an attribute. By registering an attribute, a developer
is able to customize how Elixir will store and accumulate the
attribute values.
## Options
When registering an attribute, two options can be given:
* `:accumulate` - Several calls to the same attribute will
accumulate instead of override the previous one;
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are true. Which means that registering
an attribute without passing any options will revert the attribute
behavior to exactly the same expected in Erlang.
## Examples
defmodule MyModule do
Module.register_attribute __MODULE__,
:custom_threshold_for_lib,
accumulate: true, persist: false
@custom_threshold_for_lib 10
@custom_threshold_for_lib 20
@custom_threshold_for_lib #=> [20, 10]
end
"""
def register_attribute(module, new, opts // []) do
assert_not_compiled!(:register_attribute, module)
table = data_table_for(module)
if Keyword.get(opts, :persist, true) do
old = ETS.lookup_element(table, :__persisted_attributes, 2)
ETS.insert(table, { :__persisted_attributes, [new|old] })
end
if Keyword.get(opts, :accumulate, true) do
old = ETS.lookup_element(table, :__acc_attributes, 2)
ETS.insert(table, { :__acc_attributes, [new|old] })
end
end
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
def compile_doc(module, line, kind, pair, signature) do
doc = read_attribute(module, :doc)
result = add_doc(module, line, kind, pair, signature, doc)
delete_attribute(module, :doc)
result
end
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def compile_type(module, key, value) when is_atom(key) do
assert_not_compiled!(:add_attribute, module)
table = data_table_for(module)
new =
case ETS.lookup(table, key) do
[{^key,old}] -> [value|old]
[] -> [value]
end
ETS.insert(table, { key, new })
end
## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
{ atom, 0 }
end
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
Code.ensure_compiled(atom)
atom
end
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), line}
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
defp normalize_attribute(key, atom) when key in [:before_compile, :after_compile] and is_atom(atom) do
{ atom, key }
end
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
"must be set via Kernel.Typespec"
end
defp normalize_attribute(_key, value) do
value
end
defp compiled?(module) do
table = data_table_for(module)
table == ETS.info(table, :name)
end
defp data_table_for(module) do
list_to_atom Erlang.lists.concat([:d, module])
end
defp function_table_for(module) do
list_to_atom Erlang.lists.concat([:f, module])
end
defp docs_table_for(module) do
list_to_atom Erlang.lists.concat([:o, module])
end
defp assert_not_compiled!(fun, module) do
compiled?(module) ||
raise ArgumentError,
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
end
end
-116
View File
@@ -1,116 +0,0 @@
defmodule Node do
@moduledoc """
Functions related to Erlang nodes.
"""
@doc """
Returns the current node. It returns the same as the built-in node().
"""
def self do
:erlang.node()
end
@doc """
Returns true if the local node is alive; that is, if the node can be
part of a distributed system. Otherwise, it returns false.
"""
def alive? do
:erlang.is_alive()
end
@doc """
Returns a list of all visible nodes in the system, excluding
the local node. Same as list(visible).
"""
def list do
:erlang.nodes()
end
@doc """
Returns a list of nodes according to argument given. The result
returned when the argument is a list, is the list of nodes
satisfying the disjunction(s) of the list elements.
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
"""
def list(args) do
:erlang.nodes(args)
end
@doc """
Monitors the status of the node. If flag is true, monitoring is
turned on. If flag is false, monitoring is turned off.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
"""
def monitor(node, flag) do
:erlang.monitor_node(node, flag)
end
@doc """
Behaves as monitor_node/2 except that it allows an extra
option to be given, namely :allow_passive_connect.
See http://www.erlang.org/doc/man/erlang.html#monitor_node-3 for more info.
"""
def monitor(node, flag, options) do
:erlang.monitor_node(node, flag, options)
end
@doc """
Forces the disconnection of a node. This will appear to the `node` as if
the local node has crashed. This BIF is mainly used in the Erlang network
authentication protocols. Returns true if disconnection succeeds, otherwise
false. If the local node is not alive, the function returns ignored.
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
"""
def disconnect(node) do
:erlang.disconnect_node(node)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. If `node` does not exist, a useless pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
the list of available options.
"""
def spawn(node, fun, opts // []) do
:erlang.spawn_opt(node, fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. If `node` does not exists, a useless
pid is returned.
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
the list of available options.
"""
def spawn(node, module, fun, args, opts // []) do
:erlang.spawn(node, module, fun, args, opts)
end
@doc """
Returns the pid of a new process started by the application of `fun`
on `node`. A link is created between the calling process and the
new process, atomically. If `node` does not exist, a useless pid is returned
(and due to the link, an exit signal with exit reason :noconnection will be
received).
"""
def spawn_link(node, fun) do
:erlang.spawn_link(node, fun)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)` on `node`. A link is created between the calling
process and the new process, atomically. If `node` does not exist, a useless
pid is returned (and due to the link, an exit signal with exit reason
:noconnection will be received).
"""
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
end
-140
View File
@@ -1,140 +0,0 @@
defmodule OptionParser do
@doc """
Parses the argv and returns one tuple with parsed options
and the arguments.
## Examples
OptionParser.parse(["--debug"])
#=> { [debug: true], [] }
OptionParser.parse(["--source", "lib"])
#=> { [source: "lib"], [] }
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
## Aliases
A set of aliases can be given as second argument:
OptionParser.parse(["-d"], aliases: [d: :debug])
#=> { [debug: true], [] }
## Flags
A set of flags can be given as argument too. Those are considered
boolean and never consume the next value unless it is a boolean:
OptionParser.parse(["--unlock path/to/file"], flags: [:unlock])
#=> { [unlock: true], ["path/to/file"] }
OptionParser.parse(["--unlock false path/to/file"], flags: [:unlock])
#=> { [unlock: false], ["path/to/file"] }
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value.
OptionParser.parse(["--no-op path/to/file"])
#=> { [no_op: true], ["path/to/file"] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], true)
end
@doc """
Similar to parse but only parses the head of the argv.
I.e. as soon as it finds a non switch, it stops parsing.
Check `parse/2` for more info.
## Example
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
aliases = opts[:aliases] || []
flags = opts[:flags] || []
dict = Keyword.new(flags, fn(k) -> { k, false } end)
parse(argv, aliases, flags, dict, [], false)
end
## Helpers
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
{ option, value } = normalize_option(option, aliases)
if value == nil do
{ value, t } = if is_flag?(flags, option) do
flag_from_tail(t)
else
value_from_tail(t)
end
end
dict = store_option dict, option, value
parse(t, aliases, flags, dict, args, all)
end
defp parse([], _, _, dict, args, true) do
{ dict, List.reverse(args) }
end
defp parse([h|t], aliases, flags, dict, args, true) do
parse(t, aliases, flags, dict, [h|args], true)
end
defp parse(value, _, _, dict, _args, false) do
{ dict, value }
end
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
defp flag_from_tail(t) , do: { true, t }
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
defp value_from_tail([h|t]), do: { h, t }
defp value_from_tail([]), do: { true, [] }
defp store_option(dict, option, value) when value in ["false", "true"] do
store_option(dict, option, binary_to_atom(value))
end
defp store_option(dict, option, value) do
Keyword.put dict, option, value
end
defp normalize_option(<<?-, option|:binary>>, aliases) do
normalize_option(option, aliases)
end
defp normalize_option(option, aliases) do
{ option, value } = split_option(option)
if is_no?(option), do: value = true
atom = option /> to_underscore /> binary_to_atom
{ aliases[atom] || atom, value }
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h|t] -> { h, Enum.join(t, "=") }
end
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
end
defp is_no?("no-" <> _), do: true
defp is_no?(_), do: false
defp is_flag?(flags, option), do: List.member?(flags, option)
end
-84
View File
@@ -1,84 +0,0 @@
defimpl Dict, for: Orddict do
defmacrop dict(data) do
quote do
{ Orddict, unquote(data) }
end
end
def keys(dict(data)) do
lc { k, _ } inlist data, do: k
end
def values(dict(data)) do
lc { _, v } inlist data, do: v
end
def size(dict(data)) do
length(data)
end
def has_key?(dict(data), key) do
:orddict.is_key key, data
end
def get(dict(data), key, default // nil) do
case :orddict.find(key, data) do
{:ok, value} ->
value
:error ->
default
end
end
def put(dict(data), key, value) do
dict(:orddict.store key, value, data)
end
def delete(dict(data), key) do
dict(:orddict.erase key, data)
end
def merge(dict(d1), dict(d2)) do
dict(:orddict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
end
def merge(dict(d1), dict(d2), fun) do
dict(:orddict.merge fun, d1, d2)
end
def update(dict(data), key, fun) do
dict(:orddict.update key, fun, data)
end
def update(dict(data), key, initial, fun) do
dict(:orddict.update key, fun, initial, data)
end
def empty(_) do
dict([])
end
def to_list(dict(data)) do
data
end
end
defimpl Enum.Iterator, for: Orddict do
def iterator({ Orddict, data }), do: data
def count({ Orddict, data }), do: length(data)
end
defimpl Enum.OrdIterator, for: Orddict do
def iterator({ Orddict, data }), do: data
def to_list({ h, next }, _), do: [h|next]
end
defmodule Orddict do
@moduledoc """
This module implements a dictionary based that stores items
as a list of tuples. It is a simple wrapper around
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
and exposed via the `Dict` protocol.
"""
use Dict.Common, Dict.Orddict
end
-68
View File
@@ -1,68 +0,0 @@
defmodule Port do
@moduledoc """
Functions related to Erlang ports.
"""
@doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
"""
def open(name, settings) do
:erlang.open_port(name, settings)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
"""
def close(port) do
:erlang.port_close(port)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
"""
def command(port, data, options // []) do
:erlang.port_command(port, data, options)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
"""
def connect(port, pid) do
:erlang.port_connect(port, pid)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
"""
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
"""
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
"""
def info(port) do
:erlang.port_info(port)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
"""
def info(port, item) do
:erlang.port_info(port, item)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0.
"""
def list do
:erlang.ports
end
end
-290
View File
@@ -1,290 +0,0 @@
defmodule Process do
@moduledoc """
This module provides convenience functions around processes and
the process dictionary. In Erlang, most of these functions are
auto-imported, but in Elixir they are grouped in a module for
convenience. Notice that these functions, different from Erlang's,
always return nil instead of undefined. You can use their Erlang
version if you want the undefined value.
"""
@doc """
Returns true if the process exists and is alive, that is,
is not exiting and has not exited. Otherwise, returns false.
`pid` must refer to a process at the local node.
"""
def alive?(pid) do
:erlang.is_process_alive(pid)
end
@doc """
Returns the current process.
"""
def self do
:erlang.self()
end
@doc """
Returns all key-values in the dictionary
with no specific ordering (i.e. they are
not a keywords list).
"""
def get do
:erlang.get()
end
@doc """
Returns the value for the given key.
"""
def get(key, default // nil) do
case :erlang.get(key) do
:undefined ->
default
value ->
value
end
end
@doc """
Returns all keys that have the given `value`.
"""
def get_keys(value) do
:erlang.get_keys(value)
end
@doc """
Stores the given key-value in the process dictionary.
"""
def put(key, value) do
nillify :erlang.put(key, value)
end
@doc """
Deletes all items in the dictionary.
"""
def delete() do
:erlang.erase()
end
@doc """
Deletes the given key from the dictionary.
"""
def delete(key) do
nillify :erlang.erase(key)
end
@doc """
Sends an exit signal with the given reason to the pid.
The following behavior apply if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid itself will exist with the given reason;
2) If pid is trapping exits, the exit signal is transformed into a message
{'EXIT', from, reason} and delivered to the message queue of pid;
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
the exit signal is transformed into a message {'EXIT', from, :normal} and
delivered to its message queue;
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
untrappable exit signal is sent to pid which will unconditionally exit with
exit reason `:killed`.
## Examples
Process.exit(pid, :kill)
"""
def exit(pid, status) do
:erlang.exit(pid, status)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as the `Kernel.spawn/1` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
def spawn(fun, opts // []) do
:erlang.spawn_opt(fun, opts)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. The new process created will be placed in the system
scheduler queue and be run some time later.
It behaves exactly the same as the `Kernel.spawn/3` function except
it also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
def spawn(mod, fun, args, opts // []) do
:erlang.spawn_opt(mod, fun, args, opts)
end
@doc """
Returns the pid of a new process started by the application of `fun`.
A link is created between the calling process and the new
process, atomically.
"""
def spawn_link(fun) do
:erlang.spawn_link(fun)
end
@doc """
Returns the pid of a new process started by the application of
`module.function(args)`. A link is created between the calling process
and the new process, atomically. Otherwise works like spawn/3.
"""
def spawn_link(mod, fun, args) do
:erlang.spawn_link(mod, fun, args)
end
@doc """
Returns the pid of a new process started by the application of `fun`
and reference for a monitor created to the new process.
"""
def spawn_monitor(fun) do
:erlang.spawn_monitor(fun)
end
@doc """
A new process is started by the application of `module.function(args)`
and the process is monitored at the same time. Returns the pid and a
reference for the monitor. Otherwise works like spawn/3.
"""
def spawn_monitor(mod, fun, args) do
:erlang.spawn_monitor(mod, fun, args)
end
@doc """
The calling process starts monitoring the item given.
It returns the monitor reference.
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
"""
def monitor(item) do
:erlang.monitor(:process, item)
end
@doc """
If monitor_ref is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off.
If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
"""
def demonitor(monitor_ref, options // []) do
:erlang.demonitor(monitor_ref, options)
end
@doc """
Returns a list of process identifiers corresponding to all the
processes currently existing on the local node.
Note that a process that is exiting, exists but is not alive, i.e.,
alive?/1 will return false for a process that is exiting,
but its process identifier will be part of the result returned.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
"""
def list do
:erlang.processes()
end
@doc """
Creates a link between the calling process and another process
(or port) `pid`, if there is not such a link already.
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
"""
def link(pid) do
:erlang.link(pid)
end
@doc """
Removes the link, if there is one, between the calling process and
the process or port referred to by `pid`. Returns true and does not
fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
"""
def unlink(pid) do
:erlang.unlink(pid)
end
@doc """
Associates the name with a pid or a port identifier. name, which must
be an atom, can be used instead of the pid / port identifier in the
send operator (name <- message).
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
"""
def register(name, pid) do
:erlang.register(name, pid)
end
@doc """
Removes the registered name, associated with a pid or a port identifier.
See http://www.erlang.org/doc/man/erlang.html#unregister-1 for more info.
"""
def unregister(name) do
:erlang.unregister(name)
end
@doc """
Returns the pid or port identifier with the registered name.
Returns undefined if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
def whereis(name) do
:erlang.whereis(name)
end
@doc """
Returns a list of names which have been registered using register/2.
"""
def registered do
:erlang.registered()
end
@doc """
Sets certain flags for the process which calls this function.
Returns the old value of the flag.
See http://www.erlang.org/doc/man/erlang.html#process_flag-2 for more info.
"""
def flag(flag, value) do
:erlang.process_flag(flag, value)
end
@doc """
Sets certain flags for the process Pid, in the same manner as flag/2.
Returns the old value of the flag. The allowed values for Flag are
only a subset of those allowed in flag/2, namely: save_calls.
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
@doc """
Returns information about the process identified by pid
or undefined if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
def info(pid, spec) do
:erlang.process_info(pid, spec)
end
defp nillify(:undefined), do: nil
defp nillify(other), do: other
end
-308
View File
@@ -1,308 +0,0 @@
defmodule Protocol do
@moduledoc false
# We need to use Erlang.lists because Enum is not available yet
require Erlang.lists, as: L
@doc """
Handle `defprotocol`. It will define a function for each
protocol plus two extra functions:
* `__protocol__/1` - returns the protocol name when :name is given,
and a keywords list with the protocol functions
when :functions is given;
* `__impl_for__/1` - receives one argument and returns a module
that implements the protocol for the given
data type. If no implementation matches, returns nil;
* `__impl_for__!/1` - same as above but raises an error if an implementation is not found
"""
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# Remove "harmful" macros
# We don't want to allow function definition inside protocols
import Kernel, except: [
defmacro: 1, defmacro: 2, defmacro: 4,
defp: 1, defp: 2, defp: 4,
def: 1, def: 2, def: 4
]
# Import the new dsl that holds the new def
import Protocol.DSL
# Set up a clear slate to store defined functions
@functions []
# Invoke the user given block
unquote(block)
# Define callbacks and meta information
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
Protocol.impl_for(__ENV__, conversions)
Protocol.meta(__ENV__, @functions, fallback)
end
end
end
@doc """
Implement the given protocol for the given module.
It also defines a `__impl__` function which
returns the protocol being implemented.
"""
def defimpl(protocol, [do: block, for: for]) do
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
Protocol.assert_protocol(protocol)
defmodule name do
def __impl__, do: unquote(protocol)
unquote(block)
end
Protocol.assert_impl(name, protocol)
end
end
@doc """
Check if the given module is a protocol. Raises an error
if not loaded or not a protocol.
"""
def assert_protocol(module) do
case :code.ensure_loaded(module) do
{ :module, ^module } -> nil
_ -> raise ArgumentError, message: "#{module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{module} is not a protocol"
end
end
@doc """
Check if the given `impl` is a valid impl for `protocol`.
Raises an error if not.
"""
def assert_impl(impl, protocol) do
remaining = protocol.__protocol__(:functions) -- impl.__info__(:functions)
if remaining != [] do
pp = Enum.map_join remaining, ", ", fn {x,y} -> "#{x}/#{y}" end
raise ArgumentError,
message: "#{inspect impl} did not implement #{inspect protocol}, missing: #{pp}"
end
end
@doc """
Defines meta information about the protocol and internal callbacks.
"""
def meta(env, functions, fallback) do
contents = quote do
def __protocol__(:name), do: __MODULE__
def __protocol__(:functions), do: unquote(:lists.sort(functions))
def __impl_for__(arg) do
case __raw_impl__(arg) do
__MODULE__.Record ->
target = Module.concat(__MODULE__, :erlang.element(1, arg))
try do
target.__impl__
target
rescue
UndefinedFunctionError -> __fallback__
end
other ->
other
end
end
def __impl_for__!(arg) do
if module = __impl_for__(arg) do
module
else
raise Protocol.UndefinedError, protocol: __MODULE__, structure: arg
end
end
defp __fallback__, do: unquote(fallback)
end
Module.eval_quoted env, contents
end
@doc """
Implements the function that detects the protocol and returns
the module to dispatch to. Returns module.Record for records
which should be properly handled by the dispatching function.
"""
def impl_for(env, conversions) do
contents = lc kind inlist conversions, do: each_impl_for(kind, conversions)
# If we don't implement all protocols and any is not in the
# list, we need to add a final clause that returns nil.
if !L.member({ Any, :is_any }, conversions) && length(conversions) < 10 do
contents = contents ++ [quote do
defp __raw_impl__(_) do
nil
end
end]
end
Module.eval_quoted env, contents
end
@doc """
Returns the default conversions according to the given
only/except options.
"""
def conversions_for(module, only, except) do
kinds = all_types
conversions =
if only do
L.map(fn i -> L.keyfind(i, 1, kinds) end, only)
else
except = except || [Any]
L.foldl(fn i, list -> L.keydelete(i, 1, list) end, kinds, except)
end
fallback = cond do
L.keyfind(Tuple, 1, conversions) ->
Module.concat module, Tuple
L.keyfind(Any, 1, conversions) ->
Module.concat module, Any
true ->
nil
end
{ conversions, fallback }
end
## Helpers
defp all_types do
[
{ Record, :is_record },
{ Tuple, :is_tuple },
{ Atom, :is_atom },
{ List, :is_list },
{ BitString, :is_bitstring },
{ Number, :is_number },
{ Function, :is_function },
{ PID, :is_pid },
{ Port, :is_port },
{ Reference, :is_reference },
{ Any, :is_any }
]
end
# Returns a quoted expression that allow to checks
# if a variable named first is built in or not.
defp is_builtin?([{h,_}]) do
quote do
first == unquote(h)
end
end
defp is_builtin?([{h,_}|t]) do
quote do
first == unquote(h) or unquote(is_builtin?(t))
end
end
# Specially handle tuples as they can also be record.
# If this is the case, module.Record will be returned.
defp each_impl_for({ _, :is_record }, conversions) do
quote do
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
first = :erlang.element(1, arg)
case unquote(is_builtin?(conversions)) do
true -> __MODULE__.Tuple
false ->
case atom_to_list(first) do
'Elixir-' ++ _ -> __MODULE__.Record
_ -> __MODULE__.Tuple
end
end
end
end
end
# Special case any as we don't need to generate a guard.
defp each_impl_for({ _, :is_any }, _) do
quote do
defp __raw_impl__(_) do
__MODULE__.Any
end
end
end
# Generate all others protocols.
defp each_impl_for({ kind, fun }, _) do
quote do
defp __raw_impl__(arg) when unquote(fun).(arg) do
Module.concat __MODULE__, unquote(kind)
end
end
end
end
defmodule Protocol.DSL do
@moduledoc false
defmacro def(expression) do
{ name, arity } =
case expression do
{ _, _, args } when args == [] or is_atom(args) ->
raise ArgumentError, message: "protocol functions expect at least one argument"
{ name, _, args } when is_atom(name) and is_list(args) ->
{ name, length(args) }
_ ->
raise ArgumentError, message: "invalid args for defprotocol"
end
# Generate arguments according the arity. The arguments
# are named xa, xb and so forth. We cannot use string
# interpolation to generate the arguments because of compile
# dependencies, so we use the <<>> instead.
args = lc i inlist :lists.seq(1, arity) do
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
end
quote do
# Append new function to the list
@functions [unquote({name, arity})|@functions]
Kernel.def unquote(name).(unquote_splicing(args)) do
args = [unquote_splicing(args)]
case __raw_impl__(xA) do
__MODULE__.Record ->
try do
target = Module.concat(__MODULE__, :erlang.element(1, xA))
apply target, unquote(name), args
rescue
UndefinedFunctionError ->
case __fallback__ do
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
end
end
nil ->
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
other ->
apply other, unquote(name), args
end
end
end
end
end
-84
View File
@@ -1,84 +0,0 @@
defmodule Range do
def __access__(caller, arg), do: Record.access(caller, __MODULE__, __record__(:fields), arg)
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: __MODULE__
def __record__(:fields), do: [{:first,nil},{:last,nil}]
@doc """
Returns a new range based on the given options.
## Examples
Range.new first: 1, last: 10
"""
def new(options) do
{
__MODULE__,
Keyword.get!(options, :first),
Keyword.get!(options, :last)
}
end
@doc """
Returns the first item of the range.
"""
def first(range) do
elem(range, 2)
end
@doc """
Returns the last item of the range.
"""
def last(range) do
elem(range, 3)
end
end
defprotocol Range.Iterator do
@doc """
How to iterate the range, receives the first
and range as arguments. It needs to return a
function that receives an item and returns
a tuple with two elements: the given item
and the next item in the iteration.
"""
def iterator(first, range)
@doc """
Count how many items are in the range.
"""
def count(first, range)
end
defimpl Enum.Iterator, for: Range do
def iterator(Range[first: first] = range) do
iterator = Range.Iterator.iterator(first, range)
{ iterator, iterator.(first) }
end
def count(Range[first: first] = range) do
Range.Iterator.count(first, range)
end
end
defimpl Enum.OrdIterator, for: Range do
def iterator(range) do
Enum.Iterator.Range.iterator(range)
end
def to_list({ h, next }, iterator), do: [h|to_list(iterator.(next), iterator)]
def to_list(:stop, _), do: []
end
defimpl Range.Iterator, for: Number do
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
fn(current) ->
if current > last, do: :stop, else: { current, current + 1 }
end
end
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
last - first + 1
end
end
-384
View File
@@ -1,384 +0,0 @@
defmodule Record do
@moduledoc """
Functions to define and interact with Erlang records
"""
@doc """
Extract record information from an Erlang file and
return the fields as a list of tuples.
## Examples
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
"""
def extract(name, opts) do
Record.Extractor.retrieve(name, opts)
end
@doc """
Implements the access macro used by records.
It returns a quoted expression that represents
the access given by the keywords.
"""
def access(caller, atom, fields, keyword) do
unless is_orddict(keyword) do
raise "expected contents inside brackets to be a Keyword"
end
in_match = caller.in_match?
has_underscore_value = Keyword.key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, 0, nil })
keyword = Keyword.delete keyword, :_
iterator = fn({field, default}, each_keyword) ->
new_fields =
case Keyword.key?(each_keyword, field) do
true -> Keyword.get(each_keyword, field)
false ->
case in_match or has_underscore_value do
true -> underscore_value
false -> Macro.escape(default)
end
end
{ new_fields, Keyword.delete(each_keyword, field) }
end
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
case remaining do
[] -> { :{}, caller.line, [atom|match] }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
end
end
defp is_orddict(list) when is_list(list), do: :lists.all(is_orddict_tuple(&1), list)
defp is_orddict(_), do: false
defp is_orddict_tuple({ x, _ }) when is_atom(x), do: true
defp is_orddict_tuple(_), do: false
@doc """
Main entry point for records definition.
This is invoked directly by `Kernel.defrecord`.
Returns the quoted expression of a module given by name.
"""
def defrecord(name, values, opts) do
moduledoc = Keyword.get(opts, :moduledoc, false)
block = Keyword.get(opts, :do)
definition = Keyword.get(opts, :definition, Record.Definition)
quote do
defmodule unquote(name) do
@moduledoc unquote(moduledoc)
Record.define_functions(__ENV__, unquote(values), unquote(definition))
unquote(block)
end
end
end
@doc false
# Private endpoint that defines the functions for the Record.
def define_functions(env, values, definition) do
# Escape the values so they are valid syntax nodes
values = Macro.escape(values)
contents = [
reflection(values),
getters_and_setters(values, 1, [], definition),
initializers(values),
converters(values)
]
Module.eval_quoted env, contents
end
# Define __record__/1 and __record__/2 as reflection functions
# that returns the record names and fields.
#
# Note that fields are *not* keywords. They are in the same
# order as given as parameter and reflects the order of the
# fields in the tuple.
#
# ## Examples
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# FileInfo.__record__(:name) #=> FileInfo
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
#
defp reflection(values) do
quote do
def __access__(caller, args), do: Record.access(caller, __MODULE__, __record__(:fields), args)
def __record__(kind, _), do: __record__(kind)
def __record__(:name), do: __MODULE__
def __record__(:fields), do: unquote(values)
end
end
# Define initializers methods. For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define three methods:
#
# def new() do
# new([])
# end
#
# def new([]) do
# { FileInfo, nil, nil }
# end
#
# def new(opts) do
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
# end
#
defp initializers(values) do
defaults = lc value inlist values, do: elem(value, 2)
# For each value, define a piece of code that will receive
# an ordered dict of options (opts) and it will try to fetch
# the given key from the ordered dict, falling back to the
# default value if one does not exist.
selective = lc { k, v } inlist values do
quote do: Keyword.get(opts, unquote(k), unquote(v))
end
quote do
def new(), do: new([])
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
def new(tuple) when is_tuple(tuple), do: setelem(tuple, 1, __MODULE__)
end
end
# Define converters method(s). For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define one method, to_keywords, which will return a Keyword
#
# [atime: nil, mtime: nil]
#
defp converters(values) do
sorted = lc { k, _ } inlist values do
index = find_index(values, k, 1)
{ k, quote(do: :erlang.element(unquote(index + 1), record)) }
end
quote do
def to_keywords(record) do
unquote(:orddict.from_list(sorted))
end
end
end
defp find_index([{ k, _ }|_], k, i), do: i
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
# Implement getters and setters for each attribute.
# For a declaration like:
#
# defrecord FileInfo, atime: nil, mtime: nil
#
# It will define four methods:
#
# def :atime.(record) do
# elem(record, 2)
# end
#
# def :atime.(record, value) do
# setelem(record, 2, value)
# end
#
# def :mtime.(record) do
# elem(record, 3)
# end
#
# def :mtime.(record, value) do
# setelem(record, value, 3)
# end
#
# `element` and `setelement` will simply get and set values
# from the record tuple. Notice that `:atime.(record)` is just
# a dynamic way to say `atime(record)`. We need to use this
# syntax as `unquote(key)(record)` wouldn't be valid (as Elixir
# allows you to parenthesis just on specific cases as `foo()`
# and `foo.bar()`)
defp getters_and_setters([{ key, default }|t], i, acc, definition) do
i = i + 1
functions = definition.functions_for(key, default, i)
getters_and_setters(t, i, [functions | acc], definition)
end
defp getters_and_setters([], _i, acc, _), do: acc
end
defmodule Record.Extractor do
@moduledoc false
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def retrieve(name, from: string) do
file = to_char_list(string)
case Erlang.code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> nil
end
retrieve_record(name, realfile)
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def retrieve(name, from_lib: file) do
[app|path] = Erlang.filename.split(to_char_list(file))
case Erlang.code.lib_dir(to_char_list(app)) do
{ :error, _ } ->
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
libpath ->
retrieve_record name, Erlang.filename.join([libpath|path])
end
end
# Retrieve the record with the given name from the given file
defp retrieve_record(name, file) do
records = retrieve_from_file(file)
if record = List.keyfind(records, name, 1) do
parse_record(record)
else
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_from_file(file) do
lc { :attribute, _, :record, record } inlist read_file(file), do: record
end
# Read a file and return its abstract syntax form that also
# includes record and other preprocessor modules. This is done
# by using Erlang's epp_dodger.
defp read_file(file) do
case Erlang.epp_dodger.quick_parse_file(file) do
{ :ok, form } ->
form
other ->
raise "Error parsing file #{to_binary(file)}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of second order tuples where the first element
# is the field and the second is its default value.
defp parse_record({ _name, fields }) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
{ :value, list, _ } = Erlang.erl_eval.expr(cons, [])
list
end
defp parse_field({ :typed_record_field, record_field, _type }) do
parse_field(record_field)
end
defp parse_field({ :record_field, _, key }) do
{ :tuple, 0, [key, {:atom, 0, :nil}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
end
defmodule Record.Definition do
@moduledoc false
# Main entry point. It defines both default functions
# via `default_for` and extensions via `extension_for`.
def functions_for(key, default, i) do
[
default_for(key, default, i),
extension_for(key, default, i)
]
end
# Skip the __exception__ for defexception.
def default_for(:__exception__, _default, _i) do
nil
end
# Define the default functions for each field.
def default_for(key, _default, i) do
bin_update = "update_" <> atom_to_binary(key)
update = binary_to_atom(bin_update)
quote do
def unquote(key).(record) do
:erlang.element(unquote(i), record)
end
def unquote(key).(value, record) do
:erlang.setelement(unquote(i), record, value)
end
def unquote(update).(function, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, function.(current))
end
end
end
# Define extensions based on the default type.
def extension_for(key, default, i) when is_list(default) do
bin_key = atom_to_binary(key)
prepend = :"prepend_#{bin_key}"
merge = :"merge_#{bin_key}"
quote do
def unquote(prepend).(value, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, value ++ current)
end
def unquote(merge).(value, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, Keyword.merge(current, value))
end
end
end
def extension_for(key, default, i) when is_number(default) do
bin_key = atom_to_binary(key)
increment = :"increment_#{bin_key}"
quote do
def unquote(increment).(value // 1, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, current + value)
end
end
end
def extension_for(key, default, i) when is_boolean(default) do
bin_key = atom_to_binary(key)
toggle = :"toggle_#{bin_key}"
quote do
def unquote(toggle).(value // false, record) do
current = :erlang.element(unquote(i), record)
:erlang.setelement(unquote(i), record, not current)
end
end
end
def extension_for(_, _, _), do: nil
end
-267
View File
@@ -1,267 +0,0 @@
defmodule Regex do
@moduledoc %B"""
Regular expressions for Elixir built on top of the re module
in the Erlang Standard Library. More information can be found
on re documentation: http://www.erlang.org/doc/man/re.html
Regular expressions in Elixir can be created using Regex.compile!
or using the special form with `%r`:
# A simple regular expressions that matches foo anywhere in the string
%r/foo/
# A regular expression with case insensitive options and handle unicode chars
%r/foo/iu
The re module provides several options, the one available in Elixir, followed by
their shortcut in parenthesis, are:
* unicode (u) - used when you want to match against specific unicode characters
* caseless (i) - add case insensitivity
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
`(*CRLF)` or `(*ANY)` according to re documentation
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
You need to use `\A` and `\z` to match the end or beginning of the string
* extended (x) - whitespace characters are ignored except when escaped and
allow `#` to delimit comments
* firstline (f) - forces the unanchored pattern to match before or at the first
newline, though the matched text may continue over the newline
* ungreedy (r) - invert the "greediness" of the regexp
The options not available are:
* anchored - not available, use `^` or `\A` instead
* dollar_endonly - not available, use `\z` instead
* no_auto_capture - not available, use `?:` instead
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
or `(*ANY)` at the beginning of the regexp according to the re documentation
Most of the functions in this module accept either a binary or a char list
as subject. The result is based on the argument (a binary will return
a binary, a char list will return a char list).
"""
defexception CompileError, message: "regex could not be compiled"
@doc """
Compiles the regular expression according to the given options.
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
"""
def compile(source, options // "") do
source = to_binary(source)
options = to_binary(options)
re_opts = translate_options(options)
case Erlang.re.compile(source, re_opts) do
{ :ok, compiled } ->
{ :ok, { Regex, compiled, source, options } }
error ->
error
end
end
@doc """
Compiles the regular expression according to the given options.
Fails with `Regex.CompileError` if the regex cannot be compiled.
"""
def compile!(source, options // "") do
case compile(source, options) do
{ :ok, regex } -> regex
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
end
end
@doc """
Runs the regular expression against the given string
and returns the index (zero indexes) where the first
match occurs, nil otherwise.
## Examples
Regex.index %r/c(d)/, "abcd" #=> 3
Regex.index %r/e/, "abcd" #=> nil
"""
def index({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :first, :index }]) do
:nomatch -> nil
{ :match, [{index,_}] } -> index
end
end
@doc """
Returns a boolean if there was a match or not.
## Examples
Regex.match? %r/foo/, "foo" #=> true
Regex.match? %r/foo/, "bar" #=> false
"""
def match?({ Regex, compiled, _, _ }, string) do
:nomatch != Erlang.re.run(string, compiled)
end
@doc """
Runs the regular expression against the given string.
It returns a list with all matches or nil if no match ocurred.
## Examples
Regex.run %r/c(d)/, "abcd" #=> ["cd", "d"]
Regex.run %r/e/, "abcd" #=> nil
"""
def run({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :all, return_for(string) }]) do
:nomatch ->
nil
{ :match, results } ->
results
end
end
@doc """
Returns a list with the match indexes in the given string.
The matches are tuples where the first element is the index
(zero indexed) the match happened and the second is the length
of the match.
## Examples
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
Regex.indexes %r/e/, "abcd" #=> nil
"""
def indexes({ Regex, compiled, _, _ }, string) do
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }]) do
:nomatch ->
nil
{ :match, results } ->
results
end
end
@doc """
Returns the underlying re_pattern in the regular expression.
"""
def re_pattern({ Regex, compiled, _, _ }) do
compiled
end
@doc """
Returns the regex source as binary.
## Examples
Regex.source %r(foo) #=> "foo"
"""
def source({ Regex, _, source, _ }) do
source
end
@doc """
Returns the regex options as a list.
## Examples
Regex.opts %r(foo)m #=> 'm'
"""
def opts({ Regex, _, _, opts }) do
opts
end
@doc """
Same as run, but scans the target several times collecting all matches of
the regular expression. A list is returned with each match. If the item in
the list is a binary, it means there were no captures. If the item is another
list, each element in this secondary list is a capture.
## Examples
Regex.scan %r/c(d|e)/, "abcd abce" #=> [["d"], ["e"]]
Regex.scan %r/c(?:d|e)/, "abcd abce" #=> ["cd", "ce"]
Regex.scan %r/e/, "abcd" #=> []
"""
def scan({ Regex, compiled, _, _ }, string) do
options = [{ :capture, :all, return_for(string) }, :global, { :offset, 0 }]
case Erlang.re.run(string, compiled, options) do
:nomatch -> []
{ :match, results } ->
lc result inlist results do
case result do
[t] -> t
[h|t] -> t
end
end
end
end
@doc """
Split the given target in the number of parts specified. If no ammount
of parts is given, it defaults to :infinity.
"""
def split({ Regex, compiled, _, _ }, string, parts // :infinity) do
options = [{ :return, return_for(string) }, :trim, { :parts, parts }]
Erlang.re.split(string, compiled, options)
end
@doc %B"""
Receives a string and a replacement and returns a string where the
first match of the regular expressions is replaced by replacement.
Inside the replacement, you can either give "&" to access the whole
regular expression or \N, where N is in integer to access an specific
matching parens.
## Examples
Regex.replace(%r/d/, "abc", "d") #=> "abc"
Regex.replace(%r/b/, "abc", "d") #=> "adc"
Regex.replace(%r/b/, "abc", "[&]") #=> "a[b]c"
Regex.replace(%r/b/, "abc", "[\\&]") #=> "a[&]c"
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
"""
def replace({ Regex, compiled, _, _ }, string, replacement) do
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }])
end
@doc """
The same as replace, but replaces all parts where the regular
expressions matches in the string. Please read `replace/3` for
documentation and examples.
"""
def replace_all({ Regex, compiled, _, _ }, string, replacement) do
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
end
# Helpers
@doc false
# Unescape map function used by Binary.unescape.
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(_), do: false
# Private Helpers
defp return_for(element) when is_binary(element), do: :binary
defp return_for(element) when is_list(element), do: :list
defp translate_options(<<?u, t|:binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t|:binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t|:binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t|:binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t|:binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t|:binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
defp translate_options(<<?m, t|:binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
end
-156
View File
@@ -1,156 +0,0 @@
defmodule System do
@moduledoc """
The System module provides access to some variables used or
maintained by the VM and to functions that interact strongly
with the VM or the host system.
"""
# Tries to run `git rev-parse HEAD`. In case of success
# returns the commit sha, otherwise returns an empty string.
defmacrop get_head_sha do
if :os.find_executable('git') do
data = :os.cmd('git rev-parse HEAD')
Regex.replace_all %r/\n/, to_binary(data), ""
else
""
end
end
# Get the date at compilation time.
defmacrop get_date do
list_to_binary :httpd_util.rfc1123_date
end
@doc """
Returns Elixir's version as binary.
"""
def version, do: "0.6.0"
@doc """
Returns a tuple { Elixir version, commit sha-1, build date }.
The format of the return value may change in a future release. Please
make sure your code doesn't depend on it.
"""
def build_info do
{ version, get_head_sha, get_date }
end
@doc """
Returns the list of command-line arguments passed to the program.
"""
def argv do
Erlang.gen_server.call(:elixir_code_server, :argv)
end
@doc """
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook on scripted mode.
The function must expect the exit status code
as argument.
"""
def at_exit(fun) when is_function(fun, 1) do
server_call { :at_exit, fun }
end
@doc """
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) do
list_to_binary :os.cmd(to_char_list(command))
end
@doc """
This functions looks up an executable program given
its name using the environment variable PATH on Unix
and Windows.
If `command` is a char list, a char list is returned.
Returns a binary otherwise.
"""
def find_executable(command) when is_list(command) do
:os.find_executable(command) || nil
end
def find_executable(command) do
case :os.find_executable(to_char_list(command)) do
false -> nil
other -> list_to_binary(other)
end
end
@doc """
Returns a list of all environment variables. Each environment variable is
given as a single string of the format "VarName=Value", where VarName is the
name of the variable and Value its value.
"""
def get_env do
Enum.map :os.getenv, list_to_binary &1
end
@doc """
Returns the value of the environment variable
`varname` as a binary, or nil if the environment
variable is undefined.
"""
def get_env(varname) do
case :os.getenv(to_char_list(varname)) do
false -> nil
other -> list_to_binary(other)
end
end
@doc """
Returns the process identifier of the current Erlang emulator
in the format most commonly used by the operating system environment.
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
"""
def get_pid, do: list_to_binary(:os.getpid)
@doc """
Sets a new `value` for the environment variable `varname`.
"""
def put_env(varname, value) do
:os.putenv to_char_list(varname), to_char_list(value)
end
@doc """
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@doc """
Get the stacktrace.
"""
def stacktrace do
filter_stacktrace Erlang.erlang.get_stacktrace
end
## Helpers
# Filter stacktrace by removing internal BOOTSTRAP calls.
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: filter_stacktrace(t)
defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, info }|t]),
do: filter_stacktrace([{ Kernel, :defmodule, 2, info }|t])
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
defp filter_stacktrace([]), do: []
defp server_call(args) do
Erlang.gen_server.call(:elixir_code_server, args)
end
end
-2
View File
@@ -1,2 +0,0 @@
defmodule Tuple do
end
-189
View File
@@ -1,189 +0,0 @@
defmodule URI do
@on_load :preload_parsers
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil,
specifics: nil]
import Bitwise
@moduledoc """
Utilities for working with and creating URIs.
"""
@doc """
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of k=v&k2=v2... where keys and values are
URL encoded as per encode. Keys and values can be any term
that implements the Binary.Chars protocol (i.e. can be converted
to binary).
"""
def encode_query(l), do: Enum.join(Enum.map(l, pair(&1)), "&")
@doc """
Given a query string of the form "key1=value1&key=value2...", produces an
orddict with one entry for each key-value pair. Each key and value will be a
binary. It also does percent-unescaping of both keys and values.
Returns nil if the query string is malformed.
"""
def decode_query(q, dict // Orddict.new) do
if Regex.match?(%r/^\s*$/, q) do
dict
else
parts = Regex.split %r/&/, to_binary(q)
impl = Dict.__impl_for__!(dict)
try do
List.foldl parts, dict, fn kvstr, acc ->
case Regex.split(%r/=/, kvstr) do
[ key, value ] when key != "" ->
impl.put acc, decode(key), decode(value)
_ ->
throw :malformed_query_string
end
end
catch
:malformed_query_string -> nil
end
end
end
defp pair({k, v}) do
encode(to_binary(k)) <> "=" <> encode(to_binary(v))
end
@doc """
Percent (URL) encodes a URI.
"""
def encode(s), do: bc <<c>> inbits s, do: <<percent(c)|:binary>>
defp percent(32), do: <<?+>>
defp percent(?-), do: <<?->>
defp percent(?_), do: <<?_>>
defp percent(?.), do: <<?.>>
defp percent(c) when
c >= ?0 and c <= ?9 when
c >= ?a and c <= ?z when
c >= ?A and c <= ?Z do
<<c>>
end
defp percent(c), do: escape_byte(c)
defp escape_byte(c), do: "%" <> hex(c)
defp hex(n) when n <= 9, do: <<n + ?0>>
defp hex(n) when n > 15 do
hex(bsr(n, 4)) <> hex(band(n, 15))
end
defp hex(n), do: <<n + ?A - 10>>
@doc """
Unpercent (URL) decodes a URI.
"""
def decode(<<?%, hex1, hex2, tail |:binary >>) do
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
end
def decode(<<head, tail |:binary >>) do
<<check_plus(head)>> <> decode(tail)
end
def decode(<<>>), do: <<>>
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
defp hex2dec(n) when n in ?0..?9, do: n - ?0
defp check_plus(?+), do: 32
defp check_plus(c), do: c
@doc """
Parses a URI into components.
URIs have portions that are handled specially for the
particular scheme of the URI. For example, http and https
have different default ports. Sometimes the parsing
of portions themselves are different. This parser
is extensible via behavior modules. If you have a
module named URI.MYSCHEME with a function called
'parse' that takes a single argument, the generically
parsed URI, that function will be called when this
parse function is passed a URI of that scheme. This
allows you to build on top of what the URI library
currently offers. You also need to define default_port
which takes 0 arguments and returns the default port
for that particular scheme. Take a look at URI.HTTPS for an
example of one of these extension modules.
"""
def parse(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
info = URI.Info[
scheme: scheme, path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
scheme_specific(scheme, info)
end
defp scheme_specific(scheme, info) do
if scheme do
module =
try do
Module.safe_concat(URI, :string.to_upper(binary_to_list(scheme)))
rescue
ArgumentError -> nil
end
if module && match?({:module,^module}, Code.ensure_loaded(module)) do
module.parse(default_port(info, module))
else
info
end
else
info
end
end
defp default_port(info, module) do
if info.port, do: info, else: info.port(module.default_port)
end
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: list_to_integer(binary_to_list(port))
{ userinfo, host, port }
end
# Regex.run returns empty strings sometimes. We want
# to replace those with nil for consistency.
defp nillify(l) do
lc s inlist l do
if size(s) > 0 do
s
else
nil
end
end
end
# Reference parsers so the parse/1 doesn't fail
# on safe_concat.
defp preload_parsers do
parsers = [URI.FTP, URI.HTTP, URI.HTTPS, URI.LDAP, URI.SFTP, URI.TFTP]
Enum.each parsers, Code.ensure_loaded(&1)
:ok
end
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.FTP do
@behavior URI.Parser
def default_port(), do: 21
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTP do
@behavior URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.HTTPS do
@behavior URI.Parser
def default_port(), do: 443
def parse(info), do: info
end
-7
View File
@@ -1,7 +0,0 @@
defmodule URI.LDAP do
@behavior URI.Parser
def default_port(), do: 389
# TODO: LDAP specific parsing.
def parse(info), do: info
end
-11
View File
@@ -1,11 +0,0 @@
defmodule URI.Parser do
@moduledoc """
Defines the behavior for each URI.Parser.
Check URI.HTTP for a possible implementation.
"""
def behaviour_info(:callbacks) do
[parse: 1,
default_port: 0]
end
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.SFTP do
@behavior URI.Parser
def default_port(), do: 22
def parse(info), do: info
end
-5
View File
@@ -1,5 +0,0 @@
defmodule URI.TFTP do
@behavior URI.Parser
def default_port(), do: 69
def parse(info), do: info
end
-27
View File
@@ -1,27 +0,0 @@
{erl_first_files, ["elixir_transform"]}.
{erl_opts, [
warn_unused_vars,
warn_export_all,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_bif_clash,
warn_unused_record,
warn_deprecated_function,
warn_obsolete_guard,
strict_validation,
warn_exported_vars,
%% warn_export_vars,
%% warn_missing_spec,
%% warn_untyped_record,
%% warnings_as_errors,
no_debug_info
]}.
{yrl_opts, [
{report, true},
{verbose, false}
]}.
{require_otp_vsn,"R15"}.
-127
View File
@@ -1,127 +0,0 @@
-module(elixir).
-behaviour(application).
-export([start_cli/0, start_app/0,
scope_for_eval/1, eval/2, eval/3, eval/4,
eval_quoted/2, eval_quoted/3, eval_quoted/4,
eval_forms/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
% OTP APPLICATION API
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
%% Set the shell to unicode so printing inside files work
io:setopts(standard_io, [{encoding,unicode}]),
io:setopts(standard_error, [{encoding,unicode}]),
elixir_sup:start_link([]).
stop(_S) ->
ok.
config_change(_Changed, _New, _Remove) ->
ok.
%% ELIXIR ENTRY POINTS
% Start the Elixir app. This is the proper way to boot Elixir from
% inside an Erlang process.
start_app() ->
case lists:keyfind(?MODULE, 1, application:loaded_applications()) of
false -> application:start(?MODULE);
_ -> ok
end.
% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
start_app(),
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
%% EVAL HOOKS
scope_for_eval(Opts) ->
File = case orddict:find(file, Opts) of
{ ok, F } -> to_binary(F);
error -> <<"nofile">>
end,
Local = case orddict:find(delegate_locals_to, Opts) of
{ ok, L } -> L;
error -> nil
end,
#elixir_scope{file=File,local=Local}.
%% String evaluation
eval(String, Binding) -> eval(String, Binding, []).
eval(String, Binding, Opts) ->
case orddict:find(line, Opts) of
{ ok, Line } -> [];
error -> Line = 1
end,
eval(String, Binding, Line, scope_for_eval(Opts)).
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
Forms = elixir_translator:forms(String, Line, File),
eval_forms(Forms, Binding, S).
%% Quoted evaluation
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
eval_quoted(Tree, Binding, Opts) ->
case orddict:find(line, Opts) of
{ ok, Line } -> [];
error -> Line = 1
end,
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) ->
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
%% Handle forms evaluation internally, it is an
%% internal API not meant for external usage.
eval_forms(Tree, Binding, RawScope) ->
Scope = RawScope#elixir_scope{vars=binding_dict(Binding)},
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
case ParseTree of
[] -> { nil, Binding, NewScope };
_ ->
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, normalize_binding(Binding)),
{Value, final_binding(NewBinding, NewScope#elixir_scope.vars), NewScope }
end.
%% INTERNAL HELPERS
to_binary(Bin) when is_binary(Bin) -> Bin;
to_binary(List) when is_list(List) -> list_to_binary(List).
binding_dict(List) -> binding_dict(List, dict:new()).
binding_dict([{H,_}|T], Dict) -> binding_dict(T, dict:store(H, H, Dict));
binding_dict([], Dict) -> Dict.
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
case atom_to_list(Var) of
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
_ ->
RealName = dict:fetch(Var, Vars),
RealValue = proplists:get_value(RealName, Binding, nil),
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
end;
final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
normalize_binding(Binding) ->
Keyword = orddict:from_list(Binding),
case orddict:find('_@MODULE', Keyword) of
{ ok, _ } -> Keyword;
_ -> orddict:store('_@MODULE', nil, Keyword)
end.
-84
View File
@@ -1,84 +0,0 @@
-module(elixir_aliases).
-export([first/1, last/1, concat/1, safe_concat/1, lookup/2,
format_error/1, ensure_loaded/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Ensure a module is loaded before its usage.
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
ok;
ensure_loaded(Line, Ref, S) ->
try
Ref:module_info(compile)
catch
error:undef ->
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
end.
%% Receives an atom and returns the first alias.
first(Atom) ->
First = first(atom_to_list(Atom), []),
list_to_atom("Elixir-" ++ First).
first("Elixir-" ++ Rest, []) -> first(Rest, []);
first([$-|_], Acc) -> lists:reverse(Acc);
first([H|T], Acc) -> first(T, [H|Acc]);
first([], Acc) -> lists:reverse(Acc).
%% Receives an atom and returns the last alias.
last(Atom) ->
Last = last(lists:reverse(atom_to_list(Atom)), []),
list_to_atom("Elixir-" ++ Last).
last([$-|_], Acc) -> Acc;
last([H|T], Acc) -> last(T, [H|Acc]);
last([], Acc) -> Acc.
%% Receives a list of atoms representing modules
%% and concatenate them.
concat(Args) -> list_to_atom(raw_concat(Args)).
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
raw_concat(['Elixir'|Args]) -> do_concat(Args);
raw_concat(Args) -> do_concat(Args).
do_concat(Args) ->
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
"Elixir" ++ lists:concat(Aliases).
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
dot_to_dash(List) ->
[case X of
$. -> $-;
_ -> X
end || X <- List].
%% Lookup an alias in the current scope
lookup(Else, Dict) ->
case orddict:find(Else, Dict) of
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
_ -> Else
end.
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
format_error({scheduled_module, Module}) ->
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
[elixir_errors:inspect(Module)]).
-237
View File
@@ -1,237 +0,0 @@
%% Handle code related to rocket args, guard and -> matching
%% for case, fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
-import(elixir_scope, [umergec/2]).
-include("elixir.hrl").
%% Get pairs from a clause.
get_pairs(Line, Key, Clauses, S) ->
get_pairs(Line, Key, Clauses, S, false).
get_pairs(Line, Key, Clauses, S, AllowNil) ->
case orddict:find(Key, Clauses) of
{ ok, { '->', _, Pairs } } ->
[{ Key, Left, Right } || { Left, Right } <- Pairs];
{ ok, nil } when AllowNil ->
[];
{ ok, _ } ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
_ ->
[]
end.
% Function for translating assigns.
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=dict:new()}),
{ Result, NewS#elixir_scope{context=Context} };
assigns(Fun, Args, S) -> Fun(Args, S).
%% Function for translating a block that is preceeded by an
%% assignment and optional guards. This is used by def* and fn.
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
{ Args, Guards } = extract_guards(BareArgs),
assigns_block(Line, Fun, Args, Exprs, Guards, S).
assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
FArgs = listify(TArgs),
SG = SA#elixir_scope{context=guard, extra_guards=nil},
Extra = SA#elixir_scope.extra_guards,
FGuards = case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end,
% Uncompact expressions from the block.
case TExprs of
[{ block, _, FExprs }] -> [];
_ -> FExprs = TExprs
end,
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
% Translate/Extract guards from the given expression.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
extract_guards(Else) -> { Else, [] }.
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
extract_or_clauses(Term, Acc) -> [Term|Acc].
% Extract name and args from the given expression.
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
% Extract guards when it is in the last element of the args
extract_last_guards([]) -> { [], [] };
extract_last_guards(Args) ->
{ Left, Right } = elixir_tree_helpers:split_last(Args),
{ Bare, Guards } = extract_guards(Right),
{ Left ++ [Bare], Guards }.
% Function for translating macros with match style like case and receive.
match(Line, DecoupledClauses, RawS) ->
S = RawS#elixir_scope{clause_vars=dict:new()},
case DecoupledClauses of
[DecoupledClause] ->
{ TDecoupledClause, TS } = each_clause(Line, DecoupledClause, S),
{ [TDecoupledClause], TS };
_ ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {Acc, CV}) ->
{ TX, TAcc } = each_clause(Line, X, Acc),
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
end,
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
% Now get all the variables defined inside each clause
CV = lists:reverse(RawCV),
NewVars = lists:umerge([lists:sort(dict:fetch_keys(X)) || X <- CV]),
case NewVars of
[] -> { TClauses, TS };
_ ->
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
% a list of tuples where the first element is the variable name,
% the second one is the new pointer to the variable and the third
% is the old pointer.
{ FinalVars, FS } = lists:mapfoldl(fun normalize_vars/2, TS, NewVars),
% Defines a tuple that will be used as left side of the match operator
LeftVars = [{var, Line, NewValue} || {_, NewValue,_} <- FinalVars],
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, FS),
% Expand all clauses by adding a match operation at the end that assigns
% variables missing in one clause to the others.
Expander = fun(Clause, Counter) ->
ClauseVars = lists:nth(Counter, CV),
RightVars = [normalize_clause_var(Var, OldValue, ClauseVars) || {Var, _, OldValue} <- FinalVars],
AssignExpr = generate_match(Line, LeftVars, RightVars),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
% If the last sentence has a match clause, we need to assign its value
% in the variable list. If not, we insert the variable list before the
% final clause in order to keep it tail call optimized.
FinalClauseExprs = case has_match_tuple(Final) of
true ->
case Final of
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
[UserVar,AssignExpr,Final|RawClauseExprs];
_ ->
StorageExpr = { match, Line, StorageVar, Final },
[StorageVar,AssignExpr,StorageExpr|RawClauseExprs]
end;
false ->
[Final,AssignExpr|RawClauseExprs]
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
{ FinalClause, Counter + 1 }
end,
{ FClauses, _ } = lists:mapfoldl(Expander, 1, TClauses),
{ FClauses, SS }
end
end.
% Handle each key/value clause pair and translate them accordingly.
each_clause(Line, { do, [Condition], Expr }, S) ->
assigns_block(Line, fun elixir_translator:translate_each/2, Condition, [Expr], S);
each_clause(Line, { 'after', [Condition], Expr }, S) ->
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
{ TBody, SB } = elixir_translator:translate([Expr], SC),
{ { clause, Line, [TCondition], [], TBody }, SB };
each_clause(Line, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
each_clause(Line, { Key, _, _ }, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s", [Key]).
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
% existing assignments instead of creating new ones every time.
has_match_tuple({'receive', _, _, _, _}) ->
true;
has_match_tuple({'receive', _, _}) ->
true;
has_match_tuple({'case', _, _, _}) ->
true;
has_match_tuple({match, _, _, _}) ->
true;
has_match_tuple(H) when is_tuple(H) ->
has_match_tuple(tuple_to_list(H));
has_match_tuple(H) when is_list(H) ->
lists:any(fun has_match_tuple/1, H);
has_match_tuple(_) -> false.
% Normalize the given var checking its existence in the scope var dictionary.
normalize_vars(Var, #elixir_scope{vars=Vars, clause_vars=ClauseVars} = S) ->
{ { _, _, NewValue }, NS } = elixir_scope:build_erl_var(0, S),
FS = NS#elixir_scope{
vars=dict:store(Var, NewValue, Vars),
clause_vars=dict:store(Var, NewValue, ClauseVars)
},
Expr = case dict:find(Var, Vars) of
{ ok, OldValue } -> { var, 0, OldValue };
error -> { atom, 0, nil }
end,
{ { Var, NewValue, Expr }, FS }.
% Normalize a var by checking if it was defined in the clause.
% If so, use it, otherwise use from main scope.
normalize_clause_var(Var, OldValue, ClauseVars) ->
case dict:find(Var, ClauseVars) of
{ ok, ClauseValue } -> { var, 0, ClauseValue };
error -> OldValue
end.
%% generate_match
generate_match(Line, [Left], [Right]) ->
{ match, Line, Left, Right };
generate_match(Line, LeftVars, RightVars) ->
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
%% Listify
listify(Expr) when not is_list(Expr) -> [Expr];
listify(Expr) -> Expr.
-65
View File
@@ -1,65 +0,0 @@
-module(elixir_code_server).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-behavior(gen_server).
-record(elixir_code_server, {
argv=[],
loaded=[],
at_exit=[],
compiler_options=[{docs,true}]
}).
start_link() ->
{ ok, _ } = gen_server:start_link({local, elixir_code_server}, ?MODULE, [], []).
init(_args) ->
process_flag(trap_exit, true),
{ ok, #elixir_code_server{} }.
handle_call({loaded, Path}, _From, Config) ->
Current = Config#elixir_code_server.loaded,
case lists:member(Path, Current) of
true ->
{ reply, duplicated, Config#elixir_code_server{loaded=Current} };
false ->
{ reply, ok, Config#elixir_code_server{loaded=[Path|Current]} }
end;
handle_call({at_exit, AtExit}, _From, Config) ->
{ reply, ok, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
handle_call({argv, Argv}, _From, Config) ->
{ reply, ok, Config#elixir_code_server{argv=Argv} };
handle_call({compiler_options, Options}, _From, Config) ->
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
{ reply, ok, Config#elixir_code_server{compiler_options=Final} };
handle_call(loaded, _From, Config) ->
{ reply, Config#elixir_code_server.loaded, Config };
handle_call(at_exit, _From, Config) ->
{ reply, Config#elixir_code_server.at_exit, Config };
handle_call(argv, _From, Config) ->
{ reply, Config#elixir_code_server.argv, Config };
handle_call(compiler_options, _From, Config) ->
{ reply, Config#elixir_code_server.compiler_options, Config };
handle_call(_Request, _From, Config) ->
{ reply, undef, Config }.
handle_cast(_Request, Config) ->
{ noreply, Config }.
handle_info(_Request, Config) ->
{ noreply, Config }.
terminate(Reason, Config) ->
io:format("[FATAL] ~p crashed:\n~p~n", [?MODULE, Reason]),
io:format("[FATAL] ~p snapshot:\n~p~n", [?MODULE, Config]),
ok.
code_change(_Old, Config, _Extra) ->
{ ok, Config }.
-234
View File
@@ -1,234 +0,0 @@
-module(elixir_compiler).
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/5]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Public API
%% Get compilation options.
get_opt(Key) -> get_opt(Key, get_opts()).
get_opt(Key, Dict) ->
case orddict:find(Key, Dict) of
{ ok, Value } -> Value;
error -> false
end.
get_opts() ->
gen_server:call(elixir_code_server, compiler_options).
%% Compiles the given string.
string(Contents, File) when is_list(Contents), is_binary(File) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
Forms = elixir_translator:forms(Contents, 1, File),
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
end.
%% Compile a file, return a tuple of module names and binaries.
file(Relative) when is_binary(Relative) ->
File = filename:absname(Relative),
case file:read_file(File) of
{ok, Bin} ->
string(unicode:characters_to_list(Bin), File);
Error ->
erlang:error(Error)
end.
%% Compiles a file to the given path (directory).
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File),
[binary_to_path(X, Path) || X <- Lists],
Lists.
%% Evaluates the contents/forms by compiling them to an Erlang module.
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=nil} = S) ->
eval_forms(Forms, Line, Module, nil, Vars, S);
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
eval_forms(Forms, Line, Module, Value, Vars, S).
eval_forms(Forms, Line, RawModule, Value, Vars, S) ->
case (Value == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, S);
false -> code_loading_compilation(Forms, Line, RawModule, Value, Vars, S)
end.
eval_compilation(Forms, Vars, S) ->
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
{ Result, FS }.
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
Module = escape_module(RawModule),
{ Exprs, FS } = elixir_translator:translate(Forms, S),
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
Args = [X || { _, _, X } <- Vars],
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
Res = Mod:'BOOTSTRAP'(Value, Args),
code:purge(Module),
code:delete(Module),
Res
end), FS }.
%% Internal API
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, S, Callback) ->
Options = case get_opt(debug_info) of
true -> [debug_info];
_ -> []
end,
module(Forms, S#elixir_scope.file, Options, false, Callback).
module(Forms, File, Options, Bootstrap, Callback) when
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Listname = binary_to_list(File),
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, File, Warnings),
format_errors(File, Errors)
end.
%% Compile core files for bootstrap.
%% Invoked from the Makefile.
core() ->
elixir:start_app(),
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
[core_file(File) || File <- core_main()],
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
Files = lists:append(AllLists) -- core_main(),
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
%% HELPERS
no_auto_import() ->
{ attribute, 0, compile, {
no_auto_import, erlang:module_info(exports) } }.
module_form(Exprs, Line, File, Module, Vars) when
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
{ cons, Line, { var, Line, Var }, Acc }
end, { nil, Line }, Vars),
Args = [{ var, Line, '_@MODULE'}, Cons],
[
{ attribute, Line, file, { binary_to_list(File), 1 } },
{ attribute, Line, module, Module },
{ attribute, Line, export, [{ 'BOOTSTRAP', 2 }] },
{ function, Line, 'BOOTSTRAP', length(Args), [
{ clause, Line, Args, [], Exprs }
] }
].
%% Fast compilation is available?
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
allows_fast_compilation([]) -> true;
allows_fast_compilation(_) -> false.
%% Escape the module name, removing slashes, dots,
%% so it can be loaded by Erlang.
escape_module(Module) when is_atom(Module) ->
escape_module(atom_to_list(Module));
escape_module(Module) when is_binary(Module) ->
escape_module(binary_to_list(Module));
escape_module(Module) when is_list(Module) ->
list_to_atom(escape_each(Module)).
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
[H|escape_each(T)];
escape_each([_|T]) ->
[$_|escape_each(T)];
escape_each([]) -> [].
%% Receives a module Binary and outputs it in the given path.
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
ok = file:write_file(Path, Binary),
Path.
%% CORE FILES COMPILATION
core_file(File) ->
try
Lists = file(list_to_binary(File)),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~s~n", [File])
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
exit(1)
end.
core_list() ->
[
"lib/elixir/lib/uri/parser.ex",
"lib/elixir/lib/elixir/formatter.ex",
"lib/elixir/lib/dict.ex",
"lib/elixir/lib/*/*.ex",
"lib/elixir/lib/*.ex"
].
core_main() ->
[
"lib/elixir/lib/kernel.ex",
"lib/elixir/lib/macro/env.ex",
"lib/elixir/lib/macro.ex",
"lib/elixir/lib/keyword.ex",
"lib/elixir/lib/record.ex",
"lib/elixir/lib/module.ex",
"lib/elixir/lib/list.ex",
"lib/elixir/lib/protocol.ex",
"lib/elixir/lib/enum.ex",
"lib/elixir/lib/exception.ex",
"lib/elixir/lib/binary/inspect.ex",
"lib/elixir/lib/binary/chars.ex",
"lib/elixir/lib/list/chars.ex",
"lib/elixir/lib/gen_server/behavior.ex",
"lib/elixir/lib/code.ex"
].
%% ERROR HANDLING
format_errors(_File, []) ->
exit({nocompile, "compilation failed but no error was raised"});
format_errors(File, Errors) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, File, Warnings) ->
lists:foreach(fun ({_, Each}) ->
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
end, Warnings).
-304
View File
@@ -1,304 +0,0 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
-module(elixir_def).
-export([table/1,
build_table/1,
delete_table/1,
reset_last/1,
wrap_definition/7,
store_definition/8,
store_each/8,
unwrap_stored_definitions/1,
format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Table management functions. Called internally.
table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
build_table(Module) ->
FunctionTable = table(Module),
ets:new(FunctionTable, [set, named_table, public]),
ets:insert(FunctionTable, { last, [] }),
FunctionTable.
delete_table(Module) ->
ets:delete(table(Module)).
%% Reset the last item. Useful when evaling code.
reset_last(Module) ->
ets:insert(table(Module), { last, [] }).
%% Wraps the function into a call to store_definition once the function
%% definition is read. The function is compiled into a meta tree to ensure
%% we will receive the full function.
%%
%% We need to wrap functions instead of eagerly defining them to ensure
%% functions inside branches won't propagate, for example:
%%
%% if false do
%% def bar, do: 1
%% else
%% def bar, do: 2
%% end
%%
%% If we just analyzed the compiled structure (i.e. the function availables
%% before evaluating the function body), we would see both definitions.
wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
MetaS = elixir_scope:serialize(S),
Invoke = [
{atom, Line, Kind},
{integer, Line, Line},
{var, Line, '_@MODULE'},
Name,
Args,
Guards,
Expr,
MetaS
],
?ELIXIR_WRAP_CALL(Line, ?MODULE, store_definition, Invoke).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Expr, RawS) ->
S = elixir_scope:deserialize(RawS),
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
store_definition(Kind, Line, Module, Name, Args, Guards, RawExpr, RawS) ->
Arity = length(Args),
DS = elixir_scope:deserialize(RawS),
S = DS#elixir_scope{function={Name,Arity}, module=Module},
Expr = def_body(Line, RawExpr),
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
CO = elixir_compiler:get_opts(),
compile_docs(Kind, Line, Module, Name, Arity, Args, TS, CO),
File = TS#elixir_scope.file,
Table = table(Module),
Stack = S#elixir_scope.macro,
Location = retrieve_file(Module, CO),
%% Store function
case RawExpr of
skip_definition -> [];
_ ->
compile_super(Module, TS),
CheckClauses = S#elixir_scope.check_clauses,
store_each(CheckClauses, Kind, File, Location,
Stack, Table, length(Defaults), Function)
end,
%% Store defaults
[store_each(false, Kind, File, Location, Stack, Table, 0,
function_for_default(Kind, Name, Default)) || Default <- Defaults],
{ Name, Arity }.
def_body(_Line, skip_definition) -> nil;
def_body(_Line, [{ do, Expr }]) -> Expr;
def_body(Line, Else) -> { 'try', Line, [Else] }.
%% Compile super clause
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
elixir_def_overridable:store(Module, Function, true);
compile_super(_Module, _S) -> [].
%% Compile docs
compile_docs(Kind, Line, Module, Name, Arity, Args, S, CO) ->
case elixir_compiler:get_opt(docs, CO) of
false -> [];
true ->
case 'Elixir.Module':compile_doc(Module, Line, Kind, { Name, Arity }, Args) of
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.file,
{ Line, ?MODULE, { Message, { Name, Arity } } });
_ -> []
end
end.
retrieve_file(Module, CO) ->
case elixir_compiler:get_opt(internal, CO) of
true -> [];
_ ->
case 'Elixir.Module':read_attribute(Module, file) of
nil -> [];
Else ->
'Elixir.Module':delete_attribute(Module, file),
Else
end
end.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
Arity = length(Args),
IsMacro = is_macro(Kind),
%% Macros receive a special argument on invocation. Notice it does
%% not affect the arity of the stored function, but the clause
%% already contains it.
ExtendedArgs = case IsMacro of
true -> [{ '_@CALLER', Line, nil }|Args];
false -> Args
end,
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
{ TClause, TS } = elixir_clauses:assigns_block(Line,
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
%% Add names to args
NClause = case TS#elixir_scope.name_args of
true ->
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
setelement(3, TClause, NArgs);
false -> TClause
end,
%% Set __CALLER__ if used
FClause = case IsMacro andalso TS#elixir_scope.caller of
true ->
FBody = { 'match', Line,
{ 'var', Line, '__CALLER__' },
?ELIXIR_WRAP_CALL(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
},
setelement(5, NClause, [FBody|element(5, NClause)]);
false -> NClause
end,
Function = { function, Line, Name, Arity, [FClause] },
{ Function, Defaults, TS }.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
is_macro(_) -> false.
% Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_stored_definitions(Module) ->
Table = table(Module),
ets:delete(Table, last),
unwrap_stored_definition(ets:tab2list(Table), [], [], [], [], [], {[],[]}).
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
Tuple = element(1, Fun),
unwrap_stored_definition(
T, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
function_for_stored_definition(Fun, Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
{ Name, Arity } = Tuple = element(1, Fun),
Macro = { ?ELIXIR_MACRO(Name), Arity + 1 },
unwrap_stored_definition(
T, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
function_for_stored_definition(setelement(1, Fun, Macro), Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
unwrap_stored_definition(
T, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
function_for_stored_definition(Fun, Functions)
);
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
unwrap_stored_definition(
T, Exports, [element(1, Fun)|Private], Def, Defmacro,
[{ element(1, Fun), element(3, Fun) }|Defmacrop], Functions
);
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
%% Helpers
function_for_stored_definition({{Name, Arity}, _, Line, _, [], _, _, Clauses}, {Functions,Tail}) ->
{
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
Tail
};
function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Clauses}, {Functions,Tail}) ->
{
Functions,
[
{ function, Line, Name, Arity, lists:reverse(Clauses) },
{ attribute, Line, file, Location } | Tail
]
}.
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
when Kind == defmacro; Kind == defmacrop ->
{ function, Line, Name, length(Args) - 1, [Clause] };
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
{ function, Line, Name, length(Args), [Clause] }.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
case ets:lookup(Table, {Name, Arity}) of
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
FinalLocation = StoredLocation,
FinalDefaults = Defaults + StoredDefaults,
FinalClauses = Clauses ++ StoredClauses,
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
[] ->
FinalLocation = Location,
FinalDefaults = Defaults,
FinalClauses = Clauses,
Check andalso ets:insert(Table, { last, { Name, Arity } })
end,
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
%% Validations
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
check_valid_clause(Line, File, Name, Arity, Table) ->
case ets:lookup_element(Table, last, 2) of
{Name,Arity} -> [];
[] -> [];
{ElseName, ElseArity} ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
end.
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
check_valid_defaults(Line, File, Name, Arity, _) ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
%% Format errors
format_error({clauses_with_docs,{Name,Arity}}) ->
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
format_error({private_doc,{Name,Arity}}) ->
io_lib:format("function ~s/~B is private, @doc's are always discarded for private functions", [Name, Arity]);
format_error({existing_doc,{Name,Arity}}) ->
io_lib:format("@doc's for function ~s/~B have been given more than once, the first version is being kept", [Name, Arity]);
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
io_lib:format("~s ~s/~B already defined as ~s", [Current, Name, Arity, Previous]).
-58
View File
@@ -1,58 +0,0 @@
% Handle default clauses for function definitions.
-module(elixir_def_defaults).
-export([unpack/4]).
-include("elixir.hrl").
unpack(Kind, Name, Args, S) ->
unpack_each(Kind, Name, Args, [], [], S).
%% Helpers
%% Unpack default from given args.
%% Returns the given arguments without their default
%% clauses and a list of clauses for the default calls.
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
Base = build_match(Acc, Line, []),
{ Args, Invoke } = extract_defaults(List, [], []),
SM = S#elixir_scope{counter=length(Base)},
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, SM),
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
Call = { call, Line,
{ atom, Line, name_for_kind(Kind, Name) },
InvokeArgs
},
Clause = { clause, Line, DefArgs, [], [Call] },
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
unpack_each(Kind, Name, T, [H|Acc], Clauses, S);
unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
{ lists:reverse(Acc), lists:reverse(Clauses) }.
% Extract default values from args following the current default clause.
extract_defaults([{'//', _, [_Expr, Default]}|T], NewArgs, NewInvoke) ->
extract_defaults(T, NewArgs, [Default|NewInvoke]);
extract_defaults([H|T], NewArgs, NewInvoke) ->
extract_defaults(T, [H|NewArgs], [H|NewInvoke]);
extract_defaults([], NewArgs, NewInvoke) ->
{ lists:reverse(NewArgs), lists:reverse(NewInvoke) }.
% Build matches for all the previous argument until the current default clause.
build_match([], _Line, Acc) -> Acc;
build_match([_|T], Line, Acc) ->
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
build_match(T, Line, [Var|Acc]).
% Given the invoked function name based on the kind
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> ?ELIXIR_MACRO(Name);
name_for_kind(_Kind, Name) -> Name.
-77
View File
@@ -1,77 +0,0 @@
%% Module responsible for local invocation of macros and functions.
-module(elixir_def_local).
-export([
macro_for/3,
function_for/3,
format_error/1,
check_unused_local_macros/3
]).
-include("elixir.hrl").
%% Used by elixir_dispatch, returns false if no macro is found
macro_for(_Tuple, _All, nil) -> false;
macro_for(Tuple, All, Module) ->
try ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
get_function(Line, Module, Clauses);
_ ->
false
catch
error:badarg -> false
end.
%% Used on runtime by rewritten clauses, raises an error if function is not found
function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
get_function(Line, Module, Clauses);
_ ->
[_|T] = erlang:get_stacktrace(),
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
end.
%% Helpers
get_function(Line, Module, Clauses) ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result.
%% TODO: Consider caching functions in a table for performance.
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
},
%% If we have a macro, its arity in the table is
%% actually one less than in the function call
{ Name, Arity } = case atom_to_list(RawName) of
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
_ -> { RawName, length(Args) }
end,
FunCall = { call, Line, Remote, [
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
] },
{ call, Line, FunCall, Args };
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
rewrite_clause(List, Module) when is_list(List) ->
[rewrite_clause(Item, Module) || Item <- List];
rewrite_clause(Else, _) -> Else.
%% Error handling
check_unused_local_macros(File, Recorded, PMacros) ->
[elixir_errors:handle_file_warning(File,
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
format_error({unused_macro,{Name, Arity}}) ->
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
-94
View File
@@ -1,94 +0,0 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_def_overridable).
-export([store_pending/1, is_defined/2, ensure_defined/4,
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
overridable(Module, Value) ->
ets:insert(elixir_module:data_table(Module), { '__overridable', Value }).
%% Check if an overridable function is defined.
is_defined(Module, Tuple) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
{ ok, { _, [_|_] } } -> true;
_ -> false
end.
ensure_defined(Line, Module, Tuple, S) ->
case is_defined(Module, Tuple) of
true -> [];
_ -> elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
end.
%% Retrieve args defined for the given arity.
retrieve_args(Line, Arity, S) ->
{
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
S#elixir_scope{name_args=true}
}.
%% Assign pseudo variables to the given vars.
assign_args(Line, Args, S) ->
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
FArgs.
assign_args(Line, X, Acc, _) ->
Match = { match, Line, X, { var, Line, super_arg(Acc) } },
{ Match, Acc + 1 }.
super_arg(Counter) ->
?ELIXIR_ATOM_CONCAT(['_@S', Counter]).
%% Gets the name based on the function and stored overridables
name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, { Name, _ } = Function, Overridable) ->
{ Count, _ } = orddict:fetch(Function, Overridable),
?ELIXIR_ATOM_CONCAT(["OVERRIDABLE-", Count, "-", Name]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
{ FinalKind, FinalName } = case GenerateName of
true -> { defp, name(Module, Function, Overridable) };
false -> { Kind, Name }
end,
Def = { function, Line, FinalName, Arity, Clauses },
elixir_def:store_each(false, FinalKind, File, Location,
Stack, elixir_def:table(Module), Defaults, Def).
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
%% Error handling
format_error({ no_super, Module, { Name, Arity } }) ->
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
[Name, Arity, elixir_errors:inspect(Module), Joined]).
format_fa({ Name, Arity }) ->
A = atom_to_binary(Name, utf8),
B = list_to_binary(integer_to_list(Arity)),
<< A/binary, $/, B/binary >>.
-377
View File
@@ -1,377 +0,0 @@
%% Helpers related to dispatching to imports and references.
%% This module access the information stored on the scope
%% by elixir_import and therefore assumes it is normalized (ordsets)
-module(elixir_dispatch).
-export([default_macros/0, default_functions/0, default_requires/0,
dispatch_require/6, dispatch_import/5,
require_function/5, import_function/4,
expand_import/8, expand_require/8,
format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
-import(ordsets, [is_element/2]).
-define(BUILTIN, 'Elixir.Kernel').
default_functions() ->
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
default_macros() ->
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
default_requires() ->
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
%% Function retrieval
import_function(Line, Name, Arity, S) ->
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false -> { { 'fun', Line, { function, Name, Arity } }, S };
_ -> false
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
remote_function(Line, Receiver, Name, Arity, S)
end.
require_function(Line, Receiver, Name, Arity, S) ->
Tuple = { Name, Arity },
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> false;
false -> remote_function(Line, Receiver, Name, Arity, S)
end.
%% Function dispatch
dispatch_import(Line, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case find_dispatch(Tuple, S#elixir_scope.functions) of
false ->
case expand_import(Line, Tuple, Args, Module, S#elixir_scope.function,
S#elixir_scope.requires, S#elixir_scope.macros, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
elixir_macros:translate_macro({ Name, Line, Args }, S);
{ ok, Receiver, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
elixir_translator:translate_each({ { '.', Line, [Endpoint, Name] }, Line, Args }, S)
end.
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
Module = S#elixir_scope.module,
Arity = length(Args),
Tuple = { Name, Arity },
case (Receiver == Module) andalso is_element(Tuple, in_erlang_functions()) of
true ->
elixir_translator:translate_each({ { '.', Line, [erlang, Name] }, Line, Args }, S);
false ->
case expand_require(Line, Receiver, Tuple, Args, Module,
S#elixir_scope.function, S#elixir_scope.requires, S) of
{ error, noexpansion } ->
Callback();
{ error, internal } ->
elixir_macros:translate_macro({ Name, Line, Args }, S);
{ ok, Tree } ->
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
end
end.
%% Macros expansion
expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
case find_dispatch(Tuple, Macros) of
false ->
Fun = (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, true, Module),
case Fun of
false -> { error, noexpansion };
_ ->
elixir_import:record(import, Tuple, Module, Module),
{ ok, Module, expand_macro_fun(Line, Fun, Module, Name, Args, Module, Requires, SEnv) }
end;
?BUILTIN ->
case is_element(Tuple, in_elixir_macros()) of
false -> { error, internal };
true ->
elixir_import:record(import, Tuple, ?BUILTIN, Module),
{ ok, ?BUILTIN, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
end;
Receiver ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, Receiver, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
end.
expand_require(Line, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
case is_element(Tuple, in_erlang_macros()) of
true -> { error, internal };
false ->
case is_element(Tuple, in_elixir_macros()) of
true -> { ok, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end
end;
expand_require(Line, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
elixir_def_local:macro_for(Tuple, false, Module),
case Fun of
false ->
case is_element(Tuple, get_optional_macros(Receiver)) of
true -> { ok, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
false -> { error, noexpansion }
end;
_ ->
elixir_import:record(import, Tuple, Receiver, Module),
{ ok, expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
end.
%% Expansion helpers
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
case (Receiver == Module) or is_element(Receiver, Requires) of
true -> ok;
false ->
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
elixir_errors:form_error(Line, elixir_scope:filename(SEnv), ?MODULE, Tuple)
end,
SArg = {Line,SEnv},
try
apply(Fun, [SArg|Args])
catch
Kind:Reason ->
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
end.
expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
ProperName = ?ELIXIR_MACRO(Name),
ProperArity = Arity + 1,
Fun = fun Receiver:ProperName/ProperArity,
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
%% Helpers
find_dispatch(Tuple, [{ Name, Values }|T]) ->
case is_element(Tuple, Values) of
true -> Name;
false -> find_dispatch(Tuple, T)
end;
find_dispatch(_Tuple, []) -> false.
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
[Info];
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
[Info];
munge_stacktrace(Info, [H|T], S) ->
[H|munge_stacktrace(Info, T, S)];
munge_stacktrace(_, [], _) ->
[].
%% ERROR HANDLING
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
[elixir_errors:inspect(Receiver), Name, Arity, String]).
%% INTROSPECTION
remote_function(Line, Receiver, Name, Arity, S) ->
Final =
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
true -> erlang;
false -> Receiver
end,
{ { 'fun', Line, { function,
{ atom, Line, Final },
{ atom, Line, Name },
{ integer, Line, Arity}
} }, S }.
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
get_optional_macros(erlang) -> [];
get_optional_macros(Receiver) ->
case code:ensure_loaded(Receiver) of
{ module, Receiver } ->
try
Receiver:'__info__'(macros)
catch
error:undef -> []
end;
{ error, _ } -> []
end.
%% Functions imported from Kernel module. Sorted on compilation.
in_elixir_functions() ->
try
?BUILTIN:'__info__'(functions) -- [{'__info__',1}]
catch
error:undef -> []
end.
%% Macros imported from Kernel module. Sorted on compilation.
in_elixir_macros() ->
try
?BUILTIN:'__info__'(macros)
catch
error:undef -> []
end.
%% Functions imported from Erlang module. MUST BE SORTED.
in_erlang_functions() ->
[
{ abs, 1 },
{ atom_to_binary, 2 },
{ atom_to_list, 1 },
{ binary_part, 3 },
{ binary_to_atom, 2 },
{ binary_to_existing_atom, 2 },
{ binary_to_list, 1 },
{ binary_to_list, 3 },
{ binary_to_term, 1 },
{ binary_to_term, 2 },
{ bit_size, 1 },
{ bitstring_to_list, 1 },
{ byte_size, 1 },
% { date, 0 },
{ exit, 1 },
{ float, 1 },
{ float_to_list, 1 },
{ halt, 0 },
{ halt, 1 },
{ halt, 2 },
{ hd, 1 },
{ integer_to_list, 1 },
{ integer_to_list, 2 },
{ iolist_size, 1 },
{ iolist_to_binary, 1 },
{ is_atom, 1 },
{ is_binary, 1 },
{ is_bitstring, 1 },
{ is_boolean, 1 },
{ is_float, 1 },
{ is_function, 1 },
{ is_function, 2 },
{ is_integer, 1 },
{ is_list, 1 },
{ is_number, 1 },
{ is_pid, 1 },
{ is_port, 1 },
{ is_reference, 1 },
{ is_tuple, 1 },
{ length, 1 },
{ list_to_atom, 1 },
{ list_to_binary, 1 },
{ list_to_bitstring, 1 },
{ list_to_existing_atom, 1 },
{ list_to_float, 1 },
{ list_to_integer, 1 },
{ list_to_integer, 2 },
{ list_to_pid, 1 },
{ list_to_tuple, 1 },
{ make_ref, 0 },
{ max, 2 },
{ min, 2 },
{ node, 0 },
{ node, 1 },
% { now, 0 },
{ pid_to_list, 1 },
{ round, 1 },
{ self, 0 },
{ size, 1 },
{ spawn, 1 },
{ spawn, 3 },
{ spawn_link, 1 },
{ spawn_link, 3 },
% { split_binary, 2 },
{ term_to_binary, 1 },
{ term_to_binary, 2 },
{ throw, 1 },
% { time, 0 },
{ tl, 1 },
{ trunc, 1 },
{ tuple_size, 1 },
{ tuple_to_list, 1 }
].
%% Macros implemented in Erlang. MUST BE SORTED.
in_erlang_macros() ->
[
{'!',1},
{'!=',2},
{'!==',2},
{'*',2},
{'+',1},
{'+',2},
{'++',2},
{'-',1},
{'-',2},
{'--',2},
{'/',2},
{'<',2},
{'<-',2},
{'<=',2},
{'==',2},
{'===',2},
{'>',2},
{'>=',2},
{'@',1},
{'and',2},
{apply,2},
{apply,3},
{'case',2},
{def,1},
{def,2},
{def,4},
{defmacro,1},
{defmacro,2},
{defmacro,4},
{defmacrop,1},
{defmacrop,2},
{defmacrop,4},
{defmodule,2},
{defp,1},
{defp,2},
{defp,4},
{function,1},
{function,2},
{function,3},
{in,2},
{'not',1},
{'or',2},
{'receive',1},
{'try',1},
{'var!',1},
{'xor',2}
].
-156
View File
@@ -1,156 +0,0 @@
% A bunch of helpers to help to deal with errors in Elixir source code.
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([syntax_error/3, syntax_error/4, inspect/1,
form_error/4, parse_error/4, assert_module_scope/3,
assert_no_function_scope/3, assert_function_scope/3,
assert_no_assign_scope/3, assert_no_guard_scope/3,
assert_no_assign_or_guard_scope/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
deprecation/3, deprecation/4, file_format/3]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
%% Handle inspecting for exceptions
inspect(Atom) when is_atom(Atom) ->
case atom_to_list(Atom) of
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
Else -> Else
end;
inspect(Other) -> Other.
to_dot($-) -> $.;
to_dot(L) -> L.
%% Raised during macros translation.
syntax_error(Line, File, Message) when is_list(Message) ->
syntax_error(Line, File, iolist_to_binary(Message));
syntax_error(Line, File, Message) when is_binary(Message) ->
raise(Line, File, 'Elixir.SyntaxError', Message).
syntax_error(Line, File, Format, Args) ->
Message = io_lib:format(Format, Args),
raise(Line, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
%% Raised on tokenizing/parsing
parse_error(Line, File, Error, []) ->
Message = case Error of
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
_ -> iolist_to_binary(Error)
end,
raise(Line, File, 'Elixir.TokenMissingError', Message);
parse_error(Line, File, "syntax error before: ", "'end'") ->
raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Line, File, Error, Token) ->
BinError = if
is_atom(Error) -> atom_to_binary(Error, utf8);
true -> iolist_to_binary(Error)
end,
BinToken = if
Token == [] -> <<>>;
true -> iolist_to_binary(Token)
end,
Message = <<BinError / binary, BinToken / binary >>,
raise(Line, File, 'Elixir.SyntaxError', Message).
%% Raised during compilation
form_error(Line, File, Module, Desc) ->
Message = iolist_to_binary(format_error(Module, Desc)),
raise(Line, File, 'Elixir.CompileError', Message).
%% Shows a deprecation message
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
deprecation(Line, File, Message, Args) ->
io:format(file_format(Line, File, io_lib:format(Message, Args))).
%% Handle warnings and errors (called during module compilation)
%% Ignore on bootstrap
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
%% Ignore always
handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
%% Rewrite
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
Raw = "undefined callback function ~s/~B (behaviour ~s)",
Message = io_lib:format(Raw, [Fun,Arity,inspect(Module)]),
io:format(file_format(Line, File, Message));
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
Message = case erlang:function_exported(Module, behavior_info, 1) of
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
false -> Raw
end,
io:format(file_format(Line, File, Message));
%% Default behavior
handle_file_warning(_, File, {Line,Module,Desc}) ->
Message = format_error(Module, Desc),
io:format(file_format(Line, File, Message)).
handle_file_warning(File, Desc) ->
handle_file_warning(false, File, Desc).
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
%% Assertions
assert_no_function_scope(_Line, _Kind, #elixir_scope{function=nil}) -> [];
assert_no_function_scope(Line, Kind, S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
assert_no_assign_or_guard_scope(Line, Kind, S) ->
assert_no_assign_scope(Line, Kind, S),
assert_no_guard_scope(Line, Kind, S).
assert_no_assign_scope(Line, Kind, #elixir_scope{context=assign} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
assert_no_assign_scope(_Line, _Kind, _S) -> [].
assert_no_guard_scope(Line, Kind, #elixir_scope{context=guard} = S) ->
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
assert_no_guard_scope(_Line, _Kind, _S) -> [].
assert_module_scope(Line, Kind, #elixir_scope{module=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside module", [Kind]);
assert_module_scope(_Line, _Kind, #elixir_scope{module=Module}) -> Module.
assert_function_scope(Line, Kind, #elixir_scope{function=nil,file=File}) ->
syntax_error(Line, File, "cannot invoke ~s outside function", [Kind]);
assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Function.
%% Helpers
raise(Line, File, Kind, Message) ->
Stacktrace = erlang:get_stacktrace(),
erlang:raise(error, { Kind, '__exception__', Message, iolist_to_binary(File), Line }, Stacktrace).
file_format(Line, File, Message) ->
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
format_error([], Desc) ->
io_lib:format("~p", [Desc]);
format_error(Module, Desc) ->
Module:format_error(Desc).
-284
View File
@@ -1,284 +0,0 @@
%% Modified version of filelib:wildcard that can handle "**"
%% and automatically skips directories starting with "." by default.
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
-module(elixir_glob).
-compile({no_auto_import,[error/1]}).
-export([wildcard/1]).
-include_lib("kernel/include/file.hrl").
-define(HANDLE_ERROR(Expr),
try
Expr
catch
error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
%% Get the stack backtrace correct.
erlang:error(UnUsUalVaRiAbLeNaMe)
end).
wildcard(Pattern) when is_list(Pattern) ->
?HANDLE_ERROR(do_wildcard(Pattern, file)).
do_wildcard(Pattern, Mod) when is_list(Pattern) ->
do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
case eval_read_file_info(File, Mod) of
{ok,_} -> [File];
_ -> []
end;
do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
do_wildcard_1([Base], Rest, Mod).
%%%
%%% Pattern matching using a compiled wildcard.
%%%
do_wildcard_1(Files, Pattern, Mod) ->
do_wildcard_2(Files, Pattern, [], Mod).
do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
do_wildcard_2([], _, Result, _Mod) ->
Result.
do_wildcard_3(Base, [[double_star]|Rest], Result, Mod) ->
lists:sort(do_double_star(current, [Base], Rest, Result, Mod, true));
do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
case do_list_dir(Base, Mod) of
{ok, Files0} ->
Files = lists:sort(Files0),
Matches = wildcard_4(Pattern, Files, Base, []),
do_wildcard_2(Matches, Rest, Result, Mod);
_ ->
Result
end;
do_wildcard_3(Base, [], Result, _Mod) ->
[Base|Result].
wildcard_4(Pattern, [File|Rest], Base, Result) when is_binary(File) ->
case wildcard_5(Pattern, binary_to_list(File)) of
true ->
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
false ->
wildcard_4(Pattern, Rest, Base, Result)
end;
wildcard_4(Pattern, [File|Rest], Base, Result) ->
case wildcard_5(Pattern, File) of
true ->
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
false ->
wildcard_4(Pattern, Rest, Base, Result)
end;
wildcard_4(_Patt, [], _Base, Result) ->
Result.
wildcard_5([question|Rest1], [_|Rest2]) ->
wildcard_5(Rest1, Rest2);
wildcard_5([accept], _) ->
true;
wildcard_5([double_star], _) ->
true;
wildcard_5([star|Rest], File) ->
do_star(Rest, File);
wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
case ordsets:is_element(C, Ordset) of
true -> wildcard_5(Rest, File);
false -> false
end;
wildcard_5([{alt, Alts}], File) ->
do_alt(Alts, File);
wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
wildcard_5(Rest1, Rest2);
wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
false;
wildcard_5([], []) ->
true;
wildcard_5([], [_|_]) ->
false;
wildcard_5([_|_], []) ->
false.
do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
Full = join(Base, H),
Result1 = case do_list_dir(Full, Mod) of
{ok, Files} ->
do_double_star(Full, Files, Rest, Result, Mod, false);
_ -> Result
end,
Result2 = case Root andalso Rest == [] of
true -> Result1;
false -> do_wildcard_3(Full, Rest, Result1, Mod)
end,
do_double_star(Base, T, Rest, Result2, Mod, Root);
do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
Result.
do_star(Pattern, [X|Rest]) ->
case wildcard_5(Pattern, [X|Rest]) of
true -> true;
false -> do_star(Pattern, Rest)
end;
do_star(Pattern, []) ->
wildcard_5(Pattern, []).
do_alt([Alt|Rest], File) ->
case wildcard_5(Alt, File) of
true -> true;
false -> do_alt(Rest, File)
end;
do_alt([], _File) ->
false.
do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
join(current, File) -> File;
join(Base, File) -> filename:join(Base, File).
%%% Compiling a wildcard.
do_compile_wildcard(Pattern) ->
{compiled_wildcard,compile_wildcard_1(Pattern)}.
compile_wildcard_1(Pattern) ->
[Root|Rest] = filename:split(Pattern),
case filename:pathtype(Root) of
relative ->
compile_wildcard_2([Root|Rest], current);
_ ->
compile_wildcard_2(Rest, [Root])
end.
compile_wildcard_2([Part|Rest], Root) ->
case compile_part(Part) of
Part ->
compile_wildcard_2(Rest, join(Root, Part));
Pattern ->
compile_wildcard_3(Rest, [Pattern,Root])
end;
compile_wildcard_2([], Root) -> {exists,Root}.
compile_wildcard_3([Part|Rest], Result) ->
compile_wildcard_3(Rest, [compile_part(Part)|Result]);
compile_wildcard_3([], Result) ->
lists:reverse(Result).
compile_part(Part) ->
compile_part(Part, false, []).
compile_part_to_sep(Part) ->
compile_part(Part, true, []).
compile_part([], true, _) ->
error(missing_delimiter);
compile_part([$,|Rest], true, Result) ->
{ok, $,, lists:reverse(Result), Rest};
compile_part([$}|Rest], true, Result) ->
{ok, $}, lists:reverse(Result), Rest};
compile_part([$?|Rest], Upto, Result) ->
compile_part(Rest, Upto, [question|Result]);
compile_part([$*,$*], Upto, Result) ->
compile_part([], Upto, [double_star|Result]);
compile_part([$*,$*|Rest], Upto, Result) ->
compile_part(Rest, Upto, [star|Result]);
compile_part([$*], Upto, Result) ->
compile_part([], Upto, [accept|Result]);
compile_part([$*|Rest], Upto, Result) ->
compile_part(Rest, Upto, [star|Result]);
compile_part([$[|Rest], Upto, Result) ->
case compile_charset(Rest, ordsets:new()) of
{ok, Charset, Rest1} ->
compile_part(Rest1, Upto, [Charset|Result]);
error ->
compile_part(Rest, Upto, [$[|Result])
end;
compile_part([${|Rest], Upto, Result) ->
case compile_alt(Rest) of
{ok, Alt} ->
lists:reverse(Result, [Alt]);
error ->
compile_part(Rest, Upto, [${|Result])
end;
compile_part([X|Rest], Upto, Result) ->
compile_part(Rest, Upto, [X|Result]);
compile_part([], _Upto, Result) ->
lists:reverse(Result).
compile_charset([$]|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element($], Ordset));
compile_charset([$-|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element($-, Ordset));
compile_charset([], _Ordset) ->
error;
compile_charset(List, Ordset) ->
compile_charset1(List, Ordset).
compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
compile_charset1([$]|Rest], Ordset) ->
{ok, {one_of, Ordset}, Rest};
compile_charset1([X|Rest], Ordset) ->
compile_charset1(Rest, ordsets:add_element(X, Ordset));
compile_charset1([], _Ordset) ->
error.
compile_range(Lower, Current, Ordset) when Lower =< Current ->
compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
compile_range(_, _, Ordset) ->
Ordset.
compile_alt(Pattern) ->
compile_alt(Pattern, []).
compile_alt(Pattern, Result) ->
case compile_part_to_sep(Pattern) of
{ok, $,, AltPattern, Rest} ->
compile_alt(Rest, [AltPattern|Result]);
{ok, $}, AltPattern, Rest} ->
NewResult = [AltPattern|Result],
RestPattern = compile_part(Rest),
{ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
Pattern ->
error
end.
error(Reason) ->
erlang:error({badpattern,Reason}).
eval_read_file_info(File, file) ->
file:read_file_info(File);
eval_read_file_info(File, erl_prim_loader) ->
case erl_prim_loader:read_file_info(File) of
error -> {error, erl_prim_loader};
Res-> Res
end;
eval_read_file_info(File, Mod) ->
Mod:read_file_info(File).
eval_list_dir(Dir, file) ->
file:list_dir(Dir);
eval_list_dir(Dir, erl_prim_loader) ->
case erl_prim_loader:list_dir(Dir) of
error -> {error, erl_prim_loader};
Res-> Res
end;
eval_list_dir(Dir, Mod) ->
Mod:list_dir(Dir).
-283
View File
@@ -1,283 +0,0 @@
%% Module responsible for handling imports and conflicts.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, recorded_locals/1, format_error/1,
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
build_table/1, delete_table/1, record/4]).
-include("elixir.hrl").
table(Module) -> ?ELIXIR_ATOM_CONCAT([i, Module]).
build_table(Module) ->
ets:new(table(Module), [set, named_table, public]).
delete_table(Module) ->
ets:delete(table(Module)).
record(_Kind, _Tuple, _Receiver, nil) ->
false;
record(import, Tuple, Receiver, Module) ->
try
ets:insert(table(Module), { Tuple, Receiver })
catch
error:badarg -> false
end.
recorded_locals(Module) ->
Table = table(Module),
Match = { '$1', Module },
Result = ets:match(Table, Match),
ets:match_delete(Table, Match),
lists:append(Result).
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
import(Line, Ref, Opts, Selector, S) ->
SF = case (Selector == all) or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun() -> get_functions(Ref) end,
Functions = calculate(Line, Ref, Opts,
S#elixir_scope.functions, FunctionsFun, S),
S#elixir_scope{functions=Functions}
end,
SM = case (Selector == all) or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun() ->
case Selector of
all -> get_optional_macros(Ref);
_ -> get_macros(Line, Ref, SF)
end
end,
Macros = calculate(Line, Ref, Opts,
SF#elixir_scope.macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros}
end,
SM.
%% IMPORT FUNCTION RELATED HELPERS
%% Calculates the imports based on only and except
calculate(Line, Key, Opts, Old, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case orddict:find(only, Opts) of
{ ok, Only } ->
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
_ -> intersection(Only, AvailableFun())
end;
error ->
case orddict:find(except, Opts) of
{ ok, Except } ->
case keyfind(Key, Old) of
false -> AvailableFun() -- Except;
{Key,ToRemove} -> ToRemove -- Except
end;
error -> AvailableFun()
end
end,
%% Normalize the data before storing it
Final = ordsets:from_list(New -- internal_funs()),
case Final of
[] -> All;
_ ->
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Line, File, Key, Final, internal_conflict),
[{ Key, Final }|All]
end.
%% Retrieve functions and macros from modules
get_exports(Module) ->
try
Module:'__info__'(functions) ++ Module:'__info__'(macros)
catch
error:undef -> Module:module_info(exports)
end.
get_functions(Module) ->
try
Module:'__info__'(functions)
catch
error:undef -> Module:module_info(exports)
end.
get_macros(Line, Module, S) ->
try
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
case code:ensure_loaded(Module) of
{ module, Module } ->
try
Module:'__info__'(macros)
catch
error:undef -> []
end;
{ error, _ } -> []
end.
%% VALIDATION HELPERS
%% Check if any of the locals defined conflicts with an invoked
%% Elixir "implemented in Erlang" macro. Checking if a local
%% conflicts with an import is automatically done by Erlang.
ensure_no_local_conflict(Line, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Line, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
ensure_no_import_conflict(Line, File, Module, AllDefined) ->
Table = table(Module),
Matches = [X || X <- AllDefined, ets:member(Table, X)],
case Matches of
[{Name,Arity}|_] ->
Key = ets:lookup_element(Table, {Name, Arity }, 2),
Tuple = { import_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
[] ->
ok
end.
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Line, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Line, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Line, _File, _Key, [], _) -> ok.
%% Find conlicts in the given list of functions with the set of imports.
%% Used internally to ensure a newly imported fun or macro does not
%% conflict with an already imported set.
ensure_no_conflicts(Line, File, Functions, [{Key,Value}|T]) ->
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
case Filtered of
[{Name,Arity}|_] ->
Tuple = { already_imported, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Line, File, Functions, T)
end;
ensure_no_conflicts(_Line, _File, _Functions, _S) -> ok.
%% ERROR HANDLING
format_error({already_imported,{Receiver, Name, Arity}}) ->
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({import_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("imported ~s.~s/~B conflicts with local function",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir internal macros", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir internal macros",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
io_lib:format("could not load macros from module ~s", [elixir_errors:inspect(Module)]).
%% LIST HELPERS
keyfind(Key, List) ->
lists:keyfind(Key, 1, List).
keydelete(Key, List) ->
lists:keydelete(Key, 1, List).
intersection([H|T], All) ->
case lists:member(H, All) of
true -> [H|intersection(T, All)];
false -> intersection(T, All)
end;
intersection([], _All) -> [].
%% INTROSPECTION
%% Internal funs that are never imported etc.
internal_funs() ->
[
{ module_info, 0 },
{ module_info, 1 },
{ '__info__', 1 },
{ '__using__', 1 }
].
%% Macros implemented in Erlang that are not overridable.
non_overridable_macros() ->
[
{'^',1},
{'=',2},
{'__op__',2},
{'__op__',3},
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','2'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
{'alias',1},
{'alias',2},
{'require',1},
{'require',2},
{'import',1},
{'import',2},
{'import',3},
{'__ENV__',0},
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
{'unquote',1},
{'unquote_splicing',1},
{'fn','*'},
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'}
].

Some files were not shown because too many files have changed in this diff Show More