Compare commits

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

Adds a local EchoServer test that exercises the streaming route end-to-end.
2026-09-12 10:50:47 +00:00
hermes-agent beb9b1b3ef Add temporary chat-request logging (diagnose SwiftChat error) 2026-09-11 18:44:45 +00:00
hermes-agent cb512a7f17 admin: serve the page without requiring the Bearer header
A browser opening /admin can't send an Authorization header, so the
admin page was unreachable (401 blank). Serve the HTML form openly — it
exposes no data — and let the in-page ADMIN_API_KEY field drive the
auth'd /admin/agents CRUD calls.
2026-09-11 15:36:15 +00:00
hermes-agent e4fdeb6b79 nixos-module: set RELEASE_COOKIE so the release starts
The Elixir release's start script reads releases/COOKIE which isn't baked
in, so the service crashed on boot (cat: releases/COOKIE: No such file).
Set RELEASE_COOKIE in the systemd Environment to fix startup.
2026-09-10 14:35:28 +00:00
hermes-agent 6b22171018 flake: fill mixFodDeps hash 2026-09-10 07:21:42 +00:00
hermes-agent f0112289e6 flake: add mixFodDeps (fetchMixDeps) for Hex deps 2026-09-10 07:17:56 +00:00
hermes-agent 72e18a0a0c Remove AGENTS env seeding; agents managed only via admin API
The store now starts empty and agents are added/removed exclusively through
the web admin page / admin API, persisted to AGENTS_FILE. No AGENTS env var
needed in the sops secret.
2026-09-10 07:06:26 +00:00
hermes-agent e2be3f652e Add web admin page to manage agents
GET /admin serves a self-contained HTML page (ADMIN_API_KEY protected)
that lists agents and lets you add/update/remove them via the admin API —
no redeploy needed to add an agent.
2026-09-10 06:59:08 +00:00
hermes-agent 490bd32322 Add admin API to manage agents at runtime
- AgentRegistry is now file-backed (AGENTS_FILE, default
  /var/lib/n8n-openai/agents.json): agents persist across restarts and
  can be added/removed without a redeploy.
- New admin endpoints (separate ADMIN_API_KEY):
    GET    /admin/agents
    POST   /admin/agents   {model, webhook}
    DELETE /admin/agents/:model
- AGENTS env only seeds the store on first boot; the file is authoritative.
- NixOS module sets AGENTS_FILE under the writable StateDirectory.
2026-09-10 06:46:05 +00:00
hermes-agent 1bfcba133a Add NixOS module for the adapter service
Export nixosModules.default so the service (systemd unit, service user,
sops secret) is defined in the flake, not re-declared in each host config.
Consume with imports = [ inputs.n8n-openai-adapter.nixosModules.default ]
+ services.n8n-openai-adapter = { enable = true; domain = ...; port = ...; }.
2026-09-10 06:36:44 +00:00
hermes-agent c3d024c16c Apply mix format 2026-09-09 21:46:16 +00:00
hermes-agent 1530a761d5 Add flake.lock pinning nixpkgs 2026-09-09 21:44:55 +00:00
hermes-agent 5197b6ece6 OpenAI-compatible adapter for n8n chat agents (Elixir)
Exposes self-hosted n8n chat agents behind /v1/chat/completions and
/v1/models. Model -> n8n webhook routing via a GenServer registry, so
multiple agents map to multiple models. Plug + Bandit, req for the
n8n webhook call, Bearer auth (ADAPTER_API_KEY). Ships a flake.nix
(beamPackages.mixRelease) so it can be consumed as a NixOS flake input.
2026-09-09 21:43:21 +00:00
404 changed files with 1181 additions and 91273 deletions
-1
View File
@@ -1 +0,0 @@
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
+10 -16
View File
@@ -1,17 +1,11 @@
/.eunit/*
/.release
/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
.formatter.exs
/_build/
/cover/
/deps/
/doc/
/.fetch
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.*.swp
docs
*.ez
n8n_openai_adapter-*.tar
/tmp/
/result
-9
View File
@@ -1,9 +0,0 @@
language: erlang
script: "make compile && make test"
notifications:
irc: "irc.freenode.org#elixir-lang"
recipients:
- jose.valim@plataformatec.com.br
- yrashk@gmail.com
otp_release:
- R16B
-651
View File
@@ -1,651 +0,0 @@
# v0.11.0 (2013-11-02)
* Enhancements
* [Code] Eval now returns variables from other contexts
* [Dict] Document and enforce all dicts use the match operator (`===`) when checking for keys
* [Enum] Add `Enum.slice/2` with a range
* [Enum] Document and enforce `Enum.member?/2` to use the match operator (`===`)
* [IEx] Split `IEx.Evaluator` from `IEx.Server` to allow custom evaluators
* [IEx] Add support for `IEx.pry` which halts a given process for inspection
* [IO] Add specs and allow some IO APIs to receive any data that implements `String.Chars`
* [Kernel] Improve stacktraces on command line interfaces
* [Kernel] Sigils can now handle balanced tokens as in `%s(f(o)o)`
* [Kernel] Emit warnings when an alias is not used
* [Macro] Add `Macro.pipe/3` and `Macro.unpipe/1` for building pipelines
* [Mix] Allow umbrella children to share dependencies between them
* [Mix] Allow mix to be escriptize'd
* [Mix] Speed mix projects compilation by relying on more manifests information
* [Protocol] Protocols now provide `impl_for/1` and `impl_for!/1` functions which receive a structure and returns its respective implementation, otherwise returns nil or an error
* [Set] Document and enforce all sets use the match operator (`===`) when checking for keys
* [String] Update to Unicode 6.3.0
* [String] Add `Enum.slice/2` with a range
* Bug fixes
* [Exception] Ensure `defexception` fields can be set dynamically
* [Kernel] Guarantee aliases hygiene is respected when the current module name is not known upfront
* [Kernel] `Kernel.access/2` no longer flattens lists
* [Mix] Ensure cyclic dependencies are properly handled
* [String] Implement the extended grapheme cluster algorithm for `String` operations
* Deprecations
* [Kernel] `pid_to_list/1`, `list_to_pid/1`, `binary_to_atom/2`, `binary_to_existing_atom/2` and `atom_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
* [Kernel] `insert_elem/3` and `delete_elem/2` are deprecated in favor of `Tuple.insert_at/3` and `Tuple.delete_at/2`
* [Kernel] Use of `in` inside matches (as in `x in [1,2,3] -> x`) is deprecated in favor of the guard syntax (`x when x in [1,2,3]`)
* [Macro] `Macro.expand_all/2` is deprecated
* [Protocol] `@only` and `@except` in protocols are now deprecated
* [Protocol] Protocols no longer fallback to `Any` out of the box (this functionality needs to be explicitly enabled by setting `@fallback_to_any` to true)
* [String] `String.to_integer/1` and `String.to_float/1` are deprecated in favor of `Integer.parse/1` and `Float.parse/1`
* Backwards incompatible changes
* [CLI] Reading `.elixirrc` has been dropped in favor of setting env vars
* [Kernel] `Kernel.access/2` now expects the second argument to be a compile time list
* [Kernel] `fn -> end` quoted expression is no longer wrapped in a `do` keyword
* [Kernel] Quoted variables from the same module must be explicitly shared. Previously, if a function returned `quote do: a = 1`, another function from the same module could access it as `quote do: a`. This has been fixed and the variables must be explicitly shared with `var!(a, __MODULE__)`
* [Mix] Umbrella apps now treat children apps as dependencies. This means all dependencies will be checked out in the umbrela `deps` directory. On upgrade, child apps need to point to the umbrella project by setting `deps_path: "../../deps_path", lockfile: "../../mix.lock"` in their project config
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
# v0.10.3 (2013-10-02)
* Enhancements
* [Enum] Add `Enum.take_every/2`
* [IEx] IEx now respects signals sent from the Ctrl+G menu
* [Kernel] Allow documentation for types with `@typedoc`
* [Mix] Allow apps to be selected in umbrella projects
* [Record] Generated record functions `new` and `update` also take options with strings as keys
* [Stream] Add `Stream.unfold/1`
* Bug fixes
* [Dict] Fix a bug when a HashDict was marked as equal when one was actually a subset of the other
* [EEx] Solve issue where `do` blocks inside templates were not properly aligned
* [ExUnit] Improve checks and have better error reports on poorly aligned doctests
* [Kernel] Fix handling of multiple heredocs on the same line
* [Kernel] Provide better error messages for match, guard and quoting errors
* [Kernel] Make `Kernel.raise/2` a macro to avoid messing up stacktraces
* [Kernel] Ensure `&()` works on quoted blocks with only one expression
* [Mix] Address an issue where a dependency was not compiled in the proper order when specified in different projects
* [Mix] Ensure `compile: false` is a valid mechanism for disabling the compilation of dependencies
* [Regex] Fix bug on `Regex.scan/3` when capturing groups and the regex has no groups
* [String] Fix a bug with `String.split/2` when given an empty pattern
* [Typespec] Guarantee typespecs error reports point to the proper line
* Deprecations
* [Kernel] The previous partial application syntax (without the `&` operator) has now been deprecated
* [Regex] `Regex.captures/3` is deprecated in favor of `Regex.named_captures/3`
* [String] `String.valid_codepoint?/1` is deprecated in favor of pattern matching with `<<_ :: utf8 >>`
* Backwards incompatible changes
* [IEx] The `r/0` helper has been removed as it caused surprising behaviour when many modules with dependencies were accumulated
* [Mix] `Mix.Version` was renamed to `Version`
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
# v0.10.2 (2013-09-03)
* Enhancements
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
* [IEx] Allow for strings in `h` helper
* [IEx] Helpers `r` and `c` can handle erlang sources
* [Integer] Add `odd?/1` and `even?/1`
* [IO] Added support to specifying a number of bytes to stream to `IO.stream`, `IO.binstream`, `File.stream!` and `File.binstream!`
* [Kernel] Include file and line on error report for overriding an existing function/macro
* [Kernel] Convert external functions into quoted expressions. This allows record fields to contain functions as long as they point to an `&Mod.fun/arity`
* [Kernel] Allow `foo?` and `bar!` as valid variable names
* [List] Add `List.replace_at/3`
* [Macro] Improve printing of the access protocol on `Macro.to_string/1`
* [Macro] Add `Macro.to_string/2` to support annotations on the converted string
* [Mix] Automatically recompile a project if the Elixir version changes
* [Path] Add `Path.relative_to_cwd/2`
* [Regex] Allow erlang `re` options when compiling Elixir regexes
* [Stream] Add `Stream.concat/1`, `Stream.concat/2` and `Stream.flat_map/2`
* [String] Add regex pattern support to `String.replace/3`
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
* [URI] `URI.parse/1` supports IPv6 addresses
* Bug fixes
* [Behaviour] Do not compile behaviour docs if docs are disabled on compilation
* [ExUnit] Doctests no longer eat too much space and provides detailed reports for poorly indented lines
* [File] Fix a bug where `File.touch(file, datetime)` was not setting the proper datetime when the file did not exist
* [Kernel] Limit `inspect` results to 50 items by default to avoid printing too much data
* [Kernel] Return a readable error on oversized atoms
* [Kernel] Allow functions ending with `?` or `!` to be captured
* [Kernel] Fix default shutdown of child supervisors to `:infinity`
* [Kernel] Fix regression when calling a function/macro ending with bang, followed by `do/end` blocks
* [List] Fix bug on `List.insert_at/3` that added the item at the wrong position for negative indexes
* [Macro] `Macro.escape/2` can now escape improper lists
* [Mix] Fix `Mix.Version` matching on pre-release info
* [Mix] Ensure `watch_exts` trigger full recompilation on change with `mix compile`
* [Mix] Fix regression on `mix clean --all`
* [String] `String.strip/2` now supports removing unicode characters
* [String] `String.slice/3` still returns the proper result when there is no length to be extracted
* [System] `System.get_env/0` now returns a list of tuples as previously advertised
* Deprecations
* [Dict] `Dict.update/3` is deprecated in favor of `Dict.update!/3`
* [Enum] `Enum.min/2` and `Enum.max/2` are deprecated in favor of `Enum.min_by/2` and `Enum.max_by/2`
* [Enum] `Enum.join/2` and `Enum.map_join/3` with a char list are deprecated
* [IO] `IO.stream(device)` and `IO.binstream(device)` are deprecated in favor of `IO.stream(device, :line)` and `IO.binstream(device, :line)`
* [Kernel] `list_to_binary/1`, `binary_to_list/1` and `binary_to_list/3` are deprecated in favor of `String.from_char_list!/1` and `String.to_char_list!/1` for characters and `:binary.list_to_bin/1`, `:binary.bin_to_list/1` and `:binary.bin_to_list/3` for bytes
* [Kernel] `to_binary/1` is deprecated in favor of `to_string/1`
* [Kernel] Deprecate `def/4` and friends in favor of `def/2` with unquote and friends
* [Kernel] Deprecate `%b` and `%B` in favor of `%s` and `%S`
* [List] `List.concat/2` is deprecated in favor of `Enum.concat/2`
* [Macro] `Macro.unescape_binary/1` and `Macro.unescape_binary/2` are deprecated in favor of `Macro.unescape_string/1` and `Macro.unescape_string/2`
* [Mix] `:umbrella` option for umbrella paths has been deprecated in favor of `:in_umbrella`
* Backwards incompatible changes
* [IO] IO functions now only accept iolists as arguments
* [Kernel] `Binary.Chars` was renamed to `String.Chars`
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
# v0.10.1 (2013-08-03)
* Enhancements
* [Behaviour] Add support for `defmacrocallback/1`
* [Enum] Add `Enum.shuffle/1`
* [ExUnit] The `:trace` option now also reports run time for each test
* [ExUnit] Add support for `:color` to enable/disable ANSI coloring
* [IEx] Add the `clear` helper to clear the screen.
* [Kernel] Add the capture operator `&`
* [Kernel] Add support for `GenFSM.Behaviour`
* [Kernel] Functions now points to the module and function they were defined when inspected
* [Kernel] A documentation attached to a function that is never defined now prints warnings
* [List] Add `List.keysort/2`
* [Mix] `:test_helper` project configuration did not affect `mix test` and was therefore removed. A `test/test_helper.exs` file is still necessary albeit it doesn't need to be automatically required in each test file
* [Mix] Add manifests for yecc, leex and Erlang compilers, making it easier to detect dependencies in between compilers and providing a more useful clean behaviour
* [Mix] `mix help` now outputs information about the default mix task
* [Mix] Add `--no-deps-check` option to `mix run`, `mix compile` and friends to not check dependency status
* [Mix] Add support for `MIX_GIT_FORCE_HTTPS` system environment that forces HTTPS for known providers, useful when the regular git port is blocked. This configuration does not affect the `mix.lock` results
* [Mix] Allow coverage tool to be pluggable via the `:test_coverage` configuration
* [Mix] Add `mix cmd` as a convenience to run a command recursively in child apps in an umbrella application
* [Mix] Support `umbrella: true` in dependencies as a convenience for setting up umbrella path deps
* [Mix] `mix run` now behaves closer to the `elixir` command and properly mangles the ARGV
* [String] Add `Regex.scan/3` now supports capturing groups
* [String] Add `String.reverse/1`
* Bug fixes
* [Behaviour] Ensure callbacks are stored in the definition order
* [CLI] Speed up boot time on Elixir .bat files
* [IEx] Reduce cases where IEx parser can get stuck
* [Kernel] Improve error messages when the use of an operator has no effect
* [Kernel] Fix a bug where warnings were not being generated when imported macros conflicted with local functions or macros
* [Kernel] Document that `on_definition` can only be a function as it is evaluated inside the function context
* [Kernel] Ensure `%w` sigils with no interpolation are fully expanded at compile time
* [Mix] `mix deps.update`, `mix deps.clean` and `mix deps.unlock` no longer change all dependencies unless `--all` is given
* [Mix] Always run ` mix loadpaths` on `mix app.start`, even if `--no-compile` is given
* [OptionParser] Do not add boolean flags to the end result if they were not given
* [OptionParser] Do not parse non-boolean flags as booleans when true or false are given
* [OptionParser] Ensure `:keep` and `:integer`|`:float` can be given together as options
* [OptionParser] Ensure `--no-flag` sets `:flag` to false when `:flag` is a registered boolean switch
* Deprecations
* [Kernel] `function(Mod.fun/arity)` and `function(fun/arity)` are deprecated in favor of `&Mod.fun/arity` and `&fun/arity`
* [Kernel] `function/3` is deprecated in favor of `Module.function/3`
* [Kernel] `Kernel.ParallelCompiler` now receives a set of callbacks instead of a single one
* [Mix] `:test_coverage` option now expect keywords arguments and the `--cover` flag is now treated as a boolean
* Backwards incompatible changes
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
# v0.10.0 (2013-07-15)
* Enhancements
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
* [Kernel] Add pretty printing support for inspect
* [Kernel] Add document algebra library used as the foundation for pretty printing
* [Kernel] Add `defrecordp/3` that enables specifying the first element of the tuple
* [Kernel] Add the `Set` API and a hash based implementation via `HashSet`
* [Kernel] Add `Stream` as composable, lazy-enumerables
* [Mix] `mix archive` now includes the version of the generated archive
* [Mix] Mix now requires explicit dependency overriding to be given with `override: true`
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
* [Typespec] Improve error messages to contain file, line and the typespec itself
* Bug fixes
* [CLI] Elixir can now run on Unix directories with `:` in its path
* [Kernel] `match?/2` does not leak variables to outer scope
* [Kernel] Keep `head|tail` format when splicing at the tail
* [Kernel] Ensure variables defined in the module body are not passed to callbacks
* [Mix] On dependencies conflict, show from where each source is coming from
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* Deprecations
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
* [Kernel] Deprecate recently added `quote binding: ...` in favor of the clearer `quote bind_quoted: ...`
* [Kernel] Deprecate `Kernel.float/1` in favor of a explicit conversion
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
* Backwards incompatible changes
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
do_something 1, is_list [], 3
[1, is_atom :foo, 3]
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
# v0.9.3 (2013-06-23)
* Enhancements
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
* [Kernel] Print warnings to stderr
* [Kernel] Warn on undefined module attributes
* [Kernel] Emit warning for `x in []` in guards
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
* [Kernel] `quote` now allows a binding as an option
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
* [Mix] Implement `Mix.Version` for basic versioning semantics
* [Mix] Support creation and installation of archives (.ez files)
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
* [Record] Allow types to be given to `defrecordp`
* Bug fixes
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
* [Kernel] Improve error messages on default clauses clash
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
* [Kernel] Improve error messages when default clauses from different definitions collide
* [Kernel] `^x` variables should always refer to the value before the expression
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
* [Mix] Mix now properly evaluates rebar scripts
* Deprecations
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
* Backwards incompatible changes
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
* [Mix] No longer support beam files on `mix local`
# v0.9.2 (2013-06-13)
* Enhancements
* [ExUnit] `capture_io` now captures prompt by default
* [Mix] Automatically import git dependencies from Rebar
* [Mix] Support for dependencies directly from the umbrella application
* [Regex] Add `Regex.escape`
* [String] Add `String.contains?`
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
* Bug fixes
* [HashDict] Ensure HashDict uses exact match throughout its implementation
* [IEx] Do not interpret ANSI codes in IEx results
* [IEx] Ensure `--cookie` is set before accessing remote shell
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
* [Mix] Start the application by default on escripts
* Deprecations
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
* [Kernel] `super` no longer supports implicit arguments
* Backwards incompatible changes
* [Kernel] The `=~` operator now returns true or false instead of an index
# v0.9.1 (2013-05-30)
* Enhancements
* [IEx] Limit the number of entries kept in history and allow it to be configured
* [Kernel] Add `String.start_with?` and `String.end_with?`
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
* Bug fixes
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
* [IEx] Enable shell customization via the `IEx.Options` module
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
* [Kernel] Unused functions with cyclic dependencies are now also warned as unused
* [Mix] Fix a bug where `mix deps.get` was not retrieving nested dependencies
* [Record] Fix a bug where nested records cannot be defined
* [Record] Fix a bug where a record named Record cannot be defined
# v0.9.0 (2013-05-23)
* Enhancements
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
* [IEx] Add support for .iex files that are loaded during shell's boot process
* [IEx] Add `import_file/1` helper
* Backwards incompatible changes
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
* [Kernel] Erlang R15 is no longer supported
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
# v0.8.3 (2013-05-22)
* Enhancements
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
* [ExUnit] Add `ExUnit.CaptureIO` for IO capturing during tests
* [ExUnit] Consider load time on ExUnit time reports
* [IEx] Support `ls` with colored output
* [IEx] Add `#iex:break` to break incomplete expressions
* [Kernel] Add `Enum.at`, `Enum.fetch` and `Enum.fetch!`
* [Kernel] Add `String.to_integer` and `String.to_float`
* [Kernel] Add `Dict.take`, `Dict.drop`, `Dict.split`, `Dict.pop` and `Dict.fetch!`
* [Kernel] Many optimizations for code compilation
* [Kernel] `in` can be used with right side expression outside guards
* [Kernel] Add `Node.get_cookie/0` and `Node.set_cookie/2`
* [Kernel] Add `__DIR__`
* [Kernel] Expand macros and attributes on quote, import, alias and require
* [Kernel] Improve warnings related to default arguments
* [Keyword] Add `Keyword.delete_first/2`
* [Mix] Add `local.rebar` to download a local copy of rebar, and change `deps.compile` to use it if needed
* [Mix] Support umbrella applications
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
* [String] Add `String.valid?` and `String.valid_character?`
* Bug fixes
* [ExUnit] Handle exit messages from in ExUnit
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
* [Kernel] Ensure we don't splice keyword args unecessarily
* [Kernel] Private functions used by private macros no longer emit an unused warning
* [Kernel] Ensure Elixir won't trip on empty receive blocks
* [Kernel] `String.slice` now returns an empty string when out of range by 1
* [Mix] Generate manifest files after compilation to avoid depending on directory timestamps and to remove unused .beam files
* [Path] `Path.expand/2` now correctly expands `~` in the second argument
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
* [URI] Downcase host and scheme and URIs
* Deprecations
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
* [IO] `IO.getb` is deprecated in favor of `IO.getn`
* [List] `List.member?/2` is deprecated in favor of `Enum.member?/2`
* [Kernel] `var_context` in quote was deprecated in favor of `context`
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
* Backwards incompatible changes
* [Dict] `List.Dict` was moved to `ListDict`
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
* [Mix] `mix escriptize` now receives arguments as binaries
# v0.8.2 (2013-04-20)
* Enhancements
* [ExUnit] Use ANSI escape codes in CLI output
* [ExUnit] Include suite run time on CLI results
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
* [File] Add `File.ls` and `File.ls!`
* [IEx] Support `pwd` and `cd` helpers
* [Kernel] Better error reporting for invalid bitstring generators
* [Kernel] Improve meta-programming by allowing `unquote` on `def/2`, `defp/2`, `defmacro/2` and `defmacrop/2`
* [Kernel] Add support to R16B new functions: `insert_elem/3` and `delete_elem/2`
* [Kernel] Import conflicts are now lazily handled. If two modules import the same functions, it will fail only if the function is invoked
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
* Bug fixes
* [Binary] inspect no longer escapes standalone hash `#`
* [IEx] The `h` helper can now retrieve docs for special forms
* [Kernel] Record optimizations were not being triggered in functions inside the record module
* [Kernel] Aliases defined inside macros should be carried over
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
* Deprecations
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
* Backwards incompatible changes
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
quote do
quote do: unquote(x)
end
should become:
quote do
unquote(Macro.escape(x))
end
# v0.8.1 (2013-02-17)
* Enhancements
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
* [Kernel] Better support for Unicode lists
* [Kernel] Reduce variables footprint in `case`/`receive` clauses
* [Kernel] Disable native compilation when on_load attributes is present to work around an Erlang bug
* [Macro] `Macro.expand` also considers macros from the current `__ENV__` module
* [Mix] Improve support for compilation of `.erl` files
* [Mix] Add support for compilation of `.yrl` and `.xrl` files
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
* [Typespec] Better error reporting for invalid typespecs
* Bug fixes
* [Mix] Allow Mix projects to be generated with just one letter
* Backwards incompatible changes
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
* Deprecations
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
* [Kernel] Tidy up and clean `quote` API
* [Kernel] Old `:local.(args)` syntax is deprecated
* [Process] `Process.self` is deprecated in favor `Kernel.self`
# v0.8.0 (2013-01-28)
* Enhancements
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
* [Binary] Support `\a` escape character in binaries
* [Binary] Support syntax shortcut for specifying size in bit syntax
* [CLI] Support `--app` option to start an application and its dependencies
* [Dict] Support `put_new` in `Dict` and `Keyword`
* [Dict] Add `ListDict` and a faster `HashDict` implementation
* [ExUnit] ExUnit now supports multiple runs in the same process
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
* [Kernel] Introduce `Application.Behaviour` to define application module callbacks
* [Kernel] Introduce `Supervisor.Behaviour` to define supervisors callbacks
* [Kernel] More optimizations were added to Record handling
* [Kernel] `?\x` and `?\` are now supported ways to retrieve a codepoint
* [Kernel] Octal numbers can now be defined as `0777`
* [Kernel] Improve macros hygiene regarding variables, aliases and imports
* [Mix] Mix now starts the current application before run, iex, test and friends
* [Mix] Mix now provides basic support for compiling `.erl` files
* [Mix] `mix escriptize` only generates escript if necessary and accept `--force` and `--no-compile` as options
* [Path] Introduce `Path` module to hold filesystem paths related functions
* [String] Add `String.capitalize` and `String.slice`
* [System] Add `System.tmp_dir`, `System.cwd` and `System.user_home`
* Bug fixes
* [Kernel] `import` with `only` accepts functions starting with underscore
* [String] `String.first` and `String.last` return nil for empty binaries
* [String] `String.rstrip` and `String.lstrip` now verify if argument is a binary
* [Typespec] Support `...` inside typespec's lists
* Backwards incompatible changes
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
* Deprecations
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
* [File] Deprecate path related functions in favor of the module `Path`
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
# v0.7.2 (2012-12-04)
* Enhancements
* [CLI] `--debug-info` is now true by default
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
* [Kernel] Functions defined with `fn` can now handle many clauses
* [Kernel] Raise an error if clauses with different arities are defined in the same function
* [Kernel] `function` macro now accepts arguments in `M.f/a` and `f/a` formats
* [Macro] Improvements to `Macro.to_binary`
* [Mix] Mix now echoes the output as it comes when executing external commands such as git or rebar
* [Mix] Mix now validates `application` callback's values
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
* Bug fixes
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
* [IEx] IEx will now wait for all command line options to be processed before starting
* [Kernel] Ensure proper stacktraces when showing deprecations
* Deprecations
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
* [Record] Default-based generated functions are deprecated
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
# v0.7.1 (2012-11-18)
* Enhancements
* [IEx] Only show documented functions and also show docs for default generated functions
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
* [ExUnit] Add support for custom formatters via a well-defined behaviour
* [Kernel] Add support for `defrecordp`
* [Kernel] Improved dialyzer support
* [Kernel] Improved error messages when creating functions with aliases names
* [Mix] Improve SCM behaviour to allow more robust integration
* [Mix] Changing deps information on `mix.exs` forces users to fetch new dependencies
* [Mix] Support (parallel) requires on mix run
* [Mix] Support `-q` when running tests to compile only changed files
* [String] Support `String.downcase` and `String.upcase` according to Unicode 6.2.0
* [String] Add support for graphemes in `String.length`, `String.at` and others
* [Typespec] Support `@opaque` as attribute
* [Typespec] Define a default type `t` for protocols and records
* [Typespec] Add support for the access protocol in typespecs
* Bug fixes
* [Kernel] Fix an issue where variables inside clauses remained unassigned
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
* [File] File is opened by default on binary mode instead of utf-8
* Deprecations
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
* [Enum] `Enum.times` is deprecated in favor of using ranges
* [System] `halt` moved to `System` module
# 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 fixes
* [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)
* Backwards 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
-255
View File
@@ -1,255 +0,0 @@
# Contributing to Elixir
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved!
## Using the issue tracker
Use the issues tracker for:
* [bug reports](#bugs-reports)
* [submitting pull requests](#pull-requests)
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
* [#elixir-lang](irc://chat.freenode.net/elixir-lang)
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
We do our best to keep the issues tracker tidy and organized, making it useful
for everyone. For example, we classify open issues per application and perceived
difficulty of the issue, making it easier for developers to
[contribute to Elixir](#contributing).
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a reduced test
case.
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report. What is
your environment? What steps will reproduce the issue? What version of Erlang
and Elixir experience the problem? What would you expect to be the outcome?
All these details will help people to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the environment in which it occurs. If suitable,
> include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
## Feature requests
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
out whether your idea fits with the scope and aims of the project. It's up to *you*
to make a strong case to convince the project's developers of the merits of this
feature. Please provide as much detail and context as possible.
## Contributing
We incentivate everyone to contribute to Elixir and help us tackle
existing issues! To do so, there are a few things you need to know
about the code. First, Elixir code is divided in applications inside
the `lib` folder:
* `elixir` - Contains Elixir's kernel and stdlib
* `eex` - Template engine that allows you to embed Elixir
* `ex_unit` - Simple test framework that ships with Elixir
* `iex` — IEx, Elixir's interactive shell
* `mix` — Elixir's build tool
You can run all tests in the root directory with `make test` and you can
also run tests for a specific framework `make test_#{NAME}`, for example,
`make test_ex_unit`.
From time to time, your tests may fail in an existing Elixir checkout and
may require a clean start by running `make clean compile`. You can always
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
With tests running and passing, you are ready to contribute to Elixir and
send your pull requests.
## Contributing Documentation
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
the first paragraph is considered to be a short summary.
For functions, macros and callbacks say what it will do. For example write
something like:
```elixir
@doc """
Return only those elements for which `fun` is true.
...
"""
def filter(collection, fun) ...
```
For modules, records, protocols and types say what it is. For example write
something like:
```elixir
defrecord File.Stat, [...] do
@moduledoc """
Information about a file.
...
"""
end
```
Keep in mind that the first paragraph might show up in a summary somewhere, long
texts in the first paragraph create very ugly summaries. As a rule of thumb
anything longer than 80 characters is too long.
Try to keep unneccesary details out of the first paragraph, it's only there to
give a user a quick idea of what the documented "thing" does/is. The rest of the
documentation string can contain the details, for example when a value and when
`nil` is returned.
If possible include examples, preferably in a form that works with doctests. For
example:
```elixir
@doc """
Return only those elements for which `fun` is true.
## Examples
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[2]
"""
def filter(collection, fun) ...
```
This makes it easy to test the examples so that they don't go stale and examples
are often a great help in explaining what a function does.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**IMPORTANT**: By submitting a patch, you agree that your work will be
licensed under the license used by the project.
If you have any significant pull request in mind (e.g. implementing features,
refactoring code, porting to a different language), **please ask first**
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Please adhere to the coding conventions in the project (indentation,
accurate comments, etc.) and don't forget to add your own tests and
documentation. When working with git, we recommend the following process
in order to craft an excellent pull request:
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/elixir
# Navigate to the newly cloned directory
cd elixir
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/elixir-lang/elixir
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout master
git pull upstream master
```
3. Create a new topic branch (off of `master`) to contain your feature, change,
or fix.
**IMPORTANT**: Making changes in `master` is discouraged. You should always
keep your local `master` in sync with upstream `master` and make your
changes in topic branches.
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Keep your commit messages organized,
with a short description in the first line and more detailed information on
the following lines. Feel free to use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Make sure all the tests are still passing.
```bash
make test
```
This command will compile the code in your branch and use that
version of Elixir to run the tests. This is needed to ensure your changes can
pass all the tests.
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
8. If you haven't updated your pull request for a while, you should consider
rebasing on master and resolving any conflicts.
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
should always `git rebase` on `master` to bring your changes up to date when
necessary.
```bash
git checkout master
git pull upstream master
git checkout <your-topic-branch>
git rebase master
```
We have saved some excellent pull requests we have received in the past in case
you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
Thank you for your contributions!
-8
View File
@@ -1,8 +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_parser.erl (generated by build scripts)
-13
View File
@@ -1,13 +0,0 @@
Copyright 2012-2013 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.
-164
View File
@@ -1,164 +0,0 @@
REBAR := "$(shell echo `pwd`/rebar)"
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
VERSION := $(strip $(shell cat VERSION))
Q := @
PREFIX := /usr/local
LIBDIR := lib
INSTALL = install
INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
.NOTPARALLEL: compile
#==> Templates
define APP_TEMPLATE
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
@ echo "==> $(1) (compile)"
@ rm -rf lib/$(1)/ebin
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
#==> Compilation tasks
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
default: compile
compile: lib/elixir/src/elixir.app.src erlang elixir
lib/elixir/src/elixir.app.src: src/elixir.app.src
$(Q) rm -rf lib/elixir/src/elixir.app.src
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
erlang:
$(Q) cd lib/elixir && $(REBAR) compile
# Since Mix depends on EEx and EEx depends on
# Mix, we first compile EEx without the .app
# file, then mix and then compile eex fully
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
kernel: $(KERNEL) VERSION
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
$(Q) if [ ! -f $(KERNEL) ]; then \
echo "==> bootstrap (compile)"; \
$(ERL) -s elixir_compiler core -s erlang halt; \
fi
@ echo "==> kernel (compile)";
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
$(Q) $(MAKE) unicode
$(Q) rm -rf lib/elixir/ebin/elixir.app
$(Q) cd lib/elixir && $(REBAR) compile
unicode: $(UNICODE)
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/GraphemeBreakProperty.txt
@ echo "==> unicode (compile)";
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin --no-debug-info;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,eex,EEx))
$(eval $(call APP_TEMPLATE,mix,Mix))
$(eval $(call APP_TEMPLATE,iex,IEx))
install: compile
@ echo "==> elixir (install)"
for dir in lib/*; do \
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
done
clean:
cd lib/elixir && $(REBAR) clean
rm -rf ebin
rm -rf lib/*/ebin
rm -rf lib/*/tmp
rm -rf lib/mix/test/fixtures/git_repo
rm -rf lib/mix/test/fixtures/deps_on_git_repo
rm -rf lib/mix/test/fixtures/git_rebar
rm -rf lib/elixir/src/elixir.app.src
clean_exbeam:
$(Q) rm -f lib/*/ebin/Elixir.*.beam
#==> Release tasks
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
docs: compile ../ex_doc/bin/ex_doc
mkdir -p ebin
rm -rf docs
cp -R -f lib/*/ebin/*.beam ./ebin
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
rm -rf ebin
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@ false
release_zip: compile
rm -rf v$(VERSION).zip
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
release_docs: docs
cd ../elixir-lang.github.com
rm -rf ../elixir-lang.github.com/docs/master
mv docs ../elixir-lang.github.com/docs/master
release_erl: compile
$(Q) rm -rf rel/elixir
$(Q) cd rel && ../rebar generate
#==> Tests tasks
test: test_erlang test_elixir
test_erlang: compile
@ echo "==> elixir (eunit)"
$(Q) mkdir -p lib/elixir/test/ebin
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
@ echo ""
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_kernel: compile
@ echo "==> kernel (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
dialyze: .dialyzer.base_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
@ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
+73 -36
View File
@@ -1,57 +1,94 @@
![Elixir](https://github.com/elixir-lang/elixir-lang.github.com/raw/master/images/logo/logo.png)
=========
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.png?branch=master "Build Status")](http://travis-ci.org/elixir-lang/elixir)
# n8n-openai-adapter
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
An OpenAI-compatible HTTP adapter that exposes self-hosted **n8n chat agents**
behind a standard `/v1/chat/completions` API, so any OpenAI client (Cursor,
LibreChat, the `openai` SDK, a custom app) can talk to your n8n agents as if
they were OpenAI models.
## Usage
n8n itself does **not** ship an inbound OpenAI-compatible endpoint (its "AI
Gateway" is an outbound proxy to n8n Cloud). This small Elixir service is the
bridge: one `/v1/chat/completions` endpoint, routed to whichever n8n agent you
name in the `model` field.
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
## How it works
$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ make clean 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 Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
Multiple agents = multiple `model` names, each mapped to a different n8n webhook
in the `AGENTS` env var.
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.
## Configuration (env vars)
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
| 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. |
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
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.
If you have the correct version and tests still fail, feel free to [open an issue][2].
## Admin API (manage agents at runtime)
## Building documentation
Agents are persisted to `AGENTS_FILE` and can be added/removed without a
redeploy, using the `ADMIN_API_KEY`:
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
```bash
# list
curl -H "Authorization: Bearer $ADMIN_API_KEY" https://openai.bueso.eu/admin/agents
# After cloning and compiling Elixir
$ git clone git://github.com/elixir-lang/ex_doc.git
$ cd ex_doc && ../elixir/bin/mix compile
$ cd ../elixir && make docs
# 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
## Contributing
# remove
curl -X DELETE -H "Authorization: Bearer $ADMIN_API_KEY" \
https://openai.bueso.eu/admin/agents/media-agent
```
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. 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]
* [elixir-talk Mailing list (questions)][3]
* [elixir-core Mailing list (development)][4]
```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-talk
[4]: 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";
```
-31
View File
@@ -1,31 +0,0 @@
## Release process
This document simply outlines the release process:
1) Remove `-dev` extension from VERSION
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
4) Commit changes above with title "Release vVERSION"
5) Push master and create tag from master branch
6) Release new docs with `make release_docs`, move docs to `docs/stable`
7) Release new zip with `make release_zip`, push new zip to Github Releases
8) Push package to expm with `expm publish package.exs`
9) Merge master into stable branch and push it
10) After release, bump versions and add `-dev` back
11) `make release_docs` once again and push `elixir-lang.github.com`
## Places where version is mentioned
* VERSION
* CHANGELOG
* src/elixir.app.src
-1
View File
@@ -1 +0,0 @@
0.11.0
-83
View File
@@ -1,83 +0,0 @@
#!/bin/sh
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; 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 (*)
--app \"app\" Start the given app and its dependencies (*)
--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
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--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
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|+iex|+compile)
;;
-e|-r|-pr|-pa|-pz|--remsh|--app)
S=2
;;
--detached|--hidden)
ERL="$ERL `echo $PEEK | cut -c 2-`"
;;
--cookie)
I=$(expr $I + 1)
eval "VAL=\${$I}"
ERL="$ERL -setcookie "$VAL""
;;
--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 [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
then
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
else
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
fi
-88
View File
@@ -1,88 +0,0 @@
@echo off
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc%== 0 (
goto documentation
) else (
goto parseopts
)
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script Finds and executes the given script (*)
echo -pr file Requires the given files/patterns in parallel (*)
echo -pa path Prepends the given path to Erlang code path (*)
echo -pz path Appends the given path to Erlang code path (*)
echo --app app Start the given app and its dependencies (*)
echo --erl switches Switches to be passed down to erlang (*)
echo --name name Makes and assigns a name to the distributed node
echo --sname name Makes and assigns a short name to the distributed node
echo --cookie cookie Sets a cookie for this distributed node
echo --hidden Makes a hidden node
echo --detached Starts the Erlang VM detached from console
echo --no-halt Does 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 or --erl
goto :EOF
:parseopts
rem Parameters for Erlang
set parsErlang=
rem Make sure we keep a copy of all parameters
set allPars=%*
rem Get the original path name from the batch file
set originPath=%~dp0
rem Optional parameters before the "-extra" parameter
set beforeExtra=
rem Recursive loop called for each parameter that parses the cmd line parameters
:startloop
set par="%1"
shift
if "%par%"=="" (
rem if no parameters defined
goto :expand_erl_libs
)
if "%par%"=="""" (
rem if no parameters defined - special case for parameter that is already quoted
goto :expand_erl_libs
)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
goto:startloop
rem ******* assume all pre-params are parsed ********************
:expand_erl_libs
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
SETLOCAL enabledelayedexpansion
set ext_libs=
for /d %%d in ("%originPath%..\lib\*.") do (
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
)
SETLOCAL disabledelayedexpansion
:run
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
-29
View File
@@ -1,29 +0,0 @@
#!/bin/sh
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
-o The directory to output compiled files
--no-docs Do not attach documentation to compiled modules
--no-debug-info Do not attach debug info to compiled modules
--ignore-module-conflict
--warnings-as-errors Treat warnings as errors and return non-zero exit code
--verbose Print informational messages.
** 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
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 "$@"
-23
View File
@@ -1,23 +0,0 @@
@echo off
set argc=0
for %%x in (%*) do set /A argc+=1
if %argc% == 0 (
goto documentation
) else (
goto run
)
:documentation
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
echo -o The directory to output compiled files
echo --no-docs Do not attach documentation to compiled modules
echo --no-debug-info Do not attach debug info to compiled modules
echo --ignore-module-conflict
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
echo --verbose Print informational messages.
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 %*
-41
View File
@@ -1,41 +0,0 @@
#!/bin/sh
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version
-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 (*)
--app \"app\" Start the given app and its dependencies (*)
--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
--cookie \"cookie\" Sets a cookie for this distributed node
--hidden Makes a hidden node
--detached Starts the Erlang VM detached from console
--remsh \"name\" Connects to a node using a remote shell
--dot-iex \"path\" Overrides default .iex file and uses path instead;
path can be empty, then no file will be loaded
** 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
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")
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
-2
View File
@@ -1,2 +0,0 @@
@echo off
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env elixir
Mix.start
Mix.CLI.main
-2
View File
@@ -1,2 +0,0 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+7
View File
@@ -0,0 +1,7 @@
import Config
import_config "#{config_env()}.exs"
if config_env() == :test do
config :logger, level: :warning
end
+3
View File
@@ -0,0 +1,3 @@
import Config
# Dev: no special config — all runtime settings come from env vars.
+5
View File
@@ -0,0 +1,5 @@
import Config
# Production: no hardcoded values here. All runtime config (PORT, AGENTS,
# ADAPTER_API_KEY, CHAT_WEBHOOK_BASIC) comes from the systemd EnvironmentFile
# in the NixOS service module.
+7
View File
@@ -0,0 +1,7 @@
import Config
# Test environment: the app starts with an empty agent store (no AGENTS env
# seeding — agents are managed via the admin API). ADAPTER_API_KEY /
# ADMIN_API_KEY are set in test/test_helper.exs. AGENTS_FILE must be set HERE
# (config loads before the app boots) to a writable tmp path.
System.put_env("AGENTS_FILE", Path.join(System.tmp_dir!(), "n8n-openai-test-agents.json"))
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1788881743,
"narHash": "sha256-2V9GZGvPfrNzxFozhI9dcqV+c3QdA8YZrvAAzqEB+dI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d6524aaca2ff07876657ae2b323f24be4874944b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+48
View File
@@ -0,0 +1,48 @@
{
description = "OpenAI-compatible adapter exposing n8n chat agents behind /v1/chat/completions";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{ self, nixpkgs, ... }:
let
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in
{
packages = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
beamPackages = pkgs.beamPackages;
in
{
default = beamPackages.mixRelease {
pname = "n8n-openai-adapter";
version = "0.1.0";
src = self;
mixFodDeps = beamPackages.fetchMixDeps {
pname = "n8n-openai-adapter";
version = "0.1.0";
src = self;
hash = "sha256-sdAhpZUeF33V9xjEa/z/aTmCllfetMjO/1XyfJfUNao=";
};
};
}
);
overlays.default = final: prev: {
n8n-openai-adapter = self.packages.${final.stdenv.system}.default;
};
# Proper NixOS module: consume with
# imports = [ inputs.n8n-openai-adapter.nixosModules.default ];
# services.n8n-openai-adapter = { enable = true; domain = "..."; port = 8134; };
nixosModules.default = import ./nixos-module.nix;
};
}
-201
View File
@@ -1,201 +0,0 @@
defexception EEx.SyntaxError, message: nil
defmodule EEx do
@moduledoc %S"""
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
quote bind_quoted: binding do
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
compiled = EEx.compile_string(source, info)
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
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, file, args // [], options // []) do
quote bind_quoted: binding do
info = Keyword.merge options, [file: file, line: 1]
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
compiled = EEx.compile_file(file, info)
@file file
case kind do
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
end
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
defp do_eval(compiled, bindings, options) do
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
result
end
end
-117
View File
@@ -1,117 +0,0 @@
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
defmodule EEx.Compiler do
@moduledoc false
@doc """
This is the compilation entry point. It glues the tokenizer
and the engine together by handling the tokens and invoking
the engine every time a full expression or text is received.
"""
def compile(source, options) do
line = Keyword.get(options, :line, 1)
tokens = EEx.Tokenizer.tokenize(source, line)
state = EEx.State.new(options)
generate_buffer(tokens, "", [], state)
end
# Generates the buffers by handling each expression from the tokenizer
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, chars)
generate_buffer(t, buffer, scope, state)
end
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
expr = maybe_block :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)
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.update_dict(&[{key, buffer}|&1]) }
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__, [], 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
-57
View File
@@ -1,57 +0,0 @@
defmodule EEx.Engine do
@moduledoc %S"""
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.
"""
use Behaviour
defcallback handle_text(Macro.t, binary) :: Macro.t
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
@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_string(unquote(expr))
end
end
def handle_expr(buffer, "", expr) do
quote do
tmp = unquote(buffer)
unquote(expr)
tmp
end
end
end
-112
View File
@@ -1,112 +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.
Assigns can also be used when compiled to a function:
# sample.eex
<%= @a + @b %>
# sample.ex
defmodule Sample do
require EEx
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
end
# iex
Sample.sample(a: 1, b: 2) #=> "3"
"""
@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(arg) do
super(arg)
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
`EEx.AssignsEngine` and other conveniences. Read
`EEx.AssignsEngine` for examples.
"""
end
-159
View File
@@ -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(String.to_char_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, _line, 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, fn
{ :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, String.from_char_list!(Enum.reverse(buffer)) } | acc]
end
end
-7
View File
@@ -1,7 +0,0 @@
defmodule EEx.Mixfile do
use Mix.Project
def project do
[app: :eex, version: System.version]
end
end
-22
View File
@@ -1,22 +0,0 @@
Code.require_file "../test_helper.exs", __DIR__
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
-105
View File
@@ -1,105 +0,0 @@
Code.require_file "../test_helper.exs", __DIR__
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
-351
View File
@@ -1,351 +0,0 @@
Code.require_file "test_helper.exs", __DIR__
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 = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
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
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 nested do expressions" do
string = """
<% y = ["a", "b", "c"] %>
<%= cond do %>
<% "a" in y -> %>
Good
<% true -> %>
<% if true do %>true<% else %>false<% end %>
Bad
<% end %>
"""
assert_eval "\n\n Good\n \n", string
end
test "unicode" do
template = """
• <%= "•" %> •
<%= "Jößé Vâlìm" %> Jößé Vâlìm
"""
result = EEx.eval_string(template)
assert result == " • • •\n Jößé Vâlìm Jößé Vâlìm\n"
end
test "evaluates the source from a given file" do
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
result = EEx.eval_file(filename)
assert result == "foo bar.\n"
end
test "evaluates the source from a given file with bindings" do
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
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: to_char_list(__FILE__), line: 7]
}
}
assert EExText.Compiled.after_compile ==
{ 19,
{ EExText.Compiled,
:after_compile,
0,
[file: to_char_list(__FILE__), line: 18]
}
}
assert EExText.Compiled.unknown ==
{ 25,
{ EExText.Compiled,
:unknown,
0,
[file: 'unknown', line: 24]
}
}
end
defp assert_eval(expected, actual) do
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
assert result == expected
end
end
-1
View File
@@ -1 +0,0 @@
foo <%= if true do %>bar.<% end %>
-1
View File
@@ -1 +0,0 @@
foo <%= bar %>
-1
View File
@@ -1 +0,0 @@
ExUnit.start [trace: "--trace" in System.argv]
-86
View File
@@ -1,86 +0,0 @@
-define(wrap_call(Line, Module, Function, Args),
{ call, Line,
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
Args
}).
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
-define(line(Opts), elixir_utils:get_line(Opts)).
-record(elixir_scope, {
context=nil, %% can be assign, guards or nil
extra=nil, %% extra information about the context, like fn_match for fns
noname=false, %% when true, don't add new names (used by try)
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
backup_vars=nil, %% a copy of vars to be used on ^var
temp_vars=nil, %% a set of all variables defined in a particular assign
clause_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a counter for the variables defined
local=nil, %% the scope to evaluate local functions against
context_modules=[], %% modules defined in the current context
macro_aliases=[], %% keep aliases defined inside a macro
macro_counter=0, %% macros expansions counter
lexical_tracker=nil, %% holds the lexical tracker pid
aliases, %% an orddict with aliases by new -> old names
file, %% the current scope filename
requires, %% a set with modules required
macro_macros=[], %% a list with macros imported from module inside a macro
macros, %% a list with macros imported from module
macro_functions=[], %% a list with functions imported from module inside a macro
functions %% a list with functions imported from module
}).
-record(elixir_quote, {
line=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
imports_hygiene=true,
unquote=true,
unquoted=false,
escape=false
}).
-record(elixir_tokenizer, {
file,
terminators=[],
check_terminators=true,
existing_atoms_only=false
}).
%% Introspection
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
%% Used in tokenization and interpolation
%% Numbers
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
-define(is_bin(S), S >= $0 andalso S =< $1).
-define(is_octal(S), S >= $0 andalso S =< $7).
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
%% Digits and letters
-define(is_digit(S), S >= $0 andalso S =< $9).
-define(is_upcase(S), S >= $A andalso S =< $Z).
-define(is_downcase(S), S >= $a andalso S =< $z).
%% Atoms
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
%% Quotes
-define(is_quote(S), S == $" orelse S == $').
%% Spaces
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
-56
View File
@@ -1,56 +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 the `Access.access` protocol.
This protocol is limited and is implemented only for the
following built-in types: keywords, records and functions.
"""
@doc """
Receives the element being accessed and the access item.
"""
def access(container, key)
end
defimpl Access, for: List do
@doc """
Access the given key in a tuple list.
The key is found via the `===` operator.
## Examples
iex> keywords = [a: 1, b: 2]
...> keywords[:a]
1
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
...> star_ratings[1.5]
"★☆"
"""
def access(dict, key)
def access([{ key, value }|_], key), do: value
def access([{ _, _ }|t], key), do: access(t, key)
def access([], _key), do: nil
end
defimpl Access, for: Atom do
@doc """
The access protocol can only be accessed by atoms
at compilation time. If we reach this, we should raise
an exception.
"""
def access(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
-105
View File
@@ -1,105 +0,0 @@
defmodule Application.Behaviour do
@moduledoc """
Default callbacks for applications.
In Erlang/OTP, an application is a component that can be started
and stopped as a unit, and which can be reused in other systems.
The first step in creating an application is to define an application specification.
For example, if your application is named `:my_app`, an app specification
should exist at `ebin/my_app.app`. This file is usually defined by
build tools like Mix.
With the app specification in hand, we must define
application module callbacks that control how to start and stop
instances of the application. This module is about defining such callbacks.
There are two callbacks which must be implemented:
1. `start(type, args)` - must return `{ :ok, pid }` or
`{ :ok, pid, state }`, where `pid` is the process identifier
of the supervisor tree root and `state` is application defined
state information;
2. `stop(state)` - receives the `state` returned by `start` and should
do any necessary clean up. Notice that shutting down the supervisor
is automatically handled by the VM;
When using this module, it tags the module behaviour as
`:application` and provides a default `stop/1` callback. The `start/2` callback
still needs to be implemented by the user.
You can learn more about the `:application` module, the application
specification and the application module callbacks from these sources:
* http://www.erlang.org/doc/man/application.html
* http://www.erlang.org/doc/design_principles/applications.html
* http://learnyousomeerlang.com/building-otp-applications
## Example
defmodule MyApp do
use Application.Behaviour
def start(_type, args) do
MyApp.Sup.start_link(args)
end
end
"""
# Starts the given application and all of its dependencies that
# have not been started yet recursively.
#
# ## Supported types
#
# When starting an application, a type can be given:
#
# * `:permanent` - If a permanent application terminates, all other
# applications and the runtime system are also terminated;
# * `:transient` - If a transient application terminates with reason
# `:normal`, this is reported but no other applications are terminated.
# If a transient application terminates abnormally, all other
# applications and the runtime system are also terminated;
# * `:temporary` - If a temporary application terminates, this is reported
# but no other applications are terminated.
#
# The type only applies to the application being started. Its dependencies
# are all started with default type (which is :temporary).
#
# Note that transient mode is of little practical use, since when a
# supervision tree terminates, the reason is set to shutdown, not normal.
#
# ## Examples
#
# Application.Behaviour.start(:my_app)
#
@doc false
def start(app, type // :temporary) do
case :application.start(app, type) do
{ :error, { :not_started, dep } } ->
case start(dep) do
:ok -> start(app, type)
other -> other
end
{ :error, { :already_started, _ } } ->
:ok
other ->
other
end
end
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :application
@doc false
def stop(_state) do
:ok
end
defoverridable [stop: 1]
end
end
end
-142
View File
@@ -1,142 +0,0 @@
defmodule Behaviour do
@moduledoc """
Utilities for defining behaviour intefaces.
Behaviours can be referenced by other modules
to ensure they implement required callbacks.
For example, you can specify the `URI.Parser`
behaviour as follows:
defmodule URI.Parser do
use Behaviour
@doc "Parses the given URL"
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
@doc "Defines a default port"
defcallback default_port() :: integer
end
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
If the behaviour changes or `URI.HTTP` does
not implement one of the callbacks, a warning
will be raised.
## Implementation
Since Erlang R15, behaviours must be defined via
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the given type specification. `defcallback` allows
documentsion to be created for the callback and defines
a custom function signature.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback({ :::, _, [fun, return] }) do
do_defcallback(fun, return, __CALLER__)
end
defmacro defcallback(fun) do
do_defcallback(fun, quote(do: term), __CALLER__)
end
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback({ :::, _, [fun, return] }) do
do_defmacrocallback(fun, return, __CALLER__)
end
defmacro defmacrocallback(fun) do
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
end
defp do_defcallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } ->
do_callback(:def, name, args, name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
end
end
defp do_defmacrocallback(fun, spec, caller) do
case Macro.extract_args(fun) do
{ name, args } ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
:error ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
Enum.each args, fn
{ :::, _, [left, right] } ->
ensure_not_default(left)
ensure_not_default(right)
left
other ->
ensure_not_default(other)
other
end
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
end
end
defp ensure_not_default({ ://, _, [_, _] }) do
raise ArgumentError, message: "default arguments // not supported in defcallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(env) do
docs = if Code.compiler_options[:docs] do
Enum.reverse Module.get_attribute(env.module, :behaviour_docs)
end
quote do
@doc false
def __behaviour__(:callbacks) do
__MODULE__.behaviour_info(:callbacks)
end
def __behaviour__(:docs) do
unquote(Macro.escape(docs))
end
end
end
end
-128
View File
@@ -1,128 +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:
iex> use Bitwise
iex> bnot 1
-2
iex> 1 &&& 1
1
You can select to include only or skip operators by passing options:
iex> 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
-484
View File
@@ -1,484 +0,0 @@
defmodule Code do
defexception LoadError, file: nil do
def message(exception) do
"could not load #{exception.file}"
end
end
@moduledoc """
Utilities for managing code compilation, code evaluation and code loading.
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
to add behavior which is specific to Elixir.
"""
@doc """
List all loaded files.
"""
def loaded_files do
:elixir_code_server.call :loaded
end
@doc """
Remove files from the loaded files list.
The modules defined in the file are not removed;
calling this function only removes them from the list,
allowing them to be required again.
"""
def unload_files(files) do
:elixir_code_server.cast { :unload_files, files }
end
@doc """
Append a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being appended.
"""
def append_path(path) do
:code.add_pathz(Path.expand to_char_list(path))
end
@doc """
Prepend a path to the Erlang VM code path.
The path is expanded with `Path.expand/1` before being prepended.
"""
def prepend_path(path) do
:code.add_patha(Path.expand to_char_list(path))
end
@doc """
Delete a path from the Erlang VM code path.
The path is expanded with `Path.expand/1` before being deleted.
"""
def delete_path(path) do
:code.del_path(Path.expand to_char_list(path))
end
@doc """
Evaluate the contents given by `string`.
The `binding` argument is a keyword list of variable bindings.
The `opts` argument is a keyword list of environment options.
Those options can be:
* `:file` - the file to be considered in the evaluation
* `:line` - the line on which the script starts
* `:delegate_locals_to` - delegate local calls to the given module,
the default is to not delegate
Additionally, the following scope values can be configured:
* `:aliases` - a list of tuples with the alias and its target
* `:requires` - a list of modules required
* `:functions` - a list of tuples where the first element is a module
and the second a list of imported function names and arity. The list
of function names and arity must be sorted
* `:macros` - a list of tuples where the first element is a module
and the second a list of imported macro names and arity. The list
of function names and arity must be sorted
Notice that setting any of the values above overrides Elixir's default
values. For example, setting `:requires` to `[]`, will no longer
automatically require the `Kernel` module; in the same way setting
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
etc.
Returns a tuple of the form `{ value, binding }`,
where `value` is the the value returned from evaluating `string`.
If an error occurs while evaluating `string` an exception will be raised.
`binding` is a keyword list with the value of all variable bindings
after evaluating `string`. The binding key is usually an atom, but it
may be a tuple for variables defined in a different context.
## Examples
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
{3, [a: 1, b: 2]}
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2, c: 3]}
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
{3, [a: 3, b: 2]}
For convenience, you can pass `__ENV__` as the `opts` argument and
all imports, requires and aliases defined in the current environment
will be automatically carried over:
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
{3, [a: 1, b: 2]}
"""
def eval_string(string, binding // [], opts // [])
def eval_string(string, binding, Macro.Env[] = env) do
do_eval_string(string, binding, env.to_keywords)
end
def eval_string(string, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_string(string, binding, opts)
end
defp do_eval_string(string, binding, opts) when is_list(binding) do
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
{ value, binding }
end
@doc """
Evaluate the quoted contents.
See `eval_string/3` for a description of arguments and return values.
## Examples
iex> contents = quote(hygiene: [vars: 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 pass `__ENV__` as the `opts` argument and
all options will be automatically extracted from the current environment:
iex> contents = quote(hygiene: [vars: false], do: a + b)
...> 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
do_eval_quoted(quoted, binding, env.to_keywords)
end
def eval_quoted(quoted, binding, opts) when is_list(opts) do
validate_eval_opts(opts)
do_eval_quoted(quoted, binding, opts)
end
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
{ value, binding, _scope } =
:elixir.eval_quoted [quoted], binding, opts
{ value, binding }
end
defp validate_eval_opts(opts) do
if f = opts[:functions], do: validate_imports(:functions, f)
if m = opts[:macros], do: validate_imports(:macros, m)
if a = opts[:aliases], do: validate_aliases(:aliases, a)
if r = opts[:requires], do: validate_requires(:requires, r)
end
defp validate_requires(kind, requires) do
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
end
end
defp validate_aliases(kind, aliases) do
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
is_atom(k) and is_atom(v)
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
end
end
defp validate_imports(kind, imports) do
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
is_atom(name) and is_integer(arity)
end)
end)
unless valid do
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
end
end
@doc """
Convert the given string to its quoted form.
Returns `{ :ok, quoted_form }`
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.
## Macro.to_string/2
The opposite of converting a string to its quoted form is
`Macro.to_string/2`, which converts a quoted form to a string/binary
representation.
"""
def string_to_quoted(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
case res do
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
_ -> res
end
end
@doc """
Convert the given string to its quoted form.
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 `string_to_quoted/2` for options information.
"""
def string_to_quoted!(string, opts // []) when is_list(opts) do
file = Keyword.get opts, :file, "nofile"
line = Keyword.get opts, :line, 1
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
unpack_quote(line, res)
end
defp unpack_quote(_line, []), do: nil
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
@doc """
Load 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 a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
module defined in the file.
Notice that if `load_file` is invoked by different processes
concurrently, the target file will be invoked concurrently
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/2` 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)
:elixir_code_server.call { :acquire, file }
loaded = :elixir_compiler.file file
:elixir_code_server.cast { :loaded, file }
loaded
end
@doc """
Require the given `file`.
Accepts `relative_to` as an argument to tell where
the file is located. The return value is the same as that of `load_file/2`. If
the file was already required/loaded, doesn't do anything and returns `nil`.
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, it will be loaded only once. The first process to
call `require_file` will get the list of loaded modules, others will get `nil`.
Check `load_file/2` 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 :elixir_code_server.call({ :acquire, file }) do
:loaded ->
nil
{ :queued, ref } ->
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
:proceed ->
loaded = :elixir_compiler.file file
:elixir_code_server.cast { :loaded, file }
loaded
end
end
@doc """
Load the compilation options from the code server.
Check `compiler_options/1` for more information.
"""
def compiler_options do
:elixir_code_server.call :compiler_options
end
@doc """
Set compilation options.
These 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, `false` by default;
* `:ignore_module_conflict` - when `true`, override modules that were already defined
without raising errors, `false` by default;
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
"""
def compiler_options(opts) do
:elixir_code_server.cast { :compiler_options, opts }
end
@doc """
Compile the given string.
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.files/2`.
"""
def compile_string(string, file // "nofile") when is_binary(file) do
:elixir_compiler.string String.to_char_list!(string), file
end
@doc """
Compile the quoted expression.
Returns a list of tuples where
the first element is the module name and the second one is its
binary.
"""
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
:elixir_compiler.quoted [quoted], file
end
@doc """
Ensure the given module is loaded.
If the module is already
loaded, this works as no-op. If the module was not yet loaded,
it tries to load it.
If it succeeds loading the module, 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 in 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 used to check if a module is loaded
before using it and allows one to 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 that was not yet compiled, therefore
it can't even be loaded.
`ensure_compiled/1` halts the current process until the
module we are depending on is available.
In most cases, `ensure_loaded` is enough. `ensure_compiled`
must be used in some rare cases, usually involving macros
that need to invoke a module for callback information.
"""
def ensure_loaded(module) when is_atom(module) do
:code.ensure_loaded(module)
end
@doc """
Ensure the given module is loaded.
Similar to `ensure_loaded/1`, but returns `true` if the module
is already loaded or was successfully loaded. Returns `false` otherwise.
"""
def ensure_loaded?(module) do
match?({ :module, ^module }, ensure_loaded(module))
end
@doc """
Ensure 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
then tries to load it.
If it succeeds loading the module, 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__(:module)
{ :module, module }
rescue
UndefinedFunctionError -> error
end
end
other -> other
end
end
@doc """
Ensure the given module is compiled and loaded.
Similar to `ensure_compiled/1`, but returns `true` if the module
is already loaded or was successfully loaded and compiled.
Returns `false` otherwise.
"""
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 = if relative_to do
Path.expand(file, relative_to)
else
Path.expand(file)
end
if File.regular?(file) do
file
else
raise LoadError, file: file
end
end
end
-511
View File
@@ -1,511 +0,0 @@
defmodule Dict do
@moduledoc %S"""
This module specifies the Dict API expected to be
implemented by different dictionaries. It also provides
functions that redirect to the underlying Dict, allowing
a developer to work with different Dict implementations
using one API.
To create a new dict, use the `new` functions defined
by each dict type:
HashDict.new #=> creates an empty HashDict
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `ListDict`.
## Protocols
Besides implementing the functions in this module, all
dictionaries are required to implement the `Access`
protocol:
iex> dict = dict_impl.new
...> dict = Dict.put(dict, :hello, :world)
...> dict[:hello]
:world
And the `Enumerable` protocol, allowing one to write:
Enum.each(dict, fn ({ k, v }) ->
IO.puts "#{k}: #{v}"
end)
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
@type key :: any
@type value :: any
@type keys :: [ key ]
@type t :: tuple | list
defcallback new :: t
defcallback new(Keyword.t) :: t
defcallback delete(t, key) :: t
defcallback drop(t, keys) :: t
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback get(t, key) :: value
defcallback get(t, key, value) :: value
defcallback fetch(t, key) :: { :ok, value } | :error
defcallback fetch!(t, key) :: value | no_return
defcallback has_key?(t, key) :: boolean
defcallback keys(t) :: list(key)
defcallback merge(t, t) :: t
defcallback merge(t, t, (key, value, value -> value)) :: t
defcallback pop(t, key) :: {value, t}
defcallback pop(t, key, value) :: {value, t}
defcallback put(t, key, value) :: t
defcallback put_new(t, key, value) :: t
defcallback size(t) :: non_neg_integer()
defcallback split(t, keys) :: {t, t}
defcallback take(t, keys) :: t
defcallback to_list(t) :: list()
defcallback update(t, key, value, (value -> value)) :: t
defcallback update!(t, key, (value -> value)) :: t | no_return
defcallback values(t) :: list(value)
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
defmacrop target(dict) do
quote do
cond do
is_tuple(unquote(dict)) ->
elem(unquote(dict), 0)
is_list(unquote(dict)) ->
ListDict
true ->
unsupported_dict(unquote(dict))
end
end
end
@doc """
Returns a list of all keys in `dict`.
The keys are not guaranteed to be in any order.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.keys(d))
[:a,:b]
"""
@spec keys(t) :: [key]
def keys(dict) do
target(dict).keys(dict)
end
@doc """
Returns a list of all values in `dict`
The values are not guaranteed to be in any order.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Enum.sort(Dict.values(d))
[1,2]
"""
@spec values(t) :: [value]
def values(dict) do
target(dict).values(dict)
end
@doc """
Returns the number of elements in `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> Dict.size(d)
2
"""
@spec size(t) :: non_neg_integer
def size(dict) do
target(dict).size(dict)
end
@doc """
Returns whether the given `key` exists in the given `dict`.
## Examples
iex> d = dict_impl.new([a: 1])
iex> Dict.has_key?(d, :a)
true
iex> Dict.has_key?(d, :b)
false
"""
@spec has_key?(t, key) :: boolean
def has_key?(dict, key) do
target(dict).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
iex> d = dict_impl.new([a: 1])
iex> Dict.get(d, :a)
1
iex> Dict.get(d, :b)
nil
iex> Dict.get(d, :b, 3)
3
"""
@spec get(t, key, value) :: value
def get(dict, key, default // nil) do
target(dict).get(dict, key, default)
end
@doc """
Returns `{ :ok, value }` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch(d, :a)
{ :ok, 1 }
iex> Dict.fetch(d, :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(dict, key) do
target(dict).fetch(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## Examples
iex> d = dict_impl.new([a: 1])
iex> Dict.fetch!(d, :a)
1
iex> Dict.fetch!(d, :b)
** (KeyError) key not found: :b
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(dict, key) do
target(dict).fetch!(dict, key)
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
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put(d, :a, 3)
...> Dict.get(d, :a)
3
"""
@spec put(t, key, value) :: t
def put(dict, key, val) do
target(dict).put(dict, key, val)
end
@doc """
Puts the given `value` under `key` in `dict` unless `key` already exists.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.put_new(d, :a, 3)
...> Dict.get(d, :a)
1
"""
@spec put_new(t, key, value) :: t
def put_new(dict, key, val) do
target(dict).put_new(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
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.delete(d, :a)
...> Dict.get(d, :a)
nil
iex> d = dict_impl.new([b: 2])
...> Dict.delete(d, :a) == d
true
"""
@spec delete(t, key) :: t
def delete(dict, key) do
target(dict).delete(dict, key)
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` keys
already exists in `dict`, the `dict` value is replaced by the `enum`
value.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
def merge(dict, enum) do
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
end
@doc """
Merges the given `enum` into `dict`. If one of the `enum` entries
already exists in `dict`, the given function is invoked to resolve
the conflict.
The `enum` must yield tuples with two elements on enumeration,
where the first element represents the key and the second the value.
## Examples
iex> d1 = dict_impl.new([a: 1, b: 2])
...> d2 = dict_impl.new([a: 3, d: 4])
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict, enum, fun) do
target(dict).merge(dict, enum, fun)
end
@doc """
Returns the value associated with `key` in `dict` as
well as the `dict` without `key`.
## Examples
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :a
...> {v, Enum.sort(d)}
{1,[]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b
...> {v, Enum.sort(d)}
{nil,[a: 1]}
iex> dict = dict_impl.new [a: 1]
...> {v, d} = Dict.pop dict, :b, 3
...> {v, Enum.sort(d)}
{3,[a: 1]}
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default // nil) do
target(dict).pop(dict, key, default)
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
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update!(d, :a, fn(val) -> -val end)
...> Dict.get(d, :a)
-1
"""
@spec update!(t, key, (value -> value)) :: t
def update!(dict, key, fun) do
target(dict).update!(dict, key, fun)
end
@doc false
def update(dict, key, fun) do
target(dict).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
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
...> Dict.get(d, :c)
3
"""
@spec update(t, key, value, (value -> value)) :: t
def update(dict, key, initial, fun) do
target(dict).update(dict, key, initial, fun)
end
@doc """
Returns a tuple of two dicts, where the first dict contains only
entries from `dict` with keys in `keys`, and the second dict
contains only entries from `dict` with keys not in `keys`
Any non-member keys are ignored.
## Examples
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = dict_impl.new([])
...> { d1, d2 } = Dict.split(d, [:a, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [], [] }
iex> d = dict_impl.new([a: 1, b: 2])
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
...> { Dict.to_list(d1), Dict.to_list(d2) }
{ [a: 1, b: 2], [] }
"""
@spec split(t, keys) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@doc """
Returns a new dict where the given `keys` are removed from `dict`.
Any non-member keys are ignored.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:a, :c, :d])
...> Dict.to_list(d)
[b: 2]
iex> d = dict_impl.new([a: 1, b: 2])
...> d = Dict.drop(d, [:c, :d])
...> Dict.to_list(d)
[a: 1, b: 2]
"""
@spec drop(t, keys) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are
included.
Any non-member keys are ignored.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...>
...> d = Dict.take(d, [:a, :c, :d])
...> Dict.to_list(d)
[a: 1]
...>
...> d = Dict.take(d, [:c, :d])
...> Dict.to_list(d)
[]
"""
@spec take(t, keys) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@doc """
Returns an empty dict of the same type as `dict`.
## Examples
iex> d = dict_impl.new([a: 1, b: 2])
...> e = Dict.empty(d)
...> Dict.to_list(e)
[]
"""
@spec empty(t) :: t
def empty(dict) do
target(dict).empty(dict)
end
@doc """
Check if two dicts are equal using `===`. If the dicts are
of different types, they are first converted to lists.
## Examples
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
...> Dict.equal?(a, b)
true
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
...> b = []
...> Dict.equal?(a, b)
false
"""
@spec equal?(t, t) :: boolean
def equal?(a, b) do
a_target = target(a)
b_target = target(b)
cond do
a_target == b_target ->
a_target.equal?(a, b)
a_target.size(a) == b_target.size(b) ->
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
true ->
false
end
end
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
"""
@spec to_list(t) :: list
def to_list(dict) do
target(dict).to_list(dict)
end
defp unsupported_dict(dict) do
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
end
end
-125
View File
@@ -1,125 +0,0 @@
defmodule Dict.Behaviour do
@moduledoc """
This module makes it easier to create your own `Dict` compliant
module, by providing default implementations for some required functions.
Usage:
defmodule MyDict do
use Dict.Behaviour
# implement required functions (see below)
# override default implementations if needed
end
The client module must contain following functions: `size/1`, `fetch/2`,
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
the Dict behaviour, so no extra functions are actually required.
Based on these functions, `Dict.Behaviour` generates default implementations
for other functions such as `drop`, `take`, etc. All of the functions are
defined as overridable, so you can provide your own implementation if
needed.
If you implement `new/0` and `new/1` functions, you can also test your custom
module via `Dict` doctests:
defmodule MyDict do
def new(keywords // []) do
...
end
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
"""
defmacro __using__(_) do
quote do
@behaviour Dict
def get(dict, key, default // nil) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> default
end
end
defoverridable get: 2, get: 3
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
defoverridable fetch!: 2
def has_key?(dict, key) do
match? { :ok, _ }, fetch(dict, key)
end
defoverridable has_key?: 2
def put_new(dict, key, value) do
update(dict, key, value, fn(v) -> v end)
end
defoverridable put_new: 3
def drop(dict, []), do: dict
def drop(dict, [key|keys]) do
drop(delete(dict, key), keys)
end
defoverridable drop: 2
def take(dict, keys) do
take(dict, keys, new)
end
defoverridable take: 2
defp take(_dict, [], acc), do: acc
defp take(dict, [key|keys], acc) do
case fetch(dict, key) do
{ :ok, value } -> take(dict, keys, put(acc, key, value))
:error -> take(dict, keys, acc)
end
end
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
defoverridable to_list: 1
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
defoverridable keys: 1
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
defoverridable values: 1
def equal?(dict1, dict2) do
case Dict.size(dict1) == Dict.size(dict2) do
false -> false
true ->
try do
reduce(dict1, nil, fn({ k, v }, _acc) ->
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
end)
true
catch
:error -> false
end
end
end
defoverridable equal?: 2
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
end)
end
defoverridable merge: 2, merge: 3
end
end
end
File diff suppressed because it is too large Load Diff
-349
View File
@@ -1,349 +0,0 @@
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
"expected a function, got: #{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 TryClauseError, [actual: nil] do
def message(exception) do
"no try 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_mfa exception.module, exception.function, exception.arity
"undefined function: #{formatted}"
else
"undefined function"
end
end
end
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
def message(exception) do
if exception.function do
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
"no function clause matching in #{formatted}"
else
"no function clause matches"
end
end
end
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
def message(exception) do
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
if exception.description do
msg <> ", " <> exception.description
else
msg
end
end
end
defexception ErlangError, [original: nil] do
def message(exception) do
"erlang error: #{inspect(exception.original)}"
end
end
defexception KeyError, key: nil do
def message(exception) do
"key not found: #{inspect exception.key}"
end
end
defexception Enum.OutOfBoundsError, message: "out of bounds error"
defexception Enum.EmptyError, message: "empty error"
defmodule Exception do
@moduledoc """
Several convenience functions to work with and pretty print
exceptions and stacktraces.
"""
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. It takes the kind spilled by
`catch` as an argument as a convenience for converting only
`:errors`, ignorning the others.
"""
def normalize(:error, exception), do: normalize(exception)
def normalize(_kind, other), do: other
@doc """
Normalizes an exception, converting Erlang exceptions
to Elixir exceptions. Useful when interfacing Erlang
code with Elixir code.
"""
def normalize(exception) when is_exception(exception) do
exception
end
def normalize(:badarg) do
ArgumentError[]
end
def normalize(:badarith) do
ArithmeticError[]
end
def normalize(:system_limit) do
SystemLimitError[]
end
def normalize({ :badarity, { fun, args } }) do
BadArityError[function: fun, args: args]
end
def normalize({ :badfun, actual }) do
BadFunctionError[actual: actual]
end
def normalize({ :badmatch, actual }) do
MatchError[actual: actual]
end
def normalize({ :case_clause, actual }) do
CaseClauseError[actual: actual]
end
def normalize({ :try_clause, actual }) do
TryClauseError[actual: actual]
end
def normalize(:undef) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
UndefinedFunctionError[module: mod, function: fun, arity: arity]
end
def normalize(:function_clause) do
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
FunctionClauseError[module: mod, function: fun, arity: arity]
end
def normalize({ :badarg, payload }) do
ArgumentError[message: "argument error: #{inspect(payload)}"]
end
def normalize(other) do
ErlangError[original: other]
end
@doc """
Receives a tuple representing a stacktrace entry and formats it.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
"""
def format_stacktrace_entry(entry, cwd // nil)
# From Macro.Env.stacktrace
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
"#{format_location(file_line, cwd)}#{inspect module} (module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
"#{format_location(file_line, cwd)}(module)"
end
# From :elixir_compiler_*
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
"#{format_location(file_line, cwd)}(file)"
end
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
end
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
end
@doc """
Formats the stacktrace.
A stacktrace must be given as an argument. If not, this function
calculates the current stacktrace and formats it. As a consequence,
the value of `System.stacktrace` is changed.
"""
def format_stacktrace(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
cwd = System.cwd
case trace do
[] -> "\n"
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
end
end
@doc """
Formats the caller, i.e. the first entry in the stacktrace.
Notice that due to tail call optimization, the stacktrace
may not report the direct caller of the function.
"""
def format_caller(trace // nil) do
trace = trace || try do
throw(:stacktrace)
catch
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
end
if entry = Enum.at(trace, 1) do
format_stacktrace_entry(entry)
else
"nofile:0: "
end
end
@doc """
Receives an anonymous function and arity and formats it as
shown in stacktraces. The arity may also be a list of arguments.
## Examples
Exception.format_fa(fn -> end, 1)
#=> "#Function<...>/1"
"""
def format_fa(fun, arity) do
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
else
"#{inspect fun}/#{arity}"
end
end
@doc """
Receives a module, fun and arity and formats it
as shown in stacktraces. The arity may also be a list
of arguments.
## Examples
iex> Exception.format_mfa Foo, :bar, 1
"Foo.bar/1"
iex> Exception.format_mfa Foo, :bar, []
"Foo.bar()"
iex> Exception.format_mfa nil, :bar, []
"nil.bar()"
"""
def format_mfa(module, fun, arity) do
fun =
case inspect(fun) do
<< ?:, erl :: binary >> -> erl
elixir -> elixir
end
if is_list(arity) do
inspected = lc x inlist arity, do: inspect(x)
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
else
"#{format_module module}#{fun}/#{arity}"
end
end
defp format_module(mod), do: "#{inspect mod}."
@doc """
Formats the given file and line as shown in stacktraces.
If any of the values are nil, they are omitted.
The current working directory may be given as an argument,
otherwise one is automatically retrieved.
## Examples
iex> Exception.format_file_line("foo", 1)
"foo:1: "
iex> Exception.format_file_line("foo", nil)
"foo: "
iex> Exception.format_file_line(nil, nil)
""
"""
def format_file_line(file, line, cwd // nil) do
if file do
file = to_string(file)
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
if line && line != 0 do
"#{file}:#{line}: "
else
"#{file}: "
end
else
""
end
end
defp format_location(opts, cwd) do
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
end
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
{ module, function, length(args) }
end
defp from_stacktrace([{ module, function, arity, _ }|_]) do
{ module, function, arity }
end
defp from_stacktrace(_) do
{ nil, nil, nil }
end
end
File diff suppressed because it is too large Load Diff
-83
View File
@@ -1,83 +0,0 @@
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
"""
@doc """
Parses a binary into a float.
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Float.parse("34")
{34.0,""}
iex> Float.parse("34.25")
{34.25,""}
iex> Float.parse("56.5xyz")
{56.5,"xyz"}
iex> Float.parse("pi")
:error
"""
@spec parse(binary) :: { float, binary } | :error
def parse(binary) when is_binary(binary) do
case Integer.parse binary do
:error -> :error
{ integer_part, after_integer } -> parse after_integer, integer_part
end
end
# Dot followed by digit is required afterwards or we are done
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
parse(rest, char - ?0, 1, int)
end
defp parse(rest, int) do
{ :erlang.float(int), rest }
end
# Handle decimal points
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
parse rest, 10 * float + (char - ?0), decimal + 1, int
end
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
case Integer.parse after_e do
:error ->
# Note we rebuild the binary here instead of breaking it apart at
# the function clause because the current approach copies a binary
# just on this branch. If we broke it apart in the function clause,
# the copy would happen when calling Integer.parse/1.
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
{ exponential, after_exponential } ->
{ floatify(int, float, decimal, exponential), after_exponential }
end
end
defp parse(bitstring, float, decimal, int) do
{ floatify(int, float, decimal), bitstring }
end
defp floatify(int, float, decimal, exponential // 0) do
multiplier = if int < 0, do: -1.0, else: 1.0
# Try to ensure the minimum amount of rounding errors
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
# Try avoiding stuff like this:
# iex(1)> 0.0001 * 75
# 0.007500000000000001
# Due to IEEE 754 floating point standard
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
final_decimal_places = decimal - exponential
if final_decimal_places > 0 do
decimal_power_round = :math.pow(10, final_decimal_places)
trunc(result * decimal_power_round) / decimal_power_round
else
result
end
end
end
-101
View File
@@ -1,101 +0,0 @@
defmodule GenEvent.Behaviour do
@moduledoc """
This module is a convenience for defining GenEvent callbacks in Elixir.
GenEvent is an OTP behaviour that encapsulates event handling functionality.
## Example
Below is an example of a GenEvent that stores notifications
until they are fetched:
defmodule MyEventHandler do
use GenEvent.Behaviour
# Callbacks
def init(_) do
{ :ok, [] }
end
def handle_event({:notification, x}, notifications) do
{ :ok, [x|notifications] }
end
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{ :ok, pid } = :gen_event.start_link
#=> {:ok,#PID<0.42.0>}
:gen_event.add_handler(pid, MyEventHandler, [])
#=> :ok
:gen_event.notify(pid, {:notification, 1})
#=> :ok
:gen_event.notify(pid, {:notification, 2})
#=> :ok
:gen_event.call(pid, MyEventHandler, :notifications)
#=> [1, 2]
:gen_event.call(pid, MyEventHandler, :notifications)
#=> []
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server.
Starting and sending messages to the GenEvent is done
via Erlang's `:gen_event` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_event.html
* http://learnyousomeerlang.com/event-handlers
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :gen_event
@doc false
def init(args) do
{ :ok, args }
end
@doc false
def handle_event(_event, state) do
{ :ok, state }
end
@doc false
def handle_call(_request, state) do
{ :ok, :ok, state }
end
@doc false
def handle_info(_msg, state) do
{ :ok, state }
end
@doc false
def terminate(reason, state) do
:ok
end
@doc false
def code_change(_old, state, _extra) do
{ :ok, state }
end
defoverridable [init: 1,
handle_event: 2,
handle_call: 2, handle_info: 2,
terminate: 2, code_change: 3]
end
end
end
-211
View File
@@ -1,211 +0,0 @@
defmodule GenFSM.Behaviour do
@moduledoc """
This module is a convenience for defining GenFSM callbacks in Elixir.
A finite state machine (FSM) is responsible for reacting to events received;
GenFSM is an OTP behaviour that encapsulates common FSM
functionalities.
## Example
Below is an example of a GenFSM that runs a very simple minded
coffee vending machine (CVM). The CVM treats all coins the same. If
you press the request button then the CVM will brew coffee, if you
have paid enough coins; if not, it will wait until you have inserted
enough coins and then it will instantly brew the coffee, since you
had already pressed the request button. As we told you - a very
simple minded CVM! And greedy too. If you insert more coins than you
need it will gladly eat them until you press the request button.
We will leave it to the service-minded reader to improve the way the CVM
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
#
# API functions
#
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
#
# Callbacks
#
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p])
when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{:next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
MyFsm.insert_coin
#=> :ok
MyFsm.insert_coin
#=> :ok
MyFsm.request_coffee
#=> :ok
MyFsm.insert_coin
#=> :ok
#=> Here's your coffee!
Notice we never call the GenFSM callbacks directly; they are called by
OTP whenever we interact with the server throught the API. `send_event` is
asynchronous, whereas `sync_send_event` is synchronous. For
a GenFSM, the different values a callback can return depend
on the type of callback.
State handling returns for `send_event` callbacks:
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, new_state_data }
State handling returns for `sync_send_event` callbacks:
{ :reply, reply, next_state_name, new_state_data }
{ :reply, reply, next_state_name, new_state_data, timeout }
{ :reply, reply, next_state_name, new_state_data, :hibernate }
{ :next_state, next_state_name, new_state_data }
{ :next_state, next_state_name, new_state_data, timeout }
{ :next_state, next_state_name, new_state_data, :hibernate }
{ :stop, reason, reply, new_state_data }
{ :stop, reason, new_state_date }
There are 6 callbacks required to be implemented in a GenFsm plus 1
or 2 for each state. The `GenFSM.Behaviour` module defines
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
implemented by default, as it requires the next state to be returned.
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
`state_name/2` variant and vice-versa for synchronous events and
`state_name/3`. Keep in mind that if you mix `send_event` and
`sync_send_event` the best thing to do is to implement both
callbacks for all states.
Starting and sending messages to the GenFSM is done via Erlang's
`:gen_fsm` module. For more information, please refer to the
following:
* http://www.erlang.org/doc/man/gen_fsm.html
* http://www.erlang.org/doc/design_principles/fsm.html
* http://learnyousomeerlang.com/finite-state-machines
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_fsm
@doc false
def handle_event(_event, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :ok, state_name, state_data }
end
@doc false
def handle_info(_msg, state_name, state_data) do
{ :next_state, state_name, state_data }
end
@doc false
def terminate(_reason, _state_name, _state_data) do
:ok
end
@doc false
def code_change(_old, state_name, state_data, _extra) do
{ :ok, state_name, state_data }
end
defoverridable [handle_event: 3, handle_sync_event: 4,
handle_info: 3, terminate: 3, code_change: 4]
end
end
end
-125
View File
@@ -1,125 +0,0 @@
defmodule GenServer.Behaviour do
@moduledoc """
This module is a convenience for defining GenServer callbacks in Elixir.
A server is responsible for reacting to messages received from a client.
A GenServer is an OTP behaviour that encapsulates common server
functionalities.
## Example
Below is an example of a GenServer that pushes and pops items
onto a stack:
defmodule MyServer do
use GenServer.Behaviour
# Callbacks
def handle_call(:pop, _from, [h|t]) do
{ :reply, h, t }
end
def handle_call(request, from, config) do
# Call the default implementation from GenServer.Behaviour
super(request, from, config)
end
def handle_cast({ :push, item }, config) do
{ :noreply, [item|config] }
end
def handle_cast(request, config) do
super(request, config)
end
end
{ :ok, pid } = :gen_server.start_link(MyServer, [:hello], [])
:gen_server.call(pid, :pop)
#=> :hello
:gen_server.cast(pid, { :push, :world })
#=> :ok
:gen_server.call(pid, :pop)
#=> :world
Notice we never call the server callbacks directly, they are called
by OTP whenever we interact with the server. **cast** messages are
asynchronous while **call** ones are synchronous. For a
GenServer, there are 8 different values a callback such as
`handle_call` or `handle_cast` can return:
{ :reply, reply, new_state }
{ :reply, reply, new_state, timeout }
{ :reply, reply, new_state, :hibernate }
{ :noreply, new_state }
{ :noreply, new_state, timeout }
{ :noreply, new_state, :hibernate }
{ :stop, reason, new_state }
{ :stop, reason, reply, new_state }
There are 6 callbacks required to be implemented in a GenServer. The
`GenServer.Behaviour` module defines all of them automatically, but
allows us to customize the ones we need. The required callbacks are:
* `init(args)` - invoked when the server is started;
* `handle_call(msg, from, state)` - invoked to handle call messages;
* `handle_cast(msg, state)` - invoked to handle cast messages;
* `handle_info(msg, state)` - handle all other messages which are
normally received by processes;
* `terminate(reason, state)` - called when the server is about to
terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Starting and sending messages to the GenServer is done
via Erlang's `:gen_server` module. For more information,
please refer to the following:
* http://www.erlang.org/doc/man/gen_server.html
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
* http://learnyousomeerlang.com/clients-and-servers
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behavior :gen_server
@doc false
def init(args) do
{ :ok, args }
end
@doc false
def handle_call(_request, _from, state) do
{ :noreply, state }
end
@doc false
def handle_info(_msg, state) do
{ :noreply, state }
end
@doc false
def handle_cast(_msg, state) do
{ :noreply, state }
end
@doc false
def terminate(_reason, _state) do
:ok
end
@doc false
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
-478
View File
@@ -1,478 +0,0 @@
defmodule HashDict do
@moduledoc """
A key-value store.
The `HashDict` is meant to work well with both small and
large set of keys and it is an implementation of the `Dict`
behaviour. For more information about the functions and
their APIs, please consult the `Dict` module.
"""
use Dict.Behaviour
# A dictionary (key-value) implementation based on dynamic hashing.
#
# This implementation is based on hash tries. We first start with
# a set of 8 buckets and expand when the density is about 5 entries
# per bucket. We use bit shifting to make rehashing faster on
# expansion.
#
# Compared to dict, it provides many enhancements:
#
# 1. HashDict buckets are ordered sets, this gives us faster access
# and modification times
#
# 2. It uses phash2 to calculate the hash (instead of phash)
#
# 3. The dictionary first starts with a single bucket, instead of
# a set of 8 buckets. This allow us to skip hashing altogher
# for small dictionaries, providing faster operations and
# reducing memory consumption
#
# 4. Once we reach 8 elements, the dictionary is promoted to a
# set of buckets
# The ordered record contains a single bucket
@ordered_threshold 8
defrecordp :ordered, HashDict,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b111
@node_shift 3
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
defrecordp :trie, HashDict,
size: 0,
depth: 0,
expand_on: @node_size * @expand_load,
contract_on: @contract_load,
root: @node_template
import Bitwise
# Let's inline common instructions
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
Creates a new empty dict.
"""
@spec new :: Dict.t
def new do
ordered()
end
@doc """
Creates a new dict from the given enumerable.
## Examples
HashDict.new [{:b, 1}, {:a, 2}]
#=> #HashDict<[a: 2, b: 1]>
"""
@spec new(list({key :: term, value :: term})) :: Dict.t
def new(pairs) do
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
put(dict, k, v)
end
end
@doc """
Creates a new dict from the enumerable with the
help of the transformation function.
## Examples
HashDict.new ["a", "b"], fn x -> {x, x} end
#=> #HashDict<[{"a","a"},{"b","b"}]>
"""
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
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
def put(dict, key, value) do
{ dict, _ } = dict_put(dict, key, { :put, value })
dict
end
def update!(dict, key, fun) when is_function(fun, 1) do
case dict_put(dict, key, { :update, nil, fun }) do
{ dict, 0 } ->
dict
{ _dict, 1 } ->
raise KeyError, key: key
end
end
def update(dict, key, initial, fun) when is_function(fun, 1) do
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
dict
end
def fetch(ordered(bucket: bucket), key) do
bucket_get(bucket, key)
end
def fetch(trie(root: root, depth: depth), key) do
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
end
def pop(dict, key, default // nil) do
case dict_delete(dict, key) do
{ dict, _, 0 } -> { default, dict }
{ dict, value, _ } -> { value, dict }
end
end
def delete(dict, key) do
{ dict, _, _ } = dict_delete(dict, key)
dict
end
def size(dict) do
elem(dict, 1)
end
def empty(_) do
ordered()
end
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
dict_fold dict1, dict2, fn [k|v1], acc ->
update(acc, k, v1, &callback.(k, v1, &1))
end
end
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
dict_fold dict2, dict1, fn [k|v2], acc ->
update(acc, k, v2, &callback.(k, &1, v2))
end
end
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
super(dict, enumerable, callback)
end
def split(dict, keys) do
split(keys, new, dict)
end
defp split([], including, excluding) do
{ including, excluding }
end
defp split([key|keys], including, excluding) do
case dict_delete(excluding, key) do
{ excluding, _, 0 } -> split(keys, including, excluding)
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
end
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
bucket_reduce(bucket, acc, fun)
end
def reduce(trie(root: root, depth: depth), acc, fun) do
node_reduce(root, depth, acc, fun, @node_size)
end
## Dict-wide functions
defp dict_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
root = node_relocate(bucket, 0)
dict_put(trie(size: @ordered_threshold, root: root), key, value)
end
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
{ new, count } = bucket_put(bucket, key, value)
{ ordered(dict, size: size + count, bucket: new), count }
end
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
root = node_expand(root, depth, depth + 1)
dict = trie(dict, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
dict_put(dict, key, value)
end
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
pos = bucket_hash(key)
{ root, count } = node_put(root, depth, pos, key, value)
{ trie(dict, size: size + count, root: root), count }
end
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
case bucket_delete(bucket, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ new_bucket, value, -1 } ->
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
end
end
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
pos = bucket_hash(key)
case node_delete(root, depth, pos, key) do
{ _, value, 0 } ->
{ dict, value, 0 }
{ root, value, -1 } ->
{ if depth > 0 and trie(dict, :contract_on) == size do
root = node_contract(root, depth)
trie(dict,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(dict, :expand_on), @node_size))
else
trie(dict, size: size - 1, root: root)
end, value, -1 }
end
end
## Bucket helpers
# Get value from the bucket
defp bucket_get([[k|_]|_bucket], key) when k > key do
:error
end
defp bucket_get([[key|value]|_bucket], key) do
{ :ok, value }
end
defp bucket_get([_e|bucket], key) do
bucket_get(bucket, key)
end
defp bucket_get([], _key) do
:error
end
# Puts a value in the bucket
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
{ [[key|value]|bucket], 1 }
end
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
{ [[key|initial]|bucket], 1 }
end
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
{ [[key|value]|bucket], 0 }
end
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
{ [[key|fun.(value)]|bucket], 0 }
end
defp bucket_put([e|bucket], key, value) do
{ rest, count } = bucket_put(bucket, key, value)
{ [e|rest], count }
end
defp bucket_put([], key, { :put, value }) do
{ [[key|value]], 1 }
end
defp bucket_put([], key, { :update, initial, _fun }) do
{ [[key|initial]], 1 }
end
# Puts a value in the bucket without returning
# the operation value
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
defp bucket_put!([], key, value), do: [[key|value]]
# Deletes a key from the bucket
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
{ bucket, nil, 0 }
end
defp bucket_delete([[key|value]|bucket], key) do
{ bucket, value, -1 }
end
defp bucket_delete([e|bucket], key) do
{ rest, value, count } = bucket_delete(bucket, key)
{ [e|rest], value, count }
end
defp bucket_delete([], _key) do
{ [], nil, 0 }
end
# Reduces the bucket
defp bucket_reduce([[k|v]|t], acc, fun) do
bucket_reduce(t, fun.({ k, v }, acc), fun)
end
defp bucket_reduce([], acc, _fun) do
acc
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
## Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
# Puts a key-value into a node
defp node_put(node, 0, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), key, value)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, key, value) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, key) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
defp node_delete(node, depth, hash, key) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
{ _, value, 0 } -> { node, value, 0 }
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
end
end
# Reduces a node recursively
defp node_reduce(bucket, -1, acc, fun, _) do
bucket_reduce(bucket, acc, fun)
end
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_reduce(node, depth, acc, fun, count - 1)
end
defp node_reduce(_node, _, acc, _fun, 0) do
acc
end
# Folds a node recursively
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8)
end
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn [key|value], acc ->
pos = key |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
end, node, bucket
end
end
defimpl Enumerable, for: HashDict do
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
def member?(_dict, _), do: false
def count(dict), do: HashDict.size(dict)
end
defimpl Access, for: HashDict do
def access(dict, key), do: HashDict.get(dict, key, nil)
end
-485
View File
@@ -1,485 +0,0 @@
defmodule HashSet do
@moduledoc """
A set store.
The `HashSet` is meant to work well with both small and
large sets. It is an implementation of the `Set` behaviour.
For more information about the functions and their APIs,
please consult the `Set` module.
"""
@behaviour Set
# The ordered record contains a single bucket.
@ordered_threshold 8
defrecordp :ordered, HashSet,
size: 0,
bucket: []
# The bucketed record contains a series of buckets.
@expand_load 5
@contract_load 2
@node_bitmap 0b1111
@node_shift 4
@node_size 16
@node_template :erlang.make_tuple(@node_size, [])
@expand_default (@node_size * @expand_load)
@contract_default @contract_load
defrecordp :trie, HashSet,
size: 0,
depth: 0,
expand_on: @expand_default,
contract_on: @contract_default,
root: @node_template
import Bitwise
@compile :inline_list_funcs
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
@doc """
Creates a new empty set.
"""
def new() do
ordered()
end
@doc """
Creates a new set from the given enumerable.
## Examples
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
[1,2,3]
"""
def new(members) do
Enum.reduce members, ordered(), fn member, set ->
put(set, member)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_fold set1, set2, fn v1, acc ->
put(acc, v1)
end
end
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_fold set2, set1, fn v, acc ->
put(acc, v)
end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
set_filter set1, fn e -> set_member?(set2, e) end
end
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set2, fn e -> set_member?(set1, e) end
end
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
set_filter set1, fn m -> not set_member?(set2, m) end
end
def member?(set, member) when is_record(set, HashSet) do
set_member?(set, member)
end
def empty(_) do
ordered()
end
def size(set) do
elem(set, 1)
end
def to_list(ordered(bucket: bucket)) do
bucket
end
def to_list(set) do
set_fold(set, [], &[&1|&2]) |> :lists.reverse
end
def put(set, member) do
{ set, _ } = set_put(set, member)
set
end
def delete(set, member) do
{ set, _ } = set_delete(set, member)
set
end
def equal?(set1, set2) do
size = elem(set1, 1)
case elem(set2, 1) do
^size ->
set_equal?(set1, set2)
_ ->
false
end
end
def subset?(set1, set2) do
set_equal?(set1, set2)
end
def disjoint?(set1, set2) do
set_disjoint?(set1, set2)
end
@doc false
def reduce(ordered(bucket: bucket), acc, fun) do
:lists.foldl(fun, acc, bucket)
end
def reduce(trie() = set, acc, fun) do
set_fold(set, acc, fun)
end
## HashSet-wide functions
defp set_filter(ordered(bucket: bucket, size: size), fun) do
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
ordered(bucket: new, size: size - removed_count)
end
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
contract_trie(trie(root: new,
size: size - removed_count,
depth: depth,
contract_on: trie(set, :contract_on),
expand_on: trie(set, :expand_on)))
else
trie(size: size - removed_count, root: new, depth: depth)
end
end
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
root = node_relocate(bucket, 0)
set_put(trie(size: @ordered_threshold, root: root), member)
end
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
{ new, count } = bucket_put(bucket, member)
{ ordered(set, size: size + count, bucket: new), count }
end
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
root = node_expand(root, depth, depth + 1)
set = trie(set, root: root, depth: depth + 1,
expand_on: size * @node_size, contract_on: contract_on * @node_size)
set_put(set, member)
end
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
{ root, count } = node_put(root, depth, pos, member)
{ trie(set, size: size + count, root: root), count }
end
defp set_member?(ordered(bucket: bucket), member) do
:lists.member(member, bucket)
end
defp set_member?(trie(root: root, depth: depth), member) do
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
end
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
case bucket_delete(bucket, member) do
{ _, 0 } ->
{ set, 0 }
{ new_bucket, -1 } ->
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
end
end
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
pos = bucket_hash(member)
case node_delete(root, depth, pos, member) do
{ _, 0 } ->
{ set, 0 }
{ root, -1 } ->
{ if depth > 0 and trie(set, :contract_on) == size do
root = node_contract(root, depth)
trie(set,
root: root,
size: size - 1,
depth: depth - 1,
contract_on: div(size, @node_size),
expand_on: div(trie(set, :expand_on), @node_size))
else
trie(set, size: size - 1, root: root)
end, -1 }
end
end
defp set_equal?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
true -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_disjoint?(set1, set2) do
try do
reduce(set1, true, fn member, acc ->
case member?(set2, member) do
false -> acc
_ -> throw(:error)
end
end)
catch
:error -> false
end
end
defp set_fold(ordered(bucket: bucket), acc, fun) do
bucket_fold(bucket, acc, fun)
end
defp set_fold(trie(root: root, depth: depth), acc, fun) do
node_fold(root, depth, acc, fun, @node_size)
end
## Bucket helpers
defp bucket_filter([e|bucket], fun, acc, count) do
case fun.(e) do
true -> bucket_filter(bucket, fun, [e|acc], count)
false -> bucket_filter(bucket, fun, acc, count + 1)
end
end
defp bucket_filter([], _fun, acc, count) do
{ :lists.reverse(acc), count }
end
defp bucket_put([m|_]=bucket, member) when m > member do
{ [member|bucket], 1 }
end
defp bucket_put([member|bucket], member) do
{ [member|bucket], 0 }
end
defp bucket_put([e|bucket], member) do
{ rest, count } = bucket_put(bucket, member)
{ [e|rest], count }
end
defp bucket_put([], member) do
{ [member], 1 }
end
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
defp bucket_put!([member|bucket], member), do: [member|bucket]
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
defp bucket_put!([], member), do: [member]
# Deletes a key from the bucket
defp bucket_delete([m,_|_]=bucket, member) when m > member do
{ bucket, 0 }
end
defp bucket_delete([member|bucket], member) do
{ bucket, -1 }
end
defp bucket_delete([e|bucket], member) do
{ rest, count } = bucket_delete(bucket, member)
{ [e|rest], count }
end
defp bucket_delete([], _member) do
{ [], 0 }
end
defp bucket_fold(bucket, acc, fun) do
:lists.foldl(fun, acc, bucket)
end
defp bucket_hash(key) do
:erlang.phash2(key)
end
defp bucket_nth_index(hash, n) do
(hash >>> (@node_shift * n)) &&& @node_bitmap
end
defp bucket_index(hash) do
hash &&& @node_bitmap
end
defp bucket_next(hash) do
hash >>> @node_shift
end
# Trie resizing
defp contract_trie(trie(depth: 0) = set) do
set
end
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
new_contract_on = div(contract_on, @node_size)
new_expand_on = div(expand_on, @node_size)
if new_contract_on == 0, do: new_contract_on = @contract_default
if new_expand_on == 0, do: new_expand_on = @expand_default
contract_trie(trie(set, root: node_contract(root, depth),
size: size,
depth: depth - 1,
contract_on: new_contract_on,
expand_on: new_expand_on))
end
defp contract_trie(set) do
set
end
# Node helpers
# Gets a bucket from the node
defp node_bucket(node, 0, hash) do
elem(node, bucket_index(hash))
end
defp node_bucket(node, depth, hash) do
child = elem(node, bucket_index(hash))
node_bucket(child, depth - 1, bucket_next(hash))
end
defp node_put(node, 0, hash, member) do
pos = bucket_index(hash)
{ new, count } = bucket_put(elem(node, pos), member)
{ set_elem(node, pos, new), count }
end
defp node_put(node, depth, hash, member) do
pos = bucket_index(hash)
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
{ set_elem(node, pos, new), count }
end
# Deletes a key from the bucket
defp node_delete(node, 0, hash, member) do
pos = bucket_index(hash)
case bucket_delete(elem(node, pos), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_delete(node, depth, hash, member) do
pos = bucket_index(hash)
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
{ _, 0 } -> { node, 0 }
{ new, -1 } -> { set_elem(node, pos, new), -1 }
end
end
defp node_fold(bucket, -1, acc, fun, _) do
bucket_fold(bucket, acc, fun)
end
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
node_fold(node, depth, acc, fun, count - 1)
end
defp node_fold(_node, _, acc, _fun, 0) do
acc
end
defp node_filter(bucket, -1, fun, _) do
bucket_filter(bucket, fun, [], 0)
end
defp node_filter(node, depth, fun, count) when count >= 1 do
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
{ _, 0 } ->
node_filter(node, depth, fun, count - 1)
{ new_element, count1 } ->
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
{ new_node, count1 + count2 }
end
end
defp node_filter(node, _, _fun, 0) do
{ node, 0 }
end
defp node_relocate(node // @node_template, bucket, n) do
:lists.foldl fn member, acc ->
pos = member |> bucket_hash() |> bucket_nth_index(n)
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
end, node, bucket
end
# Node resizing
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
node_relocate(b16, n) }
end
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
depth = depth - 1
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
node_expand(b16, depth, n) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
depth = depth - 1
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
node_contract(b16, depth) }
end
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
end
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
defp each_contract([], bucket), do: bucket
defp each_contract(acc, []), do: acc
end
defimpl Enumerable, for: HashSet do
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
def member?(set, v), do: HashSet.member?(set, v)
def count(set), do: HashSet.size(set)
end
-485
View File
@@ -1,485 +0,0 @@
import Kernel, except: [inspect: 1]
import Inspect.Algebra
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol is responsible for converting any Elixir
data structure into an algebra document. This document is then
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the record
`Inspect.Opts`.
Inspection is done using the functions available in
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
receives an `Inspect.Opts` record as the second argument, it returns
the underlying algebra document instead of the formatted string.
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
implementation:
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
end
end
The `concat` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Kernel.inspect/2` and the
other string `">"`.
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a string,
although that will devoid it of any pretty-printing.
"""
def inspect(thing, opts)
end
defimpl Inspect, for: Atom do
require Macro
@doc """
Represents the atom as an Elixir term. The atoms `false`, `true`
and `nil` are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
iex> inspect(:foo)
":foo"
iex> inspect(nil)
"nil"
iex> inspect(Foo.Bar)
"Foo.Bar"
"""
def inspect(atom, _opts) do
inspect(atom)
end
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
rest
atom in [:{}, :[], :<<>>] ->
":" <> binary
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
end
end
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
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 Inspect, for: BitString do
@doc %S"""
Represents a string as itself escaping all necessary
characters. Binaries that contain non-printable characters
are printed using the bitstring syntax.
## Examples
iex> inspect("bar")
"\"bar\""
iex> inspect("f\"oo")
"\"f\\\"oo\""
iex> inspect(<<0,1,2>>)
"<<0, 1, 2>>"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
<< ?", escape(thing, ?") :: binary, ?" >>
else
inspect_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
inspect_bitstring(thing, opts)
end
## Escaping
@doc false
def escape(other, char) do
escape(other, char, <<>>)
end
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, h >>)
end
defp escape(<<>>, _char, binary), do: binary
## Bitstrings
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
each_bit(bitstring, opts.limit, "<<") <> ">>"
end
defp each_bit(_, 0, acc) do
acc <> "..."
end
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter, acc) do
acc <> integer_to_binary(h)
end
defp each_bit(<<>>, _counter, acc) do
acc
end
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Inspect, for: List do
@doc %S"""
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. Keywords are
printed in keywords syntax.
## Examples
iex> inspect('bar')
"'bar'"
iex> inspect([0|'bar'])
"[0, 98, 97, 114]"
iex> inspect([:foo,:bar])
"[:foo, :bar]"
"""
def inspect([], _opts), do: "[]"
def inspect(thing, Inspect.Opts[] = opts) do
cond do
:io_lib.printable_list(thing) ->
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
keyword?(thing) && not opts.raw ->
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
true ->
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
end
end
defp keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
Kernel.inspect(value, opts)
)
end
defp key_to_binary(key) do
case Inspect.Atom.inspect(key) do
":" <> right -> right
other -> other
end
end
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
case atom_to_list(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
defp keyword?([]), do: true
defp keyword?(_other), do: false
end
defimpl Inspect, for: Tuple do
@doc """
Represents tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
iex> inspect({1, 2, 3})
"{1, 2, 3}"
iex> inspect(ArgumentError.new)
"ArgumentError[message: \\\"argument error\\\"]"
"""
def inspect({}, _opts), do: "{}"
def inspect(tuple, opts) do
unless opts.raw do
record_inspect(tuple, opts)
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
end
## Helpers
defp record_inspect(record, opts) do
[name|tail] = tuple_to_list(record)
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
surround_record(name, tl(fields), tl(tail), opts)
else
surround_record(name, fields, tail, opts)
end
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
end
defp record_fields(name) do
case atom_to_binary(name) do
"Elixir." <> _ ->
try do
name.__record__(:fields)
rescue
_ -> nil
end
_ -> nil
end
end
defp surround_record(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
concat(
Inspect.Atom.inspect(name, opts),
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
)
end
defp keyword({ k, v }, opts) do
concat(
atom_to_binary(k) <> ": ",
Kernel.inspect(v, opts)
)
end
end
defimpl Inspect, for: Integer do
@doc """
Represents the integer as a string.
## Examples
iex> inspect(1)
"1"
"""
def inspect(thing, _opts) do
integer_to_binary(thing)
end
end
defimpl Inspect, for: Float do
@doc """
Floats are represented using the shortened, correctly rounded string
that converts to float when read back with `binary_to_float/1`. This
is done via the Erlang implementation of _Printing Floating-Point
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
Conference on Programming Language Design and Implementation.
## Examples
iex> inspect(1.0)
"1.0"
"""
def inspect(thing, _opts) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
end
defimpl Inspect, for: Regex do
@doc %S"""
Represents the Regex using the `%r""` syntax.
## Examples
iex> inspect(%r/foo/m)
"%r\"foo\"m"
"""
def inspect(regex, opts) when size(regex) == 5 do
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
end
def inspect(other, opts) do
Kernel.inspect(other, opts.raw(true))
end
end
defimpl Inspect, for: Function do
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
mod = fun_info[:module]
if fun_info[:type] == :external and fun_info[:env] == [] do
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
else
case atom_to_list(mod) do
'elixir_compiler_' ++ _ ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
else
default_inspect(mod, fun_info)
end
_ ->
default_inspect(mod, fun_info)
end
end
end
defp default_inspect(mod, fun_info) do
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
end
defp extract_name(name) do
case :binary.split(atom_to_binary(name), "-", [:global]) do
["", name | _] -> name
_ -> name
end
end
defp uniq(fun_info) do
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
iolist_to_binary :erlang.port_to_list(port)
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> iolist_to_binary(rest)
end
end
defimpl Inspect, for: HashDict do
def inspect(dict, opts) do
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
end
end
defimpl Inspect, for: HashSet do
def inspect(set, opts) do
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
end
end
-424
View File
@@ -1,424 +0,0 @@
defmodule Inspect.Algebra do
@moduledoc %S"""
A set of functions for creating and manipulating algebra
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
An algebra document is represented by an `Inspect.Algebra` node
or a regular string.
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
With the functions in this module, we can concatenate different
elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
The functions `nest/2`, `space/2` and `line/2` help you put the
document together into a rigid structure. However, the document
algebra gets interesting when using functions like `break/2`, which
converts the given string into a line break depending on how much space
there is to print. Let's glue two docs together with a break and then
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
## Implementation details
The original Haskell implementation of the algorithm by [Wadler][1]
relies on lazy evaluation to unfold document groups on two alternatives:
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
Implementing the same logic in a strict language such as Elixir leads
to an exponential growth of possible documents, unless document groups
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
to a simple document, where the layout is already decided, per [Lindig][0].
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing `:break` to mean a possible break and `:flat` to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
Custom pretty printers can be implemented using the documents returned
by this module and by providing their own rendering functions.
[0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
"""
@surround_separator ","
@tail_separator " |"
@newline "\n"
@nesting 1
@break " "
defp repeat(_, 0), do: ""
defp repeat(s, i), do: :lists.duplicate(i, s)
# Functional interface to `doc` records
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
defrecordp :doc_break, str: " " :: binary
defrecordp :doc_group, doc: :doc_nil :: t
defmacrop is_doc(doc) do
if __CALLER__.in_guard? do
do_is_doc(doc)
else
var = quote do: doc
quote do
unquote(var) = unquote(doc)
unquote(do_is_doc(var))
end
end
end
defp do_is_doc(doc) do
quote do
unquote(doc) |> is_binary or
unquote(doc) |> is_integer or
unquote(doc) == :doc_nil or
(unquote(doc) |> is_tuple and
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
end
end
@doc """
Returns `:doc_nil` which is a document entity used to represent
nothingness. Takes no arguments.
## Examples
iex> Inspect.Algebra.empty
:doc_nil
"""
@spec empty() :: :doc_nil
def empty, do: :doc_nil
@doc """
Concatenates two document entities. Takes two arguments:
left doc and right doc. Returns a DocCons doc
## Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
"""
@spec concat(t, t) :: doc_cons_t
def concat(x, y) when is_doc(x) and is_doc(y) do
doc_cons(left: x, right: y)
end
@doc """
Concatenates a list of documents.
"""
@spec concat([t]) :: doc_cons_t
def concat(docs) do
folddoc(docs, &concat(&1, &2))
end
@doc """
Nests document entity `x` positions deep. Nesting will be
appended to the line breaks.
## Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
"""
@spec nest(t, non_neg_integer) :: doc_nest_t
def nest(x, 0) when is_doc(x) do
x
end
def nest(x, i) when is_doc(x) and is_integer(i) do
doc_nest(indent: i, doc: x)
end
@doc %S"""
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
`mode` of the chosen layout or the provided separator.
## Examples
Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached
a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
"""
@spec break(binary) :: doc_break_t
def break(s) when is_binary(s), do: doc_break(str: s)
@spec break() :: doc_break_t
def break(), do: doc_break(str: @break)
@doc """
Inserts a break between two docs. See `break/1` for more info.
"""
@spec glue(t, t) :: doc_cons_t
def glue(x, y), do: concat(x, concat(break, y))
@doc """
Inserts a break, passed as the second argument, between two docs,
the first and the third arguments.
"""
@spec glue(t, binary, t) :: doc_cons_t
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
@doc %S"""
Returns a group containing the specified document.
## Examples
iex> doc = Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> "Hello,",
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "A"
...> )
...> )
...> ),
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
"""
@spec group(t) :: doc_group_t
def group(d) when is_doc(d) do
doc_group(doc: d)
end
@doc """
Inserts a mandatory single space between two document entities.
## Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
"""
@spec space(t, t) :: doc_cons_t
def space(x, y), do: concat(x, concat(" ", y))
@doc %S"""
Inserts a mandatory linebreak between two document entities.
## Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
"""
@spec line(t, t) :: doc_cons_t
def line(x, y), do: concat(x, concat(@newline, y))
@doc """
Folds a list of document entities into a document entity
using a function that is passed as the first argument.
## Examples
iex> doc = ["A", "B"]
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
"""
@spec folddoc([t], ((t, t) -> t)) :: t
def folddoc([], _), do: empty
def folddoc([doc], _), do: doc
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
# Elixir conveniences
@doc %S"""
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it.
The document is marked as a group, to show the maximum as possible
concisely together.
## Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
"""
@spec surround(binary, t, binary) :: t
def surround(left, doc, right) do
group concat [left, nest(doc, @nesting), right]
end
@doc %S"""
Maps and glues a collection of items together using the given separator
and surrounds them. A limit can be passed which, once reached, stops
gluing and outputs "..." instead.
## Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
"""
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
def surround_many(left, [], right, _, _fun) do
concat(left, right)
end
def surround_many(left, docs, right, limit, fun) do
surround(left, surround_many(docs, limit, fun), right)
end
defp surround_many(_, 0, _fun) do
"..."
end
defp surround_many([h], _limit, fun) do
fun.(h)
end
defp surround_many([h|t], limit, fun) when is_list(t) do
glue(
concat(fun.(h), @surround_separator),
surround_many(t, decrement(limit), fun)
)
end
defp surround_many([h|t], _limit, fun) do
glue(
concat(fun.(h), @tail_separator),
fun.(t)
)
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@doc """
The pretty printing function.
Takes the maximum width and a document to print as its arguments
and returns the string representation of the best layout for the
document to fit in the given width.
"""
@spec pretty(t, non_neg_integer | :infinity) :: binary
def pretty(d, w) do
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
render(sdoc)
end
defp default_mode(:infinity), do: :flat
defp default_mode(_), do: :break
# Rendering and internal helpers
# Records representing __simple__ documents, already on a fixed layout
# Those are generalized by `sdoc` type.
@type sdoc :: :s_nil | s_text_t | s_line_t
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
# Record representing the document mode to be rendered: flat or broken
@typep mode :: :flat | :break
@doc false
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
def fits?(:infinity, _), do: true # no pretty printing
def fits?(w, _) when w < 0, do: false
def fits?(_, []), do: true
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
@doc false
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
def format(_, _, []), do: :s_nil
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
k = k + byte_size(s)
if w == :infinity or fits?(w - k, t) do
s_text(str: s, sdoc: format(w, k, t))
else
s_line(indent: i, sdoc: format(w, i, t))
end
end
@doc false
@spec render(sdoc) :: binary
def render(sdoc) do
iolist_to_binary do_render sdoc
end
@spec do_render(sdoc) :: [binary]
defp do_render(:s_nil), do: [""]
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
defp do_render(s_line(indent: i, sdoc: d)) do
prefix = repeat " ", i
[@newline | [prefix | do_render d]]
end
end
-70
View File
@@ -1,70 +0,0 @@
defmodule Integer do
@moduledoc """
Functions for working with integers.
"""
import Bitwise
@doc """
Determines if an integer is odd.
Returns `true` if `n` is an odd number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro odd?(n) do
quote do: (unquote(n) &&& 1) == 1
end
@doc """
Determines if an integer is even.
Returns `true` if `n` is an even number, otherwise `false`.
Implemented as a macro so it is allowed in guard clauses.
"""
defmacro even?(n) do
quote do: (unquote(n) &&& 1) == 0
end
@doc """
Converts a binary to an integer.
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
Otherwise `:error`.
## Examples
iex> Integer.parse("34")
{34,""}
iex> Integer.parse("34.5")
{34,".5"}
iex> Integer.parse("three")
:error
"""
@spec parse(binary) :: { integer, binary } | :error
def parse(<< ?-, bin :: binary >>) do
case do_parse(bin) do
:error -> :error
{ number, remainder } -> { -number, remainder }
end
end
def parse(<< ?+, bin :: binary >>) do
do_parse(bin)
end
def parse(bin) when is_binary(bin) do
do_parse(bin)
end
defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
defp do_parse(_), do: :error
defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
do_parse rest, 10 * acc + (char - ?0)
end
defp do_parse(bitstring, acc) do
{ acc, bitstring }
end
end
-293
View File
@@ -1,293 +0,0 @@
defexception IO.StreamError, reason: nil do
def message(exception) do
formatted = iolist_to_binary(:file.format_error(reason exception))
"error during streaming: #{formatted}"
end
end
defmodule IO do
@moduledoc """
Functions handling IO.
Many functions in this module expects an IO device as argument.
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
The majority of the functions expect data encoded in UTF-8
and will do a conversion to string, via the `String.Chars`
protocol (as shown in typespecs).
The functions starting with `bin*` expects iodata as arguments,
i.e. iolists or binaries with no particular encoding.
"""
@type device :: atom | pid
@type chardata :: char_list | String.Chars.t
@type nodata :: { :error, term } | :eof
import :erlang, only: [group_leader: 0]
defmacrop is_iolist(data) do
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. 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.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
def read(device, count) when count >= 0 do
:io.get_chars(map_dev(device), '', count)
end
@doc """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. 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.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
def binread(device, count) when count >= 0 do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
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"
"""
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
Writes the given argument to the given device
as a binary, no unicode conversion happens.
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device // group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@doc """
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a chardata.
"""
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a newline. A set of options can be given.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
of 80 characters.
## Examples
IO.inspect Process.list
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
inspect group_leader(), item, opts
end
@doc """
Inspects the item with options using the given device.
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
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.
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
end
def getn(device, prompt) do
getn(device, prompt, 1)
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, chardata, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_chardata(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.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if `:line` is given or by a given
number of codepoints.
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
## Examples
Here is an example on how we mimic an echo server
from the command line:
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
end
@doc false
def stream(device, what, acc, fun) do
case read(device, what) do
:eof ->
acc
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
stream(device, what, fun.(data, acc), fun)
end
end
@doc false
def binstream(device, what, acc, fun) do
case binread(device, what) do
:eof ->
acc
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
binstream(device, what, fun.(data, acc), fun)
end
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_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end
-222
View File
@@ -1,222 +0,0 @@
defmodule IO.ANSI.Sequence do
@moduledoc false
defmacro defsequence(name, code // "", terminator // "m") do
quote bind_quoted: [name: name, code: code, terminator: terminator] do
def unquote(name)() do
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
end
end
end
end
defmodule IO.ANSI do
@moduledoc """
Functionality to render ANSI escape sequences
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
in text used to control formatting, color, and other output options
on video text terminals.
"""
import IO.ANSI.Sequence
@doc """
Checks whether the default I/O device is a terminal or a file.
Used to identify whether printing ANSI escape sequences will likely
be displayed as intended.
"""
@spec terminal? :: boolean
@spec terminal?(:io.device) :: boolean
def terminal?(device // :erlang.group_leader) do
match?({:ok, _}, :io.columns(device))
end
@doc "Resets all attributes"
defsequence :reset, 0
@doc "Bright (increased intensity) or Bold"
defsequence :bright, 1
@doc "Faint (decreased intensity), not widely supported"
defsequence :faint, 2
@doc "Italic: on. Not widely supported. Sometimes treated as inverse."
defsequence :italic, 3
@doc "Underline: Single"
defsequence :underline, 4
@doc "Blink: Slow. Less than 150 per minute"
defsequence :blink_slow, 5
@doc "Blink: Rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported"
defsequence :blink_rapid, 6
@doc "Image: Negative. Swap foreground and background"
defsequence :inverse, 7
@doc "Image: Negative. Swap foreground and background"
defsequence :reverse, 7
@doc "Conceal. Not widely supported"
defsequence :conceal, 8
@doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported."
defsequence :crossed_out, 9
@doc "Sets primary (default) font"
defsequence :primary_font, 10
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
@doc "Sets alternative font #{font_n}"
defsequence :"font_#{font_n}", font_n + 10
end
@doc "Normal color or intensity"
defsequence :normal, 22
@doc "Not italic"
defsequence :not_italic, 23
@doc "Underline: None"
defsequence :no_underline, 24
@doc "Blink: off"
defsequence :blink_off, 25
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
colors = Enum.zip(0..(length(colors)-1), colors)
lc { code, color } inlist colors do
@doc "Sets foreground color to #{color}"
defsequence color, code + 30
@doc "Sets background color to #{color}"
defsequence :"#{color}_background", code + 40
end
@doc "Default text color"
defsequence :default_color, 39
@doc "Default background color"
defsequence :default_background, 49
@doc "Framed"
defsequence :framed, 51
@doc "Encircled"
defsequence :encircled, 52
@doc "Overlined"
defsequence :overlined, 53
@doc "Not framed or encircled"
defsequence :not_framed_encircled, 54
@doc "Not overlined"
defsequence :not_overlined, 55
@doc "Send cursor home"
defsequence :home, "", "H"
@doc "Clear screen"
defsequence :clear, "2", "J"
# Catch spaces between codes
defp escape_sequence(<< ?\s, rest :: binary >>) do
escape_sequence(rest)
end
defp escape_sequence(other) do
[spec|_] = String.split(other, %r/(,|\})/)
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
end
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
It will also append a `%{reset}` to the string. If you don't want this
behaviour, use `escape_fragment/2`.
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
## Examples
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes\e[0m"
"""
@spec escape(String.t, emit :: boolean) :: String.t
def escape(string, emit // terminal?) do
{rendered, emitted} = do_escape(string, false, emit, false, [])
if emitted and emit do
rendered <> reset
else
rendered
end
end
@doc %S"""
Escapes a string by converting named ANSI sequences into actual ANSI codes.
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
multiple sequences).
An optional boolean parameter can be passed to enable or disable
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
By default, standard output will be checked if it is a terminal capable
of handling these sequences (using `terminal?/1` function)
## Examples
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
"Hello \e[31m\e[1m\e[32myes"
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
"\e[0mbye"
"""
@spec escape_fragment(String.t, emit :: boolean) :: String.t
def escape_fragment(string, emit // terminal?) do
{rendered, _emitted} = do_escape(string, false, emit, false, [])
rendered
end
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
do_escape_sequence(rest, emit, acc)
end
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, true, emit, emitted, acc)
end
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, acc)
end
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
do_escape(rest, false, emit, emitted, [x|acc])
end
defp do_escape("", false, _emit, emitted, acc) do
{iolist_to_binary(Enum.reverse(acc)), emitted}
end
defp do_escape_sequence(rest, emit, acc) do
{code, rest} = escape_sequence(rest)
if emit do
acc = [code|acc]
end
do_escape(rest, true, emit, true, acc)
end
end
File diff suppressed because it is too large Load Diff
-376
View File
@@ -1,376 +0,0 @@
defmodule Kernel.CLI do
@moduledoc false
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false
@doc """
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: String.from_char_list!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
Enum.each(errors, &IO.puts(:stderr, &1))
System.halt(1)
end
end, config.halt
end
@doc """
Runs the given function by catching any failure
and printing them to stdout. `at_exit` hooks are
also invoked before exiting.
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
try do
fun.()
if halt do
at_exit(0)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
System.halt(1)
end
end
## Helpers
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
lc hook inlist hooks do
try do
hook.(status)
catch
kind, reason ->
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors &["#{h} : Unknown option" | &1]
callback.(hs, new_config)
{ new_list, new_config } ->
callback.(new_list, new_config)
end
end
defp print_error(:error, exception, trace) do
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, format_stacktrace(trace)
end
defp print_error(kind, reason, trace) do
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, format_stacktrace(trace)
end
defp format_stacktrace(stack) do
Exception.format_stacktrace(prune_stacktrace(stack))
end
@elixir_internals [:elixir_compiler, :elixir_module]
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
[]
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
defp prune_stacktrace([]) do
[]
end
# Process shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
System.halt 0
end
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
process_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, config.update_commands &[{:app, h}|&1]
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.update_commands &[{:eval, h}|&1]
end
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands &[{:require, h}|&1]
end
defp process_shared(["-pr", h|t], config) do
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
end
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
end
# Process init options
defp process_argv(["--"|t], config) do
{ config, t }
end
defp process_argv(["+compile"|t], config) do
process_compiler t, config
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
end
defp process_argv(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_argv(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
defp process_argv([], 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(h)
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, config.verbose_compile(true)
end
defp process_compiler([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile &[pattern|&1]
end
end
defp process_compiler([], config) do
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
end
# Process iex options
defp process_iex(["--"|t], config) do
{ config, t }
end
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex", _|t], config) do
process_iex t, config
end
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
process_iex t, config
end
defp process_iex(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_iex(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
defp process_iex([], config) do
{ config, [] }
end
# Process commands
defp process_command({:cookie, h}, _config) do
if Node.alive? do
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
wrapper fn -> Code.eval_string(expr, []) end
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
:ok
end
end
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = find_elixir_executable(file) do
wrapper fn -> Code.require_file(exec) end
else
{ :error, "-S : Could not find executable #{file}" }
end
end
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
wrapper fn -> Code.require_file(file) end
else
{ :error, "No file named #{file}" }
end
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
wrapper fn -> Enum.map files, &Code.require_file(&1) end
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
wrapper fn -> Kernel.ParallelRequire.files(files) end
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
end
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
files = Enum.map patterns, &Path.wildcard(&1)
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
wrapper fn ->
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
end
else
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
end
end
defp wrapper(fun) do
fun.()
:ok
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
# a .bat file that must be in the same directory as
# the actual Elixir executable.
case :os.type() do
{ :win32, _ } ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
_ ->
exec
end
end
end
end
-27
View File
@@ -1,27 +0,0 @@
# Implement error_handler pattern for Erlang
# which is integrated with Kernel.ParallelCompiler
defmodule Kernel.ErrorHandler do
@moduledoc false
def undefined_function(module, fun, args) do
ensure_loaded(module)
: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 = :erlang.get(:elixir_compiler_pid)
parent <- { :waiting, self(), module }
receive do
{ :release, ^parent } -> :ok
end
end
end
end
-169
View File
@@ -1,169 +0,0 @@
# This is a module Elixir responsible for tracking
# the usage of aliases, imports and requires in the Elixir scope.
#
# The implementation simply stores dispatch information in an
# ETS table and then consults this table once compilation is done.
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Kernel.LexicalTracker do
@timeout 30_000
@behavior :gen_server
@import 2
@alias 3
@doc """
Returns all remotes linked to in this lexical scope.
"""
def remotes(pid) do
ets = :gen_server.call(to_pid(pid), :ets, @timeout)
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
val
end
# Internal API
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
end
@doc false
def add_import(pid, module, line, warn) do
:gen_server.cast(pid, { :add_import, module, line, warn })
end
@doc false
def add_alias(pid, module, line, warn) do
:gen_server.cast(pid, { :add_alias, module, line, warn })
end
@doc false
def remote_dispatch(pid, module) do
:gen_server.cast(pid, { :remote_dispatch, module })
end
@doc false
def import_dispatch(pid, module) do
:gen_server.cast(pid, { :import_dispatch, module })
end
@doc false
def alias_dispatch(pid, module) do
:gen_server.cast(pid, { :alias_dispatch, module })
end
@doc false
def collect_unused_imports(pid) do
unused(pid, @import)
end
@doc false
def collect_unused_aliases(pid) do
unused(pid, @alias)
end
defp unused(pid, pos) do
ets = :gen_server.call(pid, :ets, @timeout)
:ets.foldl(fn
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
[{ module, :erlang.element(pos, tuple) }|acc]
_, acc ->
acc
end, [], ets) |> Enum.sort
end
# Callbacks
def init([]) do
{ :ok, :ets.new(:lexical, [:protected]) }
end
def handle_call(:ets, _from, d) do
{ :reply, d, d }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
end
def handle_cast({ :remote_dispatch, module }, d) do
add_module(d, module)
{ :noreply, d }
end
def handle_cast({ :import_dispatch, module }, d) do
add_dispatch(d, module, @import)
{ :noreply, d }
end
def handle_cast({ :alias_dispatch, module }, d) do
add_dispatch(d, module, @alias)
{ :noreply, d }
end
def handle_cast({ :add_import, module, line, warn }, d) do
add_directive(d, module, line, warn, @import)
{ :noreply, d }
end
def handle_cast({ :add_alias, module, line, warn }, d) do
add_directive(d, module, line, warn, @alias)
{ :noreply, d }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
end
def handle_cast(_msg, d) do
{ :noreply, d }
end
def handle_info(_msg, d) do
{ :noreply, d }
end
def terminate(_reason, _d) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
end
# Callbacks helpers
# In the table we keep imports and aliases.
# If the value is false, it was not imported/aliased
# If the value is true, it was imported/aliased
# If the value is a line, it was imported/aliased and has a pending warning
defp add_module(d, module) do
:ets.insert_new(d, { module, false, false })
end
defp add_dispatch(d, module, pos) do
:ets.update_element(d, module, { pos, true })
end
defp add_directive(d, module, line, warn, pos) do
add_module(d, module)
marker = if warn, do: line, else: true
:ets.update_element(d, module, { pos, marker })
end
end
-170
View File
@@ -1,170 +0,0 @@
defmodule Kernel.ParallelCompiler do
@moduledoc """
A module responsible for compiling files in parallel.
"""
@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.
If there is an error during compilation or if `warnings_as_errors`
is set to `true` and there is a warning, this function will fail
with an exception.
This function receives a set of callbacks as options:
* `:each_file` - for each file compiled, invokes the callback passing the file
* `:each_module` - for each module compiled, invokes the callback
passing the file, module and the module bytecode
The compiler doesn't care about the return values of the callbacks.
Returns the modules generated by each compiled file.
"""
def files(files, callbacks // [])
def files(files, callbacks) when is_list(callbacks) do
spawn_compilers(files, nil, callbacks)
end
@doc """
Compiles the given files to the given path.
Read `files/2` for more information.
"""
def files_to_path(files, path, callbacks // [])
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
spawn_compilers(files, path, callbacks)
end
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
:elixir_code_server.cast(:reset_warnings)
schedulers = max(:erlang.system_info(:schedulers_online), 2)
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call(:compilation_status) do
:ok -> result
:error -> raise CompileError, [], []
end
end
# We already have 4 currently running, don't spawn new ones
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
length(queued) - length(waiting) >= schedulers do
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
end
# Spawn a compiler for each file in the list until we reach the limit
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
parent = self()
child = spawn_link fn ->
:erlang.put(:elixir_compiler_pid, parent)
:erlang.put(:elixir_ensure_compiled, true)
:erlang.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, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
end
# No more files, nothing waiting, queue is empty, we are done
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# No more files, but queue and waiting are not full or do not match
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
end
# Wait for messages from child processes
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
receive do
{ :compiled, child, file } ->
if callback = Keyword.get(callbacks, :each_file) do
callback.(file)
end
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, original, output, callbacks, new_waiting, new_queued, schedulers, result)
{ :module_available, child, ref, file, module, binary } ->
if callback = Keyword.get(callbacks, :each_module) do
callback.(file, module, binary)
end
# Release the module loader which is waiting for an ack
child <- { ref, :ack }
new_waiting = release_waiting_processes(module, waiting)
new_result = [module|result]
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
{ :waiting, child, on } ->
new_waiting = :orddict.store(child, on, waiting)
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
{ :failure, child, kind, reason, stacktrace } ->
if many_missing?(child, files, waiting, queued) do
IO.puts "== Compilation failed =="
IO.puts "Compilation failed on the following files:\n"
Enum.each Enum.reverse(queued), fn { pid, file } ->
case List.keyfind(waiting, pid, 0) do
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
_ -> :ok
end
end
IO.puts "\nThe first failure is shown below..."
end
{^child, file} = List.keyfind(queued, child, 0)
IO.puts "== Compilation error on file #{file} =="
:erlang.raise(kind, reason, stacktrace)
end
end
defp many_missing?(child, files, waiting, queued) do
waiting_length = length(waiting)
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
waiting_length > 1 and files == [] and
waiting_length == length(queued)
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
-70
View File
@@ -1,70 +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.
Returns the modules generated by each required file.
"""
def files(files, callback // default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_requires(files, [], callback, schedulers, [])
end
defp spawn_requires([], [], _callback, _schedulers, result), do: result
defp spawn_requires([], waiting, callback, schedulers, result) do
wait_for_messages([], waiting, callback, schedulers, result)
end
defp spawn_requires(files, waiting, callback, schedulers, result) when length(waiting) >= schedulers do
wait_for_messages(files, waiting, callback, schedulers, result)
end
defp spawn_requires([h|t], waiting, callback, schedulers, result) do
parent = self
compiler_pid = :erlang.get(:elixir_compiler_pid)
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
child = spawn_link fn ->
if compiler_pid != :undefined do
:erlang.put(:elixir_compiler_pid, compiler_pid)
end
if ensure_compiled != :undefined do
:erlang.put(:elixir_ensure_compiled, ensure_compiled)
end
:erlang.process_flag(:error_handler, handler)
try do
new = Code.require_file(h) || []
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
catch
kind, reason ->
parent <- { :failure, self, kind, reason, System.stacktrace }
end
end
spawn_requires(t, [child|waiting], callback, schedulers, result)
end
defp wait_for_messages(files, waiting, callback, schedulers, result) do
receive do
{ :required, child, mods, file } ->
callback.(file)
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
{ :failure, _child, kind, reason, stacktrace } ->
:erlang.raise(kind, reason, stacktrace)
end
end
end
-413
View File
@@ -1,413 +0,0 @@
# This is an optimization Elixir runs on function clauses.
# Whenever a variables matches against a record (a tagged
# tuple), this information is stored in order to optimize
# record calls.
defmodule Kernel.RecordRewriter do
@moduledoc false
def optimize_clause(module, clause) do
optimize_clause(clause, module, :orddict.new)
end
## Clause
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
{ args, dict } = optimize_args(args, module, dict)
{ body, dict, res } = optimize_body(body, module, dict, [])
{ { :clause, line, args, guards, body }, dict, res }
end
defp optimize_args(args, module, dict) do
Enum.map_reduce args, dict, fn(arg, acc) ->
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
{ new_arg, new_acc }
end
end
defp optimize_body([], _module, dict, _acc) do
{ [], dict, nil }
end
defp optimize_body([h], module, dict, acc) do
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
end
defp optimize_body([h|t], module, dict, acc) do
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
optimize_body(t, module, new_dict, [new_expr|acc])
end
## Record helpers
defp record_fields(record, record) do
fields = Module.get_attribute(record, :record_fields)
optimizable = Module.get_attribute(record, :record_optimizable)
unless nil?(fields) or nil?(optimizable) do
{ (lc { k, _ } inlist fields, do: k), optimizable }
end
end
defp record_fields(_module, record) do
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
try do
fields = lc { k, _ } inlist record.__record__(:fields), do: k
optimizable = record.__record__(:optimizable)
{ fields, optimizable }
rescue
[UndefinedFunctionError, FunctionClauseError] -> nil
end
end
end
defp record_field_info(function) do
case atom_to_list(function) do
'update' -> nil
'update_' ++ _field -> nil
_ -> { :accessor, function }
end
end
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
case record_fields(module, record) do
{ fields, optimizable } ->
opt_call =
if :lists.member({ function, length(args) + 1 }, optimizable) do
case record_field_info(function) do
{ kind, field } ->
if index = Enum.find_index(fields, &(field == &1)) do
optimize_record_accessor_call(line, res, kind, field, index, left, args)
end
nil ->
optimize_record_other_call(line, record, res, function, left, args)
end
end
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
nil ->
nil
end
end
defp optimize_call(_line, _module, _res, _left, _right, _args) do
nil
end
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
[{ :integer, 0, index + 2 }, left]
}
{ call, nil }
end
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
call = { :call, line,
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
[{ :integer, 0, index + 2 }, left, arg]
}
{ call, res }
end
defp optimize_record_other_call(line, record, res, function, left, args) do
call = { :call, line,
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
args ++ [left]
}
{ call, res }
end
## Expr
defp optimize_expr({ :call, call_line,
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
{ value, dict, _ } = optimize_expr(value, module, dict)
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
end
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
{ left, dict, res } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ args, dict } = optimize_args(args, module, dict)
case optimize_call(call_line, module, res, left, right, args) do
{ call, call_res } ->
{ call, dict, call_res }
nil ->
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
end
end
defp optimize_expr({ :call, line, expr, args }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ args, dict } = optimize_args(args, module, dict)
{ { :call, line, expr, args }, dict, nil }
end
defp optimize_expr({ :match, line, left, right }, module, dict) do
{ left, dict, left_res } = optimize_expr(left, module, dict)
{ right, dict, right_res } = optimize_expr(right, module, dict)
match = { :match, line, left, right }
if left_res do
dict = assign_vars(extract_vars(right, []), dict, left_res)
end
if right_res do
dict = assign_vars(extract_vars(left, []), dict, right_res)
end
{ match, dict, right_res || left_res }
end
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :op, line, op, left, right }, dict, nil }
end
defp optimize_expr({ :op, line, op, expr }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :op, line, op, expr }, dict, nil }
end
defp optimize_expr({ :bin, line, elements }, module, dict) do
{ elements, dict } = optimize_args(elements, module, dict)
{ { :bin, line, elements }, dict, nil }
end
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
end
defp optimize_expr({ :cons, line, left, right }, module, dict) do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { :cons, line, left, right }, dict, nil }
end
defp optimize_expr({ :block, line, args }, module, dict) do
{ args, dict, res } = optimize_body(args, module, dict, [])
{ { :block, line, args }, dict, res }
end
defp optimize_expr({ :tuple, line, args }, module, dict) do
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
args_res = if Enum.any?(args_res), do: args_res, else: nil
res =
case args do
[{ :atom, _, atom }|_] -> atom
_ -> nil
end
{ { :tuple, line, args }, dict, { res, args_res } }
end
defp optimize_expr({ :var, _, name } = var, _module, dict) do
case :orddict.find(name, dict) do
{ :ok, res } -> { var, dict, res }
:error -> { var, dict, nil }
end
end
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
{ expr, dict, _ } = optimize_expr(expr, module, dict)
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :case, line, expr, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
dict = join_dict(tuples)
res = join_result(tuples)
{ { :receive, line, clauses }, dict, res }
end
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
clauses = lc { clause, _, _ } inlist tuples, do: clause
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
dict = join_dict(tuples, dict)
res = join_result(tuples, res)
{ { :receive, line, clauses, after_key, after_value }, dict, res }
end
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
catches = lc { clause, _, _ } inlist tuples, do: clause
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
elses = lc { clause, _, _ } inlist tuples, do: clause
{ body, _, res } = optimize_body(body, module, dict, [])
res = join_result(tuples, res)
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
{ { :try, line, body, elses, catches, try_after }, dict, res }
end
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
{ name, dict, _ } = optimize_expr(name, module, dict)
{ arity, dict, _ } = optimize_expr(arity, module, dict)
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
end
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
clauses = lc clause inlist clauses do
{ clause, _, _ } = optimize_clause(clause, module, dict)
clause
end
{ { :fun, line, { :clauses, clauses } }, dict, nil }
end
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
{ args, new_dict } = optimize_args(args, module, dict)
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
{ { comprehension, line, expr, args }, dict, nil }
end
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
{ left, dict, _ } = optimize_expr(left, module, dict)
{ right, dict, _ } = optimize_expr(right, module, dict)
{ { generate, line, left, right }, dict, nil }
end
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
{ other, dict, nil }
end
## Helpers
defp optimize_tuple_args(args, module, dict) do
{ final_args, { final_dict, final_acc } } =
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
{ new_arg, { new_acc, [res|acc_res] } }
end
{ final_args, final_dict, Enum.reverse(final_acc) }
end
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
assign_vars t, assign_nested_vars(key, dict, value), res
end
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
dict =
case :orddict.find(key, dict) do
{ :ok, ^res } ->
dict
{ :ok, { ^value, _ } } ->
:orddict.store(key, { value, nil }, dict)
{ :ok, _ } ->
# We are overriding a type of an existing variable,
# which means the source code is invalid.
:orddict.store(key, nil, dict)
:error ->
:orddict.store(key, res, dict)
end
assign_vars t, dict, res
end
defp assign_vars([_|t], dict, res) do
assign_vars t, dict, res
end
defp assign_vars([], dict, _res) do
dict
end
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
end
defp assign_nested_vars([], dict, []) do
dict
end
defp extract_vars({ :match, _, left, right }, vars) do
vars = extract_vars(right, vars)
extract_vars(left, vars)
end
defp extract_vars({ :var, _, name }, vars) do
[name|vars]
end
defp extract_vars({ :tuple, _, args }, vars) do
[Enum.map(args, &extract_vars(&1, []))|vars]
end
defp extract_vars(_, vars) do
vars
end
defp join_dict([]) do
[]
end
defp join_dict([{ _, dict, _ }|t]) do
join_dict(t, dict)
end
defp join_dict([{ _, dict, _ }|t], other) do
other = Enum.reduce other, other, fn
{ key, { value, _ } = res }, acc ->
case :orddict.find(key, dict) do
{ :ok, ^res } -> acc
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
{ :ok, _ } -> :orddict.store(key, nil, acc)
:error -> :orddict.erase(key, acc)
end
{ key, nil }, acc ->
:orddict.store(key, nil, acc)
end
join_dict(t, other)
end
defp join_dict([], other) do
other
end
defp join_result([]) do
[]
end
defp join_result([{ _, _, res }|t]) do
join_result(t, res)
end
defp join_result([{ _, _, res }|t], res) do
join_result(t, res)
end
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
join_result(t, { res, nil })
end
defp join_result([{ _, _, _ }|_], _res) do
nil
end
defp join_result([], res) do
res
end
end
File diff suppressed because it is too large Load Diff
-783
View File
@@ -1,783 +0,0 @@
defmodule Kernel.Typespec do
@moduledoc """
Provides macros and functions for working with typespecs.
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
`@callback` available in modules are handled by the equivalent
macros defined by this module.
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
below.
## Defining a specification
@spec function_name(type, type) :: type
@callback function_name(type, type) :: type
For more details, see documentation for `defspec` and `defcallback` below.
## Types
The type syntax provided by Elixir is fairly similar to the one
in Erlang.
Most of the built-in types provided in Erlang (for example, `pid()`)
are expressed the same way: `pid()` or simply `pid`. Parametrized types
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
well.
Main differences lie in how bit strings and functions are defined:
### Bit Strings
Bit string with a base size of 3:
<<_ :: 3>>
Bit string with a unit size of 8:
<<_ :: _ * 8>>
### Anonymous functions
Any anonymous function:
((...) -> any)
or
(... -> any)
Anonymous function with arity of zero:
(() -> type)
Anonymous function with some arity:
((type, type) -> type)
or
(type, type -> type)
## Notes
Elixir discourages the use of type `string()` as it might be confused
with binaries which are referred to as "strings" in Elixir (as opposed to
character lists). In order to use the type that is called `string()` in Erlang,
one has to use the `char_list()` type which is a synonym for `string()`. If you
use `string()`, you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated by functions in the
String module), use `String.t()` type instead.
See http://www.erlang.org/doc/reference_manual/typespec.html
for more information.
"""
@doc """
Defines a type.
This macro is the one responsible for handling the attribute `@type`.
## Examples
@type my_type :: atom
"""
defmacro deftype(type) do
quote do
Kernel.Typespec.deftype(:type, unquote(Macro.escape type), __ENV__)
end
end
@doc """
Defines an opaque type.
This macro is the one responsible for handling the attribute `@opaque`.
## Examples
@opaque my_type :: atom
"""
defmacro defopaque(type) do
quote do
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape type), __ENV__)
end
end
@doc """
Defines a private type.
This macro is the one responsible for handling the attribute `@typep`.
## Examples
@typep my_type :: atom
"""
defmacro deftypep(type) do
quote do
Kernel.Typespec.deftype(:typep, unquote(Macro.escape type), __ENV__)
end
end
@doc """
Defines a spec.
This macro is the one responsible for handling the attribute `@spec`.
## Examples
@spec add(number, number) :: number
"""
defmacro defspec(spec) do
quote do
Kernel.Typespec.defspec(:spec, unquote(Macro.escape spec), __ENV__)
end
end
@doc """
Defines a callback.
This macro is the one responsible for handling the attribute `@callback`.
## Examples
@callback add(number, number) :: number
"""
defmacro defcallback(spec) do
quote do
Kernel.Typespec.defspec(:callback, unquote(Macro.escape spec), __ENV__)
end
end
## Helpers
@doc """
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
"""
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
{ kind, export } =
case kind do
:type -> { :type, true }
:typep -> { :type, false }
:opaque -> { :opaque, true }
end
module = caller.module
arity = length(vars)
Module.compile_typespec module, kind, type
if export do
Module.compile_typespec(module, :export_type, [{ name, arity }])
end
define_doc(caller, kind, name, arity, export)
type
end
defp define_doc(caller, kind, name, arity, export) do
module = caller.module
doc = Module.get_attribute(module, :typedoc)
if doc do
if export do
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
else
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n"
end
end
Module.delete_attribute(module, :typedoc)
end
@doc """
Defines a `spec` by receiving Erlang's typespec.
"""
def define_spec(module, tuple, definition) do
Module.compile_typespec module, :spec, { tuple, definition }
end
@doc """
Defines a `callback` by receiving Erlang's typespec.
"""
def define_callback(module, tuple, definition) do
Module.compile_typespec module, :callback, { tuple, definition }
end
@doc """
Returns `true` if the current module defines a given type
(private, opaque or not). This function is only available
for modules being compiled.
"""
def defines_type?(module, name, arity) do
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
:lists.any(finder, Module.get_attribute(module, :type)) or
:lists.any(finder, Module.get_attribute(module, :opaque))
end
@doc """
Returns `true` if the current module defines a given spec.
This function is only available for modules being compiled.
"""
def defines_spec?(module, name, arity) do
tuple = { name, arity }
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
end
@doc """
Returns `true` if the current module defines a callback.
This function is only available for modules being compiled.
"""
def defines_callback?(module, name, arity) do
tuple = { name, arity }
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
end
@doc """
Converts a spec clause back to Elixir AST.
"""
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
end
def spec_to_ast(name, { :type, line, :fun, [] }) do
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
end
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
[h|t] =
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
end
args = lc arg inlist args, do: typespec_to_ast(arg)
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
end
@doc """
Converts a type clause back to Elixir AST.
"""
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
fields = lc field inlist fields, do: typespec_to_ast(field)
args = lc arg inlist args, do: typespec_to_ast(arg)
type = { :{}, [], [record|fields] }
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
end
def type_to_ast({ name, type, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
end
@doc """
Returns all type docs available from the module's beam code.
It is returned as a list of tuples where the first element is the pair of type
name and arity and the second element is the documentation.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
@spec beam_typedocs(module | binary) :: [tuple] | nil
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
List.flatten(type_docs)
_ ->
nil
end
end
@doc """
Returns all types available from the module's beam code.
It is returned as a list of tuples where the first
element is the type (`:typep`, `:type` and `:opaque`).
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
@spec beam_types(module | binary) :: [tuple] | nil
def beam_types(module) when is_atom(module) or is_binary(module) do
case abstract_code(module) do
{ :ok, abstract_code } ->
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
exported_types = List.flatten(exported_types)
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
cond do
kind == :opaque -> { :opaque, type }
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
true -> { :typep, type }
end
end
_ ->
nil
end
end
@doc """
Returns all specs available from the module's beam code.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file on the disk which can be
located by the runtime system.
"""
@spec beam_specs(module | binary) :: [tuple] | nil
def beam_specs(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :spec)
end
@doc """
Returns all callbacks available from the module's beam code.
It is returned as a list of tuples where the first
element is spec name and arity and the second is the spec.
The module has to have a corresponding beam file on the disk
which can be located by the runtime system.
"""
@spec beam_callbacks(module | binary) :: [tuple] | nil
def beam_callbacks(module) when is_atom(module) or is_binary(module) do
from_abstract_code(module, :callback)
end
defp from_abstract_code(module, kind) do
case abstract_code(module) do
{ :ok, abstract_code } ->
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
:error ->
nil
end
end
defp abstract_code(module) do
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
{ :ok, abstract_code }
_ ->
:error
end
end
defp abstract_code_beam(module) when is_atom(module) do
case :code.get_object_code(module) do
{ ^module, beam, _filename } -> beam
:error -> module
end
end
defp abstract_code_beam(binary) when is_binary(binary) do
binary
end
## Macro callbacks
@doc false
def deftype(kind, { :::, _, [type, definition] }, caller) do
do_deftype(kind, type, definition, caller)
end
def deftype(kind, {name, _meta, args} = type, caller)
when is_atom(name) and not is_list(args) do
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
end
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification #{type_spec}"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
args =
if is_atom(args) do
[]
else
lc(arg inlist args, do: variable(arg))
end
vars = lc { :var, _, var } inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc { :var, _, _ } = var inlist args, do: var
type = { name, spec, vars }
define_type(caller, kind, type)
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
if is_atom(args), do: args = []
constraints = guard_to_constraints(constraints_guard, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
if is_atom(args), do: args = []
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
compile_error caller, "invalid function type specification #{spec}"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
line = line(meta)
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
end
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
end
## To AST conversion
defp typespec_to_ast({ :type, line, :tuple, :any }) do
typespec_to_ast({:type, line, :tuple, []})
end
defp typespec_to_ast({ :type, line, :tuple, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :{}, [line: line], args }
end
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
case unpack_typespec_kw(arg, []) do
{ :ok, ast } -> ast
:error -> [typespec_to_ast(arg)]
end
end
defp typespec_to_ast({ :type, _line, :list, args }) do
lc arg inlist args, do: typespec_to_ast(arg)
end
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
arg1 == 0 ->
quote line: line, do: <<_ :: _ * unquote(arg2)>>
true ->
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
end
end
defp typespec_to_ast({ :type, line, :union, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
Enum.reduce tl(args), hd(args),
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
end
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
end
defp typespec_to_ast({ :type, line, :fun, [] }) do
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
end
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
{ :"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)] }
end
defp typespec_to_ast({ :type, line, name, args }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
{ name, [line: line], args }
end
defp typespec_to_ast({ :var, line, var }) do
var =
case atom_to_binary(var) do
<<"_", c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("_#{String.downcase(c)}#{rest}")
<<c :: [binary, size(1)], rest :: binary>> ->
binary_to_atom("#{String.downcase(c)}#{rest}")
end
{ var, line, nil }
end
# Special shortcut(s)
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
typespec_to_ast({:type, line, :char_list, []})
end
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
typespec_to_ast({:type, line, :as_boolean, [arg]})
end
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
args = lc arg inlist args, do: typespec_to_ast(arg)
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
{ dot, [line: line], args }
end
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
{ :::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
end
defp typespec_to_ast({ :typed_record_field,
{ :record_field, line, { :atom, line1, name }},
type }) do
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
end
defp typespec_to_ast({:type, _, :any}) do
quote do: ...
end
defp typespec_to_ast({:paren_type, _, [type]}) do
typespec_to_ast(type)
end
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
atom
end
defp typespec_to_ast(other), do: other
## From AST conversion
defp line(meta) do
case :lists.keyfind(:line, 1, meta) do
{ :line, line } -> line
false -> 0
end
end
# Handle unions
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
exprs = Enum.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line(meta), :union, union }
end
# Handle binaries
defp typespec({:<<>>, meta, []}, _, _) do
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
end
defp typespec({:<<>>, meta, [{:::, _, [{:_, meta1, atom}, {:*, _, [{:_, meta2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line(meta), :binary, [{:integer, line(meta1), 0}, {:integer, line(meta2), unit}]}
end
defp typespec({:<<>>, meta, [{:::, meta1, [{:_, meta2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
end
# Handle ranges
defp typespec({:"..", meta, args}, vars, caller) do
typespec({:range, meta, args}, vars, caller)
end
# Handle special forms
defp typespec({:__MODULE__, _, atom}, vars, caller) when is_atom(atom) do
typespec(caller.module, vars, caller)
end
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
end
# Handle funs
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
end
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
args = fn_args(meta, arguments, return, vars, caller)
{ :type, line(meta), :fun, args }
end
# Handle type operator
defp typespec({:"::", meta, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var, 0)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line(meta), [left, right] }
end
# Handle unary ops
defp typespec({op, meta, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line(meta), op, {:integer, line(meta), integer} }
end
# Handle access macro
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
access = {{:., meta, [Kernel, :access]}, meta1,
[target, args ++ [_: { :any, [], [] }]]}
typespec(Macro.expand(access, caller), vars, caller)
end
# Handle remote calls
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote) do
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
end
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
end
# Handle tuples
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, meta, []}, vars, caller)
end
defp typespec({:{}, meta, []}, _, _) do
{ :type, line(meta), :tuple, :any }
end
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line(meta), :tuple, args }
end
# Handle blocks
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
typespec(arg, vars, caller)
end
# Handle variables or local calls
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
if :lists.member(name, vars) do
{ :var, line(meta), name }
else
typespec({name, meta, []}, vars, caller)
end
end
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
IO.write "warning: string() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), :string, arguments }
end
defp typespec({:char_list, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
end
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
end
defp typespec({name, meta, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line(meta), 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, [], [] }, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, [], [spec] }, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
typespec({ :nonempty_list, [], [spec] }, vars, caller)
end
defp typespec([h|t] = l, vars, caller) do
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
{ :|, [], [acc, validate_kw(x, l, caller)] }
end)
typespec({ :list, [], [union] }, vars, caller)
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 compile_error(caller, desc) do
raise CompileError, file: caller.file, line: caller.line, description: desc
end
defp remote_type({remote, meta, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line(meta), [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
end
defp fn_args(meta, args, return, vars, caller) do
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
[{:type, _, :any}, {:type, _, :any, []}] -> []
x -> x
end
end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
{ :type, line(meta), :any }
end
defp fn_args(meta, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line(meta), :product, args }
end
defp variable({name, meta, _}) do
{:var, line(meta), name}
end
defp unpack_typespec_kw({ :type, _, :union, [
next,
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
] }, acc) do
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
end
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
end
defp unpack_typespec_kw(_, _acc) do
:error
end
end
-540
View File
@@ -1,540 +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
behaves exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
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.
A handful of functions exist to handle duplicated keys, in
particular, `from_enum` allows creating a new keywords without
removing duplicated keys, `get_values` returns all values for
a given key and `delete_first` deletes just one of the existing
entries.
Since a keyword list is simply a list, all the operations defined
in `Enum` and `List` can also be applied.
"""
@type key :: atom
@type value :: any
@type t :: [{key, value}]
@doc """
Creates a Keyword from an enum. Unlike `Keyword.new`
which behaves as a dict, `Keyword.from_enum` does not remove
duplicated entries.
"""
@spec from_enum(Enum.t) :: t
def from_enum(enum) do
Enum.to_list(enum)
end
@doc """
Checks if the given argument is a keywords list or not.
"""
@spec keyword?(term) :: boolean
def keyword?([{ key, _value } | rest]) when is_atom(key) do
keyword?(rest)
end
def keyword?([]), do: true
def keyword?(_other), do: false
@doc """
Returns an empty keyword list, i.e. an empty list.
"""
@spec new :: t
def new do
[]
end
@doc """
Creates a Keyword from an enumerable. Similar to dicts,
duplicated entries are removed, the latest one prevails.
## Examples
iex> Keyword.new([{:b, 1}, {:a, 2}])
[a: 2, b: 1]
"""
@spec new(Enum.t) :: t
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
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
[a: :a, b: :b]
"""
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
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 a specific `key`.
If `key` does not exist, return default value (`nil` if no default value).
If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries.
## Examples
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get([a: 1], :b, 3)
3
"""
@spec get(t, key) :: value
@spec get(t, key, value) :: value
def get(keywords, key, default // nil) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> default
end
end
@doc """
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
iex> Keyword.fetch([a: 1], :a)
{ :ok, 1 }
iex> Keyword.fetch([a: 1], :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> { :ok, value }
false -> :error
end
end
@doc """
Fetches the value for specific `key`. If `key` does not exist,
a `KeyError` is raised.
## Examples
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key not found: :b
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(keywords, key) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, value } -> value
false -> raise(KeyError, key: key)
end
end
@doc """
Gets all values for a specific `key`.
## Examples
iex> Keyword.get_values([a: 1, a: 2], :a)
[1,2]
"""
@spec get_values(t, key) :: [value]
def get_values(keywords, key) when is_atom(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
iex> Keyword.keys([a: 1, b: 2])
[:a,:b]
iex> Keyword.keys([a: 1, b: 2, a: 3])
[:a,:b,:a]
"""
@spec keys(t) :: [key]
def keys(keywords) do
lc { key, _ } inlist keywords, do: key
end
@doc """
Returns all values from the keyword list.
## Examples
iex> Keyword.values([a: 1, b: 2])
[1,2]
"""
@spec values(t) :: [value]
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
iex> Keyword.delete([a: 1, b: 2], :a)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
[b: 2]
iex> Keyword.delete([b: 2], :a)
[b: 2]
"""
@spec delete(t, key) :: t
def delete(keywords, key) when is_atom(key) do
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
end
@doc """
Deletes the first entry in the keyword list for a specific `key`.
If the `key` does not exist, returns the keyword list unchanged.
## Examples
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
[b: 2, a: 3]
iex> Keyword.delete_first([b: 2], :a)
[b: 2]
"""
@spec delete_first(t, key) :: t
def delete_first(keywords, key) when is_atom(key) do
:lists.keydelete(key, 1, keywords)
end
@doc """
Puts the given `value` under `key`.
If a previous value is already stored, all entries are
removed and the value is overriden.
## Examples
iex> Keyword.put([a: 1, b: 2], :a, 3)
[a: 3, b: 2]
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
[a: 3, b: 2]
"""
@spec put(t, key, value) :: t
def put(keywords, key, value) when is_atom(key) do
[{key, value}|delete(keywords, key)]
end
@doc """
Puts the given `value` under `key` unless the entry `key`
already exists.
## Examples
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
[a: 1, b: 2]
"""
@spec put_new(t, key, value) :: t
def put_new(keywords, key, value) when is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{ ^key, _ } -> keywords
false -> [{key, value}|keywords]
end
end
@doc """
Checks if two keywords are equal. I.e. they contain
the same keys and those keys contain the same values.
## Examples
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
true
"""
@spec equal?(t, t) :: boolean
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
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
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
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
iex> end)
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
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 """
Returns whether a given `key` exists in the given `keywords`.
## Examples
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
"""
@spec has_key?(t, key) :: boolean
def has_key?(keywords, key) when is_atom(key) do
:lists.keymember(key, 1, keywords)
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, raises `KeyError`.
## Examples
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
[a: 2]
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
** (KeyError) key not found: :b
"""
@spec update!(t, key, (value -> value)) :: t | no_return
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(KeyError, key: key)
end
@doc """
Updates the `key` with the given function. If the `key` does
not exist, inserts the given `initial` value.
## Examples
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
[a: 1, b: 11]
"""
@spec update(t, key, value, (value -> value)) :: t
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
@doc """
Splits the given keywords in two given the given keys.
Duplicated keys are preserved in the split keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3], [b: 2, d: 4] }
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.split(d, [:a, :c, :e])
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
"""
def split(dict, keys) do
acc = { [], [] }
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
case :lists.member(k, keys) do
true -> { [{k, v}|take], drop }
false -> { take, [{k, v}|drop] }
end
end
{ Enum.reverse(take), Enum.reverse(drop) }
end
@doc """
Takes the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3, a: 5]
"""
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
@doc """
Drops the given keys from the dict.
Duplicated keys are preserved in the new keyword list.
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
"""
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
All duplicated entries are removed. See `pop_first/3` for
removing only the first entry.
## Examples
iex> Keyword.pop [a: 1], :a
{1,[]}
iex> Keyword.pop [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop [a: 1, a: 2], :a
{1,[]}
"""
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular ocurrence
of `key`.
Duplicated entries are not removed.
## Examples
iex> Keyword.pop_first [a: 1], :a
{1,[]}
iex> Keyword.pop_first [a: 1], :b
{nil,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1], :b, 3
{3,[a: 1]}
iex> Keyword.pop_first [a: 1, a: 2], :a
{1,[a: 2]}
"""
def pop_first(dict, key, default // nil) do
{ get(dict, key, default), delete_first(dict, key) }
end
end
-417
View File
@@ -1,417 +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 library but follow Elixir's convention
of receiving the target (in this case, a list) as the
first argument.
"""
@compile :inline_list_funcs
@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
iex> List.delete([1, 2, 3], 1)
[2,3]
iex> List.delete([1, 2, 2, 3], 2)
[1, 2, 3]
"""
def delete(list, item) do
:lists.delete(item, list)
end
@doc """
Duplicates the given element `n` times in a list.
## Examples
iex> List.duplicate("hello", 3)
["hello","hello","hello"]
iex> 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.
## Examples
iex> List.flatten([1, [[2], 3]])
[1,2,3]
"""
def flatten(list) do
:lists.flatten(list)
end
@doc """
Flattens the given `list` of nested lists.
The list `tail` will be added at the end of
the flattened list.
## Examples
iex> List.flatten([1, [[2], 3]], [4, 5])
[1,2,3,4,5]
"""
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
iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
20
iex> 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
iex> 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 """
Returns the last element in `list` or `nil` if `list` is empty.
## Examples
iex> List.last([])
nil
iex> List.last([1])
1
iex> List.last([1, 2, 3])
3
"""
def last([]), do: nil
def last(list) do
:lists.last(list)
end
@doc """
Receives a list of tuples and returns the first tuple
where the item at `position` in the tuple matches the
given `item`.
## Examples
iex> List.keyfind([a: 1, b: 2], :a, 0)
{ :a, 1 }
iex> List.keyfind([a: 1, b: 2], 2, 1)
{ :b, 2 }
iex> 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` in the tuple matches
the given `item`.
## Examples
iex> List.keymember?([a: 1, b: 2], :a, 0)
true
iex> List.keymember?([a: 1, b: 2], 2, 1)
true
iex> 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` if it exists.
## Examples
iex> 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 sorts the items
at `position` of the tuples. The sort is stable.
## Examples
iex> List.keysort([a: 5, b: 1, c: 3], 1)
[b: 1, c: 3, a: 5]
iex> List.keysort([a: 5, c: 1, b: 3], 0)
[a: 5, b: 3, c: 1]
"""
def keysort(list, position) do
:lists.keysort(position + 1, list)
end
@doc """
Receives a list of tuples and replaces the item
identified by `key` at `position`. If the item
does not exist, it is added to the end of the list.
## Examples
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
[a: 3, b: 2]
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
[a: 1, b: 2, c: 3]
"""
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` matches the
given `item`. Returns the new list.
## Examples
iex> List.keydelete([a: 1, b: 2], :a, 0)
[{ :b, 2 }]
iex> List.keydelete([a: 1, b: 2], 2, 1)
[{ :a, 1 }]
iex> 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 """
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
iex> List.wrap("hello")
["hello"]
iex> List.wrap([1, 2, 3])
[1,2,3]
iex> List.wrap(nil)
[]
"""
def wrap(list) when is_list(list) do
list
end
def wrap(nil) do
[]
end
def wrap(other) do
[other]
end
@doc """
Zips corresponding elements from each list in `list_of_lists`.
## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]])
[{1, 3, 5}, {2, 4, 6}]
iex> 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
iex> List.unzip([{1, 2}, {3, 4}])
[[1, 3], [2, 4]]
iex> 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
@doc """
Returns a list with `value` inserted at the specified `index`. Note that `index`
is capped at the list length. Negative indices indicate an offset from the
end of the list.
## Examples
iex> List.insert_at([1, 2, 3, 4], 2, 0)
[1, 2, 0, 3, 4]
iex> List.insert_at([1, 2, 3], 10, 0)
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -1, 0)
[1, 2, 3, 0]
iex> List.insert_at([1, 2, 3], -10, 0)
[0, 1, 2, 3]
"""
def insert_at(list, index, value) do
if index < 0 do
do_insert_at(list, length(list) + index + 1, value)
else
do_insert_at(list, index, value)
end
end
@doc """
Returns a list with a replaced value at the specified `index`. Negative indices
indicate an offset from the end of the list. If `index` is out of bounds, the
original `list` is returned.
## Examples
iex> List.replace_at([1, 2, 3], 0, 0)
[0, 2, 3]
iex> List.replace_at([1, 2, 3], 10, 0)
[1, 2, 3]
iex> List.replace_at([1, 2, 3], -1, 0)
[1, 2, 0]
iex> List.replace_at([1, 2, 3], -10, 0)
[1, 2, 3]
"""
def replace_at(list, index, value) do
if index < 0 do
do_replace_at(list, length(list) + index, value)
else
do_replace_at(list, index, value)
end
end
## Helpers
# replace_at
defp do_replace_at([], _index, _value) do
[]
end
defp do_replace_at(list, index, _value) when index < 0 do
list
end
defp do_replace_at([_old|rest], 0, value) do
[ value | rest ]
end
defp do_replace_at([h|t], index, value) do
[ h | do_replace_at(t, index - 1, value) ]
end
# insert_at
defp do_insert_at([], _index, value) do
[ value ]
end
defp do_insert_at(list, index, value) when index <= 0 do
[ value | list ]
end
defp do_insert_at([h|t], index, value) do
[ h | do_insert_at(t, index - 1, value) ]
end
# zip
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
-56
View File
@@ -1,56 +0,0 @@
defprotocol List.Chars do
@moduledoc %S"""
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.
"""
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
@doc """
Returns the given binary converted to a char list.
"""
def to_char_list(thing) when is_binary(thing) do
String.to_char_list!(thing)
end
def to_char_list(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a char list"
end
end
defimpl List.Chars, for: List do
def to_char_list(list), do: list
end
defimpl List.Chars, for: Integer do
def to_char_list(thing) do
integer_to_list(thing)
end
end
defimpl List.Chars, for: Float do
@digits 20
@limit :math.pow(10, @digits)
def to_char_list(thing) when thing > @limit do
float_to_list(thing, scientific: @digits)
end
def to_char_list(thing) do
float_to_list(thing, compact: true, decimals: @digits)
end
end
-146
View File
@@ -1,146 +0,0 @@
defmodule ListDict do
@moduledoc """
A Dict implementation that works on lists of two-items tuples.
This dictionary is only recommended for keeping a small amount
of values. Other dict alternatives are more viable for keeping
any other amount than a handful.
For more information about the functions and their APIs, please
consult the `Dict` module.
"""
@doc """
Returns a new `ListDict`, i.e. an empty list.
"""
def new, do: []
@doc """
Creates a new `ListDict` from the given pairs.
"""
def new(pairs) do
Enum.to_list pairs
end
@doc """
Creates a new `ListDict` from the given pairs
via the given transformation function.
"""
def new(list, transform) when is_function(transform) do
Enum.map list, transform
end
def keys(dict) do
lc { key, _ } inlist dict, do: key
end
def values(dict) do
lc { _, value } inlist dict, do: value
end
def size(dict) do
length(dict)
end
def has_key?(dict, key)
def has_key?([{ key, _ }|_], key), do: true
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
def has_key?([], _key), do: false
def get(dict, key, default // nil)
def get([{ key, value }|_], key, _default), do: value
def get([{ _, _ }|t], key, default), do: get(t, key, default)
def get([], _key, default), do: default
def fetch(dict, key)
def fetch([{ key, value }|_], key), do: { :ok, value }
def fetch([{ _, _ }|t], key), do: fetch(t, key)
def fetch([], _key), do: :error
def fetch!(dict, key) do
case fetch(dict, key) do
{ :ok, value } -> value
:error -> raise(KeyError, key: key)
end
end
def pop(dict, key, default // nil) do
{ get(dict, key, default), delete(dict, key) }
end
def put(dict, key, val) do
[{key, val}|delete(dict, key)]
end
def put_new(dict, key, val) do
case has_key?(dict, key) do
true -> dict
false -> [{key, val}|dict]
end
end
def delete(dict, key)
def delete([{ key, _ }|t], key), do: t
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
def delete([], _key), do: []
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
Enum.reduce enum, dict, fn { k, v2 }, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
end
end
def split(dict, keys) do
acc = { [], [] }
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
if :lists.member(k, keys) do
{ [{k, v}|take], drop }
else
{ take, [{k, v}|drop] }
end
end
{Enum.reverse(take), Enum.reverse(drop)}
end
def take(dict, keys) do
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
end
def drop(dict, keys) do
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
end
def update!([{key, value}|dict], key, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update!([{_, _} = e|dict], key, fun) do
[e|update!(dict, key, fun)]
end
def update!([], key, _fun) do
raise(KeyError, key: key)
end
def update([{key, value}|dict], key, _initial, fun) do
[{key, fun.(value)}|delete(dict, key)]
end
def update([{_, _} = e|dict], key, initial, fun) do
[e|update(dict, key, initial, fun)]
end
def update([], key, initial, _fun) do
[{key, initial}]
end
def empty(_dict), do: []
def equal?(dict, other) do
:lists.keysort(1, dict) === :lists.keysort(1, other)
end
def to_list(dict), do: dict
end
-704
View File
@@ -1,704 +0,0 @@
import Kernel, except: [to_string: 1]
defmodule Macro do
@moduledoc """
This module provides conveniences for working with macros.
"""
@typedoc "Abstract Syntax Tree (AST) node"
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
@doc false
defmacro binary_ops do
[ :===, :!==,
:==, :!=, :<=, :>=,
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
:<, :>, :->,
:+, :-, :*, :/, :=, :|, :.,
:and, :or, :xor, :when, :in, :inlist, :inbits,
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
end
@doc false
defmacro unary_ops do
[:!, :@, :^, :not, :+, :-, :~~~, :&]
end
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
:= -> {:right, 80}
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
o when o in [:&&, :&&&, :and] -> {:left, 140}
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
:in -> {:left, 170}
:.. -> {:left, 200}
o when o in [:+, :-] -> {:left, 210}
o when o in [:*, :/] -> {:left, 220}
o when o in [:<>] -> {:right, 230}
:^^^ -> {:left, 250}
:. -> {:left, 310}
end
end
@doc """
Breaks a pipeline expression into a list. Raises if
the pipeline is ill-formed.
"""
@spec unpipe(Macro.t) :: [Macro.t]
def unpipe({ :|> , _, [left, right] }) do
[left|unpipe(right)]
end
def unpipe(other) do
[other]
end
@doc """
Pipes the given `expr` in to the `call_expr` as the
argument in the given `position`.
"""
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
def pipe(expr, call_args, integer // 0)
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
{ call, line, List.insert_at([], integer, expr) }
end
def pipe(expr, { call, line, args }, integer) when is_list(args) do
{ call, line, List.insert_at(args, integer, expr) }
end
def pipe(expr, call_args, _integer) do
raise ArgumentError,
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
end
@doc """
Receives an expression representing a possible definition
and extracts its arguments. It returns a tuple with the
function name and the arguments list or `:error` if not
a valid call syntax.
This is useful for macros that want to provide the same
argument syntax available in def/defp/defmacro and friends.
## Examples
extract_args(quote do: foo) == { :foo, [] }
extract_args(quote do: foo()) == { :foo, [] }
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
extract_args(quote do: 1.(1, 2, 3)) == :error
"""
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
def extract_args(expr) do
:elixir_clauses.extract_args(expr)
end
@doc """
Recursively escapes a value so it can be inserted
into a syntax tree.
One may pass `unquote: true` to `escape/2`
which leaves unquote statements unescaped, effectively
unquoting the contents on escape.
## Examples
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({ :a, :b, :c })
{ :{}, [], [:a, :b, :c] }
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
1
"""
@spec escape(term) :: Macro.t
@spec escape(term, Keyword.t) :: Macro.t
def escape(expr, opts // []) do
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
end
@doc %S"""
Unescape the given chars. This is the unescaping behavior
used by default in Elixir single- and double-quoted strings.
Check `unescape_string/2` for information on how to customize
the escaping map.
In this setup, Elixir will escape the following: `\a`, `\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) which receive a raw, unescaped
string.
## Examples
iex> Macro.unescape_string("example\\n")
"example\n"
In the example above, we pass a string with `\n` escaped
and we return a version with it unescaped.
"""
@spec unescape_string(String.t) :: String.t
def unescape_string(chars) do
:elixir_interpolation.unescape_chars(chars)
end
@doc %S"""
Unescape the given chars according to the map given.
Check `unescape_string/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(?a), do: ?\a
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.
## Hex
Hexadecimals will by default be escaped unless the map function
returns false for ?x.
## Examples
Using the unescape_map defined above is easy:
Macro.unescape_string "example\\n", &unescape_map(&1)
"""
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
def unescape_string(chars, map) do
:elixir_interpolation.unescape_chars(chars, map)
end
@doc """
Unescape the given tokens according to the default map.
Check `unescape_string/1` and `unescape_string/2` for more
information about unescaping.
Only tokens that are binaries are unescaped, all others are
ignored. This function is useful when implementing your own
sigils. Check the implementation of `Kernel.sigil_b`
for examples.
"""
@spec unescape_tokens([Macro.t]) :: [Macro.t]
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 `unescape_string/2` for more information.
"""
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
def unescape_tokens(tokens, map) do
:elixir_interpolation.unescape_tokens(tokens, map)
end
@doc """
Converts the given expression to a binary.
## Examples
iex> Macro.to_string(quote do: foo.bar(1, 2, 3))
"foo.bar(1, 2, 3)"
"""
@spec to_string(Macro.t) :: String.t
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
def to_string(tree, fun // fn(_ast, string) -> string end)
# Variables
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
fun.(ast, atom_to_binary(var))
end
# Aliases
def to_string({ :__aliases__, _, refs } = ast, fun) do
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
end
# Blocks
def to_string({ :__block__, _, [expr] } = ast, fun) do
fun.(ast, to_string(expr, fun))
end
def to_string({ :__block__, _, _ } = ast, fun) do
block = adjust_new_lines block_to_string(ast, fun), "\n "
fun.(ast, "(\n " <> block <> "\n)")
end
# Bits containers
def to_string({ :<<>>, _, args } = ast, fun) do
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
end)
end
# Tuple containers
def to_string({ :{}, _, args } = ast, fun) do
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
end
# Fn keyword
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
end
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
end
def to_string({ :fn, _, [block] } = ast, fun) do
block = adjust_new_lines block_to_string(block, fun), "\n "
fun.(ast, "fn\n " <> block <> "\nend")
end
# left -> right
def to_string({ :->, _, _ } = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
end
# Binary ops
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
end
# Splat when
def to_string({ :when, _, args } = ast, fun) do
{ left, right } = :elixir_utils.split_last(args)
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
end
# Unary ops
def to_string({ :not, _, [arg] } = ast, fun) do
fun.(ast, "not " <> to_string(arg, fun))
end
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
end
# Access
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string(right, fun))
end
# All other calls
def to_string({ target, _, args } = ast, fun) when is_list(args) do
{ list, last } = :elixir_utils.split_last(args)
fun.(ast, case is_kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
# Two-item tuples
def to_string({ left, right }, fun) do
to_string({ :{}, [], [left, right] }, fun)
end
# Lists
def to_string(list, fun) when is_list(list) do
if Keyword.keyword?(list) do
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
else
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
end
end
# All other structures
def to_string(other, fun), do: fun.(other, 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 module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
defp call_to_string(other, fun), do: to_string(other, fun)
defp call_to_string_with_args(target, args, fun) do
{ list, last } = :elixir_utils.split_last(args)
target = call_to_string(target, fun)
case last != [] and Keyword.keyword?(last) do
true ->
args = Enum.map_join(list, ", ", &to_string(&1, fun))
if list != [], do: args = args <> ", "
args = args <> kw_list_to_string(last, fun)
target <> "(" <> args <> ")"
false ->
args = Enum.map_join(args, ", ", &to_string(&1, fun))
target <> "(" <> args <> ")"
end
end
defp kw_blocks_to_string(kw, fun) do
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
false -> acc
end
end) <> "end"
end
defp kw_block_to_string(key, value, fun) do
block = adjust_new_lines block_to_string(value, fun), "\n "
atom_to_binary(key) <> "\n " <> block <> "\n"
end
defp block_to_string({ :->, _, exprs }, fun) do
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, fun, false)
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
end)
end
defp block_to_string({ :__block__, _, exprs }, fun) do
Enum.map_join(exprs, "\n", &to_string(&1, fun))
end
defp block_to_string(other, fun), do: to_string(other, fun)
defp kw_list_to_string(list, fun) do
Enum.map_join(list, ", ", fn { key, value } ->
atom_to_binary(key) <> ": " <> to_string(value, fun)
end)
end
defp parenthise(expr, fun) do
"(" <> to_string(expr, fun) <> ")"
end
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
{ _, prec } = binary_op_props(op)
cond do
parent_prec < prec -> to_string(expr, fun)
parent_prec > prec -> parenthise(expr, fun)
true ->
# parent_prec == prec, so look at associativity.
if parent_assoc == side do
to_string(expr, fun)
else
parenthise(expr, fun)
end
end
end
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
left = comma_join_or_empty_paren(left, fun, paren)
left <> "-> " <> to_string(right, fun)
end)
end
defp comma_join_or_empty_paren([], _fun, true), do: "() "
defp comma_join_or_empty_paren([], _fun, false), do: ""
defp comma_join_or_empty_paren(left, fun, _) do
Enum.map_join(left, ", ", &to_string(&1, fun)) <> " "
end
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 a AST node and expands it once. The following contents are expanded:
* Macros (local or remote);
* Aliases are expanded (if possible) and return atoms;
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
* Module attributes reader (`@foo`);
In case the expression cannot be expanded, it returns the expression
itself. Notice that `expand_once/2` performs the expansion just
once and it is not recursive. Check `expand/2` for expansion
until the node no longer represents a macro and `expand_all/2`
for recursive expansion.
## 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__, [], [: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_once(ast, env) do
elem(expand_once(ast, env, nil), 0)
end
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
receiver when is_atom(receiver) ->
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
aliases ->
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
case :lists.all(&is_atom/1, aliases) do
true ->
receiver = :elixir_aliases.concat(aliases)
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
{ receiver, true, cache }
false -> { original, false, cache }
end
end
end
# Expand @ calls
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
case (module = env.module) && Module.open?(module) do
true -> { Module.get_attribute(module, name), true, cache }
false -> { original, false, cache }
end
end
# Expand pseudo-variables
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
do: { env.module, true, cache }
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
do: { env.file, true, cache }
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
do: { :filename.dirname(env.file), true, cache }
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
do: { env, true, cache }
# Expand possible macro import invocation
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
args = case is_atom(args) do
true -> []
false -> args
end
case not is_partial?(args) do
false -> { original, false, cache }
true ->
module = env.module
extra = if function_exported?(module, :__info__, 1) do
[{ module, module.__info__(:macros) }]
else
[]
end
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
env.module, extra, cache)
case expand do
{ :ok, _, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
end
end
end
# Expand possible macro require invocation
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
{ receiver, _, _ } = expand_once(left, env, cache)
case is_atom(receiver) and not is_partial?(args) do
false -> { original, false, cache }
true ->
cache = to_erl_env(env, cache)
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
args, env.module, cache)
case expand do
{ :ok, _receiver, expanded } -> { expanded, true, cache }
{ :error, _ } -> { original, false, cache }
end
end
end
# Anything else is just returned
defp expand_once(other, _env, cache), do: { other, false, cache }
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
defp to_erl_env(_env, cache), do: cache
defp is_partial?(args) do
:lists.any(&match?({ :&, _, [_] }, &1), args)
end
@doc """
Receives a AST node and expands it until it no longer represents
a macro. Check `expand_once/2` for more information on how
expansion works and `expand_all/2` for recursive expansion.
"""
def expand(tree, env) do
elem(expand(tree, env, nil), 0)
end
@doc false # Used internally by Elixir
def expand(tree, env, cache) do
expand_until({ tree, true, cache }, env)
end
defp expand_until({ tree, true, cache }, env) do
expand_until(expand_once(tree, env, cache), env)
end
defp expand_until({ tree, false, cache }, _env) do
{ tree, cache }
end
@doc false
def expand_all(tree, env) do
IO.write "Macro.expand_all/2 is deprecated, please avoid recursive code expansion\n#{Exception.format_stacktrace}"
elem(expand_all(tree, env, nil), 0)
end
@doc false # Used internally by Elixir
def expand_all(tree, env, cache) do
expand_all_until(expand(tree, env, cache), env)
end
defp expand_all_until({ { left, meta, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, meta, right }, cache }
end
defp expand_all_until({ { left, right }, cache }, env) do
{ left, cache } = expand_all(left, env, cache)
{ right, cache } = expand_all(right, env, cache)
{ { left, right }, cache }
end
defp expand_all_until({ list, cache }, env) when is_list(list) do
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
end
defp expand_all_until({ other, cache }, _env) do
{ other, cache }
end
@doc """
Recurs the quoted expression checking if all sub-terms are
safe (i.e. they represent data structures 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
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
do_safe_term(terms)
end
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
do_safe_term(term)
end
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
defp do_safe_term(_), do: nil
end
-84
View File
@@ -1,84 +0,0 @@
defmodule Macro.Env do
@moduledoc """
A record that holds compile time environment information.
The current environment can be accessed at any time as
`__ENV__`. Inside macros, the caller environment can be
accessed as `__CALLER__`. It contains the following fields:
* `module` - the current module name.
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - 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
* `aliases` - a list of two item tuples, where the first
item is the aliased name and the second the actual name
* `context` - the context of the environment. It can be nil
(default context), inside a guard or inside an assign
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
* `macros` - a list of macros imported from each module
* `context_modules` - a list of modules defined in the current context
* `macro_aliases` - a list of aliases defined inside the current macro
* `vars` - a list keeping all defined varaibles as { var, context }
"""
@type name_arity :: { atom, non_neg_integer }
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{ module, module }]
@type context :: :match | :guard | nil
@type requires :: [module]
@type functions :: [{ module, [name_arity] }]
@type macros :: [{ module, [name_arity] }]
@type context_modules :: [module]
@type vars :: [{ atom, atom }]
@type lexical_tracker :: pid
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
types = quote do: [module: module, file: file, line: line,
function: name_arity, aliases: aliases, requires: requires,
functions: functions, macros: macros, context_modules: context_modules,
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
Record.deffunctions(fields, __MODULE__)
Record.deftypes(fields, types, __MODULE__)
@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) == :match
@doc """
Returns the environment stacktrace.
"""
def stacktrace(record) do
cond do
nil?(record.module) ->
[{ :elixir_compiler, :__FILE__, 2, location(record) }]
nil?(record.function) ->
[{ module(record), :__MODULE__, 0, location(record) }]
true ->
{ name, arity } = record.function
[{ module(record), name, arity, location(record) }]
end
end
end
-930
View File
@@ -1,930 +0,0 @@
defmodule Module do
defmacrop is_env(env) do
quote do
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
end
end
@moduledoc %S'''
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 a 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.
## Module attributes
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take two arguments: the module environment and its bytecode.
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
### Example
defmodule M do
@after_compile __MODULE__
def __after_compile__(env, _bytecode) do
IO.inspect env
end
end
* `@before_compile`
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
### Example
defmodule M do
@before_compile __MODULE__
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
end
end
end
* `@behaviour` (notice the british spelling)
Specify an OTP or user-defined behaviour.
### Example
defmodule M do
@behaviour gen_event
# ...
end
* `@compile`
Define options for module compilation that are passed to the Erlang
compiler.
Accepts an atom, a tuple, or a list of atoms and tuples.
See http://www.erlang.org/doc/man/compile.html for the list of supported
options.
### Example
defmodule M do
@compile { :inline, myfun: 1 }
def myfun(arg) do
to_string(arg)
end
end
* `@doc`
Provide documentation for the function or macro that follows the
attribute.
Accepts a string (often a heredoc) or `false` where `@doc false` will
make the function/macro invisible to the documentation extraction tools
like ExDoc.
Can be invoked more than once.
### Example
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sum.
"""
def sum(a, b) do
a + b
end
end
* `@file`
Change the filename used in stacktraces for the function or macro that
follows the attribute.
Accepts a string. Can be used more than once.
### Example
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
* `@moduledoc`
Provide documentation for the current module.
Accepts a string (which is often a heredoc) or `false` where
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
### Example
defmodule M do
@moduledoc """
A very useful module
"""
end
* `@on_definition`
A hook that will be invoked after each function or macro in the current
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
must take 6 arguments:
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
Note that you can\'t provide the current module to `@on_definition`
because the hook function will not be defined in time. Finally, since
the `on_definition` hook is executed inside the context of the defined
function (i.e. `env.function` returns the current function), the hook
can only be a function, not a macro.
### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_string(body)
end
end
defmodule M do
@on_definition { H, :on_def }
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
end
def hello(_) do
:ok
end
end
* `@on_load`
A hook that will be invoked whenever the module is loaded.
Accepts a function atom of a function in the current module. The function
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
### Example
defmodule M do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
end
end
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
### Example
defmodule M do
@vsn "1.0"
end
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for the behavior callback
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
`@` and followed by a valid Elixir value:
defmodule M do
@custom_attr [some: "stuff"]
end
For more advanced options available when defining custom attributes, see
`register_attribute/3`.
## Runtime information about a module
It is possible to query a module at runtime to find out which functions and
macros it defines, extract its docstrings, etc. See `__info__/1`.
'''
@doc """
Provides runtime information about functions and macros defined by the
module, enables docstring extraction, etc.
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:docs` - list of all docstrings attached to functions and macros
using the `@doc` attribute
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
which module definition starts and `doc` is the string
attached to the module using the `@moduledoc` attribute
* `:module` - module name (`Module == Module.__info__(:module)`)
In addition to the above, you may also pass to `__info__/1` any atom supported
by Erlang's `module_info` function which also gets defined for each compiled
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
more information.
"""
def __info__(kind)
@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 """
Evaluates the quoted contents in the given module's context.
A list of environment options can also be given as argument.
See `Code.eval_string` for more information.
Raises an error if the module was already compiled.
## 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
For convenience, you can my pass `__ENV__` as argument and
all options will be automatically extracted from the environment:
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, [], __ENV__
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(module, quoted, binding // [], opts // [])
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
end
def eval_quoted(module, quoted, binding, env) when is_env(env) do
eval_quoted(module, quoted, binding, env.to_keywords)
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__.location)
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 `Module.create`
allows you to control the environment variables used
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts // [])
def create(module, quoted, env) when is_env(env) do
create(module, quoted, env.to_keywords)
end
def create(module, quoted, opts) when is_atom(module) do
line = Keyword.get(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
iex> Module.concat([Foo, Bar])
Foo.Bar
iex> Module.concat([Foo, "Bar"])
Foo.Bar
iex> 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
iex> Module.concat(Foo, Bar)
Foo.Bar
iex> Module.concat(Foo, "Bar")
Foo.Bar
iex> 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
iex> Module.safe_concat([Unknown, Module])
** (ArgumentError) argument error
iex> 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
iex> Module.safe_concat(Unknown, Module)
** (ArgumentError) argument error
iex> Module.safe_concat(List, Chars)
List.Chars
"""
def safe_concat(left, right) do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Gets an anonymous function from the given module, function
and arity. The module and function are not verified to exist.
iex> fun = Module.function(Kernel, :is_atom, 1)
iex> fun.(:hello)
true
"""
def function(mod, fun, arity) do
:erlang.make_fun(mod, fun, arity)
end
@doc """
Attaches documentation to a given function or type. It expects
the module the function/type belongs to, the line (a non negative
integer), the kind (def or defmacro), a tuple representing
the function and its arity, the function signature (the signature
should be omitted for types) 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)
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
if doc, do: { :error, :private_doc }, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :type, :opaque] 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 }] ->
:ets.insert(table, {
tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
if(nil?(doc), do: old_doc, else: doc)
})
:ok
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_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. See `Kernel.defoverridable` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
lc tuple inlist tuples do
case :elixir_def.lookup_definition(module, tuple) do
false ->
{ name, arity } = tuple
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
:elixir_def.delete_definition(module, tuple)
neighbours = Module.DispatchTracker.yank(module, tuple)
old = get_attribute(module, :__overridable)
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
{ count + 1, clause, neighbours, false }
end, { 1, clause, neighbours, false }, old)
put_attribute(module, :__overridable, merged)
end
end
end
@doc """
Returns true if the given tuple in module is marked as overridable.
"""
def overridable?(module, tuple) do
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
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 :lists.member(key, acc) do
case :ets.lookup(table, key) do
[{^key, old}] -> [value|old]
[] -> [value]
end
else
value
end
:ets.insert(table, { key, new })
end
@doc """
Gets the given attribute from a module. If the attribute
was marked with `accumulate` with `Module.register_attribute`,
a list is always returned.
The `@` macro compiles to a call to this function. For example,
the following code:
@foo
Expands to:
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument is used to indicate if a warning
should be emitted when the attribute was not previously defined.
This is true for `@foo` attributes but false for direct calls.
## 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, warn // false) when is_atom(key) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
if :lists.member(key, acc) do
[]
else
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
end
end
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. New attributes
are always added to the top of the accumulated list.
* `:persist` - The attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are false.
## 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) when is_atom(new) do
assert_not_compiled!(:register_attribute, module)
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, :__persisted_attributes, 2)
:ets.insert(table, { :__persisted_attributes, [new|old] })
end
if Keyword.get(opts, :accumulate) 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(String.Chars.to_string(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)
case add_doc(module, line, kind, pair, args, doc) do
:ok ->
:ok
{ :error, :private_doc } ->
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
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_typespec(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, file) when is_binary(file) do
file
end
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
:elixir_def.table(module)
end
defp docs_table_for(module) do
:elixir_module.docs_table(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
-364
View File
@@ -1,364 +0,0 @@
# This is a module Elixir responsible for tracking
# calls in order to extract Elixir modules' behaviour
# during compilation time.
#
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with one main vertice:
#
# * `:local` - points to local functions
#
# We also have can the following vertices:
#
# * `Module` - a module that was invoked via an import or remotely
# * `{ name, arity }` - a local function/arity pair
# * `{ :import, name, arity }` - an invoked function/arity import
# * `{ :remote, name, arity }` - an remotely invoked function/arity
#
# Each of those vertices can associate to other vertices
# as described below:
#
# * `Module`
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
#
# * `{ name, arity }`
# * in neighbours: `:local`, `{ name, arity }`
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
#
# * `{ :import, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# * `{ :remote, name, arity }`
# * in neighbours: `{ name, arity }`
# * out neighbours: `Module`
#
# Note that since this is required for bootstrap, we can't use
# any of the `GenServer.Behaviour` conveniences.
defmodule Module.DispatchTracker do
@moduledoc false
@timeout 30_000
@behavior :gen_server
@type ref :: pid | module
@type name :: atom
@type name_arity :: { name, arity }
@type local :: { name, arity }
@type import :: { :import, name, arity }
@type remote :: { :remote, name, arity }
# Public API
@doc """
Receives a dispatch or a module and returns all dispatches
that calls it.
In case the argument is a module, the response will be
made by import and remote dispatches.
In case the argument is another dispatch, the response
will be made by local dispatches.
This function is not recursive, so if A dispatches to
B which dispatches to C, A does not appear in the result,
only B.
"""
@spec dispatches_to(ref, module) :: [import | remote]
@spec dispatches_to(ref, local | import | remote) :: [local]
def dispatches_to(ref, dispatch) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.in_neighbours(d, dispatch) |> only_tuples
end
@doc """
Receives a local and returns all dispatches from that local.
This function is not recursive, so if A dispatches to
B which dispatches to C, C does not appear in the result,
only B.
"""
@spec dispatches_from(ref, local) :: [local | import | remote]
def dispatches_from(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
end
@doc """
Returns all imported modules that had the given
`{ name, arity }` invoked.
"""
@spec imports_with_dispatch(ref, name_arity) :: [module]
def imports_with_dispatch(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :import, name, arity })
end
@doc """
Returns all modules that had the given `{ name, arity }`
invoked remotely.
"""
@spec remotes_with_dispatch(ref, name_arity) :: [module]
def remotes_with_dispatch(ref, { name, arity }) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
:digraph.out_neighbours(d, { :remote, name, arity })
end
@doc """
Returns all locals that are reachable.
By default, all public functions are reachable.
A private function is only reachable if it has
a public function that it invokes directly.
"""
@spec reachable(ref) :: [local]
def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
reduce_reachable(d, :local, [])
end
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
remaining = :ordsets.subtract(neighbours, vertices)
vertices = :ordsets.union(neighbours, vertices)
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
end
defp to_pid(pid) when is_pid(pid), do: pid
defp to_pid(mod) when is_atom(mod) do
table = :elixir_module.data_table(mod)
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
val
end
defp only_tuples(list) do
lc x inlist list, is_tuple(x), do: x
end
# Internal API
# Starts the tracker and returns its pid.
@doc false
def start_link do
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
pid
end
# Adds a definition into the tracker. A public
# definition is connected with the :local node
# while a private one is left unreachable until
# a call is made to.
@doc false
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_definition, kind, tuple })
end
# Adds and tracks defaults for a definition into the tracker.
@doc false
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
end
# Adds a local dispatch to the given target.
def add_local(pid, to) when is_tuple(to) do
:gen_server.cast(pid, { :add_local, :local, to })
end
# Adds a local dispatch from-to the given target.
@doc false
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
:gen_server.cast(pid, { :add_local, from, to })
end
# Adds a remote dispatch to the given target.
@doc false
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
end
# Adds a import dispatch to the given target.
@doc false
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
:gen_server.cast(pid, { :add_external, :import, function, module, target })
end
# Yanks a local node. Returns its in and out vertices in a tuple.
@doc false
def yank(pid, local) do
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
end
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, { :reattach, tuple, neighbours })
end
# Collecting all conflicting imports with the given functions
@doc false
def collect_imports_conflicts(pid, all_defined) do
d = :gen_server.call(pid, :digraph, @timeout)
lc { name, arity } inlist all_defined,
n = :digraph.out_neighbours(d, { :import, name, arity }),
n != [] do
{ n, name, arity }
end
end
# Collect all unused definitions based on the private
# given also accounting the expected amount of default
# clauses a private function have.
@doc false
def collect_unused_locals(pid, private) do
reachable = reachable(pid)
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
end
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
if :lists.member(tuple, reachable) do
acc
else
[{ :unused_def, tuple, kind }|acc]
end
end
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
{ name, arity } = tuple
min = arity - default
max = arity
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
if invoked == [] do
[{ :unused_def, tuple, kind }|acc]
else
case :lists.min(invoked) - min do
0 -> acc
^default -> [{ :unused_args, tuple }|acc]
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
end
end
end
# Stops the gen server
@doc false
def stop(pid) do
:gen_server.cast(pid, :stop)
end
# Callbacks
def init([]) do
d = :digraph.new([:protected])
:digraph.add_vertex(d, :local)
{ :ok, d }
end
def handle_call({ :yank, local }, _from, d) do
in_vertices = :digraph.in_neighbours(d, local)
out_vertices = :digraph.out_neighbours(d, local)
:digraph.del_vertex(d, local)
{ :reply, { in_vertices, out_vertices }, d }
end
def handle_call(:digraph, _from, d) do
{ :reply, d, d }
end
def handle_call(_request, _from, d) do
{ :noreply, d }
end
def handle_info(_msg, d) do
{ :noreply, d }
end
def handle_cast({ :add_local, from, to }, d) do
handle_add_local(d, from, to)
{ :noreply, d }
end
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
handle_import_or_remote(d, kind, function, module, name, arity)
{ :noreply, d }
end
def handle_cast({ :add_definition, kind, tuple }, d) do
handle_add_definition(d, kind, tuple)
{ :noreply, d }
end
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
lc i inlist :lists.seq(arity - defaults, arity - 1) do
handle_add_definition(d, kind, { name, i })
handle_add_local(d, { name, i }, { name, i + 1 })
end
{ :noreply, d }
end
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
{ :noreply, d }
end
def handle_cast(:stop, d) do
{ :stop, :normal, d }
end
def handle_cast(_msg, d) do
{ :noreply, d }
end
def terminate(_reason, _d) do
:ok
end
def code_change(_old, d, _extra) do
{ :ok, d }
end
defp handle_import_or_remote(d, kind, function, module, name, arity) do
:digraph.add_vertex(d, module)
tuple = { kind, name, arity }
:digraph.add_vertex(d, tuple)
replace_edge!(d, tuple, module)
if function != nil do
replace_edge!(d, function, tuple)
end
end
defp handle_add_local(d, from, to) do
:digraph.add_vertex(d, to)
replace_edge!(d, from, to)
end
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
:digraph.add_vertex(d, tuple)
replace_edge!(d, :local, tuple)
end
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
:digraph.add_vertex(d, tuple)
end
defp replace_edge!(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$e"|_] = :digraph.add_edge(d, from, to)
end
end
defp replace_edge(d, from, to) do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
:digraph.add_edge(d, from, to)
end
end
end
-186
View File
@@ -1,186 +0,0 @@
defmodule Node do
@moduledoc """
Functions related to Erlang nodes.
"""
@type t :: atom
@doc """
Returns the current node. It returns the same as the built-in `node()`.
"""
@spec self :: t
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`.
"""
@spec alive? :: boolean
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)`.
"""
@spec list :: [t]
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.
"""
@typep list_arg :: :visible | :hidden | :connected | :this | :known
@spec list(list_arg | [list_arg]) :: [t]
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.
"""
@spec monitor(t, boolean) :: true
def monitor(node, flag) do
:erlang.monitor_node(node, flag)
end
@doc """
Behaves as `monitor/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.
"""
@spec monitor(t, boolean, [:allow_passive_connect]) :: true
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.
"""
@spec disconnect(t) :: boolean | :ignored
def disconnect(node) do
:erlang.disconnect_node(node)
end
@doc """
Establishes a connection to `node`. Returns `true` if successful,
`false` if not, and the atom `:ignored` if the local node is not
alive.
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
"""
@spec connect(t) :: boolean | :ignored
def connect(node) do
:net_kernel.connect_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-2 for
the list of available options.
"""
@spec spawn(t, (() -> any)) :: pid
def spawn(node, fun) do
:erlang.spawn(node, fun)
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-3 for
the list of available options.
"""
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
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-4 for
the list of available options.
"""
@spec spawn(t, module, atom, [any]) :: pid
def spawn(node, module, fun, args) do
:erlang.spawn(node, module, fun, args)
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-5 for
the list of available options.
"""
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
def spawn(node, module, fun, args, opts) do
:erlang.spawn_opt(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).
"""
@spec spawn_link(t, (() -> any)) :: pid
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).
"""
@spec spawn_link(t, module, atom, [any]) :: pid
def spawn_link(node, module, fun, args) do
:erlang.spawn_link(node, module, fun, args)
end
@doc """
Sets the magic cookie of `node` to the atom `cookie`. The default node
is `Node.self`, the local node. If `node` is the local node, the function also
sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
:erlang.set_cookie(node, cookie)
end
@doc """
Returns the magic cookie of the local node, if the node is alive;
otherwise `:nocookie`.
"""
def get_cookie() do
:erlang.get_cookie()
end
end
-227
View File
@@ -1,227 +0,0 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line arguments.
"""
@doc """
Parses `argv` and returns a tuple with the parsed options, its
arguments, and a list options that couldn't be parsed.
## Examples
iex> OptionParser.parse(["--debug"])
{ [debug: true], [], [] }
iex> OptionParser.parse(["--source", "lib"])
{ [source: "lib"], [], [] }
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
Notice how Elixir automatically translates the "--source-path"
switch to the underscored atom `:source_path`, which better follows
Elixir conventions.
## Aliases
A set of aliases can be given as the second argument:
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
{ [debug: true], [], [] }
## Switches
Extra information about switches can be given as arguments, too.
This is useful when a switch must behave as a boolean
or if duplicated switches should be kept, overriden or accumulated.
The following types are supported:
* `:boolean` - Marks the given switch as a boolean. Boolean switches
never consume the following value unless it is
`true` or `false`;
* `:integer` - Parses the switch as an integer;
* `:float` - Parses the switch as a float;
If a switch can't be parsed, the option is returned in the invalid
options list (third element of the returned tuple).
The following extra options are supported:
* `:keep` - Keeps duplicated items in the list instead of overriding;
Examples:
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
{ [unlock: true], ["path/to/file"], [] }
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
...> switches: [unlock: :boolean, limit: :integer])
{ [unlock: true, limit: 0], ["path/to/file"], [] }
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
{ [limit: 3], [], [] }
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
{ [], [], [limit: "yyz"] }
## Negation switches
Any switches starting with `--no-` are always considered to be
booleans and never parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{ [no_op: true], ["path/to/file"], [] }
In case the negated switch exists as a boolean, it sets the boolean to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{ [op: false], ["path/to/file"], [] }
"""
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
parse(argv, opts, true)
end
@doc """
Similar to `parse/2` but only parses the head of `argv`;
as soon as it finds a non-switch, it stops parsing.
See `parse/2` for more information.
## Example
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
"""
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
parse(argv, opts, false)
end
## Helpers
defp parse(argv, opts, bool) do
aliases = opts[:aliases] || []
switches = opts[:switches] || []
parse(argv, aliases, switches, bool)
end
defp parse(argv, aliases, switches, all) do
parse(argv, aliases, switches, [], [], [], all)
end
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
{ option, kinds, value } = normalize_option(option, switches, aliases)
if nil?(value) do
{ value, t } =
if :boolean in kinds do
{ true, t }
else
value_from_tail(t)
end
end
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
parse(t, aliases, switches, dict, args, invalid, all)
end
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
parse(t, aliases, switches, dict, [h|args], invalid, true)
end
defp parse([], _, _switches, dict, args, invalid, true) do
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
end
defp parse(value, _, _switches, dict, _args, invalid, false) do
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
end
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, invalid, option, value, kinds) do
{ invalid_option, value } =
cond do
:boolean in kinds ->
{ nil, value in [true, "true"] }
:integer in kinds ->
case Integer.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
:float in kinds ->
case Float.parse(value) do
{ value, "" } -> { nil, value }
_ -> { option, value }
end
true ->
{ nil, value }
end
if invalid_option do
{ dict, [{ option, value }|invalid] }
else
{ do_store_option(dict, option, value, kinds), invalid }
end
end
defp do_store_option(dict, option, value, kinds) do
cond do
:keep in kinds ->
[{ option, value }|dict]
true ->
[{ option, value }|Keyword.delete(dict, option)]
end
end
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
normalize_option(option, switches, aliases)
end
defp normalize_option(option, switches, aliases) do
{ option, value } = split_option(option)
if non_neg = get_non_negated(option, aliases) do
kinds = List.wrap(switches[non_neg])
if :boolean in kinds do
{ non_neg, kinds, false }
else
{ get_aliased(option, aliases), [:boolean], true }
end
else
atom = get_aliased(option, aliases)
{ atom, List.wrap(switches[atom]), value }
end
end
defp split_option(option) do
case :binary.split(option, "=") do
[h] -> { h, nil }
[h, t] -> { h, t }
end
end
defp to_underscore(option) do
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
end
defp get_aliased(option, aliases) do
atom = option |> to_underscore |> binary_to_atom
aliases[atom] || atom
end
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
defp get_non_negated(_, _), do: nil
end
-551
View File
@@ -1,551 +0,0 @@
defmodule Path do
@moduledoc """
This module provides conveniences for manipulating or
retrieving file system paths.
The functions in this module may receive a char list or
a binary as an argument and will return a value of the same
type.
The majority of the functions in this module do not
interact with the file system, except for a few functions
that require it (like `wildcard/1` and `expand/1`).
"""
alias :filename, as: FN
@type t :: char_list | atom | binary
@type r :: char_list | binary
@doc """
Converts the given path to an absolute one. Unlike
`expand/1`, no attempt is made to resolve `..`, `.` or `~`.
## Unix examples
Path.absname("foo")
#=> "/usr/local/foo"
Path.absname("../x")
#=> "/usr/local/../x"
## Windows
Path.absname("foo").
"D:/usr/local/foo"
Path.absname("../x").
"D:/usr/local/../x"
"""
def absname(path) do
FN.absname(path, get_cwd(path))
end
@doc """
Builds a path from `relative_to` to `path`. If `path` is already
an absolute path, `relative_to` is ignored. See also `relative_to/2`.
Unlike `expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
## Examples
iex> Path.absname("foo", "bar")
"bar/foo"
iex> Path.absname("../x", "bar")
"bar/../x"
"""
def absname(path, relative_to) do
FN.absname(path, relative_to)
end
@doc """
Converts the path to an absolute one and expands
any `.` and `..` characters and a leading `~`.
## Examples
Path.expand("/foo/bar/../bar")
"/foo/bar"
"""
def expand(path) do
normalize FN.absname(expand_home(path), get_cwd(path))
end
@doc """
Expands the path relative to the path given as the second argument
expanding any `.` and `..` characters. If the path is already an
absolute path, `relative_to` is ignored.
Note, that this function treats `path` with a leading `~` as
an absolute one.
The second argument is first expanded to an absolute path.
## Examples
# Assuming that the absolute path to baz is /quux/baz
Path.expand("foo/bar/../bar", "baz")
#=> "/quux/baz/foo/bar"
Path.expand("foo/bar/../bar", "/baz")
"/baz/foo/bar"
Path.expand("/foo/bar/../bar", "/baz")
"/foo/bar"
"""
def expand(path, relative_to) do
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
end
@doc """
Returns the path type.
## Unix examples
Path.type("/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("../usr/local/bin") #=> :relative
Path.type("~/file") #=> :relative
## Windows examples
Path.type("D:/usr/local/bin") #=> :absolute
Path.type("usr/local/bin") #=> :relative
Path.type("D:bar.ex") #=> :volumerelative
Path.type("/bar/foo.ex") #=> :volumerelative
"""
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
end
@doc """
Forces the path to be a relative path.
## Unix examples
Path.relative("/usr/local/bin") #=> "usr/local/bin"
Path.relative("usr/local/bin") #=> "usr/local/bin"
Path.relative("../usr/local/bin") #=> "../usr/local/bin"
## Windows examples
Path.relative("D:/usr/local/bin") #=> "usr/local/bin"
Path.relative("usr/local/bin") #=> "usr/local/bin"
Path.relative("D:bar.ex") #=> "bar.ex"
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
"""
def relative(name) do
case :os.type() do
{ :win32, _ } -> win32_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(1)
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
{ :absolute, relative }
defp unix_pathtype([?/|relative]), do:
{ :absolute, relative }
defp unix_pathtype([list|rest]) when is_list(list), do:
unix_pathtype(list ++ rest)
defp unix_pathtype([atom|rest]) when is_atom(atom), do:
unix_pathtype(atom_to_list(atom) ++ rest)
defp unix_pathtype(relative), do:
{ :relative, relative }
@slash [?/, ?\\]
defp win32_pathtype([list|rest]) when is_list(list), do:
win32_pathtype(list++rest)
defp win32_pathtype([atom|rest]) when is_atom(atom), do:
win32_pathtype(atom_to_list(atom)++rest)
defp win32_pathtype([char, list|rest]) when is_list(list), do:
win32_pathtype([char|list++rest])
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
{ :volumerelative, relative }
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
{ :absolute, relative }
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
{ :volumerelative, relative }
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
{ :absolute, relative }
defp win32_pathtype([c | relative]) when c in @slash, do:
{ :volumerelative, relative }
defp win32_pathtype([c1, c2, list|rest]) when is_list(list), do:
win32_pathtype([c1, c2|list++rest])
defp win32_pathtype([_letter, ?:, c | relative]) when c in @slash, do:
{ :absolute, relative }
defp win32_pathtype([_letter, ?: | relative]), do:
{ :volumerelative, relative }
defp win32_pathtype(relative), do:
{ :relative, relative }
@doc """
Returns the given `path` relative to the given `from` path.
In other words, it tries to strip the `from` prefix from `path`.
This function does not query the file system, so it assumes
no symlinks in between the paths.
In case a direct relative path cannot be found, it returns
the original path.
## Examples
iex> Path.relative_to("/usr/local/foo", "/usr/local")
"foo"
iex> Path.relative_to("/usr/local/foo", "/")
"usr/local/foo"
iex> Path.relative_to("/usr/local/foo", "/etc")
"/usr/local/foo"
"""
def relative_to(path, from) when is_list(path) and is_binary(from) do
path = filename_string_to_binary(path)
relative_to(FN.split(path), FN.split(from), path)
end
def relative_to(path, from) when is_binary(path) and is_list(from) do
relative_to(FN.split(path), FN.split(filename_string_to_binary(from)), path)
end
def relative_to(path, from) do
relative_to(FN.split(path), FN.split(from), path)
end
defp relative_to([h|t1], [h|t2], original) do
relative_to(t1, t2, original)
end
defp relative_to([_|_] = l1, [], _original) do
FN.join(l1)
end
defp relative_to(_, _, original) do
original
end
@doc """
Convenience to get the path relative to the current working
directory. If, for some reason, the current working directory
cannot be retrieved, returns the full path.
"""
def relative_to_cwd(path) do
case :file.get_cwd do
{ :ok, base } -> relative_to(path, base)
_ -> path
end
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
## Examples
iex> Path.basename("foo")
"foo"
iex> Path.basename("foo/bar")
"bar"
iex> Path.basename("/")
""
"""
def basename(path) do
FN.basename(path)
end
@doc """
Returns the last component of `path` with the `extension`
stripped. This function should be used to remove a specific
extension which may, or may not, be there.
## Examples
iex> Path.basename("~/foo/bar.ex", ".ex")
"bar"
iex> Path.basename("~/foo/bar.exs", ".ex")
"bar.exs"
iex> Path.basename("~/foo/bar.old.ex", ".ex")
"bar.old"
"""
def basename(path, extension) do
FN.basename(path, extension)
end
@doc """
Returns the directory component of `path`.
## Examples
Path.dirname("/foo/bar.ex")
#=> "/foo"
Path.dirname("/foo/bar/baz.ex")
#=> "/foo/bar"
"""
def dirname(path) do
FN.dirname(path)
end
@doc """
Returns the extension of the last component of `path`.
## Examples
iex> Path.extname("foo.erl")
".erl"
iex> Path.extname("~/foo/bar")
""
"""
def extname(path) do
FN.extension(path)
end
@doc """
Returns the `path` with the `extension` stripped.
## Examples
iex> Path.rootname("/foo/bar")
"/foo/bar"
iex> Path.rootname("/foo/bar.ex")
"/foo/bar"
"""
def rootname(path) do
FN.rootname(path)
end
@doc """
Returns the `path` with the `extension` stripped. This function should be used to
remove a specific extension which might, or might not, be there.
## Examples
iex> Path.rootname("/foo/bar.erl", ".erl")
"/foo/bar"
iex> Path.rootname("/foo/bar.erl", ".ex")
"/foo/bar.erl"
"""
def rootname(path, extension) do
FN.rootname(path, extension)
end
@doc """
Returns a string with one or more path components joined by the path separator.
This function should be used to convert a list of strings to a path.
## Examples
iex> Path.join(["~", "foo"])
"~/foo"
iex> Path.join(["foo"])
"foo"
iex> Path.join(["/", "foo", "bar"])
"/foo/bar"
"""
def join([name1, name2|rest]), do:
join([join(name1, name2)|rest])
def join([name]) when is_list(name), do:
binary_to_filename_string(do_join(filename_string_to_binary(name), <<>>, [], major_os_type()))
def join([name]) when is_binary(name), do:
do_join(name, <<>>, [], major_os_type())
@doc """
Joins two paths.
## Examples
iex> Path.join("foo", "bar")
"foo/bar"
"""
def join(left, right) when is_binary(left) and is_binary(right), do:
do_join(left, Path.relative(right), [], major_os_type())
def join(left, right) when is_binary(left) and is_list(right), do:
join(left, filename_string_to_binary(right))
def join(left, right) when is_list(left) and is_binary(right), do:
join(filename_string_to_binary(left), right)
def join(left, right) when is_list(left) and is_list(right), do:
binary_to_filename_string join(filename_string_to_binary(left), filename_string_to_binary(right))
def join(left, right) when is_atom(left), do:
join(atom_to_binary(left), right)
def join(left, right) when is_atom(right), do:
join(left, atom_to_binary(right))
defp major_os_type do
:os.type |> elem(0)
end
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
do_join(rest, relativename, [?/|result], os_type)
defp do_join(<<>>, <<>>, result, os_type), do:
iolist_to_binary(maybe_remove_dirsep(result, os_type))
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
do_join(relativename, <<>>, [?:|rest], :win32)
defp do_join(<<>>, relativename, [?/|result], os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<>>, relativename, result, os_type), do:
do_join(relativename, <<>>, [?/|result], os_type)
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
do_join(rest, relativename, [char|result], os_type)
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
[letter, ?:, ?/]
defp maybe_remove_dirsep([?/], _), do:
[?/]
defp maybe_remove_dirsep([?/|name], _), do:
:lists.reverse(name)
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
## Examples
iex> Path.split("")
[]
iex> Path.split("foo")
["foo"]
iex> Path.split("/foo/bar")
["/", "foo", "bar"]
"""
# Work around a bug in Erlang on UNIX
def split(""), do: []
def split(path) do
FN.split(path)
end
@doc """
Traverses paths according to the given `glob` expression.
The wildcard looks like an ordinary path, except that certain
"wildcard characters" are interpreted in a special way. The
following characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
Other characters represent themselves. Only paths that have
exactly the same character in the same position will match. Note
that matching is case-sensitive; i.e. "a" will not match "A".
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
inside the `ebin` directory of each project as follows:
Path.wildcard("projects/*/ebin/**/*.beam")
If you want to search for both `.beam` and `.app` files, you could do:
Path.wildcard("projects/*/ebin/**/*.{beam,app}")
"""
def wildcard(glob) when is_binary(glob) do
paths = :filelib.wildcard binary_to_filename_string(glob)
encoding = :file.native_name_encoding()
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
end
def wildcard(glob) when is_list(glob) do
:filelib.wildcard glob
end
## Helpers
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
defp get_cwd(_), do: System.cwd!
defp binary_to_filename_string(binary) do
case :unicode.characters_to_list(binary) do
{ :error, _, _ } ->
:erlang.error(:badarg)
list when is_list(list) ->
list
end
end
defp filename_string_to_binary(list) do
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
end
defp flatten_filename_to_binary(list, encoding) do
case :unicode.characters_to_binary(list, :unicode, encoding) do
{ :error, _, _ } ->
:erlang.error(:badarg)
bin when is_binary(bin) ->
bin
end
end
# Normalize the given path by expanding "..", "." and "~".
defp expand_home(<<?~, rest :: binary>>) do
System.user_home! <> rest
end
defp expand_home('~' ++ rest) do
(System.user_home! |> binary_to_filename_string) ++ rest
end
defp expand_home(other), do: other
defp normalize(path), do: normalize(FN.split(path), [])
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
normalize t, acc
end
defp normalize([top|t], acc) when top in [".", '.'] do
normalize t, acc
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp normalize([], acc) do
join Enum.reverse(acc)
end
end
-68
View File
@@ -1,68 +0,0 @@
defmodule Port do
@moduledoc """
Functions related to Erlang ports.
"""
@doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
"""
def open(name, settings) do
:erlang.open_port(name, settings)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
"""
def close(port) do
:erlang.port_close(port)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
"""
def command(port, data, options // []) do
:erlang.port_command(port, data, options)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
"""
def connect(port, pid) do
:erlang.port_connect(port, pid)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
"""
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
"""
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
"""
def info(port) do
:erlang.port_info(port)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
"""
def info(port, item) do
:erlang.port_info(port, item)
end
@doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0.
"""
def list do
:erlang.ports
end
end
-375
View File
@@ -1,375 +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.
"""
@spec alive?(pid) :: boolean
def alive?(pid) do
:erlang.is_process_alive(pid)
end
@doc """
Returns all key-values in the dictionary.
"""
@spec get :: [{term, term}]
def get do
:erlang.get()
end
@doc """
Returns the value for the given key.
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
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`.
"""
@spec get_keys(term) :: [term]
def get_keys(value) do
:erlang.get_keys(value)
end
@doc """
Stores the given key-value in the process dictionary.
"""
@spec put(term, term) :: term | nil
def put(key, value) do
nillify :erlang.put(key, value)
end
@doc """
Deletes all items in the dictionary.
"""
@spec delete :: [{term, term}]
def delete() do
:erlang.erase()
end
@doc """
Deletes the given key from the dictionary.
"""
@spec delete(term) :: term | nil
def delete(key) do
nillify :erlang.erase(key)
end
@doc """
Sends an exit signal with the given reason to the pid.
The following behavior applies if reason is any term except `:normal` or `:kill`:
1) If pid is not trapping exits, pid will exit 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)
"""
@spec exit(pid, term) :: true
def exit(pid, reason) do
:erlang.exit(pid, reason)
end
@doc """
Returns the pid (process identifier) of the calling process.
"""
@spec self() :: pid
def self() do
:erlang.self()
end
@doc """
Returns the pid of a new process started by the application of `fun`.
It behaves exactly the same as `Kernel.spawn/1`.
"""
@spec spawn((() -> any)) :: pid
def spawn(fun) do
:erlang.spawn(fun)
end
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
{:fullsweep_after, non_neg_integer} |
{:min_heap_size, non_neg_integer} |
{:min_bin_vheap_size, non_neg_integer}
@type spawn_opts :: [spawn_opt]
@doc """
Returns the pid of a new process started by the application of `fun`.
It also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
"""
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
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.
"""
@spec spawn(module, atom, [any]) :: pid
def spawn(mod, fun, args) do
:erlang.spawn(mod, fun, args)
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 also accepts extra options, for the list of available options
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
"""
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
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.
"""
@spec spawn_link((() -> any)) :: pid
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.
"""
@spec spawn_link(module, atom, [any]) :: pid
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.
"""
@spec spawn_monitor((() -> any)) :: {pid, reference}
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.
"""
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
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.
"""
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
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.
"""
@spec demonitor(reference) :: true
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
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.
"""
@spec list :: [pid]
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.
"""
@spec link(pid | port) :: true
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.
"""
@spec unlink(pid | port) :: true
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.
"""
@spec register(pid | port, atom) :: true
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.
"""
@spec unregister(atom) :: true
def unregister(name) do
:erlang.unregister(name)
end
@doc """
Returns the pid or port identifier with the registered name.
Returns nil if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@spec whereis(atom) :: pid | port | nil
def whereis(name) do
nillify :erlang.whereis(name)
end
@doc """
Returns the pid of the group leader for the process which evaluates the function.
"""
@spec group_leader :: pid
def group_leader do
:erlang.group_leader
end
@doc """
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
"""
@spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do
:erlang.group_leader(leader, pid)
end
@doc """
Returns a list of names which have been registered using register/2.
"""
@spec registered :: [atom]
def registered do
:erlang.registered()
end
@typep process_flag :: :trap_exit | :error_handler | :min_heap_size |
:min_bin_vheap_size | :priority | :save_calls |
:sensitive
@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.
"""
@spec flag(process_flag, term) :: term
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.
"""
@spec flag(pid, process_flag, term) :: term
def flag(pid, flag, value) do
:erlang.process_flag(pid, flag, value)
end
@doc """
Returns information about the process identified by pid or nil if the process
is not alive.
Use this only for debugging information.
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
"""
@spec info(pid) :: Keyword.t
def info(pid) do
nillify :erlang.process_info(pid)
end
@doc """
Returns information about the process identified by pid
or nil if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
@spec info(pid, atom) :: {atom, term}
def info(pid, spec) do
nillify :erlang.process_info(pid, spec)
end
@compile { :inline, nillify: 1 }
defp nillify(:undefined), do: nil
defp nillify(other), do: other
end
-298
View File
@@ -1,298 +0,0 @@
defmodule Protocol do
@moduledoc false
# Callback for defprotocol.
@doc false
def defprotocol(name, [do: block]) do
quote do
defmodule unquote(name) do
# We don't allow function definition inside protocols
import Kernel, except: [
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
defp: 1, defp: 2, def: 1, def: 2
]
# Import the new dsl that holds the new def
import Protocol.DSL, only: :macros
# Compile with debug info for consolidation
@compile :debug_info
# Set up a clear slate to store defined functions
@functions []
@fallback_to_any false
# Deprecated
@only nil
@except nil
# Invoke the user given block
unquote(block)
# Finalize expansion
unquote(after_defprotocol)
end
end
end
defp after_defprotocol do
quote unquote: false do
msg = "is deprecated and should be removed. " <>
"Note if you want to fallback to Any, you have to set @fallback_to_any true"
if @only do
IO.puts "warning: @only in protocol #{inspect __MODULE__} " <> msg
@fallback_to_any @fallback_to_any || Any in @only
end
if @except do
IO.puts "warning: @except in protocol #{inspect __MODULE__} " <> msg
@fallback_to_any @fallback_to_any || not(Any in @except)
end
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
@spec impl_for(term) :: module | nil
Kernel.def impl_for(data)
lc { guard, body } inlist bodies do
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
end
@spec impl_for!(term) :: module | no_return
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
# Handle special Record type
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
# Handle special Any type
if @fallback_to_any do
Kernel.defp any_impl_for do
try do
__MODULE__.Any.__impl__(:name)
catch
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
nil
end
end
else
Kernel.defp any_impl_for, do: nil
end
# Inline both helpers
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: term
end
# Store information as an attribute so it
# can be read without loading the module.
Module.register_attribute(__MODULE__, :protocol, persist: true)
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
# Callback for defimpl.
@doc false
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)
@protocol unquote(protocol)
@for unquote(for)
unquote(block)
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
def __impl__(:name), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
end
end
end
# Check if the given module is a protocol. Raises an error
# if not loaded or not a protocol.
@doc false
def assert_protocol(module) do
case Code.ensure_compiled(module) do
{ :module, ^module } -> nil
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
end
try do
module.__protocol__(:name)
rescue
UndefinedFunctionError ->
raise ArgumentError, message: "#{inspect module} is not a protocol"
end
end
# Builtin types.
@doc false
def builtin do
[ Tuple, Atom, List, BitString, Integer, Float,
Function, PID, Port, Reference, Any ]
end
# Implements the function that detects the protocol and
# returns the module to dispatch to.
@doc false
def impl_for(current) do
arg = quote(do: arg)
all = [Record|builtin]
{ arg,
lc(mod inlist all, do: impl_for(current, mod, arg)),
rec_impl_for(current, arg) }
end
defp rec_impl_for(current, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
quote do
target = Module.concat(unquote(current), unquote(arg))
try do
target.__impl__(:name)
catch
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
unquote(fallback)
end
end
end
defp impl_for(current, Record, arg) do
fallback = impl_for(current, Tuple, arg) |> elem(1)
dispatch = quote do
atom = :erlang.element(1, unquote(arg))
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
true -> rec_impl_for(atom)
false -> unquote(fallback)
end
end
quote do
{ is_record(unquote(arg)), unquote(dispatch) }
end
end
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, Any, arg)
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, Any, arg)
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, Any, arg)
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, Any, arg)
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, Any, arg)
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, Any, arg)
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, Any, arg)
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, Any, arg)
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, Any, arg)
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, Any, arg)
defp impl_for(_current, Any, _arg) do
{ true, quote(do: any_impl_for) }
end
# Defines an implementation with fallback to the given module.
defp impl_with_fallback(mod, guard, current, fallback, arg) do
quote do
{ unquote(guard)(unquote(arg)),
unquote(with_fallback(Module.concat(current, mod), current, fallback, arg)) }
end
end
# Tries to dispatch to a given target, fallbacks to the
# given `fallback` implementation if the target does not exist.
defp with_fallback(target, current, fallback, arg) when is_atom(target) do
quote do
try do
unquote(target).__impl__(:name)
catch
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
unquote(impl_for(current, fallback, arg) |> elem(1))
end
end
end
end
defmodule Protocol.DSL do
@moduledoc false
@doc false
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
raise ArgumentError, message: "protocol functions expect at least one argument"
end
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
arity = length(args)
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
type_args = [quote(do: t) | type_args]
call_args = lc i inlist :lists.seq(2, arity),
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
call_args = [quote(do: t) | call_args]
quote do
name = unquote(name)
arity = unquote(arity)
@functions [{name, arity}|@functions]
# Generate a fake definition with the user
# signature that will be used by docs
Kernel.def unquote(name)(unquote_splicing(args))
# Generate the actual implementation
Kernel.def unquote(name)(unquote_splicing(call_args)) do
impl_for!(t).unquote(name)(unquote_splicing(call_args))
end
# Convert the spec to callback if possible,
# otherwise generate a dummy callback
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
@callback unquote(name)(unquote_splicing(type_args)) :: term
end
end
defmacro def(_) do
raise ArgumentError, message: "invalid args for def inside defprotocol"
end
@doc false
def callback_from_spec(module, name, arity) do
tuple = { name, arity }
specs = Module.get_attribute(module, :spec)
found = lc { k, v } inlist specs, k == tuple do
Kernel.Typespec.define_callback(module, tuple, v)
true
end
found != []
end
end
-266
View File
@@ -1,266 +0,0 @@
defmodule Protocol.Consolidation do
@moduledoc """
Module responsible for consolidating protocols and helpers for
extracting protocols and implementations from code paths for
consolidation.
"""
@doc """
Extract all protocols from the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_protocols([path])
iex> Enumerable in mods
true
"""
@spec extract_protocols([char_list | String.t]) :: [atom]
def extract_protocols(paths) do
extract_matching_by_attribute paths, 'Elixir.',
fn module, attributes ->
case attributes[:protocol] do
[fallback_to_any: _, consolidated: _] -> module
_ -> nil
end
end
end
@doc """
Extract all types implemented for the given protocol from
the given paths.
The paths can be either a char list or a string. Internally
they are worked on as char lists, so passing them as lists
avoid extra conversion.
## Examples
# Get Elixir's ebin and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
iex> List in mods
true
"""
@spec extract_impls(module, [char_list | String.t]) :: [atom]
def extract_impls(protocol, paths) when is_atom(protocol) do
prefix = atom_to_list(protocol) ++ '.'
extract_matching_by_attribute paths, prefix, fn
_mod, attributes ->
case attributes[:impl] do
[protocol: ^protocol, for: for] -> for
_ -> nil
end
end
end
defp extract_matching_by_attribute(paths, prefix, callback) do
lc path inlist paths,
file inlist list_dir(path),
mod = extract_from_file(path, file, prefix, callback),
do: mod
end
defp list_dir(path) when is_list(path) do
case :file.list_dir(path) do
{ :ok, files } -> files
_ -> []
end
end
defp list_dir(path), do: list_dir(to_char_list(path))
defp extract_from_file(path, file, prefix, callback) do
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
extract_from_beam(Path.join(path, file), callback)
end
end
defp extract_from_beam(file, callback) do
case :beam_lib.chunks(file, [:attributes]) do
{:ok, { module, [attributes: attributes] } } ->
callback.(module, attributes)
_ ->
nil
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
other -> other
end
end
end
@doc """
Receives a protocol and a list of implementations and
consolidates the given protocol. Consolidation happens
by changing the protocol `impl_for` in the abstract
format to have fast lookup rules.
It returns the updated version of the protocol bytecode.
A given bytecode or protocol implementation can be checked
to be consolidated or not by analyzing the protocol
attribute:
Enumerable.__info__(:attributes)[:protocol]
If the first element of the tuple is true, it means
the protocol was consolidated.
This function does not load the protocol at any point
nor loads the new bytecode for the compiled module.
"""
@spec apply_to(module, [module]) ::
{ :ok, binary } |
{ :error, :not_a_protocol } |
{ :error, :no_beam_info }
def apply_to(protocol, types) when is_atom(protocol) do
ensure_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
# Ensure the given module is loaded and is a protocol.
defp ensure_protocol(protocol) do
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
attributes: attributes] } } ->
case attributes[:protocol] do
[fallback_to_any: any, consolidated: _] ->
{ :ok, { protocol, any, abstract_code } }
_ ->
{ :error, :not_a_protocol }
end
_ ->
{ :error, :no_beam_info }
end
end
defp beam_file(module) when is_atom(module) do
case :code.which(module) do
:non_existing -> module
file -> file
end
end
# Change the debug information to the optimized
# impl_for/1 dispatch version.
defp change_debug_info({ protocol, any, code }, types) do
types = if any, do: types, else: List.delete(types, Any)
records = types -- Protocol.builtin
builtin = Protocol.builtin -- (Protocol.builtin -- types)
builtin = if records != [], do: [Record|builtin], else: builtin
change_impl_for(code, protocol, builtin, records, false, [])
end
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
attr = [fallback_to_any: Any in builtin, consolidated: true]
change_impl_for(t, protocol, builtin, records, true,
[{ :attribute, line, :protocol, attr }|acc])
end
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
unless Any in builtin do
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
end
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :impl_for, 1, clauses }|acc])
end
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
change_impl_for(t, protocol, builtin, records, is_protocol,
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
end
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
end
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
if is_protocol do
{ :ok, { protocol, Enum.reverse(acc) } }
else
{ :error, :not_a_protocol }
end
end
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
defp clause_for(Any, protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, Module.concat(protocol, Any) }]}
end
defp clause_for(Record, _protocol, line) do
{:clause, line, [{:var, line, :x}],
[[{:op, line, :andalso,
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
[{:var, line, :x}]},
{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]
}]},
}]],
[{:call, line,
{:atom, line, :rec_impl_for},
[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
[{:integer, line, 1}, {:var, line, :x}]}]}]}
end
defp builtin_clause_for(mod, guard, protocol, line) do
{:clause, line,
[{:var, line, :x}],
[[{:call, line,
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
[{:var, line, :x}],
}]],
[{:atom, line, Module.concat(protocol, mod)}]}
end
defp record_clause_for(other, protocol, line) do
{:clause, line, [{:atom, line, other}], [],
[{:atom, line, Module.concat(protocol, other)}]}
end
defp fallback_clause_for(value, _protocol, line) do
{:clause, line, [{:var, line, :_}], [],
[{ :atom, line, value }]}
end
# Finally compile the module and emit its bytecode.
defp compile({ protocol, code }) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
{ :ok, binary }
end
end
-72
View File
@@ -1,72 +0,0 @@
defrecord Range, [:first, :last] do
@moduledoc """
Defines a Range.
"""
end
defprotocol Range.Iterator do
@doc """
Reduces the range based on the type of the first argument.
"""
def reduce(first, range, acc, fun)
@doc """
Count how many items are in the range.
"""
def count(first, range)
end
defimpl Enumerable, for: Range do
def reduce(first .. _ = range, acc, fun) do
Range.Iterator.reduce(first, range, acc, fun)
end
def member?(first .. last, value) do
first <= value and value <= last
end
def count(first .. _ = range) do
Range.Iterator.count(first, range)
end
end
defimpl Range.Iterator, for: Integer do
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
reducer = if last >= first do
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
else
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
end
Enumerable.Function.reduce(reducer, acc, fun)
end
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
acc
end
defp do_reducer_up(counter, last, acc, fun) do
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
end
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
acc
end
defp do_reducer_down(counter, last, acc, fun) do
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
end
def count(first, Range[last: last]) when is_integer(last) and last >= first do
last - first + 1
end
def count(first, Range[last: last]) when is_integer(last) do
first - last + 1
end
end
defimpl Inspect, for: Range do
def inspect(Range[first: first, last: last], opts) do
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
end
end
-929
View File
@@ -1,929 +0,0 @@
defmodule Record do
@moduledoc %S"""
Functions for working with Records.
A record is a tagged tuple which contains one or more elements
where the first element is an atom. We can manually create a record
by simply defining such a tuple:
iex> record = { User, "José", 25 }
iex> is_record(record, User)
true
However, manually constructing tuples can be quite error prone.
If we need to add a new field to our User, it would require
us to carefully change all places where the tuple is used.
Furthermore, as more items are added to the tuple, they
lose semantic value.
This module solves these problems by allowing us to name each
element and encapsulate the generation and manipulation of
such tuples. Much of the functionality provided by this module happens
at compilation time, meaning they don't add any runtime overhead while
considerably improving the quality of our code.
For these reasons, Records are frequently used in Elixir and
are also very useful when combined with Protocols. This module
provides different mechanisms for working with records and we
are going to explore them in the following sections.
## defrecordp
The simplest way of working with records is via `defrecordp`:
defmodule User do
defrecordp :user, name: "José", age: 25
end
In the example above, `defrecordp` is going to generate a set of
macros named `user` that allows us to create, update and match
on a record. Our record is going to have two fields, a `name` with
default value of "José" and `age` with default value 25.
Let's see some examples:
# To create records
user() #=> { :user, "José", 25 }
user(age: 26) #=> { :user, "José", 26 }
By using the `user` macro, we no longer need to explicitly create
a tuple with all elements. It also allows us to create and modify
values by name:
# Create a new record
sample_user = user()
# And now change its age to 26
user(sample_user, age: 26)
Since `user` is a macro, all the work happens at compilation time.
This means all operations, like changing the `age` above, work
as a simple tuple operation at runtime:
# This update operation...
user(sample_user, age: 26)
# Literally translates to this one:
set_elem(sample_user, 2, 26)
For this reason, the following operation is not allowed as all
values need to be explicit:
new_values = [age: 26]
user(sample_user, new_values)
As the name says, `defrecordp` is useful when you don't want to
expose the record definition. The `user` macro used above, for
example, is only available inside the `User` module and nowhere else.
You can find more information in `Kernel.defrecordp/3` docs.
## defrecord
By using `defrecord`, a developer can make a Record definition
available everywhere within Elixir code. Let's see an example:
defrecord User, name: "José", age: 25
Notice that, unlike `defrecordp`, `defrecord` expects an
alias as the first argument. This is because `defrecord` is going
to create a module named `User` with all the relevant metadata.
This module can then be imported and we can manipulate the user
as with `defrecordp`:
Record.import User, as: :user
user() #=> User[name: "José", age: 25]
user(age: 26) #=> User[name: "José", age: 26]
Notice that now since the record definition is accessible, Elixir
shows the record nicely formatted, no longer as a simple tuple. We
can get the raw formatting by passing `raw: true` to `inspect`:
inspect user(), raw: true
{ User, "José", 25 }
Since working with external records is common, Elixir allows
developers to skip importing the record altogether in favor
of a `Module[args]` syntax:
# Skipping a field uses its default value
User[] #=> User[name: "José", age: 25]
User[age: 26] #=> User[name: "José", age: 26]
The macro name is replaced by the module name and the parentheses
are replaced by brackets. When this syntax is used, there is no
need to import the record.
Before we sum up the differences between `defrecord` and
`defrecordp`, there is one last functionality introduced by
`defrecord` that we need to discuss.
## Runtime access
All the functionality discussed above happens at compilation time.
This means that both `user(age: 26)` and `User[age: 26]` are expanded
into a tuple at compile time.
However, there are some situations where we want to set or update
fields dynamically. `defrecord` (and not `defrecordp` ) supports
this behaviour out of the box:
defrecord User, name: "José", age: 25
opts = [name: "Hello"]
user = User.new(opts)
#=> User[name: "Hello", age: 25]
user.update(age: 26)
#=> User[name: "Hello", age: 26]
All the calls above happen at runtime. It gives Elixir records
flexibility at the cost of performance since there is more work
happening at runtime.
The above calls (`new` and `update`) can accept both atom and string
keys for field names, however not both at the same time. This feature
allows to "sanitize" untrusted dictionaries and initialize/update
records without using `Kernel.binary_to_existing_atom/1`.
To sum up, `defrecordp` should be used when you don't want to expose
the record information while `defrecord` should be used whenever you
want to share a record within your code or with other libraries or
whenever you need to dynamically set or update fields.
The standard library contains excellent examples of both use cases,
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
and [`Range`](Range.html) with `defrecord`.
You can learn more about records in the `Kernel.defrecord/3` docs. Now
let's discuss the usefulness of combining records with protocols.
## Protocols
Developers can extend existing protocols by creating their own
records and implementing the desired protocols. For instance,
imagine that you have created a new representation for storing
date and time, represented by the year, the week of the year
and the week day:
defrecord WeekDate, year: nil, week: nil, week_day: nil
Now we want this date to be represented as a string and this
can be done by implementing the `String.Chars` protocol for
our record:
defimpl String.Chars, for: WeekDate do
def to_string(WeekDate[year: year, week: week, week_day: day]) do
"#{year}-W#{week}-#{day}"
end
end
Now we can explicitly convert our `WeekDate`:
to_string WeekDate[year: 2013, week: 26, week_day: 4]
"2013-W26-4"
A protocol can be implemented for any record, whether
generated with `defrecordp` or `defrecord`.
"""
@type t :: tuple
@doc """
Extract record information from an Erlang file.
Returns the fields as a list of tuples.
## Examples
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
major_device: :undefined, minor_device: :undefined, inode: :undefined,
uid: :undefined, gid: :undefined]
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
"""
def extract(name, opts) do
Record.Extractor.retrieve(name, opts)
end
@doc """
Import a public record definition as a set of private macros.
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
desires to manipulate a record via a set of macros instead
of the regular access syntax.
## Example
defmodule Test do
Record.import File.Stat, as: :file_stat
def size(file_stat(size: size)), do: size
end
"""
defmacro import(module, as: name) do
quote do
module = unquote(module)
fields = if module == __MODULE__ do
@record_fields
else
module.__record__(:fields)
end
Record.defmacros(unquote(name), fields, __ENV__, module)
end
end
@doc false
def defrecord(name, fields, opts) do
block = Keyword.get(opts, :do, nil)
record_check!(fields)
quote do
unquoted_fields = unquote(fields)
defmodule unquote(name) do
import Record.DSL
@record_fields []
@record_types []
Record.deffunctions(unquoted_fields, __ENV__)
value = unquote(block)
Record.deftypes(@record_fields, @record_types, __ENV__)
value
end
end
end
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
"please use record_type instead"
end
defp record_check!([_|t]), do: record_check!(t)
defp record_check!(_), do: :ok
@doc false
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
{ fields, types, def_type } = recordp_split(fields, [], [], false)
type = binary_to_atom(atom_to_binary(name) <> "_t")
tag = tag || name
quote do
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
if unquote(def_type) do
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
end
end
end
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
recordp_split t, [{ field, default }|defaults], [type|types], true
end
defp recordp_split([other|t], defaults, types, def_type) do
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
end
defp recordp_split([], defaults, types, def_type) do
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
end
@doc """
Define record functions skipping the module definition.
This is called directly by `Kernel.defrecord/3`. It expects the record
values, a set of options and the module environment.
## Examples
defmodule CustomRecord do
Record.deffunctions [:name, :age], __ENV__
Record.deftypes [:name, :age], [name: :binary, age: :integer], __ENV__
end
"""
def deffunctions(values, env) do
values = lc value inlist values, do: convert_value(value)
escaped = Macro.escape(values)
contents = [
reflection(escaped),
initializer(escaped),
conversions(values),
record_optimizable(),
updater(values),
accessors(values, 1),
switch_recorder()
]
contents = [quote(do: @record_fields unquote(escaped))|contents]
# Special case for bootstrapping purposes
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Define types and specs for the record.
"""
def deftypes(values, types, env) do
types = types || []
values = lc value inlist values do
{ name, default } = convert_value(value)
{ name, default, find_spec(types, name) }
end
contents = [
core_specs(values),
accessor_specs(values, 1, [])
]
if env == Macro.Env do
Module.eval_quoted(env, contents, [], [])
else
Module.eval_quoted(env.module, contents, [], env.location)
end
end
@doc """
Define macros for manipulating records.
This is called
directly by `Kernel.defrecordp/3`. It expects the macro name, the
record values and the environment.
## Examples
defmodule CustomRecord do
Record.defmacros :user, [:name, :age], __ENV__
end
"""
def defmacros(name, values, env, tag // nil) do
escaped = lc value inlist values do
{ key, value } = convert_value(value)
{ key, Macro.escape(value) }
end
tag = tag || name
contents = quote do
defmacrop unquote(name)() do
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
end
defmacrop unquote(name)(record) when is_tuple(record) do
Record.to_keywords(unquote(tag), unquote(escaped), record)
end
defmacrop unquote(name)(args) do
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
end
defmacrop unquote(name)(record, args) do
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
end
end
Module.eval_quoted(env.module, contents, [], env.location)
end
## Callbacks
# Store all optimizable fields in the record as well
@doc false
defmacro __before_compile__(_) do
quote do
def __record__(:optimizable), do: @record_optimizable
end
end
# Store fields that can be optimized and that cannot be
# optimized as they are overriden
@doc false
def __on_definition__(env, kind, name, args, _guards, _body) do
tuple = { name, length(args) }
module = env.module
functions = Module.get_attribute(module, :record_optimizable)
functions =
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
[tuple|functions]
else
List.delete(functions, tuple)
end
Module.put_attribute(module, :record_optimizable, functions)
end
# 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(atom, fields, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
end
in_match = caller.in_match?
has_underscore_value = Keyword.has_key?(keyword, :_)
underscore_value = Keyword.get(keyword, :_, { :_, [], 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
[] ->
quote do: { unquote_splicing([atom|match]) }
_ ->
keys = lc { key, _ } inlist remaining, do: key
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
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(_atom, fields, record) do
{ var, extra } = cache_var(record)
keywords = Enum.map fields,
fn { key, _default } ->
index = find_index(fields, key, 0)
quote do
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
end
end
quote do
unquote_splicing(extra)
unquote(keywords)
end
end
# Dispatch the call to either get, update or to_list depending on the args given.
@doc false
def dispatch(atom, fields, record, args, caller) do
cond do
is_atom(args) ->
get(atom, fields, record, args)
is_keyword(args) ->
update(atom, fields, record, args, caller)
is_list(args) ->
to_list(atom, fields, record, args)
true ->
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
end
end
# Implements the update macro defined by defmacros.
# It returns a quoted expression that represents
# the access given by the keywords.
defp update(atom, fields, var, keyword, caller) do
unless is_keyword(keyword) do
raise ArgumentError, message: "expected arguments to be compile time keywords"
end
if caller.in_match? do
raise ArgumentError, message: "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 ArgumentError, message: "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.
defp get(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 ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
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(atom, fields, record, keys) do
unless is_list(fields) do
raise ArgumentError, message: "expected arguments to be a compile time list"
end
{ var, extra } = cache_var(record)
list = Enum.map keys,
fn(key) ->
index = find_index(fields, key, 0)
if index do
quote do: :erlang.element(unquote(index + 2), unquote(var))
else
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
end
end
quote do
unquote_splicing(extra)
unquote(list)
end
end
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
{ tuple, [] }
end
defp cache_var(other) do
quote do
{ x, [x = unquote(other)] }
end
end
## Function generation
# Define __record__/1, __record__/2, __record__/3 as reflection
# functions that return 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]
# FileInfo.__record__(:index, :atime) #=> 1
# FileInfo.__record__(:index, :mtime) #=> 2
#
defp reflection(values) do
quoted = lc { k, _ } inlist values do
index = find_index(values, k, 0)
quote do
def __record__(:index, unquote(k)), do: unquote(index + 1)
end
end
quote do
unquote(quoted)
@doc false
def __record__(:index, _), do: nil
@doc false
def __record__(:index, arg, _), do: __record__(:index, arg)
@doc false
def __record__(kind, _), do: __record__(kind)
@doc false
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.
atom_selective = lc { k, v } inlist values, do: initialize_lookup(k, v)
string_selective = lc { k, v } inlist values, do: initialize_lookup(atom_to_binary(k), v)
quote do
@doc false
def new(), do: new([])
@doc false
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
end
end
defp initialize_lookup(k, v) do
quote do
case :lists.keyfind(unquote(k), 1, opts) do
false -> unquote(v)
{_, v} -> v
end
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
@doc false
def to_keywords(record) do
unquote(:orddict.from_list(sorted))
end
end
end
# Implement accessors. 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
#
# def atime(value, record) do
# set_elem(record, 1, value)
# end
#
# def mtime(record) do
# set_elem(record, 2, value)
# end
#
# def atime(callback, record) do
# set_elem(record, 1, callback.(elem(record, 1)))
# end
#
# def mtime(callback, record) do
# set_elem(record, 2, callback.(elem(record, 2)))
# end
#
defp accessors([{ :__exception__, _ }|t], 1) do
accessors(t, 2)
end
defp accessors([{ key, _default }|t], i) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@doc false
def unquote(key)(record) do
:erlang.element(unquote(i + 1), record)
end
@doc false
def unquote(key)(value, record) do
:erlang.setelement(unquote(i + 1), record, value)
end
@doc false
def unquote(update)(function, record) do
:erlang.setelement(unquote(i + 1), record,
function.(:erlang.element(unquote(i + 1), record)))
end
end
[contents|accessors(t, i + 1)]
end
defp accessors([], _i) do
[]
end
# Define an updater method that receives a
# keyword list and updates the record.
defp updater(values) do
atom_fields =
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
string_fields =
lc {key, _default} inlist values, do: updater_lookup(atom_to_binary(key), key, values)
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
quote do
@doc false
def update([], record) do
record
end
def update([{key, _}|_] = keywords, record) when is_atom(key) do
unquote(atom_contents)
end
def update([{key, _}|_] = keywords, record) when is_binary(key) do
unquote(string_contents)
end
end
end
defp updater_lookup(k, key, values) do
v = find_index(values, key, 1)
quote do
case :lists.keyfind(unquote(k), 1, keywords) do
false -> elem(record, unquote(v))
{_, value} -> value
end
end
end
defp record_optimizable do
quote do
@record_optimized true
@record_optimizable []
@before_compile { unquote(__MODULE__), :__before_compile__ }
@on_definition { unquote(__MODULE__), :__on_definition__ }
end
end
defp switch_recorder do
quote do: @record_optimized false
end
## Types/specs generation
defp core_specs(values) do
types = lc { _, _, spec } inlist values, do: spec
options = if values == [], do: [], else: [options_specs(values)]
values_specs = if values == [], do: [], else: values_specs(values)
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: { __MODULE__, unquote_splicing(types) }
end
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
end
@spec new :: t
@spec new(options) :: t
@spec to_keywords(t) :: options
@spec update(options, t) :: t
@spec __record__(:name) :: atom
@spec __record__(:fields) :: [{atom, any}]
@spec __record__(:index, atom) :: non_neg_integer | nil
end
end
defp options_specs([{ k, _, v }|t]) do
:lists.foldl fn { k, _, v }, acc ->
{ :|, [], [{ k, v }, acc] }
end, { k, v }, t
end
defp values_specs([{ _, _, v }|t]) do
:lists.foldl fn { _, _, v }, acc ->
{ :|, [], [v, acc] }
end, v, t
end
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
accessor_specs(t, 2, acc)
end
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
update = binary_to_atom "update_" <> atom_to_binary(key)
contents = quote do
@spec unquote(key)(t) :: unquote(spec)
@spec unquote(key)(unquote(spec), t) :: t
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
end
accessor_specs(t, i + 1, [contents | acc])
end
defp accessor_specs([], _i, acc), do: acc
## Helpers
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
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
defp convert_value({ atom, other }) when is_atom(atom), do:
{ atom, check_value(atom, other) }
defp convert_value({ field, _ }), do:
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
defp check_value(atom, other) when is_list(other) do
lc(i inlist other, do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_tuple(other) do
lc(i inlist tuple_to_list(other), do: check_value(atom, i))
other
end
defp check_value(atom, other) when is_function(other) do
unless :erlang.fun_info(other, :env) == { :env, [] } and
:erlang.fun_info(other, :type) == { :type, :external } do
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
"functions that point to an existing &Mod.fun/arity"
end
end
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
end
defp check_value(_atom, other), do: other
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
defp find_spec(types, name) do
matches = lc { k, v } inlist types, name == k, do: v
case matches do
[h|_] -> h
_ -> quote do: term
end
end
end
defmodule Record.DSL do
@moduledoc false
@doc """
Defines the type for each field in the record.
Expects a keyword list.
"""
defmacro record_type(opts) when is_list(opts) do
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
quote do
@record_types Keyword.merge(@record_types || [], unquote(escaped))
end
end
end
-91
View File
@@ -1,91 +0,0 @@
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: file) when is_binary(file) do
file = String.to_char_list!(file)
case :code.where_is_file(file) do
:non_existing -> realfile = file
realfile -> :ok
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) when is_binary(file) do
[app|path] = :filename.split(String.to_char_list!(file))
case :code.lib_dir(list_to_atom(app)) do
{ :error, _ } ->
raise ArgumentError, message: "lib file #{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
form = read_file(file)
records = retrieve_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
raise ArgumentError, message: "no record #{name} found at #{file}"
end
end
# Parse the given file and retrieve all existent records.
defp retrieve_records(form) do
lc { :attribute, _, :record, record } inlist form, 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 #{file}, got: #{inspect(other)}"
end
end
# Parse a tuple with name and fields and returns a
# list of tuples where the first element is the field
# and the second is its default value.
defp parse_record({ _name, fields }, form) do
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
{ :cons, 0, parse_field(f), acc }
end
eval_record(cons, form)
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, :undefined}] }
end
defp parse_field({ :record_field, _, key, value }) do
{ :tuple, 0, [key, value] }
end
defp eval_record(cons, form) do
form = form ++
[ { :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ cons ] } ] } ]
{ :function, 0, :hello, 0, [
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
record
end
end
-396
View File
@@ -1,396 +0,0 @@
defmodule Regex do
@moduledoc %S"""
Regular expressions for Elixir built on top of the `re` module
in the Erlang Standard Library. More information can be found
in the [`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
%r/foo/i
The `re` module provides several options, the ones available in Elixir, followed by
their shortcut in parenthesis, are:
* `caseless` (i) - add case insensitivity
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
The new line setting can be overridden 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.
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) - inverts the "greediness" of the regexp
* `groups` (g) - compiles 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).
"""
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
@type t :: { Regex, term, binary, binary, [atom] | nil }
defexception CompileError, message: "regex could not be compiled"
@doc """
Compiles the regular expression.
The given options can either be a binary with the characters
representing the same regex options given to the `%r` sigil,
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
It returns `{ :ok, regex }` in case of success,
`{ :error, reason }` otherwise.
## Examples
iex> Regex.compile("foo")
{:ok, %r"foo"}
iex> Regex.compile("*foo")
{:error, {'nothing to repeat', 0}}
"""
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
def compile(source, options // "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
{ :error, rest } ->
{ :error, { :invalid_option, rest } }
translated_options ->
# Always use the unicode option, we don't have a latin1 legacy like
# Erlang.
compile(source, [:unicode|translated_options], options)
end
end
def compile(source, options) when is_list(options) do
compile(source, options, "")
end
defp compile(source, opts, doc_opts) when is_binary(source) do
re_opts = opts -- [:groups]
groups = if opts != re_opts, do: parse_groups(source)
case :re.compile(source, re_opts) do
{ :ok, re_pattern } ->
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: 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 """
Returns a boolean indicating whether there was a match or not.
## Examples
iex> Regex.match?(%r/foo/, "foo")
true
iex> Regex.match?(%r/foo/, "bar")
false
"""
def match?(regex(re_pattern: compiled), string) do
:re.run(string, compiled, [{ :capture, :none }]) == :match
end
@doc """
Runs the regular expression against the given string until the first match.
It returns a list with all captures or `nil` if no match occurred.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Examples
iex> Regex.run(%r/c(d)/, "abcd")
["cd", "d"]
iex> Regex.run(%r/e/, "abcd")
nil
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
[{2,2},{3,1}]
"""
def run(regex, string, options // [])
def run(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
case :re.run(string, compiled, [{ :capture, captures, return }]) do
:nomatch -> nil
:match -> []
{ :match, results } -> results
end
end
@doc """
Returns the given captures as a keyword list or `nil` if no captures
are found. Requires the regex to be compiled with the groups option.
## Examples
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
[foo: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
[foo: "b", bar: "d"]
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
nil
"""
def named_captures(regex(groups: groups) = regex, string, options // []) do
options = Keyword.put_new(options, :capture, :groups)
results = run(regex, string, options)
if results, do: Enum.zip(groups, results)
end
@doc """
Returns the underlying `re_pattern` in the regular expression.
"""
def re_pattern(regex(re_pattern: compiled)) do
compiled
end
@doc """
Returns the regex source as a binary.
## Examples
iex> Regex.source(%r(foo))
"foo"
"""
def source(regex(source: source)) do
source
end
@doc """
Returns the regex options as a string.
## Examples
iex> Regex.opts(%r(foo)m)
"m"
"""
def opts(regex(options: options)) do
options
end
@doc """
Returns a list of named groups in the regex.
## Examples
iex> Regex.groups(%r/(?<foo>bar)/g)
[:foo]
"""
def groups(regex(groups: groups)) do
groups
end
@doc """
Same as `run/3`, but scans the target several times collecting all
matches of the regular expression. A list of lists is returned,
where each entry in the primary list represents a match and each
entry in the secondary list represents the captured contents.
The captured contents defaults to `:all`, which includes the whole
regex match and each capture.
When the option `:capture` is set to `:groups`, it will capture all
the groups in the regex.
## Examples
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
[["cd", "d"], ["ce", "e"]]
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
[["cd"], ["ce"]]
iex> Regex.scan(%r/e/, "abcd")
[]
"""
def scan(regex, string, options // [])
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
return = Keyword.get(options, :return, return_for(string))
captures =
case Keyword.get(options, :capture, :all) do
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
others -> others
end
options = [{ :capture, captures, return }, :global]
case :re.run(string, compiled, options) do
:match -> []
:nomatch -> []
{ :match, results } -> results
end
end
@doc """
Splits the given target into the number of parts specified.
If no number of parts is given, it defaults to `:infinity`.
## Examples
iex> Regex.split(%r/-/, "a-b-c")
["a","b","c"]
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
["a","b-c"]
iex> Regex.split(%r/-/, "abc")
["abc"]
iex> Regex.split(%r//, "abc")
["a", "b", "c", ""]
iex> Regex.split(%r//, "abc", trim: true)
["a", "b", "c"]
"""
def split(regex, string, options // [])
def split(regex(re_pattern: compiled), string, options) do
parts =
cond do
Keyword.get(options, :global) == false -> 2
p = Keyword.get(options, :parts) -> p
true -> :infinity
end
return = Keyword.get(options, :return, return_for(string))
opts = [return: return, parts: parts]
splits = :re.split(string, compiled, opts)
if Keyword.get(options, :trim, false) do
lc split inlist splits, split != "", do: split
else
splits
end
end
@doc %S"""
Receives a regex, a binary and a replacement, 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
iex> Regex.replace(%r/d/, "abc", "d")
"abc"
iex> Regex.replace(%r/b/, "abc", "d")
"adc"
iex> Regex.replace(%r/b/, "abc", "[&]")
"a[b]c"
iex> Regex.replace(%r/b/, "abc", "[\\&]")
"a[&]c"
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
"a[b]c"
"""
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
return = Keyword.get(options, :return, return_for(string))
opts = [{ :return, return }|opts]
:re.replace(string, compiled, replacement, opts)
end
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
@escape_pattern pattern
@doc %S"""
Escapes a string to be literally matched in a regex.
## Examples
iex> Regex.escape(".")
"\\."
iex> Regex.escape("\\what if")
"\\\\what\\ if"
"""
@spec escape(String.t | char_list) :: String.t | char_list
def escape(string) do
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
end
# Helpers
@doc false
# Unescape map function used by Macro.unescape_string.
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(?a), do: ?\a
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
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
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 translate_options(rest), do: { :error, rest }
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
@groups_pattern pattern
defp parse_groups(source) do
options = [:global, {:capture, ['G'], :binary}]
case :re.run(source, @groups_pattern, options) do
:nomatch -> []
{ :match, results } ->
lc [group] inlist results, do: binary_to_atom(group)
end
end
end
-248
View File
@@ -1,248 +0,0 @@
defmodule Set do
@moduledoc %S"""
This module specifies the Set API expected to be
implemented by different representations.
It also provides functions that redirect to the
underlying Set, allowing a developer to work with
different Set implementations using one API.
To create a new set, use the `new` functions defined
by each set type:
HashSet.new #=> creates an empty HashSet
For simplicity's sake, in the examples below every time
`new` is used, it implies one of the module-specific
calls like above.
## Protocols
Sets are required to implement the `Enumerable` protocol,
allowing one to write:
Enum.each(set, fn k ->
IO.inspect k
end)
## Match
Sets are required to implement all operations
using the match (`===`) operator. Any deviation from
this behaviour should be avoided and explicitly documented.
"""
use Behaviour
@type value :: any
@type values :: [ value ]
@type t :: tuple
defcallback delete(t, value) :: t
defcallback difference(t, t) :: t
defcallback disjoint?(t, t) :: boolean
defcallback empty(t) :: t
defcallback equal?(t, t) :: boolean
defcallback intersection(t, t) :: t
defcallback member?(t, value) :: boolean
defcallback put(t, value) :: t
defcallback size(t) :: non_neg_integer
defcallback subset?(t, t) :: boolean
defcallback to_list(t) :: list()
defcallback union(t, t) :: t
defmacrop target(set) do
quote do
if is_tuple(unquote(set)) do
elem(unquote(set), 0)
else
unsupported_set(unquote(set))
end
end
end
@doc """
Deletes `value` from `set`.
## Examples
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 4) |> HashSet.to_list
[1, 2, 3]
iex> s = HashSet.new([1, 2, 3])
...> Set.delete(s, 2) |> HashSet.to_list
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
target(set).delete(set, value)
end
@doc """
Returns a set that is `set1` without the members of `set2`.
## Examples
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target(set1).difference(set1, set2)
end
@doc """
Checks if `set1` and `set2` have no members in common.
## Examples
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
true
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target(set1).disjoint?(set1, set2)
end
@doc """
Returns an empty set of the same type as `set`.
"""
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
end
@doc """
Checks if `set1` and `set2` are equal.
## Examples
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
true
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target(set1).equal?(set1, set2)
end
@doc """
Returns a set containing only members in common between `set1` and `set2`.
## Examples
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[2]
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target(set1).intersection(set1, set2)
end
@doc """
Checks if `set` contains `value`.
## Examples
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
true
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(set, value) do
target(set).member?(set, value)
end
@doc """
Inserts `value` into `set` if it does not already contain it.
## Examples
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
[1, 2, 3]
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
[1, 2, 3, 4]
"""
@spec put(t, value) :: t
def put(set, value) do
target(set).put(set, value)
end
@doc """
Returns the number of elements in `set`.
## Examples
iex> Set.size(HashSet.new([1, 2, 3]))
3
"""
@spec size(t) :: non_neg_integer
def size(set) do
target(set).size(set)
end
@doc """
Checks if `set1`'s members are all contained in `set2`.
## Examples
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
true
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target(set1).subset?(set1, set2)
end
@doc """
Converts `set` to a list.
## Examples
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
[1,2,3]
"""
@spec to_list(t) :: list
def to_list(set) do
target(set).to_list(set)
end
@doc """
Returns a set containing all members of `set1` and `set2`.
## Examples
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
[1,2,3,4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target(set1).union(set1, set2)
end
defp unsupported_set(set) do
raise ArgumentError, message: "unsupported set: #{inspect set}"
end
end
-494
View File
@@ -1,494 +0,0 @@
defmodule Stream do
@moduledoc """
Module for creating and composing streams.
Streams are composable, lazy enumerables. Any enumerable that generates
items one by one during enumeration is called a stream. For example,
Elixir's `Range` is a stream:
iex> range = 1..5
1..5
iex> Enum.map range, &(&1 * 2)
[2,4,6,8,10]
In the example above, as we mapped over the range, the elements being
enumerated were created one by one, during enumeration. The `Stream`
module allows us to map the range, without triggering its enumeration:
iex> range = 1..3
iex> stream = Stream.map(range, &(&1 * 2))
iex> Enum.map(stream, &(&1 + 1))
[3,5,7]
Notice we started with a range and then we created a stream that is
meant to multiply each item in the range by 2. At this point, no
computation was done yet. Just when `Enum.map/2` is called we
enumerate over each item in the range, multiplying it by 2 and adding 1.
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
Due to their laziness, streams are useful when working with large
(or even infinite) collections. When chaining many operations with `Enum`,
intermediate lists are created, while `Stream` creates a recipe of
computations that are executed at a later moment. Let's see another
example:
1..3 |>
Enum.map(&IO.inspect(&1)) |>
Enum.map(&(&1 * 2)) |>
Enum.map(&IO.inspect(&1))
1
2
3
2
4
6
#=> [2,4,6]
Notice that we first printed each item in the list, then multiplied each
element by 2 and finally printed each new value. In this example, the list
was iterated three times. Let's see an example with streams:
stream = 1..3 |>
Stream.map(&IO.inspect(&1)) |>
Stream.map(&(&1 * 2)) |>
Stream.map(&IO.inspect(&1))
Enum.to_list(stream)
1
2
2
4
3
6
#=> [2,4,6]
Although the end result is the same, the order in which the items were
printed changed! With streams, we print the first item and then print
its double. In this example, the list was iterated just once!
That's what we meant when we first said that streams are composable,
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
effectively composing the streams and they are lazy. The computations
are performed only when you call a function from the `Enum` module.
## Creating Streams
There are many functions in Elixir's standard library that return
streams, some examples are:
* `IO.stream/1` - Streams input lines, one by one;
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
This module also allows us to create streams from any enumerable:
iex> stream = Stream.map([1,2,3], &(&1 * 2))
iex> Enum.map(stream, &(&1 + 1))
[3,5,7]
By simply passing a list (which is an enumerable) as the first argument
to `Stream.map/2`, we have automatically created a stream that will
multiply the items in the list by 2 on enumeration.
This module also provides other functions for creating streams, such as
`Stream.cycle/1`.
"""
defrecord Lazy, [:enumerable, :fun, :acc]
defimpl Enumerable, for: Lazy do
def reduce(Lazy[] = lazy, acc, fun) do
do_reduce(lazy, acc, fun, 0)
end
def count(Lazy[] = lazy) do
do_reduce(lazy, 0, fn _, acc -> acc + 1 end, 0)
end
def member?(Lazy[] = lazy, value) do
do_reduce(lazy, false, fn(entry, _) ->
if entry === value, do: throw({ :stream_lazy, 0, true }), else: false
end, 0)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun, nesting) do
do_reduce(enumerable, acc, f1.(fun), nesting)
end
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun, nesting) do
do_reduce(enumerable, { acc, side }, f1.(fun, nesting), nesting + 1)
end
defp do_reduce(enumerable, acc, fun, nesting) do
Enumerable.reduce(enumerable, acc, fun) |> remove_nesting(nesting)
catch
{ :stream_lazy, nesting, res } -> remove_nesting(res, nesting)
end
defp remove_nesting(acc, 0), do: acc
defp remove_nesting(acc, nesting), do: remove_nesting(elem(acc, 0), nesting - 1)
end
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type default :: any
@doc """
Creates a stream that enumerates each enumerable in an enumerable.
## Examples
iex> stream = Stream.concat([1..3, 4..6, 7..9])
iex> Enum.to_list(stream)
[1,2,3,4,5,6,7,8,9]
"""
@spec concat(Enumerable.t) :: t
def concat(enumerables) do
&do_concat(enumerables, &1, &2)
end
@doc """
Creates a stream that enumerates the first argument, followed by the second.
## Examples
iex> stream = Stream.concat(1..3, 4..6)
iex> Enum.to_list(stream)
[1,2,3,4,5,6]
iex> stream1 = Stream.cycle([1, 2, 3])
iex> stream2 = Stream.cycle([4, 5, 6])
iex> stream = Stream.concat(stream1, stream2)
iex> Enum.take(stream, 6)
[1,2,3,1,2,3]
"""
@spec concat(Enumerable.t, Enumerable.t) :: t
def concat(first, second) do
&do_concat([first, second], &1, &2)
end
defp do_concat(enumerables, acc, fun) do
Enumerable.reduce(enumerables, acc, &Enumerable.reduce(&1, &2, fun))
end
@doc """
Creates a stream that cycles through the given enumerable,
infinitely.
## Examples
iex> stream = Stream.cycle([1,2,3])
iex> Enum.take(stream, 5)
[1,2,3,1,2]
"""
@spec cycle(Enumerable.t) :: t
def cycle(enumerable) do
&do_cycle(enumerable, &1, &2)
end
defp do_cycle(enumerable, acc, fun) do
acc = Enumerable.reduce(enumerable, acc, fun)
do_cycle(enumerable, acc, fun)
end
@doc """
Lazily drops the next `n` items from the enumerable.
## Examples
iex> stream = Stream.drop(1..10, 5)
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop(Enumerable.t, non_neg_integer) :: t
def drop(enumerable, n) when n >= 0 do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn
_entry, { acc, n } when n > 0 ->
{ acc, n - 1 }
entry, { acc, n } ->
{ f1.(entry, acc), n }
end
end,
acc: n]
end
@doc """
Lazily drops elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.drop_while(1..10, &(&1 <= 5))
iex> Enum.to_list(stream)
[6,7,8,9,10]
"""
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
def drop_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn
entry, { acc, true } ->
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
entry, { acc, false } ->
{ f1.(entry, acc), false }
end
end,
acc: true]
end
@doc """
Creates a stream that will filter elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[2]
"""
@spec filter(Enumerable.t, (element -> as_boolean(term))) :: t
def filter(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
if f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Emit a sequence of values, starting with `start_value`. Successive
values are generated by calling `next_fun` on the previous value.
## Examples
iex> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
[0,1,2,3,4]
"""
@spec iterate(element, (element -> element)) :: t
def iterate(start_value, next_fun) do
fn acc, fun ->
do_iterate(start_value, next_fun, fun.(start_value, acc), fun)
end
end
defp do_iterate(value, next_fun, acc, fun) do
next = next_fun.(value)
do_iterate(next, next_fun, fun.(next, acc), fun)
end
@doc """
Creates a stream that will apply the given function on
enumeration.
## Examples
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
iex> Enum.to_list(stream)
[2,4,6]
"""
@spec map(Enumerable.t, (element -> any)) :: t
def map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
f1.(f.(entry), acc)
end
end]
end
@doc """
Creates a stream that will apply the given function on enumeration and
flatten the result.
## Examples
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
iex> Enum.to_list(stream)
[1, 2, 2, 4, 3, 6]
"""
@spec flat_map(Enumerable.t, (element -> any)) :: t
def flat_map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) -> do_flat_map(f.(entry), acc, f1) end
end]
end
defp do_flat_map(Lazy[] = lazy, acc, f1) do
try do
Enumerable.reduce(lazy, acc, fn x, y ->
try do
f1.(x, y)
catch
{ :stream_lazy, nesting, rest } ->
throw({ :stream_flat_map, nesting, rest })
end
end)
catch
{ :stream_flat_map, nesting, rest } ->
throw({ :stream_lazy, nesting, rest })
end
end
defp do_flat_map(enum, acc, f1) do
Enumerable.reduce(enum, acc, f1)
end
@doc """
Creates a stream that will reject elements according to
the given function on enumeration.
## Examples
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
iex> Enum.to_list(stream)
[1,3]
"""
@spec reject(Enumerable.t, (element -> as_boolean(term))) :: t
def reject(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
unless f.(entry), do: f1.(entry, acc), else: acc
end
end]
end
@doc """
Returns a stream generated by calling `generator_fun` repeatedly.
## Examples
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
"""
@spec repeatedly((() -> element)) :: t
def repeatedly(generator_fun)
when is_function(generator_fun, 0) do
&do_repeatedly(generator_fun, &1, &2)
end
defp do_repeatedly(generator_fun, acc, fun) do
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
end
@doc """
Lazily takes the next `n` items from the enumerable and stops
enumeration.
## Examples
iex> stream = Stream.take(1..100, 5)
iex> Enum.to_list(stream)
[1,2,3,4,5]
iex> stream = Stream.cycle([1, 2, 3]) |> Stream.take(5)
iex> Enum.to_list(stream)
[1,2,3,1,2]
"""
@spec take(Enumerable.t, non_neg_integer) :: t
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: &(&1)]
def take(enumerable, n) when n > 0 do
Lazy[enumerable: enumerable,
fun: fn(f1, nesting) ->
fn(entry, { acc, n }) ->
res = f1.(entry, acc)
if n > 1, do: { res, n-1 }, else: throw { :stream_lazy, nesting, res }
end
end,
acc: n]
end
@doc """
Lazily takes elements of the enumerable while the given
function returns true.
## Examples
iex> stream = Stream.take_while(1..100, &(&1 <= 5))
iex> Enum.to_list(stream)
[1,2,3,4,5]
"""
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
def take_while(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1, nesting) ->
fn(entry, { acc, true }) ->
if f.(entry) do
{ f1.(entry, acc), true }
else
throw { :stream_lazy, nesting, acc }
end
end
end,
acc: true]
end
@doc """
Emit a sequence of values and accumulators. Successive values are generated by
calling `next_fun` with the previous accumulator.
If the return value is nil iteration ends.
## Examples
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
[5, 4, 3, 2, 1]
"""
@spec unfold(acc, (acc -> { element, acc } | nil)) :: t
def unfold(acc, f) do
fn acc1, f1 ->
do_unfold(acc, f, acc1, f1)
end
end
defp do_unfold(gen_acc, gen_fun, acc, fun) do
case gen_fun.(gen_acc) do
nil -> acc
{ v, new_gen_acc } -> do_unfold(new_gen_acc, gen_fun, fun.(v, acc), fun)
end
end
@doc """
Creates a stream where each item in the enumerable will
be accompanied by its index.
## Examples
iex> stream = Stream.with_index([1, 2, 3])
iex> Enum.to_list(stream)
[{1,0},{2,1},{3,2}]
"""
@spec with_index(Enumerable.t) :: t
def with_index(enumerable) do
Lazy[enumerable: enumerable,
fun: fn(f1, _) ->
fn(entry, { acc, counter }) ->
acc = f1.({ entry, counter }, acc)
{ acc, counter + 1 }
end
end,
acc: 0]
end
end
File diff suppressed because it is too large Load Diff
-84
View File
@@ -1,84 +0,0 @@
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc %S"""
The String.Chars protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_string` which does the conversion.
The `to_string` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_string in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_string(bar)`.
"""
def to_string(thing)
end
defimpl String.Chars, for: Atom do
@doc """
Convert the atom literally to a binary, except
`nil` which is converted to an empty string.
"""
def to_string(nil) do
""
end
def to_string(atom) do
atom_to_binary(atom)
end
end
defimpl String.Chars, for: BitString do
@doc """
Returns the given binary or raises an error for bitstrings.
"""
def to_string(thing) when is_binary(thing) do
thing
end
def to_string(thing) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: thing,
description: "cannot convert a bitstring to a string"
end
end
defimpl String.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
iex> to_string('foo')
"foo"
iex> to_string(["foo", 'bar'])
"foobar"
"""
def to_string(char_list), do: String.from_char_list!(char_list)
end
defimpl String.Chars, for: Integer do
@doc """
Simply converts the integer to a string.
"""
def to_string(thing) do
integer_to_binary(thing)
end
end
defimpl String.Chars, for: Float do
@doc """
Simply converts the float to a string.
"""
def to_string(thing) do
iolist_to_binary(:io_lib_format.fwrite_g(thing))
end
end
-184
View File
@@ -1,184 +0,0 @@
defmodule Supervisor.Behaviour do
@moduledoc """
This module is a convenience to define Supervisor
callbacks in Elixir. By using this module, you get
the module behaviour automatically tagged as
`:supervisor` and some helper functions are imported
to make defining supervisors easier.
For more information on supervisors, please check the
remaining functions defined in this module or refer to
the following:
http://www.erlang.org/doc/man/supervisor.html
http://www.erlang.org/doc/design_principles/sup_princ.html
http://learnyousomeerlang.com/supervisors
## Example
defmodule ExUnit.Sup do
use Supervisor.Behaviour
def init(user_options) do
tree = [ worker(ExUnit.Runner, [user_options]) ]
supervise(tree, strategy: :one_for_one)
end
end
{ :ok, pid } = :supervisor.start_link(MyServer, [])
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@behaviour :supervisor
import unquote(__MODULE__)
end
end
@doc """
Receives a list of children (worker or supervisors) to
supervise and a set of options. Returns a tuple containing
the supervisor specification.
## Examples
supervise children, strategy: :one_for_one
## Options
* `:strategy` - the restart strategy option It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
`:simple_one_for_one`;
* `:max_restarts` - the maximum amount of restarts allowed in
a time frame. Defaults to 5;
* `:max_seconds` - the time frame in which max_restarts applies.
Defaults to 5;
The `:strategy` option is required and by default maximum 5 restarts
are allowed in 5 seconds.
## Strategies
* `:one_for_one` - If a child process terminates, only that
process is restarted;
* `:one_for_all` - If a child process terminates, all other child
processes are terminated and then all child processes, including
the terminated one, are restarted;
* `:rest_for_one` - If a child process terminates, the "rest" of
the child processes, i.e. the child processes after the terminated
process in start order, are terminated. Then the terminated child
process and the rest of the child processes are restarted;
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
when dynamically attaching children;
"""
def supervise(children, options) do
unless strategy = options[:strategy] do
raise ArgumentError, message: "expected :strategy option to be given to supervise"
end
maxR = Keyword.get(options, :max_restarts, 5)
maxS = Keyword.get(options, :max_seconds, 5)
{ :ok, { { strategy, maxR, maxS }, children } }
end
@child_doc """
## Options
* `:id` - a name used to identify the child specification
internally by the supervisor. Defaults to the module name;
* `:function` - the function to invoke on the child to start it.
Defaults to `:start_link`;
* `:restart` - defines when the child process should restart.
Defaults to `:permanent`;
* `:shutdown` - defines how a child process should be terminated.
Defaults to `5000` for a worker and `:infinity` for a supervisor;
* `:modules` - it should be a list with one element `[module]`,
where module is the name of the callback module only if the
child process is a supervisor, `gen_server` or `gen_fsm`. If the
child process is a gen_event, modules should be `:dynamic`.
Defaults to a list with the given module;
## Restart values
The following restart values are supported:
* `:permanent` - the child process is always restarted;
* `:temporary` - the child process is never restarted (not even
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
* `:transient` - the child process is restarted only if it
terminates abnormally, i.e. with another exit reason than
`:normal`, `:shutdown` or `{ :shutdown, term }`;
## Shutdown values
The following shutdown values are supported:
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`;
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care;
* Finally, it can also be any integer meaning that the supervisor tells
the child process to terminate by calling `exit(child, :shutdown)` and
then waits for an exit signal back. If no exit signal is received within
the specified time (in miliseconds), the child process is unconditionally
terminated using `exit(child, :kill)`;
"""
@doc """
Defines the given `module` as a worker which will be started
with the given arguments.
worker ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def worker(module, args, options // []) do
child(:worker, module, args, options)
end
@doc """
Defines the given `module` as a supervisor which will be started
with the given arguments.
supervisor ExUnit.Runner, [], restart: :permanent
By default, the function `:start_link` is invoked on the given module.
#{@child_doc}
"""
def supervisor(module, args, options // []) do
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
child(:supervisor, module, args, options)
end
defp child(type, module, args, options) do
id = Keyword.get(options, :id, module)
modules = Keyword.get(options, :modules, [module])
function = Keyword.get(options, :function, :start_link)
restart = Keyword.get(options, :restart, :permanent)
shutdown = Keyword.get(options, :shutdown, 5000)
{ id, { module, function, args },
restart, shutdown, type, modules }
end
end
-388
View File
@@ -1,388 +0,0 @@
defmodule System do
defexception NoHomeError,
message: "could not find the user home, please set the HOME environment variable"
defexception NoTmpDirError,
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
defexception NoAccessCwdError,
message: "could not get a current working directory, the current location is not accessible"
@moduledoc """
The System module provides access to variables used or
maintained by the VM and to functions that interact directly
with the VM or the host system.
"""
defp strip_re(iodata, pattern) do
:re.replace(iodata, pattern, "", [return: :binary])
end
defp read_stripped(path) do
case :file.read_file(path) do
{ :ok, binary } ->
strip_re(binary, "^\s+|\s+$")
_ -> ""
end
end
# Read and strip the version from the `VERSION` file.
defmacrop get_version do
case read_stripped("VERSION") do
"" -> raise CompileError, message: "could not read the version number from VERSION"
data -> data
end
end
# Tries to run `git describe --always --tags`. In the case of success returns
# the most recent tag. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_describe do
dirpath = ".git"
case :file.read_file_info(dirpath) do
{ :ok, _ } ->
if :os.find_executable('git') do
data = :os.cmd('git describe --always --tags')
strip_re(data, "\n")
else
read_stripped(:filename.join(".git", "HEAD"))
end
_ -> ""
end
end
# Get the date at compilation time.
defmacrop get_date do
iolist_to_binary :httpd_util.rfc1123_date
end
@doc """
Elixir version information.
Returns Elixir's version as binary.
"""
@spec version() :: String.t
def version, do: get_version
@doc """
Elixir build information.
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: Keyword.t
def build_info do
[version: version, tag: get_describe, date: get_date]
end
@doc """
List command line arguments.
Returns the list of command line arguments passed to the program.
"""
@spec argv() :: [String.t]
def argv do
:elixir_code_server.call :argv
end
@doc """
Modify command line arguments.
Changes the list of command line arguments. Use it with caution,
as it destroys any previous argv information.
"""
@spec argv([String.t]) :: :ok
def argv(args) do
:elixir_code_server.cast({ :argv, args })
end
@doc """
Current working directory.
Returns the current working directory or `nil` if one
is not available.
"""
def cwd do
case :file.get_cwd do
{ :ok, base } -> String.from_char_list!(base)
_ -> nil
end
end
@doc """
Current working directory, exception on error.
Returns the current working directory or raises `System.NoAccessCwdError`.
"""
def cwd! do
cwd || raise NoAccessCwdError
end
@doc """
User home directory.
Returns the user home directory (platform independent).
Returns `nil` if no user home is set.
"""
def user_home do
case :os.type() do
{ :win32, _ } -> get_windows_home
_ -> get_unix_home
end
end
@doc """
User home directory, exception on error.
Same as `user_home/0` but raises `System.NoHomeError`
instead of returning `nil` if no user home is set.
"""
def user_home! do
user_home || raise NoHomeError
end
defp get_unix_home do
get_env("HOME")
end
defp get_windows_home do
:filename.absname(
get_env("USERPROFILE") || (
hd = get_env("HOMEDRIVE")
hp = get_env("HOMEPATH")
hd && hp && hd <> hp
)
)
end
@doc %S"""
Writable temporary directory.
Returns a writable temporary directory.
Searches for directories in the following order:
1. The directory named by the TMPDIR environment variable
2. The directory named by the TEMP environment variable
3. The directory named by the TMP environment variable
4. `C:\TMP` on Windows or `/tmp` on Unix
5. As a last resort, the current working directory
Returns `nil` if none of the above are writable.
"""
def tmp_dir do
write_env_tmp_dir('TMPDIR') ||
write_env_tmp_dir('TEMP') ||
write_env_tmp_dir('TMP') ||
write_tmp_dir('/tmp') ||
((cwd = cwd()) && write_tmp_dir(cwd))
end
@doc """
Writable temporary directory, exception on error.
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
instead of returning `nil` if no temp dir is set.
"""
def tmp_dir! do
tmp_dir || raise NoTmpDirError
end
defp write_env_tmp_dir(env) do
case :os.getenv(env) do
false -> nil
tmp -> write_tmp_dir(tmp)
end
end
defp write_tmp_dir(dir) do
case :file.read_file_info(dir) do
{:ok, info} ->
type_index = File.Stat.__record__(:index, :type)
access_index = File.Stat.__record__(:index, :access)
case { elem(info, type_index), elem(info, access_index) } do
{ :directory, access } when access in [:read_write, :write] ->
String.from_char_list!(dir)
_ ->
nil
end
{ :error, _ } -> nil
end
end
@doc """
Register a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
invoking a hook in "script" mode.
The function must receive the exit status code
as an argument.
"""
def at_exit(fun) when is_function(fun, 1) do
:elixir_code_server.cast { :at_exit, fun }
end
@doc """
Execute a system command.
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.
"""
@spec cmd(binary) :: binary
@spec cmd(char_list) :: char_list
def cmd(command) when is_list(command) do
:os.cmd(command)
end
def cmd(command) when is_binary(command) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
end
@doc """
Locate an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
and Windows. It also considers the proper executable
extension for each OS, so for Windows it will try to
lookup files with `.com`, `.cmd` or similar extensions.
If `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
def find_executable(program) when is_list(program) do
:os.find_executable(program) || nil
end
def find_executable(program) when is_binary(program) do
# Notice we don't use unicode for conversion
# because the OS is expecting and returning raw bytes
case :os.find_executable(:binary.bin_to_list(program)) do
false -> nil
other -> :binary.list_to_bin(other)
end
end
@doc """
System environment variables.
Returns a list of all environment variables. Each variable is given as a
`{name, value}` tuple where both `name` and `value` are strings.
"""
@spec get_env() :: [{String.t, String.t}]
def get_env do
Enum.map(:os.getenv, fn var ->
var = String.from_char_list! var
[k, v] = String.split var, "=", global: false
{k, v}
end)
end
@doc """
Environment variable value.
Returns the value of the environment variable
`varname` as a binary, or `nil` if the environment
variable is undefined.
"""
@spec get_env(binary) :: binary | nil
def get_env(varname) when is_binary(varname) do
case :os.getenv(String.to_char_list!(varname)) do
false -> nil
other -> String.from_char_list!(other)
end
end
@doc """
Erlang VM process identifier.
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.
"""
@spec get_pid() :: binary
def get_pid, do: iolist_to_binary(:os.getpid)
@doc """
Set an environment variable value.
Sets a new `value` for the environment variable `varname`.
"""
@spec put_env(binary, binary) :: :ok
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
:ok
end
@doc """
Set multiple environment variables.
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@spec put_env(Dict.t) :: :ok
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@doc """
Last exception stacktrace.
Note that the Erlang VM (and therefore this function) does not
return the current stacktrace but rather the stacktrace of the
latest exception.
"""
def stacktrace do
:erlang.get_stacktrace
end
@doc """
Halt the Erlang runtime system.
Halts the Erlang runtime system where the argument `status` must be a
non-negative integer, the atom `:abort` or a binary.
* If an integer, the runtime system exits with the integer value which
is returned to the operating system;
* If `:abort`, the runtime system aborts producing a core dump, if that is
enabled in the operating system;
* If a char list, an erlang crash dump is produced with status as slogan,
and then the runtime system exits with status code 1;
Note that on many platforms, only the status codes 0-255 are supported
by the operating system.
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-1
## Examples
System.halt(0)
System.halt(1)
System.halt(:abort)
"""
@spec halt() :: no_return
@spec halt(non_neg_integer | binary | :abort) :: no_return
def halt(status // 0)
def halt(status) when is_integer(status) or status == :abort do
:erlang.halt(status)
end
def halt(status) when is_binary(status) do
:erlang.halt(:binary.bin_to_list(status))
end
end
-60
View File
@@ -1,60 +0,0 @@
defmodule Tuple do
@moduledoc """
Functions for working with tuples.
"""
@doc """
Create a new tuple.
Creates a tuple of size `size` containing the
given `data` at every position.
## Examples
iex> Tuple.duplicate(:hello, 3)
{ :hello, :hello, :hello }
"""
@spec duplicate(term, non_neg_integer) :: tuple
def duplicate(data, size) do
:erlang.make_tuple(size, data)
end
@doc """
Insert an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is greater than the
length of `tuple`.
## Examples
iex> tuple = { :bar, :baz }
...> Tuple.insert_at(tuple, 0, :foo)
{ :foo, :bar, :baz }
"""
@spec insert_at(tuple, non_neg_integer, term) :: tuple
def insert_at(tuple, index, term) do
:erlang.insert_element(index + 1, tuple, term)
end
@doc """
Remove an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is greater than
or equal to the length of `tuple`.
## Examples
iex> tuple = { :foo, :bar, :baz }
...> Tuple.delete_at(tuple, 0)
{ :bar, :baz }
"""
@spec delete_at(tuple, non_neg_integer) :: tuple
def delete_at(tuple, index) do
:erlang.delete_element(index + 1, tuple)
end
end
-233
View File
@@ -1,233 +0,0 @@
defmodule URI do
@moduledoc """
Utilities for working with and creating URIs.
"""
defrecord Info, [scheme: nil, path: nil, query: nil,
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil]
import Bitwise
@ports [
{ "ftp", 21 },
{ "http", 80 },
{ "https", 443 },
{ "ldap", 389 },
{ "sftp", 22 },
{ "tftp", 69 },
]
Enum.each @ports, fn { scheme, port } ->
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
def default_port(unquote(scheme)), do: unquote(port)
end
@doc """
Normalizes the scheme according to the spec by downcasing it.
"""
def normalize_scheme(nil), do: nil
def normalize_scheme(scheme), do: String.downcase(scheme)
@doc """
Returns the default port for a given scheme.
If the scheme is unknown to URI, returns `nil`.
Any scheme may be registered via `default_port/2`.
"""
def default_port(scheme) when is_binary(scheme) do
{ :ok, dict } = :application.get_env(:elixir, :uri)
Dict.get(dict, scheme)
end
@doc """
Registers a scheme with a default port.
"""
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
{ :ok, dict } = :application.get_env(:elixir, :uri)
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
end
@doc """
Takes an enumerable (containing a sequence of two-item tuples)
and returns a string of the form "k=v&k2=v2..." where keys and values are
URL encoded as per `encode/1`. Keys and values can be any term
that implements the `String.Chars` protocol (i.e. can be converted
to a 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 `query_decoder/1` if you want to iterate over each value manually.
"""
def decode_query(q, dict // HashDict.new) when is_binary(q) 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) when is_binary(q) do
fn(acc, fun) ->
do_decoder(q, acc, fun)
end
end
defp do_decoder("", acc, _fun) do
acc
end
defp do_decoder(q, acc, fun) 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
do_decoder(next, fun.(current, acc), fun)
end
defp pair({k, v}) do
encode(to_string(k)) <> "=" <> encode(to_string(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: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
defp hex(n) when n <= 9, do: <<n + ?0>>
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 no 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) when is_binary(s) do
# From http://tools.ietf.org/html/rfc3986#appendix-B
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
parts = nillify(Regex.run(regex, s))
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
{ userinfo, host, port } = split_authority(authority)
if authority do
authority = ""
if userinfo, do: authority = authority <> userinfo <> "@"
if host, do: authority = authority <> host
if port, do: authority = authority <> ":" <> integer_to_binary(port)
end
scheme = normalize_scheme(scheme)
if nil?(port) and not nil?(scheme) do
port = default_port(scheme)
end
URI.Info[
scheme: scheme, path: path, query: query,
fragment: fragment, authority: authority,
userinfo: userinfo, host: host, port: port
]
end
# Split an authority into its userinfo, host and port parts.
defp split_authority(s) do
s = s || ""
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
destructure [_, _, userinfo, host, _, port], nillify(components)
port = if port, do: binary_to_integer(port)
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
{ 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
defimpl String.Chars, for: URI.Info do
def to_string(URI.Info[] = uri) do
scheme = uri.scheme
if scheme && (port = URI.default_port(scheme)) do
if uri.port == port, do: uri = uri.port(nil)
end
result = ""
if uri.scheme, do: result = result <> uri.scheme <> "://"
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
if uri.host, do: result = result <> uri.host
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
result
end
end
-528
View File
@@ -1,528 +0,0 @@
defmodule Version do
@moduledoc %S"""
Functions for parsing and matching versions against requirements.
A version is a string in a specific format or a `Version.Schema`
generated after parsing via `Version.parse/1`.
`Version` parsing and requirements follow
[SemVer 2.0 schema](http://semver.org/) and you will get
the most of Mix's version system by following it. In order
to support integration with projects that may
follow different versioning schemas, Elixir won't choke
on unknown versions, however you won't be able to use
Mix requirements with such unformatted versions.
## Versions
In a nutshell, a version is given by three numbers:
MAJOR.MINOR.PATCH
Pre-releases are supported by appending `-[0-9A-Za-z-\.]`:
"1.0.0-alpha.3"
Build information can be added by appending `+[0-9A-Za-z-\.]`:
"1.0.0-alpha.3+20130417140000"
## Requirements
Requirements allow you to specify which versions of a given
dependency you are willing to work against. It supports common
operators like `>=`, `<=`, `>`, `==` and friends that
work as one would expect:
# Only version 2.0.0
"== 2.0.0"
# Anything later than 2.0.0
"> 2.0.0"
Requirements also support `and` and `or` for complex conditions:
# 2.0.0 and later until 2.1.0
">= 2.0.0 and < 2.1.0"
Since the example above is such a common requirement, it can
be expressed as:
"~> 2.0.0"
"""
@type t :: String.t | Version.Schema.t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: { major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t] }
import Kernel, except: [match?: 2]
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
defrecord Requirement, source: nil, matchspec: nil
defexception InvalidRequirement, reason: :invalid_requirement do
def message(InvalidRequirement[reason: reason]) when is_binary(reason) do
{ first, rest } = String.next_grapheme(reason)
String.downcase(first) <> rest
end
def message(InvalidRequirement[]) do
"invalid version specification"
end
end
@doc """
Check if the given version matches the specification.
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
## Examples
iex> Version.match?("2.0", ">1.0")
true
iex> Version.match?("2.0", "==1.0")
false
"""
@spec match?(t, requirement) :: boolean
def match?(version, requirement) when is_binary(requirement) do
case Version.Parser.parse_requirement(requirement) do
{ :ok, req } ->
match?(version, req)
{ :error, reason } ->
raise InvalidRequirement, reason: reason
end
end
def match?(version, requirement) when is_binary(version) do
match?(parse(version), requirement)
end
def match?(Schema[] = version, Requirement[matchspec: spec]) do
case :ets.test_ms(to_matchable(version), spec) do
{ :ok, result } ->
result != false
{ :error, reason } ->
raise InvalidRequirement, reason: reason
end
end
@doc """
Check if a version string is compatible with [semver](http://semver.org/).
## Examples
iex> Version.valid?("2.0")
true
iex> Version.valid?("invalid")
false
"""
@spec valid?(String.t | Schema.t) :: boolean
def valid?(string) when is_binary(string) do
Version.Parser.valid_version?(string)
end
def valid?(Version.Schema[major: nil]), do: false
def valid?(Version.Schema[]), do: true
@doc """
Parse a version string into a `Version.Schema`.
## Examples
> Version.parse("2.0.1-alpha1")
#Version.Schema<2.0.1-alpha1>
"""
@spec parse(String.t) :: Schema.t
def parse(string) when is_binary(string) do
case Version.Parser.parse_version(string) do
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
{ :error, _ } -> Version.Schema[source: string]
end
end
defp get_build(string) do
case Regex.run(%r/\+([^\s]+)$/, string) do
nil ->
nil
[_, build] ->
build
end
end
@doc """
Convert a version to a `Version.matchable`
## Examples
iex> Version.to_matchable("2.0.1-alpha.1")
{2, 0, 1, ["alpha", 1]}
"""
@spec to_matchable(String.t | Schema.t) :: Version.matchable
def to_matchable(Schema[major: nil, source: source]) do
{ source, nil, nil, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
{ major, minor, patch, [] }
end
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
{ major, minor, patch, Version.Parser.parse_pre(pre) }
end
def to_matchable(string) do
to_matchable(parse(string))
end
@doc """
Convert a matchable to a `Version.Schema`.
## Examples
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
#Version.Schema<2.0.1-alpha.1>
"""
@spec from_matchable(Version.matchable) :: Schema.t
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
Version.Schema[source: source]
end
def from_matchable({ major, minor, patch, pre }) do
source = "#{major}"
if minor do
source = "#{source}.#{minor}"
if patch do
source = "#{source}.#{patch}"
case pre do
[] ->
pre = nil
list ->
pre = Enum.join(list, ".")
source = "#{source}-#{pre}"
end
end
end
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
end
defmodule Parser.DSL do
@moduledoc false
defmacro deflexer(match, do: body) when is_binary(match) do
quote do
def lexer(unquote(match) <> rest, acc) do
lexer(rest, [unquote(body) | acc])
end
end
end
defmacro deflexer(acc, do: body) do
quote do
def lexer("", unquote(acc)) do
unquote(body)
end
end
end
defmacro deflexer(char, acc, do: body) do
quote do
def lexer(<< unquote(char) :: utf8, rest :: binary >>, unquote(acc)) do
unquote(char) = << unquote(char) :: utf8 >>
lexer(rest, unquote(body))
end
end
end
end
defmodule Parser do
@moduledoc false
import Parser.DSL
deflexer ">=", do: :'>='
deflexer "<=", do: :'<='
deflexer "~>", do: :'~>'
deflexer ">", do: :'>'
deflexer "<", do: :'<'
deflexer "==", do: :'=='
deflexer "!=", do: :'!='
deflexer "!", do: :'!='
deflexer " or ", do: :'||'
deflexer " and ", do: :'&&'
deflexer " ", do: :' '
deflexer x, [] do
[x, :'==']
end
deflexer x, [h | acc] do
cond do
is_binary h ->
[h <> x | acc]
h in [:'||', :'&&'] ->
[x, :'==', h | acc]
true ->
[x, h | acc]
end
end
deflexer acc do
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
end
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
def parse_requirement(source) do
lexed = lexer(source, [])
if valid_requirement?(lexed) do
spec = to_matchspec(lexed)
case :ets.test_ms({}, spec) do
{ :ok, _ } ->
{ :ok, Requirement[source: source, matchspec: spec] }
{ :error, errors } ->
{ :error, Enum.map(errors, fn { :error, reason } ->
to_string(reason)
end) }
end
else
{ :error, :invalid_requirement }
end
end
defp nillify(""), do: nil
defp nillify(o), do: o
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
def parse_version(string) when is_binary(string) do
if valid_version?(string) do
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
major = binary_to_integer(major)
minor = binary_to_integer(minor |> nillify || "0")
patch = binary_to_integer(patch |> nillify || "0")
pre = pre && parse_pre(pre) || []
{ :ok, { major, minor, patch, pre } }
else
{ :error, :invalid_version }
end
end
@doc false
def parse_pre(pre) do
String.split(pre, ".") |> Enum.map fn piece ->
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
binary_to_integer(piece)
else
piece
end
end
end
@spec valid_requirement?(list) :: boolean
def valid_requirement?([]) do
false
end
def valid_requirement?([a | next]) do
valid_requirement?(a, next)
end
# it must finish with a version
defp valid_requirement?(a, []) when is_binary(a) do
true
end
# version version
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
false
end
# or <op> | and <op>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
valid_requirement?(b, next)
end
# <version> or | <version> and
defp valid_requirement?(a, [b | next]) when is_binary(a) and is_atom(b) and b in [:'||', :'&&'] do
valid_requirement?(b, next)
end
# or <version> | and <version>
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) and a in [:'||', :'&&'] do
valid_requirement?(b, next)
end
# <op> <version>; also checks operators work on valid versions
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
if valid_version?(b) do
valid_requirement?(b, next)
else
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
valid_requirement?(b, next)
else
false
end
end
end
defp valid_requirement?(_, _) do
false
end
@spec valid_version?(String.t) :: boolean
def valid_version?(string) do
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
end
defp approximate(version) do
Version.from_matchable(case Regex.run(@version_regex, version) do
[_, major] ->
{ binary_to_integer(major) + 1, 0, 0, [] }
[_, major, _] ->
{ binary_to_integer(major) + 1, 0, 0, [] }
[_, major, minor, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
[_, major, minor, _, _] ->
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
end)
end
defp to_matchspec(lexed) do
first = to_condition(lexed)
rest = Enum.drop(lexed, 2)
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
end
defp to_condition([:'==', version | _]) do
version = Version.to_matchable(version)
{ :'==', :'$_', { :const, version } }
end
defp to_condition([:'!=', version | _]) do
version = Version.to_matchable(version)
{ :'/=', :'$_', { :const, version } }
end
defp to_condition([:'~>', version | _]) do
from = Version.parse(version)
to = approximate(version)
{ :andalso, to_condition([:'>=', to_string(from)]),
to_condition([:'<', to_string(to)]) }
end
defp to_condition([:'>', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
{ :'/=', length(pre), 0 } },
{ :andalso, { :'/=', length(pre), 0 },
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'>', :'$4', { :const, pre } } } } } } } } }
end
defp to_condition([:'>=', version | _]) do
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'>', version]) }
end
defp to_condition([:'<', version | _]) do
{ major, minor, patch, pre } = Version.to_matchable(version)
{ :andalso, { :not, { :is_binary, :'$1' } },
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
{ :const, { major, minor, patch } } },
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :'==', length(pre), 0 } },
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
{ :'<', :'$4', { :const, pre } } } } } } } } }
end
defp to_condition([:'<=', version | _]) do
matchable = Version.to_matchable(version)
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
{ :'==', :'$_', { :const, matchable } } },
to_condition([:'<', version]) }
end
defp to_condition(current, []) do
current
end
defp to_condition(current, [:'&&', operator, version | rest]) do
to_condition({ :andalso, current, to_condition([operator, version]) }, rest)
end
defp to_condition(current, [:'||', operator, version | rest]) do
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
end
end
end
defimpl String.Chars, for: Version.Schema do
def to_string(Version.Schema[source: source]) do
source
end
end
defimpl Inspect, for: Version.Schema do
def inspect(self, _opts) do
"#Version.Schema<" <> to_string(self) <> ">"
end
end
defimpl String.Chars, for: Version.Requirement do
def to_string({ _, source, _ }) do
source
end
end
defimpl Inspect, for: Version.Requirement do
def inspect({ _, source, _ }, _opts) do
"#Version.Requirement<" <> source <> ">"
end
end
-11
View File
@@ -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
File diff suppressed because it is too large Load Diff
-103
View File
@@ -1,103 +0,0 @@
00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
File diff suppressed because it is too large Load Diff

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