diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml
index 474654e1c..06645680d 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -379,6 +379,16 @@ jobs:
./src/AtomVM ./tests/libs/alisp/test_alisp.avm
valgrind ./src/AtomVM ./tests/libs/alisp/test_alisp.avm
+ - name: "Test: Tests.avm (Elixir)"
+ timeout-minutes: 10
+ working-directory: build
+ run: |
+ if command -v elixirc &> /dev/null
+ then
+ ./src/AtomVM ./tests/libs/exavmlib/Tests.avm
+ valgrind ./src/AtomVM ./tests/libs/exavmlib/Tests.avm
+ fi
+
- name: "Install and smoke test"
working-directory: build
run: |
diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml
index 9883a5d54..e2c20908e 100644
--- a/.github/workflows/build-docs.yaml
+++ b/.github/workflows/build-docs.yaml
@@ -77,6 +77,15 @@ jobs:
python3 -m pip install breathe
python3 -m pip install pygments
+ - name: Set docs target name
+ shell: bash
+ run: |
+ if [[ ${{ github.ref_name }} == *"/merge" ]]; then
+ echo "AVM_DOCS_NAME=${{github.event.pull_request.base.ref}}" >> "$GITHUB_ENV";
+ else
+ echo "AVM_DOCS_NAME=${{ github.ref_name }}" >> "$GITHUB_ENV";
+ fi
+
- uses: actions/checkout@v4
with:
repository: ${{ vars.GITHUB_REPOSITORY }}
diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml
index 8b599e075..ff8e52f9e 100644
--- a/.github/workflows/publish-docs.yaml
+++ b/.github/workflows/publish-docs.yaml
@@ -38,6 +38,9 @@ jobs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
+ env:
+ AVM_DOCS_NAME: ${{ github.ref_name }}
+
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee0012e5a..1a8550bb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,14 +10,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a limited implementation of the OTP `ets` interface
- Added `code:all_loaded/0` and `code:all_available/0`
-## [0.6.4] - Unreleased
+## [0.6.4] - 2024-08-18
### Added
- Implement `gpio:init/1` on esp32 to initialize pins for GPIO usage, which some pins
require depending on default function and bootloader code
-
-## [0.6.3] - 20-07-2024
+- Implement missing opcode 161 (raw_raise), that looks more likely to be generated with Elixir code
+- Support for Elixir `Map.replace/3` and `Map.replace!/3`
+- Support for Elixir `Kernel.struct` and `Kernel.struct!`
+- Support for Elixir `IO.iodata_to_binary/1`
+- Support for Elixir exceptions: `Exception` module and the other error related modules such as
+`ArgumentError`, `UndefinedFunctionError`, etc...
+- Support for Elixir `Enumerable` and `Collectable` protocol
+- Support for Elixir `Enum` functions: `split_with`, `join`, `map_join`, `into`, `reverse`,
+`slice` and `to_list`
+- Support for Elixir `MapSet` module
+- Support for Elixir `Range` module
+- Support for Elixir `Kernel.min` and `Kernel.max`
+- Support (as stub) for `erlang:error/3` (that is required from Elixir code)
+
+## [0.6.3] - 2024-07-20
### Added
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index ba2365c5a..857391ba1 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -35,9 +35,18 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/edoc/edown_dep DESTINATION ${CMAKE_CURRENT
# Configure libAtomVM restucturedtext skeleton.
file(GLOB SOURCE_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_SOURCE_DIR}/src/libAtomVM/ ${CMAKE_SOURCE_DIR}/src/libAtomVM/*.c ${CMAKE_SOURCE_DIR}/src/libAtomVM/*.h)
+set(OMIT_FILES
+ "defaultatoms.c"
+ "opcodesswitch.h"
+ "scheduler.c"
+ "tempstack.h"
+)
foreach(SOURCE_FILE ${SOURCE_FILES})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/apidocs/libatomvm/file.rst.in ${CMAKE_CURRENT_BINARY_DIR}/src/apidocs/libatomvm/src/${SOURCE_FILE}.rst @ONLY)
endforeach(SOURCE_FILE)
+foreach(OMIT ${OMIT_FILES})
+ file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/src/apidocs/libatomvm/src/${OMIT}.rst)
+endforeach(OMIT)
# Support for edoc -> markdown.
add_custom_target(edown-escript
@@ -46,11 +55,21 @@ add_custom_target(edown-escript
COMMENT "Preparing edown escript" VERBATIM
)
-# Get the version tree name, tag if this is a tagged commit, otherwise main.
-execute_process(COMMAND "bash" "-c" "tag=$(git for-each-ref --points-at=HEAD --format='%(refname:lstrip=2)' refs/tags); ( [ $tag ] && echo $tag ) || echo 'main'"
- OUTPUT_VARIABLE
- DOC_TREE_VERSION
- OUTPUT_STRIP_TRAILING_WHITESPACE )
+# Get the version tree name, tag if this is a tagged commit, otherwise get the current branch name.
+if ($ENV{CI})
+ set(DOC_TREE_VERSION $ENV{AVM_DOCS_NAME})
+ message("CI building documentation for target branch ${DOC_TREE_VERSION}")
+else()
+ execute_process(COMMAND "bash" "-c" "tag=$(git for-each-ref --points-at=HEAD --format='%(refname:lstrip=2)' refs/tags); ( [ $tag ] && echo $tag )|| git branch --show-current"
+ OUTPUT_VARIABLE
+ DOC_TREE_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE )
+ message("Local documentation test build on ${DOC_TREE_VERSION}")
+endif($ENV{CI})
+
+
+## conf.py.in must be configured after DOC_TREE_VERSION is defined
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in ${CMAKE_CURRENT_BINARY_DIR}/conf.py @ONLY)
##
## Erlang API documentation
@@ -108,7 +127,6 @@ endif()
find_package(Sphinx)
if(SPHINX_FOUND)
message("Sphinx found: ${SPHINX_BUILD_EXECUTABLE}")
- configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in ${CMAKE_CURRENT_BINARY_DIR}/conf.py @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/pdf_stylesheet.rts DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/pdf_template.rtt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 48e973178..fe5eefe3a 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -465,7 +465,7 @@ EXTRACT_PACKAGE = NO
# included in the documentation.
# The default value is: NO.
-EXTRACT_STATIC = NO
+EXTRACT_STATIC = YES
# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
@@ -520,7 +520,7 @@ HIDE_FRIEND_COMPOUNDS = NO
# blocks will be appended to the function's detailed documentation block.
# The default value is: NO.
-HIDE_IN_BODY_DOCS = NO
+HIDE_IN_BODY_DOCS = YES
# The INTERNAL_DOCS tag determines if documentation that is typed after a
# \internal command is included. If the tag is set to NO then the documentation
@@ -692,7 +692,7 @@ SHOW_FILES = YES
# Folder Tree View (if specified).
# The default value is: YES.
-SHOW_NAMESPACES = YES
+SHOW_NAMESPACES = NO
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
@@ -769,7 +769,7 @@ WARN_IF_DOC_ERROR = YES
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.
-WARN_NO_PARAMDOC = NO
+WARN_NO_PARAMDOC = YES
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
@@ -903,7 +903,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
-EXCLUDE_PATTERNS = */libAtomVM/opcodesswitch.h
+EXCLUDE_PATTERNS = */defaultatoms.c */opcodesswitch.h */scheduler.c */tempstack.h
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1015,7 +1015,7 @@ SOURCE_BROWSER = YES
# classes and enums directly into the documentation.
# The default value is: NO.
-INLINE_SOURCES = NO
+INLINE_SOURCES = YES
# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
# special comment blocks from generated source code fragments. Normal C, C++ and
@@ -2030,7 +2030,7 @@ ENABLE_PREPROCESSING = YES
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-MACRO_EXPANSION = NO
+MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
@@ -2070,7 +2070,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED = DOXYGEN_SKIP_SECTION
+PREDEFINED = DOXYGEN_SKIP_SECTION AVM_TASK_DRIVER_ENABLED ENABLE_ADVANCED_TRACE OTP_SOCKET_LWIP
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@@ -2316,7 +2316,7 @@ DOT_IMAGE_FORMAT = png
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
-INTERACTIVE_SVG = NO
+INTERACTIVE_SVG = YES
# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
diff --git a/doc/_templates/versions.html b/doc/_templates/versions.html
index 7fcb58f17..854d7cb52 100644
--- a/doc/_templates/versions.html
+++ b/doc/_templates/versions.html
@@ -11,14 +11,14 @@
{# Add rst-badge after rst-versions for small badge style. #}
- AtomVM Docs
- v: {{ current_version }}
+ AtomVM Docs version:
+ {{ current_version }}
{% if versions|length >= 1 %}
- - {{ _('Versions') }}
+ - {{ _(' Versions') }}
{% for slug, url in versions %}
{% if slug == current_version %} {% endif %}
- {{ slug }}
@@ -28,7 +28,7 @@
{% endif %}
{% if downloads|length >= 1 %}
- - {{ _('Downloads') }}
+ - {{ _(' Downloads') }}
{% for type, url in downloads %}
- {{ type }}
{% endfor %}
diff --git a/doc/conf.py.in b/doc/conf.py.in
index 0682befc6..d6cdce215 100644
--- a/doc/conf.py.in
+++ b/doc/conf.py.in
@@ -63,7 +63,9 @@ extensions = [
suppress_warnings = [
'epub.unknown_project_files',
'misc.highlighting_failure',
- 'toc.excluded'
+ 'toc.excluded',
+ 'myst.*',
+ 'breathe.*'
]
# Add any paths that contain templates here, relative to this directory.
@@ -164,14 +166,26 @@ repo = Repo( search_parent_directories=True )
tag_list = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
latest_tag = tag_list[-1]
versions = list()
+release_list = list()
for tag in tag_list:
versions.append(tag.name)
+ release_list.append(tag.name)
+
+omit_branch_list = ('release-0.5')
+branch_list = sorted(repo.branches, key=lambda t: t.commit.committed_datetime)
+for branch in branch_list:
+ if branch.name not in omit_branch_list:
+ versions.append(branch.name)
-versions.append('main')
if ((repo.head.object.hexsha) == (latest_tag.commit.hexsha)):
current_version = latest_tag.name
+ download_version = current_version
else:
- current_version = 'main'
+ download_version = '@DOC_TREE_VERSION@'
+ if ((download_version) == ('main')):
+ current_version = download_version + ' branch (unstable)'
+ else:
+ current_version = download_version + ' branch (unreleased)'
print("Sphinx config found documentation candidates: %r." % (versions))
print("Sphinx config current version: %r." % (current_version))
@@ -184,7 +198,13 @@ html_context['version'] = current_version
# POPULATE LINKS TO OTHER VERSIONS
html_context['versions'] = list()
for version in versions:
- html_context['versions'].append( (version, '/doc/' +version+ '/') )
+ if ((version) == ('main')):
+ html_context['versions'].append( (version + ' branch (unstable)', '/doc/' +version+ '/') )
+ else:
+ if (version not in release_list):
+ html_context['versions'].append( (version + ' branch (unreleased)', '/doc/' +version+ '/') )
+ else:
+ html_context['versions'].append( (version, '/doc/' +version+ '/') )
html_sidebars = {
'**': [
@@ -194,14 +214,14 @@ html_sidebars = {
# POPULATE LINKS TO OTHER FORMATS/DOWNLOADS
html_context['downloads'] = list()
-html_context['downloads'].append( ('pdf', '/doc/' +current_version+ '/pdf/' +project+ '-' +current_version+ '.pdf') )
-html_context['downloads'].append( ('epub', '/doc/' +current_version+ '/epub/' +project+ '-' +current_version+ '.epub') )
+html_context['downloads'].append( ('pdf', '/doc/' +download_version+ '/pdf/' +project+ '-' +download_version+ '.pdf') )
+html_context['downloads'].append( ('epub', '/doc/' +download_version+ '/epub/' +project+ '-' +download_version+ '.epub') )
# -- Options for PDF output -------------------------------------------------
rinoh_documents = [dict(
doc=master_doc,
- target=project+ '-' +current_version,
+ target=project+ '-' +download_version,
logo='@CMAKE_CURRENT_SOURCE_DIR@/src/_static/AtomVM-logo.png',
template='pdf_template.rtt'
)]
@@ -212,4 +232,4 @@ today_fmt = "%B %d, %Y"
epub_tocdepth = 3
epub_show_urls = 'no'
-epub_basename = project+ '-' +current_version
+epub_basename = project+ '-' +download_version
diff --git a/doc/pdf_stylesheet.rts b/doc/pdf_stylesheet.rts
index db0065732..2649d354c 100644
--- a/doc/pdf_stylesheet.rts
+++ b/doc/pdf_stylesheet.rts
@@ -8,3 +8,6 @@ base = sphinx
[linked reference]
type = custom
+
+[chapter]
+page_break = any
diff --git a/doc/src/apidocs/libatomvm/data_structures.rst b/doc/src/apidocs/libatomvm/data_structures.rst
index daf9e5f44..66152eb2d 100644
--- a/doc/src/apidocs/libatomvm/data_structures.rst
+++ b/doc/src/apidocs/libatomvm/data_structures.rst
@@ -12,7 +12,7 @@ Data Structures
---------------------
.. toctree::
- :maxdepth: 3
+ :maxdepth: 4
:caption: Structs
.. doxygenstruct:: AtomsHashTable
@@ -55,24 +55,46 @@ Data Structures
:allow-dot-graphs:
.. doxygenstruct:: Heap
:allow-dot-graphs:
-.. doxygenstruct:: HNode
- :allow-dot-graphs:
+.. Doxygen mangles this structure when parsing atomshashtable.c.
+.. c:struct:: HNode
+
+ **Public Members**
+
+ .. c:var:: struct HNode *next
+ .. c:var:: AtomString key
+ .. c:var:: unsigned long value
+
.. doxygenstruct:: HNodeGroup
:allow-dot-graphs:
.. doxygenstruct:: IFFRecord
:allow-dot-graphs:
.. doxygenstruct:: InMemoryAVMPack
:allow-dot-graphs:
-.. doxygenstruct:: Int24
- :allow-dot-graphs:
-.. doxygenstruct:: Int40
- :allow-dot-graphs:
-.. doxygenstruct:: Int48
- :allow-dot-graphs:
-.. doxygenstruct:: Int56
- :allow-dot-graphs:
-.. doxygenstruct:: kv_pair
- :allow-dot-graphs:
+.. defined in excluded opcodesswitch.h
+.. c:struct:: Int24
+
+ .. c:var:: int32_t val24 : 24
+
+.. c:struct:: Int40
+
+ .. c:var:: int64_t val40 : 40
+
+.. c:struct:: Int48
+
+ .. c:var:: int64_t val48 : 48
+
+.. c:struct:: Int56
+
+ .. c:var:: int64_t val56 : 56
+
+.. c:struct:: kv_pair
+
+ **Public Members**
+
+ .. c:var:: term key
+ .. c:var:: term value
+
+.. end of opcodesswitch.h structs
.. doxygenstruct:: LineRefOffset
:allow-dot-graphs:
.. doxygenstruct:: ListHead
@@ -116,8 +138,6 @@ Data Structures
:allow-dot-graphs:
.. doxygenstruct:: SyncList
:allow-dot-graphs:
-.. doxygenstruct:: TempStack
- :allow-dot-graphs:
.. doxygenstruct:: TermSignal
:allow-dot-graphs:
.. doxygenstruct:: TimerList
@@ -164,7 +184,6 @@ Enumerations
.. doxygenenum:: OpenAVMResult
.. doxygenenum:: RefcBinaryFlags
.. doxygenenum:: SocketErrors
-.. doxygenenum:: TempStackResult
.. doxygenenum:: TermCompareOpts
.. doxygenenum:: TermCompareResult
.. doxygenenum:: UnicodeConversionResult
diff --git a/doc/src/apidocs/libatomvm/file.rst.in b/doc/src/apidocs/libatomvm/file.rst.in
index befd6a2e6..f15c0c09c 100644
--- a/doc/src/apidocs/libatomvm/file.rst.in
+++ b/doc/src/apidocs/libatomvm/file.rst.in
@@ -5,6 +5,7 @@
:orphan:
.. c:namespace:: libAtomVM
+.. c:namespace-push:: @SOURCE_FILE@
------------------
@SOURCE_FILE@
diff --git a/doc/src/apidocs/libatomvm/functions.rst b/doc/src/apidocs/libatomvm/functions.rst
index 47c389ec5..617507d5a 100644
--- a/doc/src/apidocs/libatomvm/functions.rst
+++ b/doc/src/apidocs/libatomvm/functions.rst
@@ -4,6 +4,7 @@
:orphan:
+.. c:namespace:: NULL
.. c:namespace:: libAtomVM
.. c:namespace-push:: functions
@@ -24,13 +25,22 @@ Functions
.. doxygenfunction:: avmpack_find_section_by_name
.. doxygenfunction:: avmpack_fold
.. doxygenfunction:: avmpack_is_valid
+.. doxygenfunction:: bitstring_copy_bits
.. doxygenfunction:: bitstring_copy_bits_incomplete_bytes
+.. doxygenfunction:: bitstring_insert_utf16
+.. doxygenfunction:: bitstring_insert_utf32
+.. doxygenfunction:: bitstring_insert_utf8
+.. doxygenfunction:: bitstring_match_utf16
+.. doxygenfunction:: bitstring_match_utf32
+.. doxygenfunction:: bitstring_match_utf8
.. doxygenfunction:: bitstring_utf16_decode
.. doxygenfunction:: bitstring_utf16_encode
+.. doxygenfunction:: bitstring_utf16_size
.. doxygenfunction:: bitstring_utf32_decode
.. doxygenfunction:: bitstring_utf32_encode
.. doxygenfunction:: bitstring_utf8_decode
.. doxygenfunction:: bitstring_utf8_encode
+.. doxygenfunction:: bitstring_utf8_size
.. doxygenfunction:: context_avail_free_memory
.. doxygenfunction:: context_clean_registers
.. doxygenfunction:: context_destroy
@@ -76,6 +86,21 @@ Functions
.. doxygenfunction:: externalterm_from_binary
.. doxygenfunction:: externalterm_to_binary
.. doxygenfunction:: externalterm_to_term
+.. TODO: figure out why Doxgen cant find externalterm_to_term_internal in externalterm.c
+.. c:function:: static term externalterm_to_term_internal(const void *external_term, size_t size, Context *ctx, ExternalTermOpts opts, size_t *bytes_read, bool copy)
+
+ Copy an external term to internal storage.
+
+ :param external_term: buffer containing external term
+ :param size: size of the external_term
+ :param ctx: current context in which terms may be stored
+ :param opts: additional opts, such as ExternalTermToHeapFragment for storing parsed
+ terms in a heap fragment, otherwise terms are stored in the context heap.
+ :param bytes_read: the number of bytes read off external_term in order to yield a term
+ :param copy: whether to copy binary data and atom strings (pass `true`, unless
+ `external_term` is a const binary and will not be deallocated)
+ :returns: the parsed term
+
.. doxygenfunction:: globalcontext_atomstring_from_term
.. doxygenfunction:: globalcontext_demonitor
.. doxygenfunction:: globalcontext_destroy
@@ -96,6 +121,8 @@ Functions
.. doxygenfunction:: globalcontext_maybe_unregister_process_id
.. doxygenfunction:: globalcontext_new
.. doxygenfunction:: globalcontext_process_exists
+.. doxygenfunction:: globalcontext_process_task_driver_queues
+.. doxygenfunction:: globalcontext_refc_decrement_refcount_from_task
.. doxygenfunction:: globalcontext_register_process
.. doxygenfunction:: globalcontext_send_message
.. doxygenfunction:: globalcontext_send_message_from_task
@@ -118,6 +145,7 @@ Functions
.. doxygenfunction:: mailbox_has_next
.. doxygenfunction:: mailbox_init
.. doxygenfunction:: mailbox_len
+.. doxygenfunction:: mailbox_message_create_from_term
.. doxygenfunction:: mailbox_message_dispose
.. doxygenfunction:: mailbox_next
.. doxygenfunction:: mailbox_peek
@@ -136,11 +164,16 @@ Functions
.. doxygenfunction:: memory_copy_term_tree_to_storage
.. doxygenfunction:: memory_destroy_heap
.. doxygenfunction:: memory_destroy_heap_fragment
+.. doxygenfunction:: memory_destroy_heap_from_task
+.. doxygenfunction:: memory_ensure_free_opt
.. doxygenfunction:: memory_ensure_free_with_roots
+.. doxygenfunction:: memory_erl_nif_env_ensure_free
.. doxygenfunction:: memory_estimate_usage
+.. doxygenfunction:: memory_heap_alloc
.. doxygenfunction:: memory_heap_append_fragment
.. doxygenfunction:: memory_heap_append_heap
.. doxygenfunction:: memory_heap_fragment_memory_size
+.. doxygenfunction:: memory_heap_trim
.. doxygenfunction:: memory_heap_youngest_size
.. doxygenfunction:: memory_heap_memory_size
.. doxygenfunction:: memory_init_heap
@@ -157,6 +190,7 @@ Functions
.. doxygenfunction:: module_new_from_iff_binary
.. doxygenfunction:: module_resolve_function
.. doxygenfunction:: module_search_exported_function
+.. doxygenfunction:: otp_socket_lwip_enqueue
.. doxygenfunction:: platform_nifs_get_nif
.. doxygenfunction:: posix_errno_to_term
.. doxygenfunction:: process_listener_handler
diff --git a/doc/src/apidocs/libatomvm/index.rst b/doc/src/apidocs/libatomvm/index.rst
index 62d912e00..73d25daf5 100644
--- a/doc/src/apidocs/libatomvm/index.rst
+++ b/doc/src/apidocs/libatomvm/index.rst
@@ -20,7 +20,7 @@ libAtomVM
macros
-------------------------
-libAtomVM Header Files
+libAtomVM source files
-------------------------
.. toctree::
diff --git a/doc/src/apidocs/libatomvm/types.rst b/doc/src/apidocs/libatomvm/types.rst
index 76d5f6e1d..9d9bbc6e6 100644
--- a/doc/src/apidocs/libatomvm/types.rst
+++ b/doc/src/apidocs/libatomvm/types.rst
@@ -23,6 +23,16 @@ Types
.. doxygentypedef:: avm_uint64_t
.. doxygentypedef:: avm_uint_t
.. doxygentypedef:: avmpack_fold_fun
+.. from exclided opcodesshwitch.h
+.. c:type:: term* dreg_t
+
+.. c:type:: dreg_gc_safe_t
+
+ .. c:struct:: _
+
+ .. c:var:: term *base
+ .. c:var:: int index
+
.. doxygentypedef:: ERL_NIF_TERM
.. doxygentypedef:: ErlNifEvent
.. doxygentypedef:: ErlNifMonitor
@@ -33,4 +43,15 @@ Types
.. doxygentypedef:: ErlNifResourceType
.. doxygentypedef:: event_handler_t
.. doxygentypedef:: EventListener
+.. TODO: find out why Doxygen can parse this from mailbox.h
+.. c:type:: MailboxMessage MailboxMessage
+
+ .. c:struct:: MailboxMessage
+
+ .. c:var:: MailboxMessage *next
+ .. c:union:: _
+
+ .. c:var:: enum MessageType type
+ .. c:var:: term *heap_fragment_end
+
.. doxygentypedef:: term
diff --git a/doc/src/atomvm-internals.md b/doc/src/atomvm-internals.md
index 4e44eda4f..af6f76f72 100644
--- a/doc/src/atomvm-internals.md
+++ b/doc/src/atomvm-internals.md
@@ -124,9 +124,9 @@ This section is under construction
## Function Calls and Return Values
## Exception Handling
+-->
## The Scheduler
--->
In SMP builds, AtomVM runs one scheduler thread per core. Scheduler threads are actually started on demand. The number of scheduler threads can be queried with [`erlang:system_info/1`](./apidocs/erlang/estdlib/erlang.md#system_info1) and be modified with [`erlang:system_flag/2`](./apidocs/erlang/estdlib/erlang.md#system_flag2). All scheduler threads are considered equal and there is no notion of main thread except when shutting down (main thread is shut down last).
@@ -255,7 +255,9 @@ This list is populated at code load time. When a line reference is encountered
The memory cost of this list is `num_line_refs * sizeof(struct LineRefOffset)`, for each loaded module, or 0, if there is no `Line` chunk in the associated BEAM file.
+
TODO
diff --git a/doc/src/getting-started-guide.md b/doc/src/getting-started-guide.md
index e770a6ab0..ec0ffcddf 100644
--- a/doc/src/getting-started-guide.md
+++ b/doc/src/getting-started-guide.md
@@ -123,11 +123,9 @@ $ esptool.py --chip auto --port /dev/ttyUSB0 --baud 921600 erase_flash
```
```{note}
-Specify the device port and baud settings and AtomVM image name to suit your particular environment.
+Specify the device port and baud settings and AtomVM image name to suit your particular environment. A baud rate of 921600 works well for most ESP32 devices, some can work reliably at higher rates of 1500000, or even 2000000, but some devices (especially those with a 26Mhz crystal frequency, rather than the more common 40 Mhz crystal) may need to use a slower baud rate such as 115200.
```
-> Note. A baud rate of 921600 works well for most ESP32 devices, some can work reliably at higher rates of 1500000, or even 2000000, but some devices (especially those with a 26Mhz crystal frequency, rather than the more common 40 Mhz crystal) may need to use a slower baud rate such as 115200.
-
Download the latest [release image](https://github.com/atomvm/AtomVM/releases) for ESP32.
This image will generally take the form:
diff --git a/libs/exavmlib/lib/ArgumentError.ex b/libs/exavmlib/lib/ArgumentError.ex
new file mode 100644
index 000000000..8bec61c8d
--- /dev/null
+++ b/libs/exavmlib/lib/ArgumentError.ex
@@ -0,0 +1,27 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule ArgumentError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception message: "argument error"
+end
diff --git a/libs/exavmlib/lib/ArithmeticError.ex b/libs/exavmlib/lib/ArithmeticError.ex
new file mode 100644
index 000000000..2e652c5f0
--- /dev/null
+++ b/libs/exavmlib/lib/ArithmeticError.ex
@@ -0,0 +1,27 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule ArithmeticError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception message: "bad argument in arithmetic expression"
+end
diff --git a/libs/exavmlib/lib/BadArityError.ex b/libs/exavmlib/lib/BadArityError.ex
new file mode 100644
index 000000000..57551be18
--- /dev/null
+++ b/libs/exavmlib/lib/BadArityError.ex
@@ -0,0 +1,42 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule BadArityError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:function, :args]
+
+ @impl true
+ def message(exception) do
+ fun = exception.function
+ args = exception.args
+ insp = Enum.map_join(args, ", ", &inspect/1)
+ # TODO: enable as soon as :erlang.fun_info and Function.info are implemented
+ # {:arity, arity} = Function.info(fun, :arity)
+ # "#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
+ "#{inspect(fun)} called with #{count(length(args), insp)}"
+ end
+
+ defp count(0, _insp), do: "no arguments"
+ defp count(1, insp), do: "1 argument (#{insp})"
+ defp count(x, insp), do: "#{x} arguments (#{insp})"
+end
diff --git a/libs/exavmlib/lib/BadBooleanError.ex b/libs/exavmlib/lib/BadBooleanError.ex
new file mode 100644
index 000000000..1a343b965
--- /dev/null
+++ b/libs/exavmlib/lib/BadBooleanError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule BadBooleanError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term, :operator]
+
+ @impl true
+ def message(exception) do
+ "expected a boolean on left-side of \"#{exception.operator}\", got: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/BadFunctionError.ex b/libs/exavmlib/lib/BadFunctionError.ex
new file mode 100644
index 000000000..96d890ef5
--- /dev/null
+++ b/libs/exavmlib/lib/BadFunctionError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule BadFunctionError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "expected a function, got: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/BadMapError.ex b/libs/exavmlib/lib/BadMapError.ex
new file mode 100644
index 000000000..add5cbe08
--- /dev/null
+++ b/libs/exavmlib/lib/BadMapError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule BadMapError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "expected a map, got: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/BadStructError.ex b/libs/exavmlib/lib/BadStructError.ex
new file mode 100644
index 000000000..f8bdb6e9f
--- /dev/null
+++ b/libs/exavmlib/lib/BadStructError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule BadStructError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:struct, :term]
+
+ @impl true
+ def message(exception) do
+ "expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/CMakeLists.txt b/libs/exavmlib/lib/CMakeLists.txt
index d4c63c1fa..92deabdb6 100644
--- a/libs/exavmlib/lib/CMakeLists.txt
+++ b/libs/exavmlib/lib/CMakeLists.txt
@@ -33,14 +33,47 @@ set(ELIXIR_MODULES
LEDC
Access
Enum
+ Enumerable
+ Enumerable.List
+ Enumerable.Map
+ Enumerable.MapSet
+ Enumerable.Range
+ Exception
IO
List
Map
+ MapSet
Module
Keyword
Kernel
Process
+ Protocol.UndefinedError
+ Range
Tuple
+
+ ArithmeticError
+ ArgumentError
+ BadFunctionError
+ BadStructError
+ RuntimeError
+ SystemLimitError
+ BadMapError
+ BadBooleanError
+ MatchError
+ CaseClauseError
+ WithClauseError
+ CondClauseError
+ TryClauseError
+ BadArityError
+ UndefinedFunctionError
+ FunctionClauseError
+ KeyError
+ ErlangError
+
+ Collectable
+ Collectable.List
+ Collectable.Map
+ Collectable.MapSet
)
pack_archive(exavmlib ${ELIXIR_MODULES})
diff --git a/libs/exavmlib/lib/CaseClauseError.ex b/libs/exavmlib/lib/CaseClauseError.ex
new file mode 100644
index 000000000..c5c60551b
--- /dev/null
+++ b/libs/exavmlib/lib/CaseClauseError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule CaseClauseError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "no case clause matching: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/Collectable.List.ex b/libs/exavmlib/lib/Collectable.List.ex
new file mode 100644
index 000000000..043bf0dde
--- /dev/null
+++ b/libs/exavmlib/lib/Collectable.List.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.7.4/lib/elixir/lib/collectable.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Collectable, for: List do
+ def into(original) do
+ fun = fn
+ list, {:cont, x} -> [x | list]
+ list, :done -> original ++ :lists.reverse(list)
+ _, :halt -> :ok
+ end
+
+ {[], fun}
+ end
+end
diff --git a/libs/exavmlib/lib/Collectable.Map.ex b/libs/exavmlib/lib/Collectable.Map.ex
new file mode 100644
index 000000000..44bf17a4f
--- /dev/null
+++ b/libs/exavmlib/lib/Collectable.Map.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.7.4/lib/elixir/lib/collectable.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Collectable, for: Map do
+ def into(original) do
+ fun = fn
+ map, {:cont, {k, v}} -> :maps.put(k, v, map)
+ map, :done -> map
+ _, :halt -> :ok
+ end
+
+ {original, fun}
+ end
+end
diff --git a/libs/exavmlib/lib/Collectable.MapSet.ex b/libs/exavmlib/lib/Collectable.MapSet.ex
new file mode 100644
index 000000000..6ab2b1e9b
--- /dev/null
+++ b/libs/exavmlib/lib/Collectable.MapSet.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2024 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.17.2/lib/elixir/lib/map_set.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Collectable, for: MapSet do
+ def into(%@for{map: set} = map_set) do
+ fun = fn
+ list, {:cont, x} -> [x | list]
+ list, :done -> %{map_set | map: :sets.union(set, :sets.from_list(list, version: 2))}
+ _, :halt -> :ok
+ end
+
+ {[], fun}
+ end
+end
diff --git a/libs/exavmlib/lib/Collectable.ex b/libs/exavmlib/lib/Collectable.ex
new file mode 100644
index 000000000..a25b6d068
--- /dev/null
+++ b/libs/exavmlib/lib/Collectable.ex
@@ -0,0 +1,99 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.7.4/lib/elixir/lib/collectable.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defprotocol Collectable do
+ @moduledoc """
+ A protocol to traverse data structures.
+
+ The `Enum.into/2` function uses this protocol to insert an
+ enumerable into a collection:
+
+ iex> Enum.into([a: 1, b: 2], %{})
+ %{a: 1, b: 2}
+
+ ## Why Collectable?
+
+ The `Enumerable` protocol is useful to take values out of a collection.
+ In order to support a wide range of values, the functions provided by
+ the `Enumerable` protocol do not keep shape. For example, passing a
+ map to `Enum.map/2` always returns a list.
+
+ This design is intentional. `Enumerable` was designed to support infinite
+ collections, resources and other structures with fixed shape. For example,
+ it doesn't make sense to insert values into a range, as it has a fixed
+ shape where just the range limits are stored.
+
+ The `Collectable` module was designed to fill the gap left by the
+ `Enumerable` protocol. `into/1` can be seen as the opposite of
+ `Enumerable.reduce/3`. If `Enumerable` is about taking values out,
+ `Collectable.into/1` is about collecting those values into a structure.
+
+ ## Examples
+
+ To show how to manually use the `Collectable` protocol, let's play with its
+ implementation for `MapSet`.
+
+ iex> {initial_acc, collector_fun} = Collectable.into(MapSet.new())
+ iex> updated_acc = Enum.reduce([1, 2, 3], initial_acc, fn elem, acc ->
+ ...> collector_fun.(acc, {:cont, elem})
+ ...> end)
+ iex> collector_fun.(updated_acc, :done)
+ #MapSet<[1, 2, 3]>
+
+ To show how the protocol can be implemented, we can take again a look at the
+ implementation for `MapSet`. In this implementation "collecting" elements
+ simply means inserting them in the set through `MapSet.put/2`.
+
+ defimpl Collectable do
+ def into(original) do
+ collector_fun = fn
+ set, {:cont, elem} -> MapSet.put(set, elem)
+ set, :done -> set
+ _set, :halt -> :ok
+ end
+
+ {original, collector_fun}
+ end
+ end
+
+ """
+
+ @type command :: {:cont, term} | :done | :halt
+
+ @doc """
+ Returns an initial accumulator and a "collector" function.
+
+ The returned function receives a term and a command and injects the term into
+ the collectable on every `{:cont, term}` command.
+
+ `:done` is passed as a command when no further values will be injected. This
+ is useful when there's a need to close resources or normalizing values. A
+ collectable must be returned when the command is `:done`.
+
+ If injection is suddenly interrupted, `:halt` is passed and the function
+ can return any value as it won't be used.
+
+ For examples on how to use the `Collectable` protocol and `into/1` see the
+ module documentation.
+ """
+ @spec into(t) :: {term, (term, command -> t | term)}
+ def into(collectable)
+end
diff --git a/libs/exavmlib/lib/CondClauseError.ex b/libs/exavmlib/lib/CondClauseError.ex
new file mode 100644
index 000000000..c25e4e3b7
--- /dev/null
+++ b/libs/exavmlib/lib/CondClauseError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule CondClauseError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception []
+
+ @impl true
+ def message(_exception) do
+ "no cond clause evaluated to a true value"
+ end
+end
diff --git a/libs/exavmlib/lib/Enum.ex b/libs/exavmlib/lib/Enum.ex
index ba9bd38a5..c702db55b 100644
--- a/libs/exavmlib/lib/Enum.ex
+++ b/libs/exavmlib/lib/Enum.ex
@@ -23,14 +23,36 @@ defmodule Enum do
# This avoids crashing the compiler at build time
@compile {:autoload, false}
+ @type t :: Enumerable.t()
+ @type index :: integer
+ @type element :: any
+
+ require Stream.Reducers, as: R
+
+ defmacrop next(_, entry, acc) do
+ quote(do: [unquote(entry) | unquote(acc)])
+ end
+
def reduce(enumerable, acc, fun) when is_list(enumerable) do
:lists.foldl(fun, acc, enumerable)
end
+ def reduce(%_{} = enumerable, acc, fun) do
+ reduce_enumerable(enumerable, acc, fun)
+ end
+
def reduce(%{} = enumerable, acc, fun) do
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, enumerable)
end
+ def reduce(enumerable, acc, fun) do
+ reduce_enumerable(enumerable, acc, fun)
+ end
+
+ defp reduce_enumerable(enumerable, acc, fun) do
+ Enumerable.reduce(enumerable, {:cont, acc}, fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
+ end
+
def all?(enumerable, fun) when is_list(enumerable) do
all_list(enumerable, fun)
end
@@ -39,10 +61,30 @@ defmodule Enum do
any_list(enumerable, fun)
end
+ @doc """
+ Returns the size of the enumerable.
+
+ ## Examples
+
+ iex> Enum.count([1, 2, 3])
+ 3
+
+ """
+ @spec count(t) :: non_neg_integer
def count(enumerable) when is_list(enumerable) do
length(enumerable)
end
+ def count(enumerable) do
+ case Enumerable.count(enumerable) do
+ {:ok, value} when is_integer(value) ->
+ value
+
+ {:error, module} ->
+ enumerable |> module.reduce({:cont, 0}, fn _, acc -> {:cont, acc + 1} end) |> elem(1)
+ end
+ end
+
def each(enumerable, fun) when is_list(enumerable) do
:lists.foreach(fun, enumerable)
:ok
@@ -64,14 +106,108 @@ defmodule Enum do
find_value_list(enumerable, default, fun)
end
+ @doc """
+ Returns a list where each element is the result of invoking
+ `fun` on each corresponding element of `enumerable`.
+
+ For maps, the function expects a key-value tuple.
+
+ ## Examples
+
+ iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
+ [2, 4, 6]
+
+ iex> Enum.map([a: 1, b: 2], fn {k, v} -> {k, -v} end)
+ [a: -1, b: -2]
+
+ """
+ @spec map(t, (element -> any)) :: list
+ def map(enumerable, fun)
+
def map(enumerable, fun) when is_list(enumerable) do
:lists.map(fun, enumerable)
end
+ def map(enumerable, fun) do
+ reduce(enumerable, [], R.map(fun)) |> :lists.reverse()
+ end
+
+ @doc """
+ Maps and joins the given enumerable in one pass.
+
+ `joiner` can be either a binary or a list and the result will be of
+ the same type as `joiner`.
+ If `joiner` is not passed at all, it defaults to an empty binary.
+
+ All items returned from invoking the `mapper` must be convertible to
+ a binary, otherwise an error is raised.
+
+ ## Examples
+
+ iex> Enum.map_join([1, 2, 3], &(&1 * 2))
+ "246"
+
+ iex> Enum.map_join([1, 2, 3], " = ", &(&1 * 2))
+ "2 = 4 = 6"
+
+ """
+ @spec map_join(t, String.t(), (element -> String.Chars.t())) :: String.t()
+ def map_join(enumerable, joiner \\ "", mapper)
+
+ def map_join(enumerable, joiner, mapper) when is_binary(joiner) do
+ reduced =
+ reduce(enumerable, :first, fn
+ entry, :first -> entry_to_string(mapper.(entry))
+ entry, acc -> [acc, joiner | entry_to_string(mapper.(entry))]
+ end)
+
+ if reduced == :first do
+ ""
+ else
+ IO.iodata_to_binary(reduced)
+ end
+ end
+
+ @doc """
+ Checks if `element` exists within the enumerable.
+
+ Membership is tested with the match (`===/2`) operator.
+
+ ## Examples
+
+ iex> Enum.member?(1..10, 5)
+ true
+ iex> Enum.member?(1..10, 5.0)
+ false
+
+ iex> Enum.member?([1.0, 2.0, 3.0], 2)
+ false
+ iex> Enum.member?([1.0, 2.0, 3.0], 2.000)
+ true
+
+ iex> Enum.member?([:a, :b, :c], :d)
+ false
+
+ """
+ @spec member?(t, element) :: boolean
def member?(enumerable, element) when is_list(enumerable) do
:lists.member(element, enumerable)
end
+ def member?(enumerable, element) do
+ case Enumerable.member?(enumerable, element) do
+ {:ok, element} when is_boolean(element) ->
+ element
+
+ {:error, module} ->
+ module.reduce(enumerable, {:cont, false}, fn
+ v, _ when v === element -> {:halt, true}
+ _, _ -> {:cont, false}
+ end)
+ |> elem(1)
+ end
+ end
+
def reject(enumerable, fun) when is_list(enumerable) do
reject_list(enumerable, fun)
end
@@ -156,6 +292,137 @@ defmodule Enum do
default
end
+ @doc """
+ Inserts the given `enumerable` into a `collectable`.
+
+ ## Examples
+
+ iex> Enum.into([1, 2], [0])
+ [0, 1, 2]
+
+ iex> Enum.into([a: 1, b: 2], %{})
+ %{a: 1, b: 2}
+
+ iex> Enum.into(%{a: 1}, %{b: 2})
+ %{a: 1, b: 2}
+
+ iex> Enum.into([a: 1, a: 2], %{})
+ %{a: 2}
+
+ """
+ @spec into(Enumerable.t(), Collectable.t()) :: Collectable.t()
+ def into(enumerable, collectable) when is_list(collectable) do
+ collectable ++ to_list(enumerable)
+ end
+
+ def into(%_{} = enumerable, collectable) do
+ into_protocol(enumerable, collectable)
+ end
+
+ def into(enumerable, %_{} = collectable) do
+ into_protocol(enumerable, collectable)
+ end
+
+ def into(%{} = enumerable, %{} = collectable) do
+ Map.merge(collectable, enumerable)
+ end
+
+ def into(enumerable, %{} = collectable) when is_list(enumerable) do
+ Map.merge(collectable, :maps.from_list(enumerable))
+ end
+
+ def into(enumerable, %{} = collectable) do
+ reduce(enumerable, collectable, fn {key, val}, acc ->
+ Map.put(acc, key, val)
+ end)
+ end
+
+ def into(enumerable, collectable) do
+ into_protocol(enumerable, collectable)
+ end
+
+ defp into_protocol(enumerable, collectable) do
+ {initial, fun} = Collectable.into(collectable)
+
+ into(enumerable, initial, fun, fn entry, acc ->
+ fun.(acc, {:cont, entry})
+ end)
+ end
+
+ @doc """
+ Inserts the given `enumerable` into a `collectable` according to the
+ transformation function.
+
+ ## Examples
+
+ iex> Enum.into([2, 3], [3], fn x -> x * 3 end)
+ [3, 6, 9]
+
+ iex> Enum.into(%{a: 1, b: 2}, %{c: 3}, fn {k, v} -> {k, v * 2} end)
+ %{a: 2, b: 4, c: 3}
+
+ """
+ @spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
+
+ def into(enumerable, collectable, transform) when is_list(collectable) do
+ collectable ++ map(enumerable, transform)
+ end
+
+ def into(enumerable, collectable, transform) do
+ {initial, fun} = Collectable.into(collectable)
+
+ into(enumerable, initial, fun, fn entry, acc ->
+ fun.(acc, {:cont, transform.(entry)})
+ end)
+ end
+
+ defp into(enumerable, initial, fun, callback) do
+ try do
+ reduce(enumerable, initial, callback)
+ catch
+ kind, reason ->
+ fun.(initial, :halt)
+ :erlang.raise(kind, reason, __STACKTRACE__)
+ else
+ acc -> fun.(acc, :done)
+ end
+ end
+
+ @doc """
+ Joins the given enumerable into a binary using `joiner` as a
+ separator.
+
+ If `joiner` is not passed at all, it defaults to the empty binary.
+
+ All items in the enumerable must be convertible to a binary,
+ otherwise an error is raised.
+
+ ## Examples
+
+ iex> Enum.join([1, 2, 3])
+ "123"
+
+ iex> Enum.join([1, 2, 3], " = ")
+ "1 = 2 = 3"
+
+ """
+ @spec join(t, String.t()) :: String.t()
+ def join(enumerable, joiner \\ "")
+
+ def join(enumerable, joiner) when is_binary(joiner) do
+ reduced =
+ reduce(enumerable, :first, fn
+ entry, :first -> entry_to_string(entry)
+ entry, acc -> [acc, joiner | entry_to_string(entry)]
+ end)
+
+ if reduced == :first do
+ ""
+ else
+ IO.iodata_to_binary(reduced)
+ end
+ end
+
## reject
defp reject_list([head | tail], fun) do
@@ -169,4 +436,254 @@ defmodule Enum do
defp reject_list([], _fun) do
[]
end
+
+ @doc """
+ Returns a list of elements in `enumerable` in reverse order.
+
+ ## Examples
+
+ iex> Enum.reverse([1, 2, 3])
+ [3, 2, 1]
+
+ """
+ @spec reverse(t) :: list
+ def reverse(enumerable)
+
+ def reverse([]), do: []
+ def reverse([_] = list), do: list
+ def reverse([item1, item2]), do: [item2, item1]
+ def reverse([item1, item2 | rest]), do: :lists.reverse(rest, [item2, item1])
+ def reverse(enumerable), do: reduce(enumerable, [], &[&1 | &2])
+
+ @doc """
+ Returns a subset list of the given enumerable, from `range.first` to `range.last` positions.
+
+ Given `enumerable`, it drops elements until element position `range.first`,
+ then takes elements until element position `range.last` (inclusive).
+
+ Positions are normalized, meaning that negative positions will be counted from the end
+ (e.g. `-1` means the last element of the enumerable).
+ If `range.last` is out of bounds, then it is assigned as the position of the last element.
+
+ If the normalized `range.first` position is out of bounds of the given enumerable,
+ or this one is greater than the normalized `range.last` position, then `[]` is returned.
+
+ ## Examples
+
+ iex> Enum.slice(1..100, 5..10)
+ [6, 7, 8, 9, 10, 11]
+
+ iex> Enum.slice(1..10, 5..20)
+ [6, 7, 8, 9, 10]
+
+ # last five elements (negative positions)
+ iex> Enum.slice(1..30, -5..-1)
+ [26, 27, 28, 29, 30]
+
+ # last five elements (mixed positive and negative positions)
+ iex> Enum.slice(1..30, 25..-1)
+ [26, 27, 28, 29, 30]
+
+ # out of bounds
+ iex> Enum.slice(1..10, 11..20)
+ []
+
+ # range.first is greater than range.last
+ iex> Enum.slice(1..10, 6..5)
+ []
+
+ """
+ @doc since: "1.6.0"
+ @spec slice(t, Range.t()) :: list
+ def slice(enumerable, first..last) do
+ {count, fun} = slice_count_and_fun(enumerable)
+ corr_first = if first >= 0, do: first, else: first + count
+ corr_last = if last >= 0, do: last, else: last + count
+ amount = corr_last - corr_first + 1
+
+ if corr_first >= 0 and corr_first < count and amount > 0 do
+ fun.(corr_first, Kernel.min(amount, count - corr_first))
+ else
+ []
+ end
+ end
+
+ @doc """
+ Returns a subset list of the given enumerable, from `start` position with `amount` of elements if available.
+
+ Given `enumerable`, it drops elements until element position `start`,
+ then takes `amount` of elements until the end of the enumerable.
+
+ If `start` is out of bounds, it returns `[]`.
+
+ If `amount` is greater than `enumerable` length, it returns as many elements as possible.
+ If `amount` is zero, then `[]` is returned.
+
+ ## Examples
+
+ iex> Enum.slice(1..100, 5, 10)
+ [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+
+ # amount to take is greater than the number of elements
+ iex> Enum.slice(1..10, 5, 100)
+ [6, 7, 8, 9, 10]
+
+ iex> Enum.slice(1..10, 5, 0)
+ []
+
+ # out of bound start position
+ iex> Enum.slice(1..10, 10, 5)
+ []
+
+ # out of bound start position (negative)
+ iex> Enum.slice(1..10, -11, 5)
+ []
+
+ """
+ @spec slice(t, index, non_neg_integer) :: list
+ def slice(_enumerable, start, 0) when is_integer(start), do: []
+
+ def slice(enumerable, start, amount)
+ when is_integer(start) and is_integer(amount) and amount >= 0 do
+ slice_any(enumerable, start, amount)
+ end
+
+ @doc """
+ Splits the `enumerable` in two lists according to the given function `fun`.
+
+ Splits the given `enumerable` in two lists by calling `fun` with each element
+ in the `enumerable` as its only argument. Returns a tuple with the first list
+ containing all the elements in `enumerable` for which applying `fun` returned
+ a truthy value, and a second list with all the elements for which applying
+ `fun` returned a falsy value (`false` or `nil`).
+
+ The elements in both the returned lists are in the same relative order as they
+ were in the original enumerable (if such enumerable was ordered, e.g., a
+ list); see the examples below.
+
+ ## Examples
+
+ iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
+ {[4, 2, 0], [5, 3, 1]}
+
+ iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v < 0 end)
+ {[b: -2, d: -3], [a: 1, c: 1]}
+
+ iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v > 50 end)
+ {[], [a: 1, b: -2, c: 1, d: -3]}
+
+ iex> Enum.split_with(%{}, fn {_k, v} -> v > 50 end)
+ {[], []}
+
+ """
+ @doc since: "1.4.0"
+ def split_with(enumerable, fun) do
+ {acc1, acc2} =
+ reduce(enumerable, {[], []}, fn entry, {acc1, acc2} ->
+ if fun.(entry) do
+ {[entry | acc1], acc2}
+ else
+ {acc1, [entry | acc2]}
+ end
+ end)
+
+ {:lists.reverse(acc1), :lists.reverse(acc2)}
+ end
+
+ @doc """
+ Converts `enumerable` to a list.
+
+ ## Examples
+
+ iex> Enum.to_list(1..3)
+ [1, 2, 3]
+
+ """
+ @spec to_list(t) :: [element]
+ def to_list(enumerable) when is_list(enumerable), do: enumerable
+ def to_list(%_{} = enumerable), do: reverse(enumerable) |> :lists.reverse()
+ def to_list(%{} = enumerable), do: Map.to_list(enumerable)
+ def to_list(enumerable), do: reverse(enumerable) |> :lists.reverse()
+
+ # helpers
+
+ @compile {:inline, entry_to_string: 1, reduce: 3}
+
+ defp entry_to_string(entry) when is_binary(entry), do: entry
+
+ ## drop
+
+ defp drop_list(list, 0), do: list
+ defp drop_list([_ | tail], counter), do: drop_list(tail, counter - 1)
+ defp drop_list([], _), do: []
+
+ ## slice
+
+ defp slice_any(enumerable, start, amount) when start < 0 do
+ {count, fun} = slice_count_and_fun(enumerable)
+ start = count + start
+
+ if start >= 0 do
+ fun.(start, Kernel.min(amount, count - start))
+ else
+ []
+ end
+ end
+
+ defp slice_any(list, start, amount) when is_list(list) do
+ list |> drop_list(start) |> take_list(amount)
+ end
+
+ defp slice_any(enumerable, start, amount) do
+ case Enumerable.slice(enumerable) do
+ {:ok, count, _} when start >= count ->
+ []
+
+ {:ok, count, fun} when is_function(fun) ->
+ fun.(start, Kernel.min(amount, count - start))
+
+ {:error, module} ->
+ slice_enum(enumerable, module, start, amount)
+ end
+ end
+
+ defp slice_enum(enumerable, module, start, amount) do
+ {_, {_, _, slice}} =
+ module.reduce(enumerable, {:cont, {start, amount, []}}, fn
+ _entry, {start, amount, _list} when start > 0 ->
+ {:cont, {start - 1, amount, []}}
+
+ entry, {start, amount, list} when amount > 1 ->
+ {:cont, {start, amount - 1, [entry | list]}}
+
+ entry, {start, amount, list} ->
+ {:halt, {start, amount, [entry | list]}}
+ end)
+
+ :lists.reverse(slice)
+ end
+
+ defp slice_count_and_fun(enumerable) when is_list(enumerable) do
+ length = length(enumerable)
+ {length, &Enumerable.List.slice(enumerable, &1, &2, length)}
+ end
+
+ defp slice_count_and_fun(enumerable) do
+ case Enumerable.slice(enumerable) do
+ {:ok, count, fun} when is_function(fun) ->
+ {count, fun}
+
+ {:error, module} ->
+ {_, {list, count}} =
+ module.reduce(enumerable, {:cont, {[], 0}}, fn elem, {acc, count} ->
+ {:cont, {[elem | acc], count + 1}}
+ end)
+
+ {count, &Enumerable.List.slice(:lists.reverse(list), &1, &2, count)}
+ end
+ end
+
+ defp take_list([head | _], 1), do: [head]
+ defp take_list([head | tail], counter), do: [head | take_list(tail, counter - 1)]
+ defp take_list([], _counter), do: []
end
diff --git a/libs/exavmlib/lib/Enumerable.List.ex b/libs/exavmlib/lib/Enumerable.List.ex
new file mode 100644
index 000000000..13a34d0b2
--- /dev/null
+++ b/libs/exavmlib/lib/Enumerable.List.ex
@@ -0,0 +1,42 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2017 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.10.1/lib/elixir/lib/enumerable.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Enumerable, for: List do
+ def count(_list), do: {:error, __MODULE__}
+ def member?(_list, _value), do: {:error, __MODULE__}
+ def slice(_list), do: {:error, __MODULE__}
+
+ def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
+ def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
+ def reduce([], {:cont, acc}, _fun), do: {:done, acc}
+ def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)
+
+ @doc false
+ def slice(_list, _start, 0, _size), do: []
+ def slice(list, start, count, size) when start + count == size, do: list |> drop(start)
+ def slice(list, start, count, _size), do: list |> drop(start) |> take(count)
+
+ defp drop(list, 0), do: list
+ defp drop([_ | tail], count), do: drop(tail, count - 1)
+
+ defp take(_list, 0), do: []
+ defp take([head | tail], count), do: [head | take(tail, count - 1)]
+end
diff --git a/libs/exavmlib/lib/Enumerable.Map.ex b/libs/exavmlib/lib/Enumerable.Map.ex
new file mode 100644
index 000000000..cc3401fa0
--- /dev/null
+++ b/libs/exavmlib/lib/Enumerable.Map.ex
@@ -0,0 +1,48 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2017 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.10.1/lib/elixir/lib/enumerable.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Enumerable, for: Map do
+ def count(map) do
+ {:ok, map_size(map)}
+ end
+
+ def member?(map, {key, value}) do
+ {:ok, match?(%{^key => ^value}, map)}
+ end
+
+ def member?(_map, _other) do
+ {:ok, false}
+ end
+
+ def slice(map) do
+ size = map_size(map)
+ {:ok, size, &Enumerable.List.slice(:maps.to_list(map), &1, &2, size)}
+ end
+
+ def reduce(map, acc, fun) do
+ reduce_list(:maps.to_list(map), acc, fun)
+ end
+
+ defp reduce_list(_list, {:halt, acc}, _fun), do: {:halted, acc}
+ defp reduce_list(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce_list(list, &1, fun)}
+ defp reduce_list([], {:cont, acc}, _fun), do: {:done, acc}
+ defp reduce_list([head | tail], {:cont, acc}, fun), do: reduce_list(tail, fun.(head, acc), fun)
+end
diff --git a/libs/exavmlib/lib/Enumerable.MapSet.ex b/libs/exavmlib/lib/Enumerable.MapSet.ex
new file mode 100644
index 000000000..4ff68716f
--- /dev/null
+++ b/libs/exavmlib/lib/Enumerable.MapSet.ex
@@ -0,0 +1,39 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2024 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.17.2/lib/elixir/lib/map_set.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Enumerable, for: MapSet do
+ def count(map_set) do
+ {:ok, MapSet.size(map_set)}
+ end
+
+ def member?(map_set, val) do
+ {:ok, MapSet.member?(map_set, val)}
+ end
+
+ def reduce(map_set, acc, fun) do
+ Enumerable.List.reduce(MapSet.to_list(map_set), acc, fun)
+ end
+
+ def slice(map_set) do
+ size = MapSet.size(map_set)
+ {:ok, size, &MapSet.to_list/1}
+ end
+end
diff --git a/libs/exavmlib/lib/Enumerable.Range.ex b/libs/exavmlib/lib/Enumerable.Range.ex
new file mode 100644
index 000000000..791be52cf
--- /dev/null
+++ b/libs/exavmlib/lib/Enumerable.Range.ex
@@ -0,0 +1,80 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2020 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.10.4/lib/elixir/lib/enum.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defimpl Enumerable, for: Range do
+ def reduce(first..last, acc, fun) do
+ reduce(first, last, acc, fun, _up? = last >= first)
+ end
+
+ defp reduce(_first, _last, {:halt, acc}, _fun, _up?) do
+ {:halted, acc}
+ end
+
+ defp reduce(first, last, {:suspend, acc}, fun, up?) do
+ {:suspended, acc, &reduce(first, last, &1, fun, up?)}
+ end
+
+ defp reduce(first, last, {:cont, acc}, fun, _up? = true) when first <= last do
+ reduce(first + 1, last, fun.(first, acc), fun, _up? = true)
+ end
+
+ defp reduce(first, last, {:cont, acc}, fun, _up? = false) when first >= last do
+ reduce(first - 1, last, fun.(first, acc), fun, _up? = false)
+ end
+
+ defp reduce(_, _, {:cont, acc}, _fun, _up) do
+ {:done, acc}
+ end
+
+ def member?(first..last, value) when is_integer(value) do
+ if first <= last do
+ {:ok, first <= value and value <= last}
+ else
+ {:ok, last <= value and value <= first}
+ end
+ end
+
+ def member?(_.._, _value) do
+ {:ok, false}
+ end
+
+ def count(first..last) do
+ if first <= last do
+ {:ok, last - first + 1}
+ else
+ {:ok, first - last + 1}
+ end
+ end
+
+ def slice(first..last) do
+ if first <= last do
+ {:ok, last - first + 1, &slice_asc(first + &1, &2)}
+ else
+ {:ok, first - last + 1, &slice_desc(first - &1, &2)}
+ end
+ end
+
+ defp slice_asc(current, 1), do: [current]
+ defp slice_asc(current, remaining), do: [current | slice_asc(current + 1, remaining - 1)]
+
+ defp slice_desc(current, 1), do: [current]
+ defp slice_desc(current, remaining), do: [current | slice_desc(current - 1, remaining - 1)]
+end
diff --git a/libs/exavmlib/lib/Enumerable.ex b/libs/exavmlib/lib/Enumerable.ex
new file mode 100644
index 000000000..bbbd57f5c
--- /dev/null
+++ b/libs/exavmlib/lib/Enumerable.ex
@@ -0,0 +1,218 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2017 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.10.1/lib/elixir/lib/enum.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defprotocol Enumerable do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ @moduledoc """
+ Enumerable protocol used by `Enum` and `Stream` modules.
+
+ When you invoke a function in the `Enum` module, the first argument
+ is usually a collection that must implement this protocol.
+ For example, the expression:
+
+ Enum.map([1, 2, 3], &(&1 * 2))
+
+ invokes `Enumerable.reduce/3` to perform the reducing operation that
+ builds a mapped list by calling the mapping function `&(&1 * 2)` on
+ every element in the collection and consuming the element with an
+ accumulated list.
+
+ Internally, `Enum.map/2` is implemented as follows:
+
+ def map(enumerable, fun) do
+ reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
+ Enumerable.reduce(enumerable, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
+ end
+
+ Notice the user-supplied function is wrapped into a `t:reducer/0` function.
+ The `t:reducer/0` function must return a tagged tuple after each step,
+ as described in the `t:acc/0` type. At the end, `Enumerable.reduce/3`
+ returns `t:result/0`.
+
+ This protocol uses tagged tuples to exchange information between the
+ reducer function and the data type that implements the protocol. This
+ allows enumeration of resources, such as files, to be done efficiently
+ while also guaranteeing the resource will be closed at the end of the
+ enumeration. This protocol also allows suspension of the enumeration,
+ which is useful when interleaving between many enumerables is required
+ (as in zip).
+
+ This protocol requires four functions to be implemented, `reduce/3`,
+ `count/1`, `member?/2`, and `slice/1`. The core of the protocol is the
+ `reduce/3` function. All other functions exist as optimizations paths
+ for data structures that can implement certain properties in better
+ than linear time.
+ """
+
+ @typedoc """
+ The accumulator value for each step.
+
+ It must be a tagged tuple with one of the following "tags":
+
+ * `:cont` - the enumeration should continue
+ * `:halt` - the enumeration should halt immediately
+ * `:suspend` - the enumeration should be suspended immediately
+
+ Depending on the accumulator value, the result returned by
+ `Enumerable.reduce/3` will change. Please check the `t:result/0`
+ type documentation for more information.
+
+ In case a `t:reducer/0` function returns a `:suspend` accumulator,
+ it must be explicitly handled by the caller and never leak.
+ """
+ @type acc :: {:cont, term} | {:halt, term} | {:suspend, term}
+
+ @typedoc """
+ The reducer function.
+
+ Should be called with the enumerable element and the
+ accumulator contents.
+
+ Returns the accumulator for the next enumeration step.
+ """
+ @type reducer :: (term, term -> acc)
+
+ @typedoc """
+ The result of the reduce operation.
+
+ It may be *done* when the enumeration is finished by reaching
+ its end, or *halted*/*suspended* when the enumeration was halted
+ or suspended by the `t:reducer/0` function.
+
+ In case a `t:reducer/0` function returns the `:suspend` accumulator, the
+ `:suspended` tuple must be explicitly handled by the caller and
+ never leak. In practice, this means regular enumeration functions
+ just need to be concerned about `:done` and `:halted` results.
+
+ Furthermore, a `:suspend` call must always be followed by another call,
+ eventually halting or continuing until the end.
+ """
+ @type result ::
+ {:done, term}
+ | {:halted, term}
+ | {:suspended, term, continuation}
+
+ @typedoc """
+ A partially applied reduce function.
+
+ The continuation is the closure returned as a result when
+ the enumeration is suspended. When invoked, it expects
+ a new accumulator and it returns the result.
+
+ A continuation can be trivially implemented as long as the reduce
+ function is defined in a tail recursive fashion. If the function
+ is tail recursive, all the state is passed as arguments, so
+ the continuation is the reducing function partially applied.
+ """
+ @type continuation :: (acc -> result)
+
+ @typedoc """
+ A slicing function that receives the initial position and the
+ number of elements in the slice.
+
+ The `start` position is a number `>= 0` and guaranteed to
+ exist in the enumerable. The length is a number `>= 1` in a way
+ that `start + length <= count`, where `count` is the maximum
+ amount of elements in the enumerable.
+
+ The function should return a non empty list where
+ the amount of elements is equal to `length`.
+ """
+ @type slicing_fun :: (start :: non_neg_integer, length :: pos_integer -> [term()])
+
+ @doc """
+ Reduces the enumerable into an element.
+
+ Most of the operations in `Enum` are implemented in terms of reduce.
+ This function should apply the given `t:reducer/0` function to each
+ item in the enumerable and proceed as expected by the returned
+ accumulator.
+
+ See the documentation of the types `t:result/0` and `t:acc/0` for
+ more information.
+
+ ## Examples
+
+ As an example, here is the implementation of `reduce` for lists:
+
+ def reduce(_list, {:halt, acc}, _fun), do: {:halted, acc}
+ def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
+ def reduce([], {:cont, acc}, _fun), do: {:done, acc}
+ def reduce([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun)
+
+ """
+ @spec reduce(t, acc, reducer) :: result
+ def reduce(enumerable, acc, fun)
+
+ @doc """
+ Retrieves the number of elements in the enumerable.
+
+ It should return `{:ok, count}` if you can count the number of elements
+ in the enumerable.
+
+ Otherwise it should return `{:error, __MODULE__}` and a default algorithm
+ built on top of `reduce/3` that runs in linear time will be used.
+ """
+ @spec count(t) :: {:ok, non_neg_integer} | {:error, module}
+ def count(enumerable)
+
+ @doc """
+ Checks if an element exists within the enumerable.
+
+ It should return `{:ok, boolean}` if you can check the membership of a
+ given element in the enumerable with `===/2` without traversing the whole
+ enumerable.
+
+ Otherwise it should return `{:error, __MODULE__}` and a default algorithm
+ built on top of `reduce/3` that runs in linear time will be used.
+ """
+ @spec member?(t, term) :: {:ok, boolean} | {:error, module}
+ def member?(enumerable, element)
+
+ @doc """
+ Returns a function that slices the data structure contiguously.
+
+ It should return `{:ok, size, slicing_fun}` if the enumerable has
+ a known bound and can access a position in the enumerable without
+ traversing all previous elements.
+
+ Otherwise it should return `{:error, __MODULE__}` and a default
+ algorithm built on top of `reduce/3` that runs in linear time will be
+ used.
+
+ ## Differences to `count/1`
+
+ The `size` value returned by this function is used for boundary checks,
+ therefore it is extremely important that this function only returns `:ok`
+ if retrieving the `size` of the enumerable is cheap, fast and takes constant
+ time. Otherwise the simplest of operations, such as `Enum.at(enumerable, 0)`,
+ will become too expensive.
+
+ On the other hand, the `count/1` function in this protocol should be
+ implemented whenever you can count the number of elements in the collection.
+ """
+ @spec slice(t) ::
+ {:ok, size :: non_neg_integer(), slicing_fun()}
+ | {:error, module()}
+ def slice(enumerable)
+end
diff --git a/libs/exavmlib/lib/ErlangError.ex b/libs/exavmlib/lib/ErlangError.ex
new file mode 100644
index 000000000..fe6edba7e
--- /dev/null
+++ b/libs/exavmlib/lib/ErlangError.ex
@@ -0,0 +1,133 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule ErlangError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:original]
+
+ @impl true
+ def message(exception) do
+ "Erlang error: #{inspect(exception.original)}"
+ end
+
+ @doc false
+ def normalize(:badarg, _stacktrace) do
+ %ArgumentError{}
+ end
+
+ def normalize(:badarith, _stacktrace) do
+ %ArithmeticError{}
+ end
+
+ def normalize(:system_limit, _stacktrace) do
+ %SystemLimitError{}
+ end
+
+ def normalize(:cond_clause, _stacktrace) do
+ %CondClauseError{}
+ end
+
+ def normalize({:badarity, {fun, args}}, _stacktrace) do
+ %BadArityError{function: fun, args: args}
+ end
+
+ def normalize({:badfun, term}, _stacktrace) do
+ %BadFunctionError{term: term}
+ end
+
+ def normalize({:badstruct, struct, term}, _stacktrace) do
+ %BadStructError{struct: struct, term: term}
+ end
+
+ def normalize({:badmatch, term}, _stacktrace) do
+ %MatchError{term: term}
+ end
+
+ def normalize({:badmap, term}, _stacktrace) do
+ %BadMapError{term: term}
+ end
+
+ def normalize({:badbool, op, term}, _stacktrace) do
+ %BadBooleanError{operator: op, term: term}
+ end
+
+ def normalize({:badkey, key}, stacktrace) do
+ term =
+ case stacktrace do
+ [{Map, :get_and_update!, [map, _, _], _} | _] -> map
+ [{Map, :update!, [map, _, _], _} | _] -> map
+ [{:maps, :update, [_, _, map], _} | _] -> map
+ [{:maps, :get, [_, map], _} | _] -> map
+ [{:erlang, :map_get, [_, map], _} | _] -> map
+ _ -> nil
+ end
+
+ %KeyError{key: key, term: term}
+ end
+
+ def normalize({:badkey, key, map}, _stacktrace) do
+ %KeyError{key: key, term: map}
+ end
+
+ def normalize({:case_clause, term}, _stacktrace) do
+ %CaseClauseError{term: term}
+ end
+
+ def normalize({:with_clause, term}, _stacktrace) do
+ %WithClauseError{term: term}
+ end
+
+ def normalize({:try_clause, term}, _stacktrace) do
+ %TryClauseError{term: term}
+ end
+
+ def normalize(:undef, stacktrace) do
+ {mod, fun, arity} = from_stacktrace(stacktrace)
+ %UndefinedFunctionError{module: mod, function: fun, arity: arity}
+ end
+
+ def normalize(:function_clause, stacktrace) do
+ {mod, fun, arity} = from_stacktrace(stacktrace)
+ %FunctionClauseError{module: mod, function: fun, arity: arity}
+ end
+
+ def normalize({:badarg, payload}, _stacktrace) do
+ %ArgumentError{message: "argument error: #{inspect(payload)}"}
+ end
+
+ def normalize(other, _stacktrace) do
+ %ErlangError{original: other}
+ 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
diff --git a/libs/exavmlib/lib/Exception.ex b/libs/exavmlib/lib/Exception.ex
new file mode 100644
index 000000000..d1deeec13
--- /dev/null
+++ b/libs/exavmlib/lib/Exception.ex
@@ -0,0 +1,557 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule Exception do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ @moduledoc """
+ Functions to format throw/catch/exit and exceptions.
+
+ Note that stacktraces in Elixir are only available inside
+ catch and rescue by using the `__STACKTRACE__/0` variable.
+
+ Do not rely on the particular format returned by the `format*`
+ functions in this module. They may be changed in future releases
+ in order to better suit Elixir's tool chain. In other words,
+ by using the functions in this module it is guaranteed you will
+ format exceptions as in the current Elixir version being used.
+ """
+
+ @typedoc "The exception type"
+ @type t :: %{
+ required(:__struct__) => module,
+ required(:__exception__) => true,
+ optional(atom) => any
+ }
+
+ @typedoc "The kind handled by formatting functions"
+ @type kind :: :error | non_error_kind
+ @typep non_error_kind :: :exit | :throw | {:EXIT, pid}
+
+ @type stacktrace :: [stacktrace_entry]
+ @type stacktrace_entry ::
+ {module, atom, arity_or_args, location}
+ | {(... -> any), arity_or_args, location}
+
+ @typep arity_or_args :: non_neg_integer | list
+ @typep location :: keyword
+
+ @callback exception(term) :: t
+ @callback message(t) :: String.t()
+
+ @doc """
+ Called from `Exception.blame/3` to augment the exception struct.
+
+ Can be used to collect additional information about the exception
+ or do some additional expensive computation.
+ """
+ @callback blame(t, stacktrace) :: {t, stacktrace}
+ @optional_callbacks [blame: 2]
+
+ @doc """
+ Returns `true` if the given `term` is an exception.
+ """
+ def exception?(term)
+ def exception?(%_{__exception__: true}), do: true
+ def exception?(_), do: false
+
+ @doc """
+ Gets the message for an `exception`.
+ """
+ def message(%module{__exception__: true} = exception) do
+ try do
+ module.message(exception)
+ rescue
+ caught_exception ->
+ "got #{inspect(caught_exception.__struct__)} with message " <>
+ "#{inspect(message(caught_exception))} while retrieving Exception.message/1 " <>
+ "for #{inspect(exception)}"
+ else
+ result when is_binary(result) ->
+ result
+
+ result ->
+ "got #{inspect(result)} " <>
+ "while retrieving Exception.message/1 for #{inspect(exception)} " <>
+ "(expected a string)"
+ end
+ end
+
+ @doc """
+ Normalizes an exception, converting Erlang exceptions
+ to Elixir exceptions.
+
+ It takes the `kind` spilled by `catch` as an argument and
+ normalizes only `:error`, returning the untouched payload
+ for others.
+
+ The third argument is the stacktrace which is used to enrich
+ a normalized error with more information. It is only used when
+ the kind is an error.
+ """
+ @spec normalize(:error, any, stacktrace) :: t
+ @spec normalize(non_error_kind, payload, stacktrace) :: payload when payload: var
+ def normalize(kind, payload, stacktrace \\ [])
+ def normalize(:error, %_{__exception__: true} = payload, _stacktrace), do: payload
+ def normalize(:error, payload, stacktrace), do: ErlangError.normalize(payload, stacktrace)
+ def normalize(_kind, payload, _stacktrace), do: payload
+
+ @doc """
+ Normalizes and formats any throw/error/exit.
+
+ The message is formatted and displayed in the same
+ format as used by Elixir's CLI.
+
+ The third argument is the stacktrace which is used to enrich
+ a normalized error with more information. It is only used when
+ the kind is an error.
+ """
+ @spec format_banner(kind, any, stacktrace) :: String.t()
+ def format_banner(kind, exception, stacktrace \\ [])
+
+ def format_banner(:error, exception, stacktrace) do
+ exception = normalize(:error, exception, stacktrace)
+ "** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
+ end
+
+ def format_banner(:throw, reason, _stacktrace) do
+ "** (throw) " <> inspect(reason)
+ end
+
+ def format_banner(:exit, reason, _stacktrace) do
+ "** (exit) " <> format_exit(reason, <<"\n ">>)
+ end
+
+ def format_banner({:EXIT, pid}, reason, _stacktrace) do
+ "** (EXIT from #{inspect(pid)}) " <> format_exit(reason, <<"\n ">>)
+ end
+
+ @doc """
+ Normalizes and formats throw/errors/exits and stacktraces.
+
+ It relies on `format_banner/3` and `format_stacktrace/1`
+ to generate the final format.
+
+ If `kind` is `{:EXIT, pid}`, it does not generate a stacktrace,
+ as such exits are retrieved as messages without stacktraces.
+ """
+ @spec format(kind, any, stacktrace) :: String.t()
+ def format(kind, payload, stacktrace \\ [])
+
+ def format({:EXIT, _} = kind, any, _) do
+ format_banner(kind, any)
+ end
+
+ def format(kind, payload, stacktrace) do
+ message = format_banner(kind, payload, stacktrace)
+
+ case stacktrace do
+ [] -> message
+ _ -> message <> "\n" <> format_stacktrace(stacktrace)
+ end
+ end
+
+ @doc """
+ Attaches information to exceptions for extra debugging.
+
+ This operation is potentially expensive, as it reads data
+ from the filesystem, parses beam files, evaluates code and
+ so on.
+
+ If the exception module implements the optional `c:blame/2`
+ callback, it will be invoked to perform the computation.
+ """
+ @doc since: "1.5.0"
+ @spec blame(:error, any, stacktrace) :: {t, stacktrace}
+ @spec blame(non_error_kind, payload, stacktrace) :: {payload, stacktrace} when payload: var
+ def blame(kind, error, stacktrace)
+
+ def blame(:error, error, stacktrace) do
+ %module{} = struct = normalize(:error, error, stacktrace)
+
+ # TODO: Code.ensure_loaded?(module) is not supported right now
+ # original code: Code.ensure_loaded?(module) and function_exported?(module, :blame, 2)
+ if function_exported?(module, :blame, 2) do
+ module.blame(struct, stacktrace)
+ else
+ {struct, stacktrace}
+ end
+ end
+
+ def blame(_kind, reason, stacktrace) do
+ {reason, stacktrace}
+ end
+
+ @doc """
+ Formats an exit. It returns a string.
+
+ Often there are errors/exceptions inside exits. Exits are often
+ wrapped by the caller and provide stacktraces too. This function
+ formats exits in a way to nicely show the exit reason, caller
+ and stacktrace.
+ """
+ @spec format_exit(any) :: String.t()
+ def format_exit(reason) do
+ format_exit(reason, <<"\n ">>)
+ end
+
+ # 2-Tuple could be caused by an error if the second element is a stacktrace.
+ defp format_exit({exception, maybe_stacktrace} = reason, joiner)
+ when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
+ try do
+ Enum.map(maybe_stacktrace, &format_stacktrace_entry/1)
+ catch
+ :error, _ ->
+ # Not a stacktrace, was an exit.
+ format_exit_reason(reason)
+ else
+ formatted_stacktrace ->
+ # Assume a non-empty list formattable as stacktrace is a
+ # stacktrace, so exit was caused by an error.
+ message =
+ "an exception was raised:" <>
+ joiner <> format_banner(:error, exception, maybe_stacktrace)
+
+ Enum.join([message | formatted_stacktrace], joiner <> <<" ">>)
+ end
+ end
+
+ # :supervisor.start_link returns this error reason when it fails to init
+ # because a child's start_link raises.
+ defp format_exit({:shutdown, {:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
+ format_start_child(child, reason, joiner)
+ end
+
+ # :supervisor.start_link returns this error reason when it fails to init
+ # because a child's start_link returns {:error, reason}.
+ defp format_exit({:shutdown, {:failed_to_start_child, child, reason}}, joiner) do
+ format_start_child(child, reason, joiner)
+ end
+
+ # 2-Tuple could be an exit caused by mfa if second element is mfa, args
+ # must be a list of arguments - max length 255 due to max arity.
+ defp format_exit({reason2, {mod, fun, args}} = reason, joiner)
+ when length(args) < 256 do
+ try do
+ format_mfa(mod, fun, args)
+ catch
+ :error, _ ->
+ # Not an mfa, was an exit.
+ format_exit_reason(reason)
+ else
+ mfa ->
+ # Assume tuple formattable as an mfa is an mfa,
+ # so exit was caused by failed mfa.
+ "exited in: " <>
+ mfa <> joiner <> "** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
+ end
+ end
+
+ defp format_exit(reason, _joiner) do
+ format_exit_reason(reason)
+ end
+
+ defp format_exit_reason(:normal), do: "normal"
+ defp format_exit_reason(:shutdown), do: "shutdown"
+
+ defp format_exit_reason({:shutdown, reason}) do
+ "shutdown: #{inspect(reason)}"
+ end
+
+ defp format_exit_reason(:calling_self), do: "process attempted to call itself"
+ defp format_exit_reason(:timeout), do: "time out"
+ defp format_exit_reason(:killed), do: "killed"
+ defp format_exit_reason(:noconnection), do: "no connection"
+
+ defp format_exit_reason(:noproc) do
+ "no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started"
+ end
+
+ defp format_exit_reason({:nodedown, node_name}) when is_atom(node_name) do
+ "no connection to #{node_name}"
+ end
+
+ # :gen_server exit reasons
+
+ defp format_exit_reason({:already_started, pid}) do
+ "already started: " <> inspect(pid)
+ end
+
+ defp format_exit_reason({:bad_return_value, value}) do
+ "bad return value: " <> inspect(value)
+ end
+
+ defp format_exit_reason({:bad_call, request}) do
+ "bad call: " <> inspect(request)
+ end
+
+ defp format_exit_reason({:bad_cast, request}) do
+ "bad cast: " <> inspect(request)
+ end
+
+ # :supervisor.start_link error reasons
+
+ # If value is a list will be formatted by mfa exit in format_exit/1
+ defp format_exit_reason({:bad_return, {mod, :init, value}})
+ when is_atom(mod) do
+ format_mfa(mod, :init, 1) <> " returned a bad value: " <> inspect(value)
+ end
+
+ defp format_exit_reason({:bad_start_spec, start_spec}) do
+ "bad child specification, invalid children: " <> inspect(start_spec)
+ end
+
+ defp format_exit_reason({:start_spec, start_spec}) do
+ "bad child specification, " <> format_sup_spec(start_spec)
+ end
+
+ defp format_exit_reason({:supervisor_data, data}) do
+ "bad supervisor configuration, " <> format_sup_data(data)
+ end
+
+ defp format_exit_reason(reason), do: inspect(reason)
+
+ defp format_start_child(child, reason, joiner) do
+ "shutdown: failed to start child: " <>
+ inspect(child) <> joiner <> "** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
+ end
+
+ defp format_sup_data({:invalid_type, type}) do
+ "invalid type: " <> inspect(type)
+ end
+
+ defp format_sup_data({:invalid_strategy, strategy}) do
+ "invalid strategy: " <> inspect(strategy)
+ end
+
+ defp format_sup_data({:invalid_intensity, intensity}) do
+ "invalid max_restarts (intensity): " <> inspect(intensity)
+ end
+
+ defp format_sup_data({:invalid_period, period}) do
+ "invalid max_seconds (period): " <> inspect(period)
+ end
+
+ defp format_sup_data({:invalid_max_children, max_children}) do
+ "invalid max_children: " <> inspect(max_children)
+ end
+
+ defp format_sup_data({:invalid_extra_arguments, extra}) do
+ "invalid extra_arguments: " <> inspect(extra)
+ end
+
+ defp format_sup_data(other), do: "got: #{inspect(other)}"
+
+ defp format_sup_spec({:duplicate_child_name, id}) do
+ """
+ more than one child specification has the id: #{inspect(id)}.
+ If using maps as child specifications, make sure the :id keys are unique.
+ If using a module or {module, arg} as child, use Supervisor.child_spec/2 to change the :id, for example:
+
+ children = [
+ Supervisor.child_spec({MyWorker, arg}, id: :my_worker_1),
+ Supervisor.child_spec({MyWorker, arg}, id: :my_worker_2)
+ ]
+ """
+ end
+
+ defp format_sup_spec({:invalid_child_spec, child_spec}) do
+ "invalid child specification: #{inspect(child_spec)}"
+ end
+
+ defp format_sup_spec({:invalid_child_type, type}) do
+ "invalid child type: #{inspect(type)}. Must be :worker or :supervisor."
+ end
+
+ defp format_sup_spec({:invalid_mfa, mfa}) do
+ "invalid mfa: #{inspect(mfa)}"
+ end
+
+ defp format_sup_spec({:invalid_restart_type, restart}) do
+ "invalid restart type: #{inspect(restart)}. Must be :permanent, :transient or :temporary."
+ end
+
+ defp format_sup_spec({:invalid_shutdown, shutdown}) do
+ "invalid shutdown: #{inspect(shutdown)}. Must be an integer >= 0, :infinity or :brutal_kill."
+ end
+
+ defp format_sup_spec({:invalid_module, mod}) do
+ "invalid module: #{inspect(mod)}. Must be an atom."
+ end
+
+ defp format_sup_spec({:invalid_modules, modules}) do
+ "invalid modules: #{inspect(modules)}. Must be a list of atoms or :dynamic."
+ end
+
+ defp format_sup_spec(other), do: "got: #{inspect(other)}"
+
+ @doc """
+ Receives a stacktrace entry and formats it into a string.
+ """
+ @spec format_stacktrace_entry(stacktrace_entry) :: String.t()
+ def format_stacktrace_entry(entry)
+
+ # From Macro.Env.stacktrace
+ def format_stacktrace_entry({module, :__MODULE__, 0, location}) do
+ format_location(location) <> inspect(module) <> " (module)"
+ end
+
+ # From :elixir_compiler_*
+ def format_stacktrace_entry({_module, :__MODULE__, 1, location}) do
+ format_location(location) <> "(module)"
+ end
+
+ # From :elixir_compiler_*
+ def format_stacktrace_entry({_module, :__FILE__, 1, location}) do
+ format_location(location) <> "(file)"
+ end
+
+ def format_stacktrace_entry({module, fun, arity, location}) do
+ format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
+ end
+
+ def format_stacktrace_entry({fun, arity, location}) do
+ format_location(location) <> format_fa(fun, arity)
+ end
+
+ defp format_application(module) do
+ # We cannot use Application due to bootstrap issues
+ case :application.get_application(module) do
+ {:ok, app} -> "(" <> Atom.to_string(app) <> ") "
+ :undefined -> ""
+ end
+ end
+
+ @doc """
+ Formats the stacktrace.
+
+ A stacktrace must be given as an argument. If not, the stacktrace
+ is retrieved from `Process.info/2`.
+ """
+ def format_stacktrace(trace \\ nil) do
+ trace =
+ if trace do
+ trace
+ else
+ case Process.info(self(), :current_stacktrace) do
+ {:current_stacktrace, t} -> Enum.drop(t, 3)
+ end
+ end
+
+ case trace do
+ [] -> "\n"
+ _ -> " " <> Enum.map_join(trace, "\n ", &format_stacktrace_entry(&1)) <> "\n"
+ 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 -> nil end, 1)
+ #=> "#Function<...>/1"
+
+ """
+ def format_fa(fun, arity) when is_function(fun) do
+ "#{inspect(fun)}#{format_arity(arity)}"
+ 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()"
+
+ Anonymous functions are reported as -func/arity-anonfn-count-,
+ where func is the name of the enclosing function. Convert to
+ "anonymous fn in func/arity"
+ """
+ def format_mfa(module, fun, arity) when is_atom(module) and is_atom(fun) do
+ # Original code:
+ # case Code.Identifier.extract_anonymous_fun_parent(fun) do
+ # {outer_name, outer_arity} ->
+ # "anonymous fn#{format_arity(arity)} in " <>
+ # "#{Code.Identifier.inspect_as_atom(module)}." <>
+ # "#{Code.Identifier.inspect_as_function(outer_name)}/#{outer_arity}"
+ #
+ # :error ->
+ # "#{Code.Identifier.inspect_as_atom(module)}." <>
+ # "#{Code.Identifier.inspect_as_function(fun)}#{format_arity(arity)}"
+ # end
+ #
+ # Here we use a super simplified version:
+ "#{inspect(module)}.#{inspect(fun)}#{format_arity(arity)}"
+ end
+
+ defp format_arity(arity) when is_list(arity) do
+ inspected = for x <- arity, do: inspect(x)
+ "(#{Enum.join(inspected, ", ")})"
+ end
+
+ defp format_arity(arity) when is_integer(arity) do
+ "/" <> Integer.to_string(arity)
+ end
+
+ @doc """
+ Formats the given `file` and `line` as shown in stacktraces.
+ If any of the values are `nil`, they are omitted.
+
+ ## 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, suffix \\ "") do
+ if file do
+ if line && line != 0 do
+ "#{file}:#{line}:#{suffix}"
+ else
+ "#{file}:#{suffix}"
+ end
+ else
+ ""
+ end
+ end
+
+ defp format_location(opts) when is_list(opts) do
+ format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line), " ")
+ end
+end
diff --git a/libs/exavmlib/lib/FunctionClauseError.ex b/libs/exavmlib/lib/FunctionClauseError.ex
new file mode 100644
index 000000000..e37d4293c
--- /dev/null
+++ b/libs/exavmlib/lib/FunctionClauseError.ex
@@ -0,0 +1,39 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule FunctionClauseError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:module, :function, :arity, :kind, :args, :clauses]
+
+ @impl true
+ def message(exception) do
+ case exception do
+ %{function: nil} ->
+ "no function clause matches"
+
+ %{module: module, function: function, arity: arity} ->
+ formatted = Exception.format_mfa(module, function, arity)
+ "no function clause matching in #{formatted}"
+ end
+ end
+end
diff --git a/libs/exavmlib/lib/IO.ex b/libs/exavmlib/lib/IO.ex
index 6a54c1dfd..494a14a5a 100644
--- a/libs/exavmlib/lib/IO.ex
+++ b/libs/exavmlib/lib/IO.ex
@@ -2,6 +2,8 @@
# This file is part of AtomVM.
#
# Copyright 2024 Davide Bettio
+# Copyright 2012-2017 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.7.4/lib/elixir/lib/io.ex
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,6 +24,39 @@ defmodule IO do
# This avoids crashing the compiler at build time
@compile {:autoload, false}
+ # Taken from Elixir io.ex
+ @doc """
+ Converts iodata (a list of integers representing bytes, lists
+ and binaries) into a binary.
+ The operation is Unicode unsafe.
+
+ Notice that this function treats lists of integers as raw bytes
+ and does not perform any kind of encoding conversion. If you want
+ to convert from a charlist to a string (UTF-8 encoded), please
+ use `chardata_to_string/1` instead.
+
+ If this function receives a binary, the same binary is returned.
+
+ Inlined by the compiler.
+
+ ## Examples
+
+ iex> bin1 = <<1, 2, 3>>
+ iex> bin2 = <<4, 5>>
+ iex> bin3 = <<6>>
+ iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4 | bin3])
+ <<1, 2, 3, 1, 2, 3, 4, 5, 4, 6>>
+
+ iex> bin = <<1, 2, 3>>
+ iex> IO.iodata_to_binary(bin)
+ <<1, 2, 3>>
+
+ """
+ @spec iodata_to_binary(iodata) :: binary
+ def iodata_to_binary(item) do
+ :erlang.iolist_to_binary(item)
+ end
+
def puts(string) do
:io.put_chars([to_chardata(string), ?\n])
end
diff --git a/libs/exavmlib/lib/Kernel.ex b/libs/exavmlib/lib/Kernel.ex
index 3950f1bf8..4d5088878 100644
--- a/libs/exavmlib/lib/Kernel.ex
+++ b/libs/exavmlib/lib/Kernel.ex
@@ -2,6 +2,8 @@
# This file is part of AtomVM.
#
# Copyright 2020 Davide Bettio
+# Copyright 2012-2022 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/main/lib/elixir/lib/kernel.ex
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -121,4 +123,174 @@ defmodule Kernel do
# handle spaces and special characters
:erlang.atom_to_binary(atom, :latin1)
end
+
+ @doc """
+ Returns the biggest of the two given terms according to
+ Erlang's term ordering.
+
+ If the terms compare equal, the first one is returned.
+
+ Inlined by the compiler.
+
+ ## Examples
+
+ iex> max(1, 2)
+ 2
+ iex> max(:a, :b)
+ :b
+
+ Using Erlang's term ordering means that comparisons are
+ structural and not semantic. For example, when comparing dates:
+
+ iex> max(~D[2017-03-31], ~D[2017-04-01])
+ ~D[2017-03-31]
+
+ In the example above, `max/1` returned March 31st instead of April 1st
+ because the structural comparison compares the day before the year. In
+ such cases it is common for modules to provide functions such as
+ `Date.compare/2` that perform semantic comparison.
+ """
+ @spec max(first, second) :: first | second when first: term, second: term
+ def max(first, second) do
+ :erlang.max(first, second)
+ end
+
+ @doc """
+ Returns the smallest of the two given terms according to
+ Erlang's term ordering.
+
+ If the terms compare equal, the first one is returned.
+
+ Inlined by the compiler.
+
+ ## Examples
+
+ iex> min(1, 2)
+ 1
+ iex> min("foo", "bar")
+ "bar"
+
+ Using Erlang's term ordering means that comparisons are
+ structural and not semantic. For example, when comparing dates:
+
+ iex> min(~D[2017-03-31], ~D[2017-04-01])
+ ~D[2017-04-01]
+
+ In the example above, `min/1` returned April 1st instead of March 31st
+ because the structural comparison compares the day before the year. In
+ such cases it is common for modules to provide functions such as
+ `Date.compare/2` that perform semantic comparison.
+ """
+ @spec min(first, second) :: first | second when first: term, second: term
+ def min(first, second) do
+ :erlang.min(first, second)
+ end
+
+ # Taken from Elixir kernel.ex
+ @doc """
+ Creates and updates structs.
+
+ The `struct` argument may be an atom (which defines `defstruct`)
+ or a `struct` itself. The second argument is any `Enumerable` that
+ emits two-element tuples (key-value pairs) during enumeration.
+
+ Keys in the `Enumerable` that don't exist in the struct are automatically
+ discarded. Note that keys must be atoms, as only atoms are allowed when
+ defining a struct.
+
+ This function is useful for dynamically creating and updating structs, as
+ well as for converting maps to structs; in the latter case, just inserting
+ the appropriate `:__struct__` field into the map may not be enough and
+ `struct/2` should be used instead.
+
+ ## Examples
+
+ defmodule User do
+ defstruct name: "john"
+ end
+
+ struct(User)
+ #=> %User{name: "john"}
+
+ opts = [name: "meg"]
+ user = struct(User, opts)
+ #=> %User{name: "meg"}
+
+ struct(user, unknown: "value")
+ #=> %User{name: "meg"}
+
+ struct(User, %{name: "meg"})
+ #=> %User{name: "meg"}
+
+ # String keys are ignored
+ struct(User, %{"name" => "meg"})
+ #=> %User{name: "john"}
+
+ """
+ @spec struct(module | struct, Enum.t()) :: struct
+ def struct(struct, fields \\ []) do
+ struct(struct, fields, fn
+ {:__struct__, _val}, acc ->
+ acc
+
+ {key, val}, acc ->
+ case acc do
+ %{^key => _} -> %{acc | key => val}
+ _ -> acc
+ end
+ end)
+ end
+
+ # Taken from Elixir kernel.ex
+ @doc """
+ Similar to `struct/2` but checks for key validity.
+
+ The function `struct!/2` emulates the compile time behaviour
+ of structs. This means that:
+
+ * when building a struct, as in `struct!(SomeStruct, key: :value)`,
+ it is equivalent to `%SomeStruct{key: :value}` and therefore this
+ function will check if every given key-value belongs to the struct.
+ If the struct is enforcing any key via `@enforce_keys`, those will
+ be enforced as well;
+
+ * when updating a struct, as in `struct!(%SomeStruct{}, key: :value)`,
+ it is equivalent to `%SomeStruct{struct | key: :value}` and therefore this
+ function will check if every given key-value belongs to the struct.
+ However, updating structs does not enforce keys, as keys are enforced
+ only when building;
+
+ """
+ @spec struct!(module | struct, Enum.t()) :: struct | no_return
+ def struct!(struct, fields \\ [])
+
+ def struct!(struct, fields) when is_atom(struct) do
+ struct.__struct__(fields)
+ end
+
+ def struct!(struct, fields) when is_map(struct) do
+ struct(struct, fields, fn
+ {:__struct__, _}, acc ->
+ acc
+
+ {key, val}, acc ->
+ Map.replace!(acc, key, val)
+ end)
+ end
+
+ defp struct(struct, [], _fun) when is_atom(struct) do
+ struct.__struct__()
+ end
+
+ defp struct(struct, fields, fun) when is_atom(struct) do
+ struct(struct.__struct__(), fields, fun)
+ end
+
+ defp struct(%_{} = struct, [], _fun) do
+ struct
+ end
+
+ defp struct(%_{} = struct, fields, fun) do
+ Enum.reduce(fields, struct, fun)
+ end
end
diff --git a/libs/exavmlib/lib/KeyError.ex b/libs/exavmlib/lib/KeyError.ex
new file mode 100644
index 000000000..d19f52319
--- /dev/null
+++ b/libs/exavmlib/lib/KeyError.ex
@@ -0,0 +1,53 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule KeyError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:key, :term, :message]
+
+ @impl true
+ def message(exception = %{message: nil}), do: message(exception.key, exception.term)
+ def message(%{message: message}), do: message
+
+ def message(key, term) do
+ message = "key #{inspect(key)} not found"
+
+ if term != nil do
+ message <> " in: #{inspect(term)}"
+ else
+ message
+ end
+ end
+
+ @impl true
+ def blame(exception = %{term: nil}, stacktrace) do
+ message = message(exception.key, exception.term)
+ {%{exception | message: message}, stacktrace}
+ end
+
+ def blame(exception, stacktrace) do
+ %{term: term, key: key} = exception
+ message = message(key, term)
+ {%{exception | message: message}, stacktrace}
+ end
+end
diff --git a/libs/exavmlib/lib/Map.ex b/libs/exavmlib/lib/Map.ex
index f5bfcf322..f8fbb8b5a 100644
--- a/libs/exavmlib/lib/Map.ex
+++ b/libs/exavmlib/lib/Map.ex
@@ -23,6 +23,9 @@ defmodule Map do
# This avoids crashing the compiler at build time
@compile {:autoload, false}
+ @type key :: any
+ @type value :: any
+
def new(list) when is_list(list), do: :maps.from_list(list)
def new(%{} = map), do: map
@@ -66,4 +69,53 @@ defmodule Map do
def equal?(%{} = map1, %{} = map2), do: map1 === map2
def equal?(%{} = map1, map2), do: :erlang.error({:badmap, map2}, [map1, map2])
def equal?(term, other), do: :erlang.error({:badmap, term}, [term, other])
+
+ @doc """
+ Puts a value under `key` only if the `key` already exists in `map`.
+
+ ## Examples
+
+ iex> Map.replace(%{a: 1, b: 2}, :a, 3)
+ %{a: 3, b: 2}
+
+ iex> Map.replace(%{a: 1}, :b, 2)
+ %{a: 1}
+
+ """
+ @doc since: "1.11.0"
+ @spec replace(map, key, value) :: map
+ def replace(map, key, value) do
+ case map do
+ %{^key => _value} ->
+ %{map | key => value}
+
+ %{} ->
+ map
+
+ other ->
+ :erlang.error({:badmap, other})
+ end
+ end
+
+ @doc """
+ Puts a value under `key` only if the `key` already exists in `map`.
+
+ If `key` is not present in `map`, a `KeyError` exception is raised.
+
+ Inlined by the compiler.
+
+ ## Examples
+
+ iex> Map.replace!(%{a: 1, b: 2}, :a, 3)
+ %{a: 3, b: 2}
+
+ iex> Map.replace!(%{a: 1}, :b, 2)
+ ** (KeyError) key :b not found in: %{a: 1}
+
+ """
+ @doc since: "1.5.0"
+ @spec replace!(map, key, value) :: map
+ def replace!(map, key, value) do
+ :maps.update(key, value, map)
+ end
end
diff --git a/libs/exavmlib/lib/MapSet.ex b/libs/exavmlib/lib/MapSet.ex
new file mode 100644
index 000000000..adb20ea62
--- /dev/null
+++ b/libs/exavmlib/lib/MapSet.ex
@@ -0,0 +1,434 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2024 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.17.2/lib/elixir/lib/map_set.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule MapSet do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ @moduledoc """
+ Functions that work on sets.
+
+ A set is a data structure that can contain unique elements of any kind,
+ without any particular order. `MapSet` is the "go to" set data structure in Elixir.
+
+ A set can be constructed using `MapSet.new/0`:
+
+ iex> MapSet.new()
+ MapSet.new([])
+
+ Elements in a set don't have to be of the same type and they can be
+ populated from an [enumerable](`t:Enumerable.t/0`) using `MapSet.new/1`:
+
+ iex> MapSet.new([1, :two, {"three"}])
+ MapSet.new([1, :two, {"three"}])
+
+ Elements can be inserted using `MapSet.put/2`:
+
+ iex> MapSet.new([2]) |> MapSet.put(4) |> MapSet.put(0)
+ MapSet.new([0, 2, 4])
+
+ By definition, sets can't contain duplicate elements: when
+ inserting an element in a set where it's already present, the insertion is
+ simply a no-op.
+
+ iex> map_set = MapSet.new()
+ iex> MapSet.put(map_set, "foo")
+ MapSet.new(["foo"])
+ iex> map_set |> MapSet.put("foo") |> MapSet.put("foo")
+ MapSet.new(["foo"])
+
+ A `MapSet` is represented internally using the `%MapSet{}` struct. This struct
+ can be used whenever there's a need to pattern match on something being a `MapSet`:
+
+ iex> match?(%MapSet{}, MapSet.new())
+ true
+
+ Note that, however, the struct fields are private and must not be accessed
+ directly; use the functions in this module to perform operations on sets.
+
+ `MapSet`s can also be constructed starting from other collection-type data
+ structures: for example, see `MapSet.new/1` or `Enum.into/2`.
+
+ `MapSet` is built on top of Erlang's
+ [`:sets`](https://www.erlang.org/doc/man/sets.html) (version 2). This means
+ that they share many properties, including logarithmic time complexity. Erlang
+ `:sets` (version 2) are implemented on top of maps, so see the documentation
+ for `Map` for more information on its execution time complexity.
+ """
+
+ @type value :: term
+
+ @opaque internal(value) :: :sets.set(value)
+ @type t(value) :: %__MODULE__{map: internal(value)}
+ @type t :: t(term)
+
+ # The key name is :map because the MapSet implementation used to be based on top of maps before
+ # Elixir 1.15 (and Erlang/OTP 24, which introduced :sets version 2). :sets v2's internal
+ # representation is, anyways, exactly the same as MapSet's previous implementation. We cannot
+ # change the :map key name here because we'd break backwards compatibility with code compiled
+ # with Elixir 1.14 and earlier and executed on Elixir 1.15+.
+ # AtomVM change here: do not use :sets.new(version: 2), otherwise it may fail at compile time
+ defstruct map: %{}
+
+ @doc """
+ Returns a new set.
+
+ ## Examples
+
+ iex> MapSet.new()
+ MapSet.new([])
+
+ """
+ @spec new :: t
+ def new(), do: %MapSet{}
+
+ @doc """
+ Creates a set from an enumerable.
+
+ ## Examples
+
+ iex> MapSet.new([:b, :a, 3])
+ MapSet.new([3, :a, :b])
+ iex> MapSet.new([3, 3, 3, 2, 2, 1])
+ MapSet.new([1, 2, 3])
+
+ """
+ @spec new(Enumerable.t()) :: t
+ def new(enumerable)
+
+ def new(%__MODULE__{} = map_set), do: map_set
+
+ def new(enumerable) do
+ set =
+ enumerable
+ |> Enum.to_list()
+ |> :sets.from_list(version: 2)
+
+ %MapSet{map: set}
+ end
+
+ @doc """
+ Creates a set from an enumerable via the transformation function.
+
+ ## Examples
+
+ iex> MapSet.new([1, 2, 1], fn x -> 2 * x end)
+ MapSet.new([2, 4])
+
+ """
+ @spec new(Enumerable.t(), (term -> val)) :: t(val) when val: value
+ def new(enumerable, transform) when is_function(transform, 1) do
+ set =
+ enumerable
+ |> Enum.map(transform)
+ |> :sets.from_list(version: 2)
+
+ %MapSet{map: set}
+ end
+
+ @doc """
+ Deletes `value` from `map_set`.
+
+ Returns a new set which is a copy of `map_set` but without `value`.
+
+ ## Examples
+
+ iex> map_set = MapSet.new([1, 2, 3])
+ iex> MapSet.delete(map_set, 4)
+ MapSet.new([1, 2, 3])
+ iex> MapSet.delete(map_set, 2)
+ MapSet.new([1, 3])
+
+ """
+ @spec delete(t(val1), val2) :: t(val1) when val1: value, val2: value
+ def delete(%MapSet{map: set} = map_set, value) do
+ %{map_set | map: :sets.del_element(value, set)}
+ end
+
+ @doc """
+ Returns a set that is `map_set1` without the members of `map_set2`.
+
+ ## Examples
+
+ iex> MapSet.difference(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
+ MapSet.new([1])
+
+ """
+ @spec difference(t(val1), t(val2)) :: t(val1) when val1: value, val2: value
+ def difference(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
+ %{map_set1 | map: :sets.subtract(set1, set2)}
+ end
+
+ @doc """
+ Returns a set with elements that are present in only one but not both sets.
+
+ ## Examples
+
+ iex> MapSet.symmetric_difference(MapSet.new([1, 2, 3]), MapSet.new([2, 3, 4]))
+ MapSet.new([1, 4])
+
+ """
+ @doc since: "1.14.0"
+ @spec symmetric_difference(t(val1), t(val2)) :: t(val1 | val2) when val1: value, val2: value
+ def symmetric_difference(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
+ {small, large} = if :sets.size(set1) <= :sets.size(set2), do: {set1, set2}, else: {set2, set1}
+
+ disjointer_fun = fn elem, {small, acc} ->
+ if :sets.is_element(elem, small) do
+ {:sets.del_element(elem, small), acc}
+ else
+ {small, [elem | acc]}
+ end
+ end
+
+ {new_small, list} = :sets.fold(disjointer_fun, {small, []}, large)
+ %{map_set1 | map: :sets.union(new_small, :sets.from_list(list, version: 2))}
+ end
+
+ @doc """
+ Checks if `map_set1` and `map_set2` have no members in common.
+
+ ## Examples
+
+ iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([3, 4]))
+ true
+ iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([2, 3]))
+ false
+
+ """
+ @spec disjoint?(t, t) :: boolean
+ def disjoint?(%MapSet{map: set1}, %MapSet{map: set2}) do
+ :sets.is_disjoint(set1, set2)
+ end
+
+ @doc """
+ Checks if two sets are equal.
+
+ The comparison between elements is done using `===/2`,
+ which a set with `1` is not equivalent to a set with
+ `1.0`.
+
+ ## Examples
+
+ iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([2, 1, 1]))
+ true
+ iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([3, 4]))
+ false
+ iex> MapSet.equal?(MapSet.new([1]), MapSet.new([1.0]))
+ false
+
+ """
+ @spec equal?(t, t) :: boolean
+ def equal?(%MapSet{map: set1}, %MapSet{map: set2}) do
+ set1 === set2
+ end
+
+ @doc """
+ Returns a set containing only members that `map_set1` and `map_set2` have in common.
+
+ ## Examples
+
+ iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
+ MapSet.new([2])
+
+ iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([3, 4]))
+ MapSet.new([])
+
+ """
+ @spec intersection(t(val), t(val)) :: t(val) when val: value
+ def intersection(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
+ %{map_set1 | map: :sets.intersection(set1, set2)}
+ end
+
+ @doc """
+ Checks if `map_set` contains `value`.
+
+ ## Examples
+
+ iex> MapSet.member?(MapSet.new([1, 2, 3]), 2)
+ true
+ iex> MapSet.member?(MapSet.new([1, 2, 3]), 4)
+ false
+
+ """
+ @spec member?(t, value) :: boolean
+ def member?(%MapSet{map: set}, value) do
+ :sets.is_element(value, set)
+ end
+
+ @doc """
+ Inserts `value` into `map_set` if `map_set` doesn't already contain it.
+
+ ## Examples
+
+ iex> MapSet.put(MapSet.new([1, 2, 3]), 3)
+ MapSet.new([1, 2, 3])
+ iex> MapSet.put(MapSet.new([1, 2, 3]), 4)
+ MapSet.new([1, 2, 3, 4])
+
+ """
+ @spec put(t(val), new_val) :: t(val | new_val) when val: value, new_val: value
+ def put(%MapSet{map: set} = map_set, value) do
+ %{map_set | map: :sets.add_element(value, set)}
+ end
+
+ @doc """
+ Returns the number of elements in `map_set`.
+
+ ## Examples
+
+ iex> MapSet.size(MapSet.new([1, 2, 3]))
+ 3
+
+ """
+ @spec size(t) :: non_neg_integer
+ def size(%MapSet{map: set}) do
+ :sets.size(set)
+ end
+
+ @doc """
+ Checks if `map_set1`'s members are all contained in `map_set2`.
+
+ This function checks if `map_set1` is a subset of `map_set2`.
+
+ ## Examples
+
+ iex> MapSet.subset?(MapSet.new([1, 2]), MapSet.new([1, 2, 3]))
+ true
+ iex> MapSet.subset?(MapSet.new([1, 2, 3]), MapSet.new([1, 2]))
+ false
+
+ """
+ @spec subset?(t, t) :: boolean
+ def subset?(%MapSet{map: set1}, %MapSet{map: set2}) do
+ :sets.is_subset(set1, set2)
+ end
+
+ @doc """
+ Converts `map_set` to a list.
+
+ ## Examples
+
+ iex> MapSet.to_list(MapSet.new([1, 2, 3]))
+ [1, 2, 3]
+
+ """
+ @spec to_list(t(val)) :: [val] when val: value
+ def to_list(%MapSet{map: set}) do
+ :sets.to_list(set)
+ end
+
+ @doc """
+ Returns a set containing all members of `map_set1` and `map_set2`.
+
+ ## Examples
+
+ iex> MapSet.union(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
+ MapSet.new([1, 2, 3, 4])
+
+ """
+ @spec union(t(val1), t(val2)) :: t(val1 | val2) when val1: value, val2: value
+ def union(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
+ %{map_set1 | map: :sets.union(set1, set2)}
+ end
+
+ @doc """
+ Filters the set by returning only the elements from `map_set` for which invoking
+ `fun` returns a truthy value.
+
+ Also see `reject/2` which discards all elements where the function returns
+ a truthy value.
+
+ > #### Performance considerations {: .tip}
+ >
+ > If you find yourself doing multiple calls to `MapSet.filter/2`
+ > and `MapSet.reject/2` in a pipeline, it is likely more efficient
+ > to use `Enum.map/2` and `Enum.filter/2` instead and convert to
+ > a map at the end using `MapSet.new/1`.
+
+ ## Examples
+
+ iex> MapSet.filter(MapSet.new(1..5), fn x -> x > 3 end)
+ MapSet.new([4, 5])
+
+ iex> MapSet.filter(MapSet.new(["a", :b, "c"]), &is_atom/1)
+ MapSet.new([:b])
+
+ """
+ @doc since: "1.14.0"
+ @spec filter(t(a), (a -> as_boolean(term))) :: t(a) when a: value
+ def filter(%MapSet{map: set} = map_set, fun) when is_function(fun) do
+ pred = fn element -> !!fun.(element) end
+ %{map_set | map: :sets.filter(pred, set)}
+ end
+
+ @doc """
+ Returns a set by excluding the elements from `map_set` for which invoking `fun`
+ returns a truthy value.
+
+ See also `filter/2`.
+
+ ## Examples
+
+ iex> MapSet.reject(MapSet.new(1..5), fn x -> rem(x, 2) != 0 end)
+ MapSet.new([2, 4])
+
+ iex> MapSet.reject(MapSet.new(["a", :b, "c"]), &is_atom/1)
+ MapSet.new(["a", "c"])
+
+ """
+ @doc since: "1.14.0"
+ @spec reject(t(a), (a -> as_boolean(term))) :: t(a) when a: value
+ def reject(%MapSet{map: set} = map_set, fun) when is_function(fun) do
+ pred = fn element -> !fun.(element) end
+ %{map_set | map: :sets.filter(pred, set)}
+ end
+
+ @doc """
+ Splits the `map_set` into two `MapSet`s according to the given function `fun`.
+
+ `fun` receives each element in the `map_set` as its only argument. Returns
+ a tuple with the first `MapSet` containing all the elements in `map_set` for which
+ applying `fun` returned a truthy value, and a second `MapSet` with all the elements
+ for which applying `fun` returned a falsy value (`false` or `nil`).
+
+ ## Examples
+
+ iex> {while_true, while_false} = MapSet.split_with(MapSet.new([1, 2, 3, 4]), fn v -> rem(v, 2) == 0 end)
+ iex> while_true
+ MapSet.new([2, 4])
+ iex> while_false
+ MapSet.new([1, 3])
+
+ iex> {while_true, while_false} = MapSet.split_with(MapSet.new(), fn {_k, v} -> v > 50 end)
+ iex> while_true
+ MapSet.new([])
+ iex> while_false
+ MapSet.new([])
+
+ """
+ @doc since: "1.15.0"
+ @spec split_with(MapSet.t(), (any() -> as_boolean(term))) :: {MapSet.t(), MapSet.t()}
+ def split_with(%MapSet{map: map}, fun) when is_function(fun, 1) do
+ {while_true, while_false} = Map.split_with(map, fn {key, _} -> fun.(key) end)
+ {%MapSet{map: while_true}, %MapSet{map: while_false}}
+ end
+end
diff --git a/libs/exavmlib/lib/MatchError.ex b/libs/exavmlib/lib/MatchError.ex
new file mode 100644
index 000000000..b99b564f0
--- /dev/null
+++ b/libs/exavmlib/lib/MatchError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule MatchError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "no match of right hand side value: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/Protocol.UndefinedError.ex b/libs/exavmlib/lib/Protocol.UndefinedError.ex
new file mode 100644
index 000000000..9f71d2e14
--- /dev/null
+++ b/libs/exavmlib/lib/Protocol.UndefinedError.ex
@@ -0,0 +1,46 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule Protocol.UndefinedError do
+ defexception [:protocol, :value, description: ""]
+
+ @impl true
+ def message(%{protocol: protocol, value: value, description: description}) do
+ "protocol #{inspect(protocol)} not implemented for #{inspect(value)}" <>
+ maybe_description(description) <> maybe_available(protocol)
+ end
+
+ defp maybe_description(""), do: ""
+ defp maybe_description(description), do: ", " <> description
+
+ defp maybe_available(protocol) do
+ case protocol.__protocol__(:impls) do
+ {:consolidated, []} ->
+ ". There are no implementations for this protocol."
+
+ {:consolidated, types} ->
+ ". This protocol is implemented for: #{Enum.map_join(types, ", ", &inspect/1)}"
+
+ :not_consolidated ->
+ ""
+ end
+ end
+end
diff --git a/libs/exavmlib/lib/Range.ex b/libs/exavmlib/lib/Range.ex
new file mode 100644
index 000000000..e8f72c08a
--- /dev/null
+++ b/libs/exavmlib/lib/Range.ex
@@ -0,0 +1,118 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2020 Elixir Contributors
+# https://github.com/elixir-lang/elixir/commits/v1.10.4/lib/elixir/lib/enum.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule Range do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ @moduledoc """
+ Ranges represent a sequence of one or many, ascending
+ or descending, consecutive integers.
+
+ Ranges can be either increasing (`first <= last`) or
+ decreasing (`first > last`). Ranges are also always
+ inclusive.
+
+ A range is represented internally as a struct. However,
+ the most common form of creating and matching on ranges
+ is via the `../2` macro, auto-imported from `Kernel`:
+
+ iex> range = 1..3
+ 1..3
+ iex> first..last = range
+ iex> first
+ 1
+ iex> last
+ 3
+
+ A range implements the `Enumerable` protocol, which means
+ functions in the `Enum` module can be used to work with
+ ranges:
+
+ iex> range = 1..10
+ 1..10
+ iex> Enum.reduce(range, 0, fn i, acc -> i * i + acc end)
+ 385
+ iex> Enum.count(range)
+ 10
+ iex> Enum.member?(range, 11)
+ false
+ iex> Enum.member?(range, 8)
+ true
+
+ Such function calls are efficient memory-wise no matter the
+ size of the range. The implementation of the `Enumerable`
+ protocol uses logic based solely on the endpoints and does
+ not materialize the whole list of integers.
+ """
+
+ defstruct first: nil, last: nil
+
+ @type t :: %__MODULE__{first: integer, last: integer}
+ @type t(first, last) :: %__MODULE__{first: first, last: last}
+
+ @doc """
+ Creates a new range.
+
+ ## Examples
+
+ iex> Range.new(-100, 100)
+ -100..100
+
+ """
+ @spec new(integer, integer) :: t
+ def new(first, last) when is_integer(first) and is_integer(last) do
+ %Range{first: first, last: last}
+ end
+
+ def new(first, last) do
+ raise ArgumentError,
+ "ranges (first..last) expect both sides to be integers, " <>
+ "got: #{inspect(first)}..#{inspect(last)}"
+ end
+
+ @doc """
+ Checks if two ranges are disjoint.
+
+ ## Examples
+
+ iex> Range.disjoint?(1..5, 6..9)
+ true
+ iex> Range.disjoint?(5..1, 6..9)
+ true
+ iex> Range.disjoint?(1..5, 5..9)
+ false
+ iex> Range.disjoint?(1..5, 2..7)
+ false
+
+ """
+ @doc since: "1.8.0"
+ @spec disjoint?(t, t) :: boolean
+ def disjoint?(first1..last1 = _range1, first2..last2 = _range2) do
+ {first1, last1} = normalize(first1, last1)
+ {first2, last2} = normalize(first2, last2)
+ last2 < first1 or last1 < first2
+ end
+
+ @compile inline: [normalize: 2]
+ defp normalize(first, last) when first > last, do: {last, first}
+ defp normalize(first, last), do: {first, last}
+end
diff --git a/libs/exavmlib/lib/RuntimeError.ex b/libs/exavmlib/lib/RuntimeError.ex
new file mode 100644
index 000000000..423055eb4
--- /dev/null
+++ b/libs/exavmlib/lib/RuntimeError.ex
@@ -0,0 +1,27 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule RuntimeError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception message: "runtime error"
+end
diff --git a/libs/exavmlib/lib/SystemLimitError.ex b/libs/exavmlib/lib/SystemLimitError.ex
new file mode 100644
index 000000000..b4a36d4b3
--- /dev/null
+++ b/libs/exavmlib/lib/SystemLimitError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule SystemLimitError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception []
+
+ @impl true
+ def message(_) do
+ "a system limit has been reached"
+ end
+end
diff --git a/libs/exavmlib/lib/TryClauseError.ex b/libs/exavmlib/lib/TryClauseError.ex
new file mode 100644
index 000000000..cbd9937be
--- /dev/null
+++ b/libs/exavmlib/lib/TryClauseError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule TryClauseError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "no try clause matching: #{inspect(exception.term)}"
+ end
+end
diff --git a/libs/exavmlib/lib/UndefinedFunctionError.ex b/libs/exavmlib/lib/UndefinedFunctionError.ex
new file mode 100644
index 000000000..8653e3cda
--- /dev/null
+++ b/libs/exavmlib/lib/UndefinedFunctionError.ex
@@ -0,0 +1,77 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule UndefinedFunctionError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:module, :function, :arity, :reason, :message]
+
+ @impl true
+ def message(%{message: nil} = exception) do
+ %{reason: reason, module: module, function: function, arity: arity} = exception
+ {message, _loaded?} = message(reason, module, function, arity)
+ message
+ end
+
+ def message(%{message: message}) do
+ message
+ end
+
+ defp message(nil, module, function, arity) do
+ cond do
+ is_nil(function) or is_nil(arity) ->
+ {"undefined function", false}
+
+ is_nil(module) ->
+ formatted_fun = Exception.format_mfa(module, function, arity)
+ {"function #{formatted_fun} is undefined", false}
+
+ function_exported?(module, :module_info, 0) ->
+ message(:"function not exported", module, function, arity)
+
+ true ->
+ message(:"module could not be loaded", module, function, arity)
+ end
+ end
+
+ defp message(:"module could not be loaded", module, function, arity) do
+ formatted_fun = Exception.format_mfa(module, function, arity)
+ {"function #{formatted_fun} is undefined (module #{inspect(module)} is not available)", false}
+ end
+
+ defp message(:"function not exported", module, function, arity) do
+ formatted_fun = Exception.format_mfa(module, function, arity)
+ {"function #{formatted_fun} is undefined or private", true}
+ end
+
+ defp message(reason, module, function, arity) do
+ formatted_fun = Exception.format_mfa(module, function, arity)
+ {"function #{formatted_fun} is undefined (#{reason})", false}
+ end
+
+ @impl true
+ def blame(exception, stacktrace) do
+ %{reason: reason, module: module, function: function, arity: arity} = exception
+ {message, _loaded?} = message(reason, module, function, arity)
+ {%{exception | message: message}, stacktrace}
+ end
+end
diff --git a/libs/exavmlib/lib/WithClauseError.ex b/libs/exavmlib/lib/WithClauseError.ex
new file mode 100644
index 000000000..fe8f7790e
--- /dev/null
+++ b/libs/exavmlib/lib/WithClauseError.ex
@@ -0,0 +1,32 @@
+#
+# This file is part of elixir-lang.
+#
+# Copyright 2012-2018 Elixir Contributors
+# https://github.com/elixir-lang/elixir/tree/v1.7.4/lib/elixir/lib/exception.ex
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+defmodule WithClauseError do
+ # This avoids crashing the compiler at build time
+ @compile {:autoload, false}
+
+ defexception [:term]
+
+ @impl true
+ def message(exception) do
+ "no with clause matching: #{inspect(exception.term)}"
+ end
+end
diff --git a/src/libAtomVM/memory.h b/src/libAtomVM/memory.h
index 8303c544a..e53ef5c9d 100644
--- a/src/libAtomVM/memory.h
+++ b/src/libAtomVM/memory.h
@@ -339,6 +339,7 @@ static inline void memory_heap_append_heap(Heap *target, Heap *source)
* the copy of a term to or from a process mailbox.
* @param mso_list the list of mark-sweep object in a heap "space"
* @param global the global context
+ * @param from_task boolean, true if called from a task, false otherwise
*/
void memory_sweep_mso_list(term mso_list, GlobalContext *global, bool from_task);
diff --git a/src/libAtomVM/module.h b/src/libAtomVM/module.h
index 4420339e4..e41f8bbc1 100644
--- a/src/libAtomVM/module.h
+++ b/src/libAtomVM/module.h
@@ -149,10 +149,11 @@ enum ModuleLoadResult
#ifdef ENABLE_ADVANCED_TRACE
/**
- * @briefs Gets imported function module and name
+ * @brief Gets imported function module and name
*
* @details Gets imported function module and name given its import table index.
* @param this_module the module on which the function will be searched.
+ * @param index the modules import table offset to begin searching.
* @param module_atom module name atom string.
* @param function_atom function name atom string.
*/
@@ -207,7 +208,7 @@ static inline size_t module_get_exported_functions_list_size(Module *this_module
term module_get_exported_functions(Module *this_module, Heap *heap, GlobalContext *global);
/***
- * @brief Destoys an existing Module
+ * @brief Destroys an existing Module
*
* @details Destroys a module and free Module resources.
* @param module the module that will be freed.
diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c
index d144f72f5..117aff6f9 100644
--- a/src/libAtomVM/nifs.c
+++ b/src/libAtomVM/nifs.c
@@ -3449,6 +3449,8 @@ static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[])
return TRUE_ATOM;
}
+// TODO: WORKAROUND: this function also implements erlang:error/3, but it ignores Args and Options
+// since we don't have required machinery to make use of them
static term nif_erlang_error(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf
index 1c7c8eaca..86c523599 100644
--- a/src/libAtomVM/nifs.gperf
+++ b/src/libAtomVM/nifs.gperf
@@ -50,6 +50,7 @@ erlang:delete_element/2, &delete_element_nif
erlang:erase/1, &erase_nif
erlang:error/1, &error_nif
erlang:error/2, &error_nif
+erlang:error/3, &error_nif
erlang:exit/1, &exit_nif
erlang:exit/2, &exit_nif
erlang:display/1, &display_nif
diff --git a/src/libAtomVM/opcodes.h b/src/libAtomVM/opcodes.h
index 121d1e5a7..25e17ed91 100644
--- a/src/libAtomVM/opcodes.h
+++ b/src/libAtomVM/opcodes.h
@@ -149,6 +149,7 @@
#define OP_GET_MAP_ELEMENTS 158
#define OP_IS_TAGGED_TUPLE 159
#define OP_BUILD_STACKTRACE 160
+#define OP_RAW_RAISE 161
#define OP_GET_HD 162
#define OP_GET_TL 163
#define OP_PUT_TUPLE2 164
diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h
index da35dbd69..3fd95da72 100644
--- a/src/libAtomVM/opcodesswitch.h
+++ b/src/libAtomVM/opcodesswitch.h
@@ -6102,6 +6102,26 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
break;
}
+ case OP_RAW_RAISE: {
+
+ TRACE("raw_raise/0\n");
+
+ #ifdef IMPL_EXECUTE_LOOP
+ // This is an optimization from the compiler where we don't need to call
+ // stacktrace_create_raw here because the stack trace has already been created
+ // and set in x[2].
+ term ex_class = x_regs[0];
+ if (UNLIKELY(ex_class != ERROR_ATOM &&
+ ex_class != LOWERCASE_EXIT_ATOM &&
+ ex_class != THROW_ATOM)) {
+ x_regs[0] = BADARG_ATOM;
+ } else {
+ goto handle_error;
+ }
+ #endif
+ break;
+ }
+
case OP_GET_HD: {
term src_value;
DECODE_COMPACT_TERM(src_value, pc)
diff --git a/src/libAtomVM/otp_net.c b/src/libAtomVM/otp_net.c
index db8ea77a0..a57a0aba9 100644
--- a/src/libAtomVM/otp_net.c
+++ b/src/libAtomVM/otp_net.c
@@ -110,7 +110,6 @@ static term eai_errno_to_term(int err, GlobalContext *glb)
* @param heap the heap to create terms in, should have sufficient free space
* @details This function is called in a loop to create optimized maps that
* share keys.
- * @end
*/
static term make_getaddrinfo_result(term *keys, int ai_protocol, int ai_socktype, term inner_addr, GlobalContext *global, Heap *heap)
{
diff --git a/src/libAtomVM/otp_socket.c b/src/libAtomVM/otp_socket.c
index 69bf3296a..5f7d84c46 100644
--- a/src/libAtomVM/otp_socket.c
+++ b/src/libAtomVM/otp_socket.c
@@ -442,7 +442,6 @@ static inline int get_protocol(GlobalContext *global, term protocol_term, bool *
* @details This function is meant to be called from a nif that should return
* its result directly, to allow for further processing of a possible out of
* memory exception.
- * @end
*/
static inline term make_error_tuple(term reason, Context *ctx)
{
diff --git a/src/libAtomVM/otp_socket.h b/src/libAtomVM/otp_socket.h
index 7708e636f..e31cedfc3 100644
--- a/src/libAtomVM/otp_socket.h
+++ b/src/libAtomVM/otp_socket.h
@@ -151,7 +151,6 @@ struct LWIPEvent
* so platforms using a queue need a global variable.
* If lwIP callbacks are not called from ISR, calling handler with the event is
* sufficient.
- * @end
*/
void otp_socket_lwip_enqueue(struct LWIPEvent *event);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 42d122cf8..85a475d7b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -96,6 +96,11 @@ if (NOT "${CMAKE_GENERATOR}" MATCHES "Xcode")
add_subdirectory(libs/estdlib)
add_subdirectory(libs/eavmlib)
add_subdirectory(libs/alisp)
+ if (Elixir_FOUND)
+ add_subdirectory(libs/exavmlib)
+ else()
+ message("Unable to find elixirc -- skipping Elixir tests")
+ endif()
endif()
if (COVERAGE)
diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt
index 58fc5c3ee..1fc7db541 100644
--- a/tests/erlang_tests/CMakeLists.txt
+++ b/tests/erlang_tests/CMakeLists.txt
@@ -501,10 +501,12 @@ compile_erlang(complex_list_match_xregs)
compile_erlang(twentyone_param_fun)
compile_erlang(test_fun_to_list)
-compile_erlang(test_ets)
-
compile_erlang(maps_nifs)
+compile_erlang(test_raw_raise)
+
+compile_erlang(test_ets)
+
add_custom_target(erlang_test_modules DEPENDS
code_load_files
@@ -971,7 +973,9 @@ add_custom_target(erlang_test_modules DEPENDS
twentyone_param_fun.beam
test_fun_to_list.beam
- test_ets.beam
-
maps_nifs.beam
+
+ test_raw_raise.beam
+
+ test_ets.beam
)
diff --git a/tests/erlang_tests/test_raw_raise.erl b/tests/erlang_tests/test_raw_raise.erl
new file mode 100644
index 000000000..845ec4f10
--- /dev/null
+++ b/tests/erlang_tests/test_raw_raise.erl
@@ -0,0 +1,71 @@
+%
+% This file is part of AtomVM.
+%
+% Copyright 2024 Davide Bettio
+%
+% 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.
+%
+% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
+%
+
+-module(test_raw_raise).
+
+-export([start/0, do_raise/0, fail_with_atom/1, do_raise_not_error/0]).
+
+start() ->
+ do_catch() + do_catch2().
+
+do_catch() ->
+ try ?MODULE:do_raise() of
+ _X -> 1
+ catch
+ error:{badarith, new_reason}:ST ->
+ % TODO: verify if undefined is an acceptable value or an AtomVM only extension
+ % See also issue #1247
+ case ST of
+ L when is_list(L) -> 0;
+ undefined -> 0;
+ _ -> 2
+ end;
+ _:_ ->
+ 3
+ end.
+
+do_raise() ->
+ try ?MODULE:fail_with_atom(ciao) of
+ X -> X
+ catch
+ error:Reason:ST ->
+ erlang:raise(id(error), {Reason, new_reason}, ST)
+ end.
+
+do_catch2() ->
+ try ?MODULE:do_raise_not_error() of
+ X -> 0
+ catch
+ _:_ -> 1
+ end.
+
+do_raise_not_error() ->
+ try ?MODULE:fail_with_atom(ciao) of
+ X -> X
+ catch
+ error:Reason:ST ->
+ erlang:raise(id(not_error), {Reason, new_reason}, ST)
+ end.
+
+fail_with_atom(Atom) ->
+ Atom + 1.
+
+id(X) ->
+ X.
diff --git a/tests/libs/exavmlib/CMakeLists.txt b/tests/libs/exavmlib/CMakeLists.txt
new file mode 100644
index 000000000..4e9c419c8
--- /dev/null
+++ b/tests/libs/exavmlib/CMakeLists.txt
@@ -0,0 +1,25 @@
+#
+# This file is part of AtomVM.
+#
+# Copyright 2024 Davide Bettio
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
+#
+
+project(test_exavmlib)
+
+include(BuildElixir)
+
+pack_runnable(Tests Tests estdlib eavmlib exavmlib etest)
diff --git a/tests/libs/exavmlib/Tests.ex b/tests/libs/exavmlib/Tests.ex
new file mode 100644
index 000000000..44ae8d2a1
--- /dev/null
+++ b/tests/libs/exavmlib/Tests.ex
@@ -0,0 +1,156 @@
+#
+# This file is part of AtomVM.
+#
+# Copyright 2024 Davide Bettio
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
+#
+
+defmodule Tests do
+
+ @compile {:no_warn_undefined, :undef}
+
+ def start() do
+ :ok = IO.puts("Running Elixir tests")
+ :ok = test_enum()
+ :ok = test_exception()
+ :ok = IO.puts("Finished Elixir tests")
+ end
+
+ defp test_enum() do
+ # list
+ 3 = Enum.count([1, 2, 3])
+ true = Enum.member?([1, 2, 3], 1)
+ false = Enum.member?([1, 2, 3], 4)
+ [0, 2, 4] = Enum.map([0, 1, 2], fn x -> x * 2 end)
+ 6 = Enum.reduce([1, 2, 3], 0, fn x, acc -> acc + x end)
+ [2, 3] = Enum.slice([1, 2, 3], 1, 2)
+
+ # map
+ 2 = Enum.count(%{a: 1, b: 2})
+ true = Enum.member?(%{a: 1}, {:a, 1})
+ false = Enum.member?(%{a: 1}, {:b, 1})
+ kw = Enum.map(%{a: 1, b: 2}, fn x -> x end)
+ 1 = kw[:a]
+ 2 = kw[:b]
+ kw2 = Enum.reduce(%{a: :A, b: :B}, [], fn {k, v}, acc -> [{v, k} | acc] end)
+ :a = kw2[:A]
+ :b = kw2[:B]
+ kw3 = Enum.slice(%{a: 1, b: 2}, 0, 1)
+ true = (length(kw3) == 1) and ((kw3[:a] == 1) or (kw3[:b] == 2))
+
+ # map set
+ 3 = Enum.count(MapSet.new([0, 1, 2]))
+ true = Enum.member?(MapSet.new([1, 2, 3]), 1)
+ false = Enum.member?(MapSet.new([1, 2, 3]), 4)
+ [0, 2, 4] = Enum.map(MapSet.new([0, 1, 2]), fn x -> x * 2 end)
+ 6 = Enum.reduce(MapSet.new([1, 2, 3]), 0, fn x, acc -> acc + x end)
+ [] = Enum.slice(MapSet.new([1, 2]), 1, 0)
+
+ # range
+ 4 = Enum.count(1..4)
+ true = Enum.member?(1..4, 2)
+ false = Enum.member?(1..4, 5)
+ [1, 2, 3, 4] = Enum.map(1..4, fn x -> x end)
+ 55 = Enum.reduce(1..10, 0, fn x, acc -> x + acc end)
+ [6, 7, 8, 9, 10] = Enum.slice(1..10, 5, 100)
+
+ # into
+ %{a: 1, b: 2} = Enum.into([a: 1, b: 2], %{})
+ %{a: 2} = Enum.into([a: 1, a: 2], %{})
+ expected_mapset = MapSet.new([1, 2, 3])
+ ^expected_mapset = Enum.into([1, 2, 3], MapSet.new())
+
+ # Enum.join
+ "1, 2, 3" = Enum.join(["1", "2", "3"], ", ")
+
+ # Enum.reverse
+ [4, 3, 2] = Enum.reverse([2, 3, 4])
+
+ undef =
+ try do
+ Enum.map({1, 2}, fn x -> x end)
+ rescue
+ e -> e
+ end
+
+ case undef do
+ %Protocol.UndefinedError{description: "", protocol: Enumerable, value: {1, 2}} ->
+ :ok
+
+ %UndefinedFunctionError{arity: 3, function: :reduce, module: Enumerable} ->
+ # code compiled with OTP != 25 doesn't raise Protocol.UndefinedError
+ :ok
+ end
+
+ :ok
+ end
+
+ defp test_exception() do
+ ex1 =
+ try do
+ raise "This is a test"
+ rescue
+ e -> e
+ end
+
+ %RuntimeError{message: "This is a test"} = ex1
+
+ ex2 =
+ try do
+ :undef.ined(1, 2)
+ rescue
+ e -> e
+ end
+
+ # TODO: match for arity: 2, function: :ined, module: :undef
+ %UndefinedFunctionError{} = ex2
+
+ ex3 =
+ try do
+ fact(5) + fact(-2)
+ rescue
+ e -> e
+ end
+
+ %ArithmeticError{} = ex3
+
+ ex4 =
+ try do
+ :erlang.integer_to_list(fact(-2))
+ rescue
+ e -> e
+ end
+
+ %ArgumentError{} = ex4
+
+ ex5 =
+ try do
+ a = fact(-1)
+ b = fact(3)
+ ^a = b
+ rescue
+ e -> e
+ end
+
+ %MatchError{} = ex5
+
+ :ok
+ end
+
+ defp fact(n) when n < 0, do: :test
+ defp fact(0), do: 1
+ defp fact(n), do: fact(n - 1) * n
+end
diff --git a/tests/test.c b/tests/test.c
index 6eb223128..f470d13b5 100644
--- a/tests/test.c
+++ b/tests/test.c
@@ -533,6 +533,8 @@ struct Test tests[] = {
TEST_CASE(test_fun_to_list),
TEST_CASE(maps_nifs),
+ TEST_CASE(test_raw_raise),
+
TEST_CASE(test_ets),
// TEST CRASHES HERE: TEST_CASE(memlimit),