Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c345dab9c1 | ||
|
|
2d46eda619 | ||
|
|
beb9b1b3ef | ||
|
|
cb512a7f17 | ||
|
|
e4fdeb6b79 | ||
|
|
6b22171018 | ||
|
|
f0112289e6 | ||
|
|
72e18a0a0c | ||
|
|
e2be3f652e | ||
|
|
490bd32322 | ||
|
|
1bfcba133a | ||
|
|
c3d024c16c | ||
|
|
1530a761d5 | ||
|
|
5197b6ece6 |
+11
-13
@@ -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
|
||||
|
||||
-10
@@ -1,10 +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:
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
* enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
* [Binary] Add a Dict binary that converts its keys to binaries on insertion
|
||||
* [Binary] Optimize `Binary.Inspect` and improve inspect for floats
|
||||
* [CLI] Support `--detached` option
|
||||
* [Code] `Code.string_to_ast` supports `:existing_atoms_only` as an option in order to guarantee no new atoms is generated when parsing the code
|
||||
* [EEx] Support `<%%` and `<%#` tags
|
||||
* [ExUnit] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
|
||||
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
|
||||
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
|
||||
* [Kernel] Added `float_to_binary` and `binary_to_float`
|
||||
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
|
||||
* [Keyword] Keyword list is no longer ordered according to Erlang terms but the order in which they are specified
|
||||
* [List] Add `List.keyreplace` and `List.keystore`
|
||||
* [Macro] Support `Macro.safe_term` which returns `:ok` if an expression does not execute code and is made only of raw data types
|
||||
* [Mix] Add support for environments - the current environment can be set via `MIX_ENV`
|
||||
* [Mix] Add support for handling and fetching dependencies' dependencies
|
||||
* [Module] Support module creation via `Module.create`
|
||||
* [Range] Support decreasing ranges
|
||||
* [Record] Improvements to the Record API, added `Record.defmacros`
|
||||
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
|
||||
* [String] Add a String module responsible for handling UTf-8 binaries
|
||||
|
||||
* bug fix
|
||||
* [File] `File.cp` and `File.cp_r` now preserves the file's mode
|
||||
* [IEx] Fix a bug where printing to `:stdio` on `IEx` was causing it to hang
|
||||
* [Macro] Fix a bug where quoted expressions were not behaving the same as their non-quoted counterparts
|
||||
* [Mix] `mix deps.get [DEPS]` now only gets the specified dependencies
|
||||
* [Mix] Mix now exits with status 1 in case of failures
|
||||
* [Protocol] Avoid false positives on protocol dispatch (a bug caused the dispatch to be triggered to an invalid protocol)
|
||||
|
||||
* backwards incompatible changes
|
||||
* [ExUnit] `setup` and `teardown` callbacks now receives the test name as second argument
|
||||
* [Kernel] Raw function definition with `def/4`, `defp/4`, `defmacro/4`, `defmacrop/4` now evaluates all arguments. The previous behaviour was accidental and did not properly evaluate all arguments
|
||||
* [Kernel] Change tuple-related (`elem` and `setelem`), Enum functions (`find_index`, `nth!` and `times`) and List functions (List.key*) to zero-index
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.require_file` and `Code.load_file` now expect the full name as argument
|
||||
* [Enum] `List.reverse/1` and `List.zip/2` were moved to `Enum`
|
||||
* [GenServer] Rename `GenServer.Behavior` to `GenServer.Behaviour`
|
||||
* [Kernel] Bitstring syntax now uses `::` instead of `|`
|
||||
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
|
||||
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
|
||||
* incompatible changes
|
||||
* [Kernel] Compile 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__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
* deprecations
|
||||
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
|
||||
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
|
||||
* [File] `File.read_info` is deprecated in favor of `File.stat`
|
||||
* [IO] `IO.print` is deprecated in favor of `IO.write`
|
||||
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
|
||||
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
|
||||
* [Kernel] `refer` is deprecated in favor of `alias`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
* enhancements
|
||||
* [Enum] Enhance Enum protocol to support `Enum.count`
|
||||
* [Enum] Optimize functions when a list is given as collection
|
||||
* [Enum] Add `find_index`, `nth!` and others
|
||||
* [ExUnit] Support setup and teardown callbacks
|
||||
* [IEx] IEx now provides autocomplete if the OS supports tty
|
||||
* [IEx] IEx now supports remsh
|
||||
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation
|
||||
* [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
|
||||
* [IO/File] Many improvements to `File` and `IO` modules
|
||||
* [Kernel] Operator `!` is now allowed in guard clauses
|
||||
* [Kernel] Introduce 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
|
||||
* [Kernel] quote special form now supports line and unquote as options
|
||||
* [Kernel] Document the macro `@` and allow attributes to be read inside functions
|
||||
* [Kernel] Add 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] Add `__ENV__` which returns a `Macro.Env` record with information about the compilation environment
|
||||
* [Kernel] Add `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site
|
||||
* [Macro] Add `Macro.expand`, useful for debugging what a macro expands to
|
||||
* [Mix] First Mix public release
|
||||
* [Module] Add 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
|
||||
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing
|
||||
* [Range] Add a Range module with support to `in` operator (`x in 1..3`) and iterators
|
||||
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang
|
||||
* [Record] Records now provide a `to_keywords` function
|
||||
* [Regex] Back references are now properly supported
|
||||
* [System] Add `System.find_executable`
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
|
||||
* First official release
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -1,108 +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.7.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 iex
|
||||
|
||||
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))
|
||||
$(eval $(call TASK_TEMPLATE,iex,IEx))
|
||||
|
||||
clean:
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
rm -rf .full
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/*/test/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/tmp
|
||||
|
||||
#==> 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_iex
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
@@ -1,52 +1,94 @@
|
||||

|
||||
=========
|
||||
[](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";
|
||||
```
|
||||
|
||||
-27
@@ -1,27 +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 update stable branch
|
||||
|
||||
5) Create tag from stable branch
|
||||
|
||||
6) Release new docs, update elixir-lang.org
|
||||
|
||||
7) Push new zip to Elixir's downloads page
|
||||
|
||||
8) 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
|
||||
-77
@@ -1,77 +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)
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--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
|
||||
;;
|
||||
--detached)
|
||||
ERL="$ERL `echo $PEEK | cut -c 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 "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
fi
|
||||
@@ -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
@@ -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")
|
||||
exec "$SCRIPT_PATH"/elixir --compile "$@"
|
||||
@@ -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 %*
|
||||
@@ -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")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
@@ -0,0 +1,7 @@
|
||||
import Config
|
||||
|
||||
import_config "#{config_env()}.exs"
|
||||
|
||||
if config_env() == :test do
|
||||
config :logger, level: :warning
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
import Config
|
||||
|
||||
# Dev: no special config — all runtime settings come from env vars.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -1,203 +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 %>
|
||||
<%% EEx quotation - returns the contents inside %>
|
||||
<%# Comments - they are discarded from source %>
|
||||
|
||||
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
|
||||
@@ -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 :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 :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, 0
|
||||
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
|
||||
@@ -1,56 +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 = unquote(buffer)
|
||||
tmp <> to_binary(unquote(expr))
|
||||
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
|
||||
@@ -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
|
||||
@@ -1,159 +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
|
||||
Enum.reverse(tokenize(list, line, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
tokenize rest, current_line, new_line, buffer, acc
|
||||
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, Enum.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
|
||||
|
||||
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 = Enum.reverse(rest)
|
||||
|
||||
# Tokenize the remaining passing check_terminators as
|
||||
# false, 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, file: "eex", check_terminators: false) do
|
||||
{ :ok, tokens } ->
|
||||
tokens = Enum.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(Enum.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version]
|
||||
end
|
||||
end
|
||||
@@ -1,22 +0,0 @@
|
||||
Code.require_file "../../test_helper.exs", __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
|
||||
@@ -1,105 +0,0 @@
|
||||
Code.require_file "../../test_helper.exs", __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 "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 1, "foo <% true %>" }
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 1, "foo <% true do %>bar<% end %>" }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 1, "foo " }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 1, "foo bar" }
|
||||
]
|
||||
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
|
||||
@@ -1,342 +0,0 @@
|
||||
Code.require_file "../test_helper.exs", __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.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
@@ -1 +0,0 @@
|
||||
foo <%= if true do %>bar.<% end %>
|
||||
@@ -1 +0,0 @@
|
||||
foo <%= bar %>
|
||||
@@ -1 +0,0 @@
|
||||
ExUnit.start []
|
||||
@@ -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=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
clause_vars=[], %% a dict of all variables defined in a particular clause
|
||||
quote_vars=[], %% 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
|
||||
}).
|
||||
@@ -1,65 +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, records, 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 keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
keywords = [a: 1, b: 2]
|
||||
keywords[:a] #=> 1
|
||||
|
||||
"""
|
||||
|
||||
def access(list, atom) when is_atom(atom) do
|
||||
Keyword.get(list, atom)
|
||||
end
|
||||
|
||||
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(nil, _) do
|
||||
nil
|
||||
end
|
||||
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
|
||||
@@ -1,89 +0,0 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
A convenience module for defining behaviours.
|
||||
Behaviours can be referenced by other modules
|
||||
in order to ensure they implement the proper
|
||||
callbacks.
|
||||
|
||||
For example, you can specify the `URI.Parser`
|
||||
behaviour as follow:
|
||||
|
||||
defmodule URI.Parser do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(arg)
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port()
|
||||
end
|
||||
|
||||
And then a specific protocol may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
|
||||
In case the behaviour changes or URI.HTTP does
|
||||
not implement one of the callbacks, a warning
|
||||
will be raised.
|
||||
|
||||
## Implementation
|
||||
|
||||
Internally, Erlang call `behaviour_info(:callbacks)`
|
||||
to obtain all functions that a behaviour should
|
||||
implemented. Therefore, all this module does is
|
||||
to define `behaviour_info(:callbacks)` with the
|
||||
`defcallback` definitions.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Annotates the given function is a callback. `defcallback` is
|
||||
slightly different than simple using `def` because, even if
|
||||
`defcallback` contains default values, a default function
|
||||
won't be generated, which would happen with `def`.
|
||||
"""
|
||||
defmacro defcallback(fun) do
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
length = length(args)
|
||||
|
||||
{ args, defaults } = Enum.map_reduce(args, 0, function do
|
||||
{ ://, _, [expr, _] }, acc ->
|
||||
{ expr, acc + 1 }
|
||||
expr, acc ->
|
||||
{ expr, acc }
|
||||
end)
|
||||
|
||||
attributes = Enum.map (length - defaults)..length, fn(i) ->
|
||||
quote do
|
||||
@__behaviour_callbacks { unquote(name), unquote(i) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
__block__ unquote(attributes)
|
||||
def unquote(name)(unquote_splicing(args))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
@__behaviour_callbacks
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -1,81 +0,0 @@
|
||||
defmodule Binary.Dict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary that forces the keys to be
|
||||
converted to binaries on insertion. Currently it is implemented
|
||||
using an `OrdDict`, but this may change in the future.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Binary.Dict, 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 to_binary(key), data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store to_binary(key), value, data)
|
||||
end
|
||||
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase to_binary(key), data)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, data)
|
||||
end
|
||||
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, initial, data)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Binary.Dict do
|
||||
def iterator({ Binary.Dict, data }), do: data
|
||||
def count({ Binary.Dict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: Binary.Dict do
|
||||
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
|
||||
end
|
||||
@@ -1,444 +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 advised to
|
||||
implement the protocol in order to provide pretty
|
||||
printing.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Tuple, Atom, Number, Any]
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.Utils do
|
||||
@moduledoc false
|
||||
|
||||
## container_join
|
||||
|
||||
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
|
||||
container_join(tuple_to_list(tuple), first, last, opts)
|
||||
end
|
||||
|
||||
def container_join(list, first, last, opts) do
|
||||
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
|
||||
end
|
||||
|
||||
defp do_container_join(_, _opts, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
""
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
## escape
|
||||
|
||||
# It is considerably faster to loop the binary
|
||||
# and convert it to a list as we go compared
|
||||
# to looping the binary and creating a binary
|
||||
# as we go.
|
||||
def escape(other, char) do
|
||||
list_to_binary [char|do_escape(other, char)]
|
||||
end
|
||||
|
||||
defp do_escape(<<char, t :: binary>>, char) do
|
||||
[?\\, char | do_escape(t, char)]
|
||||
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)]
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) do
|
||||
[h | do_escape(t,char)]
|
||||
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
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
require Macro
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
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 ->
|
||||
":" <> 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
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
Represents the string as itself escaping
|
||||
all necessary characters.
|
||||
|
||||
## Examples
|
||||
|
||||
inspect("bar") #=> "bar"
|
||||
inspect("f\"oo") #=> "f\"oo"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
escape(thing, ?")
|
||||
else
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(bitstring, opts) do
|
||||
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: List do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %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, opts) do
|
||||
cond do
|
||||
printable?(thing) ->
|
||||
escape(list_to_binary(thing), ?')
|
||||
keywords?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
end
|
||||
end
|
||||
|
||||
## keywords?
|
||||
defp keywords?([]), do: true
|
||||
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
|
||||
keywords?(rest)
|
||||
end
|
||||
defp keywords?(_other), do: false
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
defp key_to_binary(key, opts) do
|
||||
case Binary.Inspect.Atom.inspect(key, opts) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
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
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
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(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_protocol(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_protocol(tuple, opts) do
|
||||
name = elem(tuple, 0)
|
||||
|
||||
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
|
||||
if name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
try do
|
||||
target = Module.concat(Binary.Inspect, name)
|
||||
target.inspect(tuple, opts)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
record_inspect(tuple, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_inspect(exception, opts) when is_exception(exception) do
|
||||
list = tuple_to_list(exception)
|
||||
[name,_|tail] = list
|
||||
[_|fields] = name.__record__(:fields)
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp record_join(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
|
||||
record_join(fields, tail, opts) <> "]"
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Binary.Inspect.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
defp record_join([], [], _opts) do
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Number do
|
||||
@moduledoc """
|
||||
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 :io_lib.format("~p", [thing])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Regex do
|
||||
@moduledoc %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
|
||||
@moduledoc """
|
||||
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 :io_lib.format('~p', [thing])
|
||||
end
|
||||
end
|
||||
@@ -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: 1, band: 2, bor: 2, bxor: 2, bsl: 2, bsr: 2]
|
||||
Keyword.get(options, :skip_operators) ->
|
||||
[~~~: 1, &&&: 2, |||: 2, ^^^: 2, <<<: 2, >>>: 2]
|
||||
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
|
||||
@@ -1,373 +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 """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
calling this function only removes it from the list,
|
||||
allowing it to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
server_cast { :unload_files, files }
|
||||
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
|
||||
: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
|
||||
:code.add_patha(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a path from Erlang VM code path.
|
||||
The path is expanded with `File.expand_path` before deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(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 keyword list:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
: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} ] }
|
||||
|
||||
For convenience, you can also pass the current __ENV__ and
|
||||
the proper information will be extracted:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.location)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
{ value, binding, _scope } =
|
||||
: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.
|
||||
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
"""
|
||||
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, opts)
|
||||
|
||||
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.
|
||||
|
||||
Check `Code.string_to_ast/2` for options information.
|
||||
"""
|
||||
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, opts)
|
||||
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 all the modules
|
||||
defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
in many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file` if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
server_call { :acquire, file }
|
||||
loaded = :elixir_compiler.file file
|
||||
server_cast { :loaded, file }
|
||||
loaded
|
||||
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, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes
|
||||
concurrently, the first process to invoke `require_file` acquires
|
||||
a lock and the remaining ones will block until the file is
|
||||
available. I.e. if `require_file` is called N times with a given
|
||||
file, the given file will be loaded only once. Check `load_file`
|
||||
if you want a file to be loaded concurrently.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case server_call({ :acquire, file }) do
|
||||
:loaded ->
|
||||
nil
|
||||
{ :queued, ref } ->
|
||||
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file file
|
||||
server_cast { :loaded, file }
|
||||
loaded
|
||||
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
|
||||
: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 for a 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
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_loaded/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, 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 :code.ensure_loaded(module) do
|
||||
{ :error, :nofile } = error ->
|
||||
case :erlang.get(:elixir_ensure_compiled) do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
module.__info__(:self)
|
||||
{ :module, module }
|
||||
rescue
|
||||
UndefinedFunctionError -> error
|
||||
end
|
||||
end
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_compiled/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
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
|
||||
IO.write "[WARNING] Passing a file without .exs extension to Code.load_file or Code.require_file is deprecated, please pass the full name instead\n#{Exception.formatted_stacktrace}"
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp server_call(args) do
|
||||
:gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
|
||||
defp server_cast(args) do
|
||||
:gen_server.cast(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
@@ -1,220 +0,0 @@
|
||||
defmodule Dict do
|
||||
@moduledoc """
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict based on
|
||||
the tuple signature.
|
||||
|
||||
The keyword list used throughout Elixir cannot be
|
||||
manipulated via the Dict module, you must use the
|
||||
Keyword module instead. This distinction is intentional:
|
||||
the Dict module is meant to work on structures that work
|
||||
as storage.
|
||||
|
||||
To create a new dict, use the `new` functions defined
|
||||
by each dict type:
|
||||
|
||||
OrdDict.new [{:a, 1}, {:b, 2}]
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
For simplicity's sake, in the examples below everytime
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like the two above. Likewise, when the result of
|
||||
a function invocation is shown in the form `[a: 1, b: 2]`,
|
||||
it implies that the returned value is actually of the
|
||||
same dict type as the input one.
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
[ delete: 2, empty: 1, get: 3, has_key?: 2,
|
||||
keys: 1, merge: 3, put: 3, size: 1, to_list: 1,
|
||||
update: 3, update: 4, values: 1 ]
|
||||
end
|
||||
|
||||
@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
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.keys d #=> [:a,:b]
|
||||
|
||||
"""
|
||||
def keys(dict) do
|
||||
elem(dict, 0).keys(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's values.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.values d #=> [1,2]
|
||||
|
||||
"""
|
||||
def values(dict) do
|
||||
elem(dict, 0).values(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.size d #=> 2
|
||||
|
||||
"""
|
||||
def size(dict) do
|
||||
elem(dict, 0).size(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the given key exists in the given dict.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.has_key?(d, :a) #=> true
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
|
||||
"""
|
||||
def has_key?(dict, key) do
|
||||
elem(dict, 0).has_key?(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, returns `default` (or nil if not provided).
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> nil
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
def get(dict, key, default // nil) do
|
||||
elem(dict, 0).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores the given `value` under `key` in `dict`.
|
||||
If `dict` already has `key`, the stored value is replaced by the new one.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put d, :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def put(dict, key, val) do
|
||||
elem(dict, 0).put(dict, key, val)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the entry stored under the given key from `dict`.
|
||||
If `dict` does not contain `key`, returns the dictionary unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
d = new [b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
elem(dict, 0).delete(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dicts into one. If the dicts have duplicated entries,
|
||||
the one given as second argument wins. In case the second argument
|
||||
is not of the same kind as the first one, it is converted to the
|
||||
same kind before merging as long as it implements the `Enum` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
def merge(dict1, dict2) do
|
||||
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dicts into one. If the dicts have duplicated entries, the given
|
||||
function is invoked to solve conflicts.
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
def merge(dict1, dict2, fun) do
|
||||
elem(dict1, 0).merge(dict1, dict2, fun)
|
||||
end
|
||||
|
||||
@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
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :a, fn val -> -val end
|
||||
#=> [a: -1, b: 2]
|
||||
|
||||
"""
|
||||
def update(dict, key, fun) do
|
||||
elem(dict, 0).update(dict, key, fun)
|
||||
end
|
||||
|
||||
@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
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :c, 3, fn val -> -val end
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
def update(dict, key, initial, fun) do
|
||||
elem(dict, 0).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
def empty(dict) do
|
||||
elem(dict, 0).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
"""
|
||||
def to_list(dict) do
|
||||
elem(dict, 0).to_list(dict)
|
||||
end
|
||||
end
|
||||
@@ -1,47 +0,0 @@
|
||||
defmodule Dict.Common do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior Dict
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
def new do
|
||||
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 ->
|
||||
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)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,226 +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
|
||||
case inspect(fun) do
|
||||
<< ?:, fun :: binary >> -> :ok
|
||||
fun -> :ok
|
||||
end
|
||||
|
||||
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 """
|
||||
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
|
||||
"""
|
||||
def formatted_stacktrace(trace // System.stacktrace) do
|
||||
case trace do
|
||||
[] -> ""
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
|
||||
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 keyword 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
@@ -1,88 +0,0 @@
|
||||
defmodule GenServer.Behavior do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
IO.puts "Using GenServer.Behavior is deprecated, use GenServer.Behaviour instead"
|
||||
quote do
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
By using this module, you get default GenServer callbacks
|
||||
for `init`, `handle_call`, `handle_info`, `handle_cast`,
|
||||
`terminate` and `code_change`. Since these functions are
|
||||
defined as overridable, they can be customized and fallback
|
||||
to the default behaviour by calling `super`.
|
||||
|
||||
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
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:peek, _from, [h|_] = state) do
|
||||
{ :reply, h, state }
|
||||
end
|
||||
|
||||
# Default behaviour
|
||||
def handle_call(request, from, config) do
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, state) do
|
||||
{ :noreply, [item|state] }
|
||||
end
|
||||
|
||||
# Default cast behaviour
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
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
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,97 +0,0 @@
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type 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` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ HashDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
:dict.fetch_keys data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
:dict.fold fn _key, value, acc ->
|
||||
[value|acc]
|
||||
end, [], data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
:dict.size data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:dict.is_key key, data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default) do
|
||||
case :dict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:dict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:dict.erase key, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:dict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:dict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:dict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict(:dict.new)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(dict(data)) do
|
||||
:dict.to_list data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
@@ -1,138 +0,0 @@
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Module responsible for doing IO. Many functions in this
|
||||
module expects an IO device and an io data encoded in UTF-8.
|
||||
|
||||
An IO device must be a pid is an atom representing a process.
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcut to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
An io data 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
|
||||
: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
|
||||
: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
|
||||
: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)
|
||||
:io.put_chars erl_dev, to_iodata(item)
|
||||
: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, opts // []) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
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
|
||||
: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
|
||||
: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
@@ -1,226 +0,0 @@
|
||||
defrecord Kernel.CLI.Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
# 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)
|
||||
:gen_server.call(:elixir_code_server, { :argv, argv })
|
||||
|
||||
all_commands = Enum.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)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
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)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
## Private
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = :gen_server.call(:elixir_code_server, :at_exit)
|
||||
lc hook inlist hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
rescue
|
||||
exception ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
catch
|
||||
kind, reason ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
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
|
||||
|
||||
# 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
|
||||
: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
|
||||
@@ -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)
|
||||
:error_handler.undefined_function(module, fun, args)
|
||||
end
|
||||
|
||||
def undefined_lambda(module, fun, args) do
|
||||
ensure_loaded(module)
|
||||
: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
|
||||
@@ -1,118 +0,0 @@
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias :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.put(:elixir_ensure_compiled, true)
|
||||
Process.flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
: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, 0)
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
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, 0)) do
|
||||
" (undefined module #{inspect module})"
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file}#{extra} =="
|
||||
: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
|
||||
@@ -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.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,458 +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 are using several functions from a given module, you can
|
||||
import those functions and reference them as local functions,
|
||||
for example:
|
||||
|
||||
import List
|
||||
flatten([1,[2],3]) #=> [1,2,3]
|
||||
|
||||
## Selector
|
||||
|
||||
By default, Elixir imports functions and macros from the given
|
||||
module, except the ones starting with underscore (which are
|
||||
usually callbacks):
|
||||
|
||||
import List
|
||||
|
||||
A developer can change this behavior to include all macros and
|
||||
functions, regardless if it starts with underscore, by passing
|
||||
`:all` as first argument:
|
||||
|
||||
import :all, List
|
||||
|
||||
It can also be customized to import only functions or only
|
||||
macros:
|
||||
|
||||
import :functions, List
|
||||
import :macros, List
|
||||
|
||||
Alternatively, Elixir allows a developer to specify `:only`
|
||||
or `:except` as a fine grained control on what to import (or
|
||||
not):
|
||||
|
||||
import List, only: [flatten: 1]
|
||||
|
||||
## 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.Behaviour
|
||||
end
|
||||
|
||||
`GenServer.Behaviour` 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.Behaviour` and not the line
|
||||
that calls `use GenServer.Behaviour`. 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.Behaviour` file, in
|
||||
particular, the macro `__FILE__` will always point to
|
||||
`GenServer.Behaviour` 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"], 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
|
||||
@@ -1,245 +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.get_attribute(module, :type) ++ Module.get_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 = Enum.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, 0)|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 Enum.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, Enum.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
|
||||
if is_atom(args), do: args = []
|
||||
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 :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
|
||||
@@ -1,300 +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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from enum. Differently from `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
|
||||
def from_enum(enum) do
|
||||
Enum.map(enum, fn(x) -> x end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty keyword 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) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
|
||||
@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!(keywords, key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(Keyword.KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get_values [a: 1, a: 2], :a
|
||||
#=> [1,2]
|
||||
|
||||
"""
|
||||
def get_values(keywords, key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys from the keyword 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 keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword 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(keywords, key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the given `value` under `key`.
|
||||
|
||||
If a previous value is already stored, all entries are
|
||||
removed and the value is overriden.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put [a: 1, b: 2], :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def put(list, key, value) do
|
||||
[{key, value}|delete(list, key)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two keywords are equal. I.e. they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
#=> true
|
||||
|
||||
"""
|
||||
def equal?(left, right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword 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
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword 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(d1, d2, fun) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
defp do_merge([{ k, v2 }|t], acc, fun) do
|
||||
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
|
||||
end
|
||||
|
||||
defp do_merge([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
@doc false
|
||||
def key?(list, key) do
|
||||
IO.write "[WARNING] Keyword.key? is deprecated, please use Keyword.has_key? instead\n#{Exception.formatted_stacktrace}"
|
||||
has_key?(list, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given key exists in the given keywords.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.has_key?([a: 1], :a)
|
||||
#=> true
|
||||
Keyword.has_key?([a: 1], :b)
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def has_key?(keywords, key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
not exist, raises `Keyword.KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, &1 * 2)
|
||||
#=> Keyword.KeyError
|
||||
|
||||
"""
|
||||
def update([{key, value}|keywords], key, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|keywords], key, fun) do
|
||||
[e|update(keywords, key, fun)]
|
||||
end
|
||||
|
||||
def update([], key, _fun) when is_atom(key) do
|
||||
raise(Keyword.KeyError, key: key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
#=> [a: 1, b: 11]
|
||||
|
||||
"""
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|keywords], key, initial, fun) do
|
||||
[e|update(keywords, key, initial, fun)]
|
||||
end
|
||||
|
||||
def update([], key, initial, _fun) when is_atom(key) do
|
||||
[{key, initial}]
|
||||
end
|
||||
end
|
||||
@@ -1,457 +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
|
||||
: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
|
||||
:lists.delete(item, 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
|
||||
:lists.duplicate(n, elem)
|
||||
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
|
||||
:lists.flatten(list)
|
||||
end
|
||||
|
||||
def flatten(list, tail) do
|
||||
: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
|
||||
: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
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reverse(list) do
|
||||
IO.write "[WARNING] List.reverse/1 is deprecated, please use Enum.reverse/1 instead\n#{Exception.formatted_stacktrace}"
|
||||
: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
|
||||
: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, 0)
|
||||
#=> { :a, 1 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], 2, 1)
|
||||
#=> { :b, 2 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], :c, 0)
|
||||
#=> nil
|
||||
|
||||
"""
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
:lists.keyfind(key, position + 1, 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, 0)
|
||||
#=> true
|
||||
|
||||
List.keymember?([a: 1, b: 2], 2, 1)
|
||||
#=> true
|
||||
|
||||
List.keymember?([a: 1, b: 2], :c, 0)
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def keymember?(list, key, position) do
|
||||
:lists.keymember(key, position + 1, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos` if it exists.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
:lists.keyreplace(key, position + 1, list, new_tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos`. If the item
|
||||
does not exist, it is added to the end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
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, 0)
|
||||
#=> [{ :b, 2 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], 2, 1)
|
||||
#=> [{ :a, 1 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], :c, 0)
|
||||
#=> [{ :a, 1 }, { :b, 2 }]
|
||||
|
||||
"""
|
||||
def keydelete(list, key, position) do
|
||||
:lists.keydelete(key, position + 1, 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 ->
|
||||
:lists.seq(first, last, 1)
|
||||
x when x < 0 ->
|
||||
[]
|
||||
_ ->
|
||||
: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 ->
|
||||
:lists.seq(first, last, -1)
|
||||
x when x > 0 ->
|
||||
[]
|
||||
_ ->
|
||||
: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 """
|
||||
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 false
|
||||
def zip(item1, item2) do
|
||||
IO.write "[WARNING] List.zip/2 is deprecated, please use Enum.zip/2 instead\n#{Exception.formatted_stacktrace}"
|
||||
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([]), do: []
|
||||
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 :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
|
||||
:lists.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
|
||||
@@ -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
|
||||
@@ -1,489 +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 %B"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_binary/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.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%b` and others).
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.unescape_binary "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_binary(chars) do
|
||||
: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:
|
||||
|
||||
Macro.unescape_binary "example\\n", unescape_map(&1)
|
||||
|
||||
"""
|
||||
def unescape_binary(chars, map) do
|
||||
: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 binaries 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
|
||||
: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
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@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 } = :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, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
|
||||
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.has_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] } = original, 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)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
end
|
||||
end
|
||||
|
||||
# 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 = :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 = :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))
|
||||
:elixir_aliases.lookup(atom, env.aliases)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
evaluate code) and returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
def do_safe_term(_), do: nil
|
||||
end
|
||||
@@ -1,73 +0,0 @@
|
||||
defmodule Macro.Env do
|
||||
Record.deffunctions([:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :macros], __MODULE__)
|
||||
|
||||
@moduledoc """
|
||||
A record that contains compile time environment information,
|
||||
It can be accessed at any time by calling __ENV__.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the current module name.
|
||||
"""
|
||||
def module(record)
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
"""
|
||||
def file(record)
|
||||
|
||||
@doc """
|
||||
Returns the current line as an integer.
|
||||
"""
|
||||
def line(record)
|
||||
|
||||
@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)
|
||||
|
||||
@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)
|
||||
|
||||
@doc """
|
||||
Returns the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign.
|
||||
"""
|
||||
def context(record)
|
||||
|
||||
@doc """
|
||||
Returns the list of required modules.
|
||||
"""
|
||||
def requires(record)
|
||||
|
||||
@doc """
|
||||
Returns a list of macros imported from each module.
|
||||
"""
|
||||
def macros(record)
|
||||
|
||||
@doc """
|
||||
Returns a keyword list containing the file and line
|
||||
information as keys.
|
||||
"""
|
||||
def location(record) do
|
||||
[file: file(record), line: line(record)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :assign
|
||||
end
|
||||
@@ -1,627 +0,0 @@
|
||||
defmodule Module do
|
||||
require :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 """
|
||||
Check if a module is open, i.e. it is currently being defined
|
||||
and its attributes and functions can be modified.
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
end
|
||||
|
||||
@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 (considering the environemnt module, line and file):
|
||||
|
||||
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)
|
||||
:elixir_module.eval_quoted(module, quoted, binding, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and given by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be given as options.
|
||||
|
||||
## Examples
|
||||
|
||||
contents =
|
||||
quote do
|
||||
def world, do: true
|
||||
end
|
||||
|
||||
Module.create(Hello, contents, __ENV__)
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
## Differences with `defmodule`
|
||||
|
||||
`Module.create` works similarly to `defmodule` and
|
||||
return the same results. While one could also use
|
||||
`defmodule` to define modules dynamically, this
|
||||
function is preferred when the module body is given
|
||||
by a quoted expression.
|
||||
|
||||
Another important distinction is that `defmodule`
|
||||
blends into the scope it is invoked, allowing you
|
||||
to access all variables, imports and requires from
|
||||
the module. `Module.create`, on the other hand, creates
|
||||
a new scope so imports, requires, etc are not inherited.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
|
||||
def create(module, quoted, Macro.Env[] = env) do
|
||||
create(module, quoted, env.location)
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = opts[:line] || 1
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
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
|
||||
: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
|
||||
: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
|
||||
: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
|
||||
: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
|
||||
kind in [:def, :defmacro, :defcallback] and (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) do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
merge_kind(old_kind, kind),
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_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
|
||||
|
||||
defp merge_kind(:defcallback, new), do: new
|
||||
defp merge_kind(old, :defcallback), do: old
|
||||
defp merge_kind(_old, new), do: new
|
||||
|
||||
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 """
|
||||
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 = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, [clause] } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
key = List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
match? { _, { _, [_|_] } }, key
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
end
|
||||
|
||||
"""
|
||||
def put_attribute(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_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 false
|
||||
def add_attribute(module, key, value) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.add_attribute is deprecated, please use Module.put_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
put_attribute(module, key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets 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.put_attribute __MODULE__, :value, 1
|
||||
Module.get_attribute __MODULE__, :value #=> 1
|
||||
|
||||
Module.register_attribute __MODULE__, :value, accumulate: true
|
||||
Module.put_attribute __MODULE__, :value, 1
|
||||
Module.get_attribute __MODULE__, :value #=> [1]
|
||||
end
|
||||
|
||||
"""
|
||||
def get_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:get_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 false
|
||||
def read_attribute(module, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.read_attribute is deprecated, please use Module.get_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
get_attribute(module, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.put_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 :
|
||||
|
||||
## 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 """
|
||||
Split the given module name into binary parts.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.split Very.Long.Module.Name.And.Even.Longer
|
||||
#=> ["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(to_binary(module), "-"))
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_doc(env, kind, name, args, _guards, body) do
|
||||
module = env.module
|
||||
line = env.line
|
||||
arity = length(args)
|
||||
pair = { name, arity }
|
||||
doc = get_attribute(module, :doc)
|
||||
kind = if kind == :def and body == nil and module != Kernel, do: :defcallback, else: kind
|
||||
|
||||
case add_doc(module, line, kind, pair, args, doc) do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
{ :error, :existing_doc } ->
|
||||
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
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!(:put_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 is_atom(atom) and
|
||||
key in [:before_compile, :after_compile, :on_definition] 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 data_table_for(module) do
|
||||
module
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom :lists.concat([:f, module])
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom :lists.concat([:o, module])
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
open?(module) ||
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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, Enum.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
|
||||
@@ -1,95 +0,0 @@
|
||||
defmodule OrdDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type 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` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ OrdDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key key, data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase key, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
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 Access, for: OrdDict do
|
||||
def access(dict, key), do: OrdDict.get(dict, key, nil)
|
||||
end
|
||||
@@ -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
|
||||
@@ -1,315 +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 keyword 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(pid, name) 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
|
||||
nillify :erlang.whereis(name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of the group leader for the process which evaluates the function.
|
||||
"""
|
||||
def group_leader do
|
||||
:erlang.group_leader
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
|
||||
started from a certain shell should have another group leader than `:init`.
|
||||
"""
|
||||
def group_leader(leader, pid) do
|
||||
:erlang.group_leader(leader, pid)
|
||||
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.
|
||||
Use this only for debugging information.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
|
||||
"""
|
||||
def info(pid) do
|
||||
:erlang.process_info(pid)
|
||||
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
|
||||
@@ -1,321 +0,0 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :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 keyword 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
|
||||
# We don't 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(conversions, fallback, __ENV__)
|
||||
Protocol.meta(@functions, fallback, __ENV__)
|
||||
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, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_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
|
||||
@behaviour unquote(protocol)
|
||||
unquote(block)
|
||||
def __impl__, do: unquote(protocol)
|
||||
end
|
||||
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_compiled(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 """
|
||||
Defines meta information about the protocol and internal callbacks.
|
||||
"""
|
||||
def meta(functions, fallback, env) do
|
||||
contents = quote do
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks), do: [{ :__impl__, 0 }|__protocol__(: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
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
__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(conversions, fallback, env) do
|
||||
contents = lc kind inlist conversions do
|
||||
each_impl_for(kind, if fallback, do: conversions)
|
||||
end
|
||||
|
||||
# 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.keyfind(Any, 1, 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 allows to check
|
||||
# if the first item in the tuple is a 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
|
||||
|
||||
# Handle records when we don't have fallbacks.
|
||||
# It simply gets the first element of the tuple.
|
||||
# This case assumes that, whenever a tuple is given
|
||||
# it is meant to be a record, so we don't need extra
|
||||
# checks.
|
||||
defp each_impl_for({ _, :is_record }, nil) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
|
||||
__MODULE__.Record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle records in the case we have fallbacks.
|
||||
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 -> __fallback__
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
_ -> __fallback__
|
||||
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
|
||||
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) ->
|
||||
:ok
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid args for defprotocol"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
# 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.
|
||||
generated = 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]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name).(unquote_splicing(args))
|
||||
|
||||
Kernel.def unquote(name).(unquote_splicing(generated)) do
|
||||
case __raw_impl__(xA) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(generated))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, unquote(name), args, _ }|_]|_] when length(args) == unquote(arity) ->
|
||||
case __fallback__ do
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,50 +0,0 @@
|
||||
defrecord Range, [:first, :last] do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
"""
|
||||
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 Range.Iterator, for: Number do
|
||||
def iterator(first, Range[first: f, last: last]) when is_integer(first) and is_integer(last) and last < f do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
@@ -1,615 +0,0 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
Functions to define Elixir 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 """
|
||||
Main entry point for records definition. It defines a module
|
||||
with the given `name` and the fields specified in `modules`.
|
||||
This is invoked directly by `Kernel.defrecord`, so check it
|
||||
for more information and documentation.
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
opts = Keyword.delete(opts, :do)
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
Record.deffunctions(unquote(values), unquote(opts), __ENV__)
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the
|
||||
module environment, the module values and a keyword list
|
||||
of options.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomRecord do
|
||||
Record.deffunctions [:name, :age], __ENV__
|
||||
end
|
||||
|
||||
"""
|
||||
def deffunctions(values, opts // [], env) do
|
||||
values = lc value inlist values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
extensions = Keyword.get(opts, :extensions, Record.Extensions)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
indexes(escaped),
|
||||
readers(values, 1, []),
|
||||
conversions(values),
|
||||
writers(values, 1, []),
|
||||
updater(values),
|
||||
extensions(values, 1, [], extensions)
|
||||
]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if env == Macro.Env do
|
||||
:elixir_module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
Module.eval_quoted(env, contents)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines three macros for reading and writing records values.
|
||||
These macros are private to the current module and are
|
||||
basically a simple mechanism for manipulating tuples when
|
||||
there isn't an interest in exposing the record as a whole.
|
||||
In some ways, it is similar to Erlang records, since it is
|
||||
only available at compilation time.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomModule do
|
||||
Record.defmacros :_user, [:name, :age], __ENV__
|
||||
|
||||
def new(name, age) do
|
||||
_user(name: name, age: age)
|
||||
end
|
||||
|
||||
def name(user, name) do
|
||||
_user(user, name: name)
|
||||
end
|
||||
|
||||
def age(user) do
|
||||
_user(user, :age)
|
||||
end
|
||||
|
||||
def to_keywords(user) do
|
||||
_user(user)
|
||||
end
|
||||
|
||||
def name_and_age(user) do
|
||||
_user(user, [:name, :age])
|
||||
end
|
||||
|
||||
def age_and_name(user) do
|
||||
_user(user, [:age, :name])
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env) do
|
||||
escaped = lc value inlist values, do: Macro.escape(convert_value(value))
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name).(record) when is_tuple(record) do
|
||||
Record.to_keywords(__CALLER__, __MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(args) do
|
||||
Record.access(__CALLER__, __MODULE__, unquote(escaped), args)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, key) when is_atom(key) do
|
||||
Record.get(__CALLER__, __MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, args) do
|
||||
Record.dispatch(__CALLER__, __MODULE__, unquote(escaped), record, args)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env, contents)
|
||||
end
|
||||
|
||||
defp convert_value(atom) when is_atom(atom) do
|
||||
{ atom, nil }
|
||||
end
|
||||
|
||||
defp convert_value(other), do: other
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
@doc false
|
||||
def access(caller, atom, fields, keyword) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, { :_, 0, nil })
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_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
|
||||
|
||||
# Dispatch the call to either update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(caller, atom, fields, record, args) do
|
||||
if is_keyword(args) do
|
||||
update(caller, atom, fields, record, args)
|
||||
else
|
||||
to_list(caller, atom, fields, record, args)
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the update macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
@doc false
|
||||
defp update(caller, atom, fields, var, keyword) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the get macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
@doc false
|
||||
def get(_caller, atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_caller, _atom, fields, record) do
|
||||
Enum.map Keyword.from_enum(fields),
|
||||
fn({key, _default}) ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{unquote(key), :erlang.element(unquote(index + 2), unquote(record))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(_caller, atom, fields, record, keys) do
|
||||
Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
# 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 __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 initializer(values) do
|
||||
defaults = lc { _, value } inlist values, do: value
|
||||
|
||||
# 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: :erlang.setelement(1, tuple, __MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
# Define method to get index of a given key.
|
||||
#
|
||||
# Useful if you need to know position of the key for such applications as:
|
||||
# - ets
|
||||
# - mnesia
|
||||
#
|
||||
# For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define following method:
|
||||
#
|
||||
# def __index__(:atime), do: 2
|
||||
# def __index__(:mtime), do: 3
|
||||
# def __index__(_), do: nil
|
||||
#
|
||||
defp indexes(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __index__(unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
quote do
|
||||
unquote(quoted)
|
||||
def __index__(_), do: nil
|
||||
def __index__(key, _), do: __index__(key)
|
||||
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 conversions(values) do
|
||||
sorted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), 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)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
# Implement readers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(record) do
|
||||
# elem(record, 1)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
defp readers([{ key, _default }|t], i, acc) do
|
||||
contents = quote do
|
||||
def unquote(key).(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
end
|
||||
|
||||
readers(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp readers([], _i, acc), do: acc
|
||||
|
||||
# Implement writers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# setelem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# setelem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
defp writers([{ key, _default }|t], i, acc) do
|
||||
contents = quote do
|
||||
def unquote(key).(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
end
|
||||
|
||||
writers(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp writers([], _i, acc), do: acc
|
||||
|
||||
# Defines update/2
|
||||
defp updater(values) do
|
||||
fields =
|
||||
lc {key, _default} inlist values do
|
||||
quote do
|
||||
Keyword.get(keywords,
|
||||
unquote(key),
|
||||
elem(record, unquote(find_index(values, key, 1))))
|
||||
end
|
||||
end
|
||||
contents = {:{}, 0, [(quote do: __MODULE__)|fields]}
|
||||
quote do
|
||||
def update(keywords, record) do
|
||||
unquote(contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Defines extra functions from the definition.
|
||||
defp extensions([{ key, default }|t], i, acc, extensions) do
|
||||
functions = extensions.functions_for(key, default, i)
|
||||
extensions(t, i + 1, [functions | acc], extensions)
|
||||
end
|
||||
|
||||
defp extensions([], _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 :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] = :filename.split(to_char_list(file))
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :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, 0) 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 :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, _ } = :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.Extensions 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(update).(function, record) do
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), 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 + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, value ++ current)
|
||||
end
|
||||
|
||||
def unquote(merge).(value, record) do
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), 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 + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), 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).(record) do
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, not current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(_, _, _), do: nil
|
||||
end
|
||||
@@ -1,343 +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
|
||||
* groups (g) - compile with info about groups available
|
||||
|
||||
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)
|
||||
opts = translate_options(options)
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
case :re.compile(source, re_opts) do
|
||||
{ :ok, compiled } ->
|
||||
{ :ok, { Regex, compiled, source, options, groups } }
|
||||
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 :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
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
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, string, options // [])
|
||||
def run({ Regex, compiled, _, _, groups }, string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise "Regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
|
||||
|
||||
"""
|
||||
def captures({ Regex, _, _, _, groups } = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
captures = if groups do
|
||||
List.sort(groups)
|
||||
else
|
||||
raise "Regex was not compiled with g"
|
||||
end
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip captures, results
|
||||
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
|
||||
IO.write "[WARNING] Regex.indexes is deprecated, please use Regex.run with return: :index as option instead\n#{Exception.formatted_stacktrace}"
|
||||
case :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 """
|
||||
Returns list of named groups in regex.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
|
||||
|
||||
"""
|
||||
def groups({ Regex, _, _, _, groups }) do
|
||||
groups
|
||||
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, string, options // [])
|
||||
def scan({ Regex, compiled, _, _, _ }, string, options) do
|
||||
return = options[:return] || return_for(string)
|
||||
options = [{ :capture, :all, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } -> flatten_result(results)
|
||||
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, string, options // [])
|
||||
|
||||
def split(regex, string, options) when is_integer(options) or is_atom(options) do
|
||||
IO.write "[WARNING] Passing an integer or atom to Regex.split/3 is deprecated, pass a :parts option instead\n#{Exception.formatted_stacktrace}"
|
||||
split(regex, string, parts: options)
|
||||
end
|
||||
|
||||
def split({ Regex, compiled, _, _, _ }, string, options) do
|
||||
parts =
|
||||
cond do
|
||||
options[:global] == false -> 2
|
||||
p = options[:parts] -> p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = options[:return] || return_for(string)
|
||||
opts = [{ :return, return }, { :parts, parts }]
|
||||
:re.split(string, compiled, opts)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a regex, a binary and a replacement and returns a new
|
||||
binary where the all matches are 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
|
||||
a specific matching parens. You can also set global to false
|
||||
if you want to replace just the first occurrence.
|
||||
|
||||
## 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, options // []) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
return = options[:return] || return_for(string)
|
||||
opts = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def replace_all({ Regex, compiled, _, _, _ }, string, replacement) do
|
||||
IO.write "[WARNING] Regex.replace_all is deprecated, simply use Regex.replace instead\n#{Exception.formatted_stacktrace}"
|
||||
:re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
# Unescape map function used by Macro.unescape_binary.
|
||||
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(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
|
||||
defp flatten_result(results) do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
case :re.run(source, pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,425 +0,0 @@
|
||||
defmodule String do
|
||||
@moduledoc """
|
||||
A string in Elixir is a utf-8 binary. This module
|
||||
contains function to work with utf-8 data and its
|
||||
codepoints.
|
||||
|
||||
For working with raw binaries, use Erlang's :binary
|
||||
module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Checks if a string is printable considering it is encoded
|
||||
as UTF-8. Returns true if so, false otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
String.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 """
|
||||
Divides a string into sub string based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into two parts by default, unless
|
||||
`global` option is true. If a pattern is not specified,
|
||||
the string is split on whitespace occurrences.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
|
||||
## Examples
|
||||
|
||||
String.split("a,b,c", ",") #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
|
||||
|
||||
String.split("foo bar") #=> ["foo", "bar"]
|
||||
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
|
||||
|
||||
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
|
||||
String.split("a,b", %r{\.}) #=> ["a,b"]
|
||||
|
||||
"""
|
||||
def split(binary, pattern // " ", options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
end
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
:binary.split(binary, pattern, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to upper case.
|
||||
|
||||
## Examples
|
||||
|
||||
String.upcase("abcd") #=> "ABCD"
|
||||
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
|
||||
"""
|
||||
def upcase(<<>>), do: <<>>
|
||||
|
||||
def upcase(<<195, c, t :: binary>>) when c in 160..191 do
|
||||
<<195, c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) when c in ?a..?z do
|
||||
<<c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) do
|
||||
<<c , upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to down case.
|
||||
|
||||
## Examples
|
||||
|
||||
String.downcase("ABCD") #=> "abcd"
|
||||
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
|
||||
"""
|
||||
def downcase(<<>>), do: <<>>
|
||||
|
||||
def downcase(<<195, c, t :: binary>>) when c in 128..159 do
|
||||
<<195, c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) when c in ?A..?Z do
|
||||
<<c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) do
|
||||
<<c , downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc ") #=> " abc"
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
|
||||
"""
|
||||
def rstrip(string, char // ?\s)
|
||||
|
||||
# Do a quick check before we traverse the whole
|
||||
# binary. :binary.last is a fast operation (it
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) do
|
||||
if :binary.last(string) == char do
|
||||
rstrip(string, "", char)
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, char) do
|
||||
rstrip(string, <<char, buffer :: binary>>, char)
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char, rstrip(string, "", another_char) :: binary>>
|
||||
end
|
||||
|
||||
defp rstrip(<<>>, _, _) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip(" abc ") #=> "abc "
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
|
||||
"""
|
||||
def lstrip(string, char // ?\s)
|
||||
|
||||
def lstrip(<<char, rest :: binary>>, char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
def lstrip(other, _char) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip(" abc ") #=> "abc"
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
|
||||
"""
|
||||
def strip(string, char // ?\s) do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` for `replacement`. If `options` is specified
|
||||
with `[global: true]`, then it will replace all matches, otherwise
|
||||
it will replace just the first one.
|
||||
|
||||
For the replaced part must be used in `replacement`, then the
|
||||
position or the positions where it is to be inserted must be specified by using
|
||||
the option `insert_replaced`.
|
||||
|
||||
## Examples
|
||||
|
||||
String.replace("a,b,c", ",", "-") #=> "a-b-c"
|
||||
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
|
||||
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
def replace(subject, pattern, replacement, options // []) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
opts = [{:insert_replaced,insert}|opts]
|
||||
end
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
|
||||
## Examples
|
||||
|
||||
String.duplicate("abc", 1) #=> "abc"
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
|
||||
"""
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with codepoints from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.codepoints("josé") #=> ["j", "o", "s", "é"]
|
||||
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
def codepoints(string) do
|
||||
do_codepoints(codepoint(string))
|
||||
end
|
||||
|
||||
defp do_codepoints({char, rest}) do
|
||||
[char|do_codepoints(codepoint(rest))]
|
||||
end
|
||||
|
||||
defp do_codepoints(:no_codepoint), do: []
|
||||
|
||||
@doc """
|
||||
Returns the first codepoint from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.first("elixir") #=> "e"
|
||||
String.first("եոգլի") #=> "ե"
|
||||
|
||||
"""
|
||||
def first(string) do
|
||||
case codepoint(string) do
|
||||
{ char, _ } -> char
|
||||
:no_codepoint -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last codepoint from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.last("elixir") #=> "r"
|
||||
String.last("եոգլի") #=> "ի"
|
||||
|
||||
"""
|
||||
def last(string) do
|
||||
do_last(codepoint(string), "")
|
||||
end
|
||||
|
||||
defp do_last({char, rest}, _) do
|
||||
do_last(codepoint(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_codepoint, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of codepoint in an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.length("elixir") #=> 6
|
||||
String.length("եոգլի") #=> 5
|
||||
|
||||
"""
|
||||
def length(string) do
|
||||
do_length(codepoint(string))
|
||||
end
|
||||
|
||||
defp do_length({_, rest}) do
|
||||
1 + do_length(codepoint(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_codepoint), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the codepoint in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
String.at("elixir", 0) #=> "1"
|
||||
String.at("elixir", 1) #=> "l"
|
||||
String.at("elixir", 10) #=> nil
|
||||
String.at("elixir", -1) #=> "r"
|
||||
String.at("elixir", -10) #=> "nil"
|
||||
|
||||
"""
|
||||
def at(string, position) when position >= 0 do
|
||||
do_at(codepoint(string), position, 0)
|
||||
end
|
||||
|
||||
def at(string, position) when position < 0 do
|
||||
real_pos = do_length(codepoint(string)) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(codepoint(string), real_pos, 0)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
|
||||
do_at(codepoint(rest), desired_pos, current_pos + 1)
|
||||
end
|
||||
|
||||
defp do_at({char, _}, desired_pos, current_pos) when desired_pos == current_pos do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_codepoint, _, _), do: ""
|
||||
|
||||
# Private implementation which returns the first codepoint
|
||||
# of any given utf8 string and the rest of it
|
||||
# If an empty string is given, :no_codepoint is returned.
|
||||
defp codepoint(<<194, char, rest :: binary>>)
|
||||
when char in 161..191,
|
||||
do: { <<194, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, char, rest :: binary>>)
|
||||
when first in 195..223 and char in 128..191,
|
||||
do: { <<first, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first == 224 and second in 160..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first in 225..239 and second in 128..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
|
||||
|
||||
defp codepoint(<<>>), do: :no_codepoint
|
||||
end
|
||||
@@ -1,153 +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 describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defmacrop get_describe do
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %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.7.0"
|
||||
|
||||
@doc """
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
"""
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
"""
|
||||
def argv do
|
||||
: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.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
|
||||
:gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
defmodule Tuple do
|
||||
end
|
||||
@@ -1,199 +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.map_join(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.
|
||||
|
||||
Use decoder/1 if you want to customize or iterate each value manually.
|
||||
"""
|
||||
def decode_query(q, dict // OrdDict.new) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an iterator function over the query string that decodes
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) do
|
||||
fn -> { do_decoder(&1), do_decoder(to_binary(q)) } end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
[first] -> ""
|
||||
end
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> { decode(key), decode(value) }
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
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, to_binary(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
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.FTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 21
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTPS do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 443
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -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
|
||||
@@ -1,18 +0,0 @@
|
||||
defmodule URI.Parser do
|
||||
@moduledoc """
|
||||
Defines the behavior for each URI.Parser.
|
||||
Check URI.HTTP for a possible implementation.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info)
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port()
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.SFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 22
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.TFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 69
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
defmodule Elixir.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
end
|
||||
end
|
||||
@@ -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"}.
|
||||
@@ -1,140 +0,0 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, 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.
|
||||
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
start_app(),
|
||||
'Elixir.Kernel.CLI':process_argv(Args).
|
||||
|
||||
%% 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 lists:keyfind(file, 1, Opts) of
|
||||
{ file, F } -> to_binary(F);
|
||||
false -> <<"nofile">>
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, L } -> L;
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
#elixir_scope{file=File,local=Local}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
eval(String, Binding) -> eval(String, Binding, []).
|
||||
|
||||
eval(String, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
false -> Line = 1;
|
||||
{ line, Line } -> []
|
||||
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 lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> [];
|
||||
false -> 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),
|
||||
temp_vars=[],
|
||||
quote_vars=[],
|
||||
clause_vars=[],
|
||||
counter=0
|
||||
},
|
||||
{ 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, orddict:new()).
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict: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 = orddict: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.
|
||||
@@ -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)]).
|
||||
@@ -1,242 +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 lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, Left, Right } || { Left, Right } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
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=orddict: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=orddict: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(orddict: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],
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(Line, TClauses, CV, LeftVars, FinalVars, [], FS)
|
||||
end
|
||||
end.
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) || { Var, Kind, _, 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, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,AssignExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Line, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
|
||||
|
||||
expand_clauses(_Line, [], [], _LeftVars, _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
|
||||
% 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, quoted }, S) ->
|
||||
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
|
||||
|
||||
normalize_vars({ Var, Kind }, S) ->
|
||||
normalize_vars(Var, Kind, #elixir_scope.vars, S).
|
||||
|
||||
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
|
||||
Vars = element(Index, S),
|
||||
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, S),
|
||||
|
||||
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
|
||||
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
|
||||
|
||||
Expr = case orddict:find(Var, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Var, Kind, NewValue, Expr }, S3 }.
|
||||
|
||||
% 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, Kind, OldValue, ClauseVars) ->
|
||||
case orddict:find({ Var, Kind }, 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.
|
||||
@@ -1,88 +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({ acquire, Path }, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ reply, loaded, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
|
||||
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
|
||||
error ->
|
||||
Queued = orddict:store(Path, { make_ref(), [] }, Current),
|
||||
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
|
||||
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, [F || { F, true } <- 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({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ noreply, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} };
|
||||
error ->
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} }
|
||||
end;
|
||||
|
||||
handle_cast({ unload_files, Files }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
|
||||
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 }.
|
||||
@@ -1,235 +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 lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
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),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
string(unicode:characters_to_list(Bin), File).
|
||||
|
||||
%% 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/range.ex",
|
||||
"lib/elixir/lib/behaviour.ex",
|
||||
"lib/elixir/lib/uri/parser.ex",
|
||||
"lib/elixir/lib/elixir/formatter.ex",
|
||||
"lib/elixir/lib/dict.ex",
|
||||
"lib/elixir/lib/dict/common.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/range.ex",
|
||||
"lib/elixir/lib/*/*.ex",
|
||||
"lib/elixir/lib/*.ex"
|
||||
].
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/code.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/behaviour.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).
|
||||
@@ -1,301 +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, _Body, 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, Body, RawS) ->
|
||||
Arity = length(Args),
|
||||
DS = elixir_scope:deserialize(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, Body),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Module, CO),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
Stack = TS#elixir_scope.macro,
|
||||
|
||||
%% Store function
|
||||
if
|
||||
(Body == nil) -> [];
|
||||
true ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Stack, Table, length(Defaults), Function)
|
||||
end,
|
||||
|
||||
[store_each(false, Kind, File, Location, Stack, Table, 0,
|
||||
function_for_default(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
|
||||
def_body(_Line, nil) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', Line, [Else] }.
|
||||
|
||||
%% @on_definition
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve @file
|
||||
|
||||
retrieve_file(Module, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> [];
|
||||
_ ->
|
||||
case 'Elixir.Module':get_attribute(Module, file) of
|
||||
nil -> [];
|
||||
Else ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
Else
|
||||
end
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
|
||||
Args = elixir_quote:linify(Line, RawArgs),
|
||||
Guards = elixir_quote:linify(Line, RawGuards),
|
||||
Expr = elixir_quote:linify(Line, RawExpr),
|
||||
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({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]).
|
||||
@@ -1,59 +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, Line, length(Base), [], []),
|
||||
|
||||
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], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, 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.
|
||||
@@ -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]).
|
||||
@@ -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([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% 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 >>.
|
||||
@@ -1,378 +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, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-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({ 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({ 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_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
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_alive, 0 },
|
||||
{ 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}
|
||||
].
|
||||
@@ -1,175 +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}}) ->
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, 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(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
erlang:raise(error, Exception, 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).
|
||||
|
||||
protocol_or_behaviour(Module) ->
|
||||
case is_protocol(Module) of
|
||||
true -> protocol;
|
||||
false -> behaviour
|
||||
end.
|
||||
|
||||
is_protocol(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
case erlang:function_exported(Module, '__protocol__', 1) of
|
||||
true -> Module:'__protocol__'(name) == Module;
|
||||
false -> false
|
||||
end;
|
||||
{ error, _ } ->
|
||||
false
|
||||
end.
|
||||
@@ -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).
|
||||
@@ -1,321 +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) ->
|
||||
IncludeAll = (Selector == all) or (Selector == default),
|
||||
|
||||
SF = case IncludeAll or (Selector == functions) of
|
||||
false -> S;
|
||||
true ->
|
||||
FunctionsFun = fun() -> remove_underscored(Selector, get_functions(Ref)) end,
|
||||
Functions = calculate(Line, Ref, Opts,
|
||||
S#elixir_scope.functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions}
|
||||
end,
|
||||
|
||||
SM = case IncludeAll or (Selector == macros) of
|
||||
false -> SF;
|
||||
true ->
|
||||
MacrosFun = fun() ->
|
||||
case IncludeAll of
|
||||
true -> remove_underscored(Selector, get_optional_macros(Ref));
|
||||
false -> 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 keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
Only = expand_fun_arity(Line, only, RawOnly, S),
|
||||
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;
|
||||
false ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun() ;
|
||||
{ except, [] } -> AvailableFun();
|
||||
{ except, RawExcept } ->
|
||||
Except = expand_fun_arity(Line, except, RawExcept, S),
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun() -- Except;
|
||||
{Key,ToRemove} -> ToRemove -- Except
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
%% Normalize the data before storing it
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
|
||||
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.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
|
||||
expand_fun_arity(Line, Kind, Value, S) ->
|
||||
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
||||
cons_to_keywords(Line, Kind, TValue, S).
|
||||
|
||||
cons_to_keywords(Line, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
||||
[tuple_to_fun_arity(Line, Kind, Left, S)];
|
||||
|
||||
cons_to_keywords(Line, Kind, { cons, _, Left, Right }, S) ->
|
||||
[tuple_to_fun_arity(Line, Kind, Left, S)|cons_to_keywords(Line, Kind, Right, S)];
|
||||
|
||||
cons_to_keywords(Line, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Line, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Line, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
|
||||
%% 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.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
remove_underscored(_, List) -> List.
|
||||
|
||||
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
||||
case atom_to_list(Name) of
|
||||
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
||||
"_" ++ _ -> remove_underscored(T);
|
||||
_ -> [H|remove_underscored(T)]
|
||||
end;
|
||||
|
||||
remove_underscored(T) ->
|
||||
T.
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% 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','*'}
|
||||
].
|
||||
@@ -1,158 +0,0 @@
|
||||
% Handle string and string-like interpolations.
|
||||
-module(elixir_interpolation).
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
extract(Line, File, Interpol, String, Last) ->
|
||||
extract(Line, File, Interpol, String, [], [], [], Last).
|
||||
|
||||
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
|
||||
finish_extraction(Line, File, Buffer, Output, []);
|
||||
|
||||
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _File, _Interpol, [Last], _Buffer, Search, _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("unexpected end of string: ~ts", [[hd(Search)]]), [Last] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
|
||||
|
||||
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
NewOutput = build_interpol(s, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
|
||||
|
||||
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
|
||||
NewOutput = build_interpol(i, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [], NewOutput, Last);
|
||||
|
||||
%% Check for available separators "", {}, [] and () inside interpolation
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
|
||||
|
||||
%% Else
|
||||
|
||||
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
unescape_tokens(Tokens) ->
|
||||
unescape_tokens(Tokens, fun unescape_map/1).
|
||||
|
||||
unescape_tokens(Tokens, Map) ->
|
||||
[unescape_token(Token, Map) || Token <- Tokens].
|
||||
|
||||
unescape_token(Token, Map) when is_binary(Token) -> unescape_chars(Token, Map);
|
||||
unescape_token(Other, _Map) -> Other.
|
||||
|
||||
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
|
||||
|
||||
-define(to_octal(List),
|
||||
|
||||
).
|
||||
|
||||
unescape_chars(String) ->
|
||||
unescape_chars(String, fun unescape_map/1).
|
||||
|
||||
unescape_chars(String, Map) ->
|
||||
Octals = case Map($0) of
|
||||
false -> false;
|
||||
_ -> true
|
||||
end,
|
||||
unescape_chars(String, Map, Octals, <<>>).
|
||||
|
||||
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
|
||||
to_octal(Rest, Map, [A,B,C], Acc);
|
||||
|
||||
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B) ->
|
||||
to_octal(Rest, Map, [A,B], Acc);
|
||||
|
||||
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Acc) when ?is_octal(A) ->
|
||||
to_octal(Rest, Map, [A], Acc);
|
||||
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Acc) ->
|
||||
case Map(Escaped) of
|
||||
false -> unescape_chars(Rest, Map, Octals, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Octals, <<Acc/binary, Other>>)
|
||||
end;
|
||||
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Acc) ->
|
||||
unescape_chars(Rest, Map, Octals, <<Acc/binary, Char>>);
|
||||
|
||||
unescape_chars(<<>>, _Map, _Octals, Acc) -> Acc.
|
||||
|
||||
to_octal(Rest, Map, Octal, Acc) ->
|
||||
unescape_chars(Rest, Map, true, <<Acc/binary, (list_to_integer(Octal, 8))/integer>>).
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
unescape_map($b) -> $\b;
|
||||
unescape_map($d) -> $\d;
|
||||
unescape_map($e) -> $\e;
|
||||
unescape_map($f) -> $\f;
|
||||
unescape_map($n) -> $\n;
|
||||
unescape_map($r) -> $\r;
|
||||
unescape_map($s) -> $\s;
|
||||
unescape_map($t) -> $\t;
|
||||
unescape_map($v) -> $\v;
|
||||
unescape_map(E) -> E.
|
||||
|
||||
% Extract Helpers
|
||||
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining) ->
|
||||
case build_interpol(s, Line, File, Buffer, Output) of
|
||||
[] -> Final = [<<>>];
|
||||
Final -> []
|
||||
end,
|
||||
{ Line, lists:reverse(Final), Remaining }.
|
||||
|
||||
build_interpol(_Kind, _Line, _File, [], Output) ->
|
||||
Output;
|
||||
|
||||
build_interpol(s, _Line, _File, Buffer, Output) ->
|
||||
[unicode:characters_to_binary(lists:reverse(Buffer))|Output];
|
||||
|
||||
build_interpol(i, Line, File, Buffer, Output) ->
|
||||
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
|
||||
|
||||
wrap_interpol(_Line, Form) when is_binary(Form) ->
|
||||
Form;
|
||||
|
||||
wrap_interpol(Line, Form) ->
|
||||
{ '::', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, { binary, Line, nil }]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:forms(String, StartLine, File, []) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', StartLine, Forms };
|
||||
{ error, Tuple } -> throw({ interpolation_error, Tuple })
|
||||
end.
|
||||
@@ -1,245 +0,0 @@
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Line, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Line, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '{}', Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ {tuple, Line, TArgs}, SE };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '[]', _Line, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', _Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
Exprs = [Left|RArgs],
|
||||
{ Tail, ST } = translate_each(Right, S),
|
||||
ListS = umergec(S, ST);
|
||||
_ ->
|
||||
Exprs = [RTail|RArgs],
|
||||
Tail = { nil, 0 },
|
||||
ST = S,
|
||||
ListS = S
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
assign ->
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end,
|
||||
|
||||
{ FExprs, umergev(ST, FS) };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', 0, [Left, Right]}, S);
|
||||
|
||||
translate(Args, S) when is_list(Args) ->
|
||||
translate({ '[]', 0, Args }, S);
|
||||
|
||||
translate(Number, S) when is_integer(Number) ->
|
||||
{ { integer, 0, Number }, S };
|
||||
|
||||
translate(Number, S) when is_float(Number) ->
|
||||
{ { float, 0, Number }, S };
|
||||
|
||||
translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '|' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Line, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Line, S, []),
|
||||
{ { bin, Line, lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Line, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_list(H) ->
|
||||
{ NewAcc, NewS } = build_bitstr_each(Fun, H, Line, S, Acc),
|
||||
build_bitstr_each(Fun, T, Line, NewS, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_bitstring(H) ->
|
||||
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
|
||||
NewAcc = lists:foldl(fun(Element, FinalAcc) -> [Element|FinalAcc] end, Acc, Elements),
|
||||
build_bitstr_each(Fun, T, Line, S, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case NS of
|
||||
{ ES, _ } -> []; %% translate_arg, no assigns
|
||||
_ -> ES = NS#elixir_scope{context=nil} %% translate_each, revert assigns
|
||||
end,
|
||||
|
||||
{ Size, Types } = extract_bit_values(Line, V, ES),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
|
||||
|
||||
%% Deprecated syntax
|
||||
build_bitstr_each(Fun, [{'|',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Just variables defined outside the binary can be accounted on subparts
|
||||
case NS of
|
||||
{ ES, _ } -> [];
|
||||
ES -> []
|
||||
end,
|
||||
|
||||
elixir_errors:deprecation(Line, ES#elixir_scope.file, "Old bitstring syntax with | is deprecated, use the new one with :: instead"),
|
||||
|
||||
%% Assigns can be made in subparts
|
||||
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
|
||||
|
||||
Final = case Types of
|
||||
[] -> default;
|
||||
_ -> lists:reverse(Types)
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_values(Line, V, S) when is_list(V) ->
|
||||
extract_bit_values(Line, V, default, [], S);
|
||||
|
||||
extract_bit_values(Line, V, S) ->
|
||||
extract_bit_values(Line, [V], S).
|
||||
|
||||
extract_bit_values(Line, [{ Value, _CallLine, Atom }|T] = All, Size, Types, S)
|
||||
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
|
||||
case extract_bit_type(Value, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
NewTypes when is_list(NewTypes) ->
|
||||
extract_bit_values(Line, T, Size, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Line, [{ Value, CallLine, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
|
||||
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
|
||||
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
{ error, Msg } ->
|
||||
elixir_errors:syntax_error(CallLine, S#elixir_scope.file, Msg);
|
||||
{ NewSize, NewTypes } ->
|
||||
extract_bit_values(Line, T, NewSize, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Line, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ Line, S })),
|
||||
extract_bit_values(Line, [Left|join_expansion(Expanded,[])], Size, Types, S);
|
||||
|
||||
extract_bit_values(Line, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
|
||||
extract_bit_values(_Line, [], Size, [], _S) ->
|
||||
{ Size, default };
|
||||
|
||||
extract_bit_values(_Line, [], Size, Types, _S) ->
|
||||
{ Size, lists:reverse(Types) }.
|
||||
|
||||
extract_bit_type(Value, Types) when
|
||||
Value == binary; Value == integer; Value == float; Value == bitstring;
|
||||
Value == bytes; Value == bits;
|
||||
Value == utf8; Value == utf16; Value == utf32;
|
||||
Value == signed; Value == unsigned;
|
||||
Value == big; Value == little; Value == native ->
|
||||
[Value|Types];
|
||||
|
||||
extract_bit_type(_Value, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
|
||||
{ Arg, Types };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, default, _Types) ->
|
||||
{ error, "size in bitstring expects an integer or a variable as argument" };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
|
||||
{ error, "duplicated size definition for bitstring" };
|
||||
|
||||
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
|
||||
{ Size, [{ unit, Arg }|Types] };
|
||||
|
||||
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
|
||||
{ error, "unit in bitstring expects an integer as argument" };
|
||||
|
||||
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Line, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ Line, S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Line, join_expansion(E,T), Size, Types, S)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
|
||||
%% Deprecated specifiers
|
||||
|
||||
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
|
||||
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
|
||||
extract_bin_values(Line, Right, LInt, LTypes, S);
|
||||
|
||||
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
|
||||
{ { integer, Line, Value }, Types };
|
||||
|
||||
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
|
||||
Translated = element(1, elixir_translator:translate_each(Expr, S)),
|
||||
{ Translated, Types };
|
||||
|
||||
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
|
||||
|
||||
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
|
||||
{ Int, [Value|Types] };
|
||||
|
||||
extract_bin_values(Line, _Value, _Int, _Types, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user